From 0ddf1d36998c37a5038761d1e17861c8ae773681 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 22 Jul 2026 20:00:05 -0700 Subject: [PATCH] Add RustDesk server and Caddy HTTPS reverse proxy Proxy forgejo/jellyfin/rustdesk info under devopshomelab.com with Let's Encrypt, publish RustDesk hbbs/hbbr ports, and wire Forgejo ROOT_URL to https://forgejo.devopshomelab.com. --- ansible/group_vars/all.yml | 8 +++- docker-compose.yml | 56 +++++++++++++++++++++++--- landing/Caddyfile | 80 ++++++++++++++++++++++++++++++++++++-- quickstart.md | 39 ++++++++++++++++++- secrets/.env.example | 4 +- 5 files changed, 175 insertions(+), 12 deletions(-) diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index 10b6812..17e4654 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -173,8 +173,14 @@ ufw_allow_ssh_from_anywhere: false ufw_lan_tcp_ports: - { port: 22, comment: "SSH" } - - { port: 80, comment: "Landing (Caddy)" } + - { port: 80, comment: "Caddy HTTP / ACME" } + - { port: 443, comment: "Caddy HTTPS" } - { port: 8080, comment: "qBittorrent" } + - { port: 21115, comment: "RustDesk" } + - { port: 21116, comment: "RustDesk" } + - { port: 21117, comment: "RustDesk relay" } + - { port: 21118, comment: "RustDesk" } + - { port: 21119, comment: "RustDesk" } - { port: 8096, comment: "Jellyfin" } - { port: 8989, comment: "Sonarr" } - { port: 7878, comment: "Radarr" } diff --git a/docker-compose.yml b/docker-compose.yml index 0ac1d3f..341a04c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -187,8 +187,11 @@ services: # devices: # - /dev/dri:/dev/dri - # ----------------------------------------------------- LAN landing (Caddy) # - # Dark-mode map of services for other PCs on the network: http://server/ + # -------------------------------- Caddy: landing + HTTPS reverse proxy --- # + # LAN map: http://192.168.8.123/ + # Public: https://forgejo.devopshomelab.com https://jellyfin.devopshomelab.com + # https://rustdesk.devopshomelab.com (info page) https://devopshomelab.com + # Let's Encrypt HTTP-01 needs WAN :80/:443 → this host and grey-cloud DNS. landing: image: caddy:2-alpine container_name: landing @@ -196,9 +199,17 @@ services: networks: [arr-net] ports: - "${LANDING_PORT:-80}:80" + - "${LANDING_HTTPS_PORT:-443}:443" + environment: + CADDY_EMAIL: ${CADDY_EMAIL:-admin@devopshomelab.com} volumes: - ./landing/Caddyfile:/etc/caddy/Caddyfile:ro - ./landing/site:/srv:ro + - ${CONFIG_DIR}/caddy-data:/data + - ${CONFIG_DIR}/caddy-config:/config + depends_on: + - forgejo + - jellyfin # ---------------------------------------------- Cloudflare dynamic DNS (A) # # Updates apex + subdomains to the host's public IPv4. Secrets from secrets/.env @@ -380,9 +391,10 @@ services: FORGEJO__server__START_SSH_SERVER: "false" FORGEJO__server__DISABLE_SSH: "false" FORGEJO__server__SSH_PORT: ${FORGEJO_SSH_PORT:-2222} - FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN:-192.168.8.123} - FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN:-192.168.8.123} - FORGEJO__server__ROOT_URL: ${FORGEJO_ROOT_URL:-http://192.168.8.123:3002/} + FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN:-forgejo.devopshomelab.com} + FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN:-forgejo.devopshomelab.com} + # Public HTTPS via Caddy (see landing/Caddyfile). LAN still works on :3002. + FORGEJO__server__ROOT_URL: ${FORGEJO_ROOT_URL:-https://forgejo.devopshomelab.com/} FORGEJO__server__HTTP_PORT: "3000" # SQLite by default (no extra DB container) FORGEJO__database__DB_TYPE: sqlite3 @@ -392,6 +404,40 @@ services: - ${CONFIG_DIR}/forgejo:/data - /etc/localtime:/etc/localtime:ro + # ------------------------------------------------------------------------- + # RustDesk server (OSS) — rustdesk.devopshomelab.com + # Clients use TCP/UDP 21115–21119 (not HTTP). Open those ports on the router. + # Public key: docker exec rustdesk-hbbs cat /root/id_ed25519.pub + # ------------------------------------------------------------------------- + rustdesk-hbbr: + image: rustdesk/rustdesk-server:latest + container_name: rustdesk-hbbr + restart: unless-stopped + networks: [arr-net] + command: hbbr + volumes: + - ${CONFIG_DIR}/rustdesk:/root + ports: + - "21117:21117" + - "21119:21119" + + rustdesk-hbbs: + image: rustdesk/rustdesk-server:latest + container_name: rustdesk-hbbs + restart: unless-stopped + networks: [arr-net] + # -r = relay host:port advertised to clients + command: hbbs -r ${RUSTDESK_DOMAIN:-rustdesk.devopshomelab.com}:21117 + volumes: + - ${CONFIG_DIR}/rustdesk:/root + ports: + - "21115:21115" + - "21116:21116" + - "21116:21116/udp" + - "21118:21118" + depends_on: + - rustdesk-hbbr + # ------------------------------------------------------------------------- # OpenHands — always deployed (this stack always brings up every service) # UI: http://host:3001 · LLM via bonsai OpenAI API diff --git a/landing/Caddyfile b/landing/Caddyfile index e52e810..323fa13 100644 --- a/landing/Caddyfile +++ b/landing/Caddyfile @@ -1,7 +1,81 @@ -# Homelab landing page — LAN only (UFW allows 80 from LAN) -:80 { - root * /srv +# Reverse proxy + LAN landing + automatic HTTPS (Let's Encrypt HTTP-01). +# +# Prerequisites: +# 1. cloudflare-ddns points A records at this host's public IPv4 +# 2. Router port-forwards WAN :80 and :443 → this host +# 3. DNS-only (grey cloud) for names used here — orange-cloud breaks HTTP-01 to origin +# 4. Domain is devopshomelab.com (Cloudflare zone). If you meant .org, change all hostnames. +# +# Email for ACME registration (set in compose): CADDY_EMAIL + +{ + email {$CADDY_EMAIL} +} + +# --- HTTPS reverse proxies --------------------------------------------------- + +forgejo.devopshomelab.com { encode gzip + reverse_proxy forgejo:3000 +} + +jellyfin.devopshomelab.com { + encode gzip + reverse_proxy jellyfin:8096 +} + +# Info page only — RustDesk clients use TCP/UDP 21115–21119, not this site +rustdesk.devopshomelab.com { + encode gzip + header Content-Type "text/html; charset=utf-8" + respond `RustDesk + + +

RustDesk server

+

ID / rendezvous server: rustdesk.devopshomelab.com

+

Relay server: rustdesk.devopshomelab.com (port 21117)

+

In the RustDesk client: Network → ID server = hostname above. Paste the server public key from the host admin.

+

Open ports on the router/firewall: 21115–21119 TCP and 21116 UDP.

+` 200 +} + +www.devopshomelab.com { + redir https://devopshomelab.com{uri} permanent +} + +devopshomelab.com { + encode gzip + root * /srv file_server try_files {path} /index.html } + +# --- HTTP :80 (ACME + LAN IP landing) ---------------------------------------- + +:80 { + @lan { + host 192.168.8.123 localhost 127.0.0.1 + } + handle @lan { + root * /srv + encode gzip + file_server + try_files {path} /index.html + } + + @acme_hosts { + host devopshomelab.com www.devopshomelab.com forgejo.devopshomelab.com jellyfin.devopshomelab.com rustdesk.devopshomelab.com + } + handle @acme_hosts { + redir https://{host}{uri} permanent + } + + # Default: still serve the map (LAN hostname / other access) + handle { + root * /srv + encode gzip + file_server + try_files {path} /index.html + } +} diff --git a/quickstart.md b/quickstart.md index f4c244e..54a33f1 100644 --- a/quickstart.md +++ b/quickstart.md @@ -102,6 +102,10 @@ LAN URLs (UFW allows **`192.168.8.0/24`**). Replace host if needed. |---------|-----| | **Landing page** | http://192.168.8.123/ | | Cloudflare DDNS | (background) apex + subdomains → public IPv4 | +| **Forgejo (HTTPS)** | https://forgejo.devopshomelab.com/ | +| **Jellyfin (HTTPS)** | https://jellyfin.devopshomelab.com/ | +| **RustDesk (info)** | https://rustdesk.devopshomelab.com/ | +| RustDesk (client) | `rustdesk.devopshomelab.com` ports **21115–21119** | | Jellyfin | http://192.168.8.123:8096 | | qBittorrent | http://192.168.8.123:8080 | | Prowlarr | http://192.168.8.123:9696 | @@ -183,12 +187,45 @@ Always edit **`secrets/.env`** first, then run only what you changed: ```bash cd /opt/stack +# include rustdesk in CLOUDFLARESUBDOMAINS if missing ./scripts/render-cloudflare-ddns-env.sh docker compose up -d cloudflare-ddns --force-recreate docker logs cloudflare-ddns --tail 30 ``` -Needs: `CLOUDFLAREAPIKEY`, `CLOUDFLAREDOMAIN`, `CLOUDFLARESUBDOMAINS` (and optional `CLOUDFLARE_PROXIED`). Token: **Zone → DNS → Edit**. Default `PROXIED=false` (DNS-only; better for VPN). +Needs: `CLOUDFLAREAPIKEY`, `CLOUDFLAREDOMAIN`, `CLOUDFLARESUBDOMAINS` (and optional `CLOUDFLARE_PROXIED`). Token: **Zone → DNS → Edit**. Default `PROXIED=false` (DNS-only; required for Let's Encrypt HTTP-01 and for VPN/RustDesk). + +**HTTPS reverse proxy (Caddy + Let's Encrypt)** + +Caddy (`landing` service) terminates TLS and routes: + +| Hostname | Backend | +|----------|---------| +| `https://forgejo.devopshomelab.com` | `forgejo:3000` | +| `https://jellyfin.devopshomelab.com` | `jellyfin:8096` | +| `https://rustdesk.devopshomelab.com` | Info page only | +| `https://devopshomelab.com` | LAN resource map | + +```bash +# 1) DNS A records must point here (DDNS) — grey cloud, not orange +# 2) Router: forward WAN TCP 80 + 443 → 192.168.8.123 +# 3) UFW: allow 80/443 (playbook) + RustDesk 21115-21119 if remote clients +docker compose up -d landing forgejo jellyfin +docker logs landing --tail 40 # watch for certificate obtain +``` + +Forgejo `ROOT_URL` is `https://forgejo.devopshomelab.com/` (set in compose/`.env`). LAN still works at `http://192.168.8.123:3002/`. + +**RustDesk server** + +```bash +docker compose up -d rustdesk-hbbs rustdesk-hbbr +# Public key for clients: +docker exec rustdesk-hbbs cat /root/id_ed25519.pub +``` + +Client settings: ID server = `rustdesk.devopshomelab.com`, key = pubkey above. +Open on router/firewall: **21115–21119/tcp** and **21116/udp**. **Usenet one-liner after secret edit:** diff --git a/secrets/.env.example b/secrets/.env.example index 2a05222..d601347 100644 --- a/secrets/.env.example +++ b/secrets/.env.example @@ -41,7 +41,7 @@ CLOUDFLAREZONEID= CLOUDFLAREACCOUNTID= CLOUDFLAREDOMAIN=devopshomelab.com # Comma-separated labels (or FQDNs). Apex is always included. -CLOUDFLARESUBDOMAINS=mc,minecraft,vpn,www,forgejo,jellyfin -# false = DNS-only (needed for VPN); true = orange-cloud proxy +CLOUDFLARESUBDOMAINS=mc,minecraft,vpn,www,forgejo,jellyfin,rustdesk +# false = DNS-only (required for Let's Encrypt HTTP-01 + VPN); true = orange-cloud proxy CLOUDFLARE_PROXIED=false CLOUDFLARE_UPDATE_CRON=@every 5m