156 lines
7.3 KiB
Markdown
156 lines
7.3 KiB
Markdown
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.
|
|
|
|
## Password management
|
|
|
|
Secrets are managed through a `.env.example` pattern:
|
|
|
|
- **`.env.example`** (tracked in git) — template with placeholder values documenting all required variables.
|
|
- **`.env`** (gitignored) — the actual secrets file on hector. A copy lives at `~/selfhosted2/.env` on agge.
|
|
- **Docker volumes** — runtime copies of passwords baked into PostgreSQL and Nextcloud config on first deploy.
|
|
|
|
Password sources:
|
|
|
|
| Variable | User | Where it's used | Where it's stored |
|
|
|----------|------|-----------------|-------------------|
|
|
| `POSTGRES_PASSWORD` | `nextcloud` | Internal PostgreSQL (DB owner) | `.env`, Docker volume |
|
|
| — | `oc_admin` | Nextcloud app DB user | Nextcloud config.php |
|
|
| `PG_PASSWORD` | `remoteuser` | Remote PostgreSQL | `.env`, Docker volume |
|
|
| `NEXTCLOUD_ADMIN_PASSWORD` | `admin` | Nextcloud web login | `.env`, personal password manager |
|
|
|
|
To rotate a password:
|
|
1. Generate: `openssl rand -base64 18`
|
|
2. Update `.env` and sync to agge
|
|
3. Run `ALTER USER <user> WITH PASSWORD '<new>';` in the appropriate PostgreSQL container
|
|
4. If rotating the Nextcloud app user, also update `config.php` via `sed`
|
|
5. Restart the service if needed
|
|
|
|
Do not commit `.env` to git. The `.env.example` template is the only password-related file that belongs in the repo.
|
|
|
|
# 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.
|
|
9. PostgreSQL passwords were rotated to strong values; `.env` replaced with `.env.example` pattern and gitignored; password management documented.
|
|
|
|
All services confirmed operational: Gitea (HTTP 200), Nextcloud (HTTP 302), PostgreSQL (port open), and static page (HTTP 200).
|