Project "selfhosted2", 23may2026 # Technical environment Cross-development environment for prototyping a residential application server behind a VPS-style reverse proxy. `hector`: The development machine. ASUS laptop running Linux Mint Mate. The machine on which this LLM session takes place (**Session ID:** ses_21ab382baffej8OpnTkrmaG5UA). `agge`: Target machine acting as the 'Q Server'. Dell Optiplex mini PC running a headless Ubuntu Server. This is where the production apps reside. LAN IP `192.168.1.188`, WireGuard IP `10.0.0.2`. `raspen`: Target machine acting as a VPS, with a reverse proxy routing all external traffic to applications on `agge` over an encrypted WireGuard tunnel. Raspberry Pi 5, running a headless Raspberry Pi OS Lite (64-bit). LAN IP `192.168.1.187`, WireGuard IP `10.0.0.1`. The machines are connected through a dedicated local wifi hotspot, SSID `NR-24`. In the production deployment, `raspen` will be replaced by a cloud VPS with a public IP; the Docker compose and nginx configuration are designed to be portable as-is. # Architecture ``` hector │ └──→ raspen.home (192.168.1.187) ← nginx reverse proxy (Docker) │ wireguard sidecar (Docker) │ wg0: 10.0.0.1/30 │ │ ────────── WireGuard tunnel ────────── │ │ wg0: 10.0.0.2/30 │ Docker: Gitea, Nextcloud, PostgreSQL └──→ agge (192.168.1.188) ``` Raspen is the single entry point for all traffic. Nginx terminates SSL and proxies requests through the encrypted WireGuard tunnel to agge. No services are exposed on the plain LAN — Docker containers on agge bind exclusively to `10.0.0.2`. # Network Hosts file on `hector` (`/etc/hosts`): ``` 192.168.1.187 raspen.home nc.home git.home pg.home 192.168.1.188 agge ``` Only `raspen.home`'s IP is published to clients. `agge` has no direct domain mapping in the hosts file (it is addressed only by its WireGuard IP). ## WireGuard VPN | Peer | Interface | IP | Port | |------|-----------|-----|------| | raspen | wg0 | 10.0.0.1/30 | ephemeral (outbound) | | agge | wg0 | 10.0.0.2/30 | 51820/udp | Raspen runs WireGuard inside a Docker sidecar container (`linuxserver/wireguard`). Agge runs WireGuard as a native systemd service (`wg-quick@wg0`). UFW on agge allows UDP 51820 inbound. Raspen's config (repo: `vps/wireguard/wg_confs/wg0.conf`): - Connects to `192.168.1.188:51820` - Keepalive 25s - Only routes `10.0.0.2/32` through the tunnel Agge's config (`/etc/wireguard/wg0.conf`): - Listens on port 51820 - Only accepts traffic from `10.0.0.1/32` # Applications ## VPS (raspen) — Docker stack Location: `vps/docker-compose.yml` Two containers sharing a network namespace: - **wireguard**: Creates the VPN tunnel. Publishes ports 80, 443, 5432, 2222, and 51820/udp. - **nginx**: Reverse proxy. Shares wireguard's network namespace (`network_mode: "service:wireguard"`). Proxies to agge via WireGuard IPs. Nginx config (`vps/nginx/conf.d/default.conf`): - `nc.home:443` → `10.0.0.2:8080` (Nextcloud) - `git.home:443` → `10.0.0.2:3000` (Gitea) - `raspen.home:80/443` → static page - HTTP redirects to HTTPS - Self-signed SSL certs at `vps/ssl/` TCP stream proxies (`vps/nginx/stream.d/`): - `pg.home:5432` → `10.0.0.2:5432` (PostgreSQL) - `git.home:2222` → `10.0.0.2:2222` (Gitea SSH) ## Backend (agge) — Docker stack Location: `backend/docker-compose.yml` Four containers on an internal `backend` bridge network: | Container | Image | Ports (bound to 10.0.0.2) | Purpose | |-----------|-------|---------------------------|---------| | postgres | postgres:16-alpine | — (internal) | Database for Nextcloud | | nextcloud | nextcloud:latest | 8080:80 | File sync & share | | gitea | gitea/gitea:latest | 3000:3000 (HTTP), 2222:22 (SSH) | Git hosting | | postgres_remote | postgres:16-alpine | 5432:5432 | Remote-access DB | All published ports bind to `10.0.0.2` only, making them inaccessible over the plain LAN — traffic must arrive via the WireGuard tunnel. Environment variables are loaded from `.env` (repo root). # Development Working directory: `/home/allan/Work/selfhosted2`, a Git repo hosted on Gitea at `agge`. Two remote URLs are configured: - `https://git.home/scoot/selfhosted2.git` (via raspen proxy — useful for read-only with self-signed cert) - `ssh://git@git.home:2222/scoot/selfhosted2.git` (via raspen stream proxy — used for push) Remote access: - `raspen`: `rasput@192.168.1.187` - `agge`: `tebarbi@192.168.1.188` opencode configuration (`opencode.json`): - Permission model asks before executing bash or accessing external directories. # Session history **Session ses_21ab382baffej8OpnTkrmaG5UA** — initial prototype deployment: Docker files written, containers configured, applications deployed and verified. **Post-migration recovery** (this session): 1. Hector was rebuilt from Omarchy Linux to Linux Mint Mate, losing the local work directory and self-signed certs. 2. The remote Gitea repo on agge was intact; the local repo was recovered via `GIT_SSL_NO_VERIFY=1 git fetch` over HTTPS (self-signed cert). 3. `/etc/hosts` was corrected — all domains now point to raspen (192.168.1.187) as the single entry point. 4. A WireGuard VPN was established between raspen (Docker sidecar) and agge (native systemd service). 5. Nginx upstream targets were changed from `192.168.1.188` to `10.0.0.2`. 6. Docker port bindings on agge were restricted to `10.0.0.2` to enforce tunnel-only access. 7. UFW on agge was configured to allow WireGuard UDP 51820. 8. Gitea SSH port 2222 was added to nginx's TCP stream proxies on raspen, enabling git push over SSH through the VPN tunnel. All services confirmed operational: Gitea (HTTP 200), Nextcloud (HTTP 302), PostgreSQL (port open), and static page (HTTP 200).