diff --git a/.env.example b/.env.example index cde8111..f2f4b24 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,7 @@ DISTRIBUTION_WEB_ROOT=/home/oier/projects/mc-mods/ulicraft-group/ulicraft-distri # Only needed to issue real TLS certs. DNS-01 needs no inbound ports. LE_EMAIL=you@example.com # space-separated subdomains; apex ${BASE_DOMAIN} is always included -LE_SUBDOMAINS=auth pack distribution www avatar +LE_SUBDOMAINS=auth pack distribution www avatar status # OVH API creds (DNS zone write). Create at https://eu.api.ovh.com/createToken/ # OVH_CK can be blank on first run — acme.sh prints an auth URL, then paste it. OVH_END_POINT=ovh-eu diff --git a/README.md b/README.md index 2a33810..8add3a2 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Optional layers (each its own `docker-compose..yml`): `avatar.${BASE_DOMAIN}`, sourced from Drasl. See **Avatar renderer** below. - `distribution` — extra static vhost at `distribution.${BASE_DOMAIN}` whose web root (`DISTRIBUTION_WEB_ROOT`) lives in another repo. +- `status` (louislam/uptime-kuma) — uptime monitoring + public status page at + `status.${BASE_DOMAIN}`. See **Status monitoring** below. Config lives in `.env`: `BASE_DOMAIN` (default `ulicraft.lan`), `HOST_LAN_IP`, `RCON_PASSWORD`, `ENABLE_MDNS`. Services are subdomains: `auth.`, `packwiz.`, @@ -96,7 +98,7 @@ nmsr). HTTPS-only: HTTP 301s to HTTPS and every TLS vhost sends HSTS. 1. **Issue certs** (OVH DNS-01, no inbound ports needed): ```sh # .env: BASE_DOMAIN, LE_EMAIL, OVH_* creds, - # LE_SUBDOMAINS="auth pack distribution www avatar" + # LE_SUBDOMAINS="auth pack distribution www avatar status" tooling/issue-letsencrypt.sh # → certs//{cert,key}.pem ``` @@ -110,7 +112,7 @@ nmsr). HTTPS-only: HTTP 301s to HTTPS and every TLS vhost sends HSTS. ```sh docker compose -f docker-compose.yml -f docker-compose.caddy.yml \ -f docker-compose.static.yml -f docker-compose.distribution.yml \ - -f docker-compose.nmsr.yml up -d --build + -f docker-compose.nmsr.yml -f docker-compose.status.yml up -d --build ``` 4. **Render + install the nginx vhost** with `tooling/render-nginx.sh`. It pulls @@ -153,6 +155,40 @@ Vulkan); if renders fail, check `docker logs nmsr` for Vulkan device init. another repo: set `DISTRIBUTION_WEB_ROOT` (absolute host path) in `.env`; it's bind-mounted read-only via `docker-compose.distribution.yml`. +## Status monitoring (Uptime Kuma) + +`status.${BASE_DOMAIN}` runs [Uptime Kuma](https://github.com/louislam/uptime-kuma) +— uptime probes for every vhost **and** a native Minecraft-protocol ping (player +count + up/down), plus a public status page. The container joins `mcnet`, so it +can probe the stack by internal service name without leaving the host. caddy +reverse-proxies `status.` → `uptime-kuma:3001`; the host port is published on +`127.0.0.1:3001` only (first-run admin setup / local debugging). + +Bring up (layer on the caddy ingress): + +```sh +docker compose -f docker-compose.yml -f docker-compose.caddy.yml \ + -f docker-compose.status.yml up -d +``` + +Then open `https://status.${BASE_DOMAIN}` (or `http://127.0.0.1:3001` first run), +create the admin account, and add monitors. Kuma has no config-as-code — add +these once via the UI (data persists in the `kuma_data` volume): + +| Monitor | Type | Target | +|---|---|---| +| Minecraft | Minecraft Server | `minecraft` : `25565` | +| Drasl auth | HTTP(s) | `https://auth.${BASE_DOMAIN}` | +| Landing (apex) | HTTP(s) | `https://${BASE_DOMAIN}` | +| Packwiz | HTTP(s) | `https://pack.${BASE_DOMAIN}` | +| Distribution | HTTP(s) | `https://distribution.${BASE_DOMAIN}` | +| Avatar (NMSR) | HTTP(s) | `https://avatar.${BASE_DOMAIN}` | + +Internal-name targets (`minecraft`, `drasl:25585`, `nmsr:8080`) isolate "service +down" from "ingress/TLS/DNS down"; public-URL targets test the whole chain. Mix +as you like. `status` is in the default `LE_SUBDOMAINS` so its TLS cert issues +with the rest; the nginx vhost adds websocket upgrade headers for Kuma's live UI. + ## Join (guests) 1. Open `http://ulicraft.lan`, download FjordLauncherUnlocked for your OS. diff --git a/caddy/conf.d/50-status.caddy b/caddy/conf.d/50-status.caddy new file mode 100644 index 0000000..fd9f55d --- /dev/null +++ b/caddy/conf.d/50-status.caddy @@ -0,0 +1,5 @@ +# Status toggle — Uptime Kuma monitoring + public status page +# (docker-compose.status.yml). Mounted only when the status override is present. +http://status.{$BASE_DOMAIN} { + reverse_proxy uptime-kuma:3001 +} diff --git a/docker-compose.status.yml b/docker-compose.status.yml new file mode 100644 index 0000000..9d04298 --- /dev/null +++ b/docker-compose.status.yml @@ -0,0 +1,36 @@ +# ────────────────────────────────────────────────────────────────── +# Uptime Kuma — status/monitoring for every vhost + the Minecraft server, +# reachable at status.${BASE_DOMAIN}. +# ────────────────────────────────────────────────────────────────── +# docker compose -f docker-compose.yml -f docker-compose.caddy.yml \ +# -f docker-compose.status.yml up -d +# +# Joins mcnet so monitors can probe the stack BY ITS INTERNAL SERVICE NAME +# (drasl:25585, nmsr:8080, minecraft:25565) without leaving the host — and can +# also hit the public https vhosts end-to-end through the host's DNS. +# +# Caddy reverse-proxies status.${BASE_DOMAIN} -> uptime-kuma:3001 internally +# (caddy/conf.d/50-status.caddy). The host port is published on 127.0.0.1:3001 +# ONLY (local debugging / first-run admin setup) — public access goes through +# nginx -> caddy. Add status.${BASE_DOMAIN} to LE_SUBDOMAINS for its TLS cert. +# +# Monitors are stored in the kuma_data volume (Kuma has no config-as-code); +# add them once via the web UI — see README for the recommended monitor list. +services: + caddy: + volumes: + - ./caddy/conf.d/50-status.caddy:/etc/caddy/conf.d/50-status.caddy:ro + + uptime-kuma: + image: louislam/uptime-kuma:1 + container_name: uptime-kuma + restart: unless-stopped + ports: + - "127.0.0.1:3001:3001" + volumes: + - kuma_data:/app/data + networks: + - mcnet + +volumes: + kuma_data: diff --git a/nginx/ulicraft-caddy.conf.tmpl b/nginx/ulicraft-caddy.conf.tmpl index 9b81950..ce8a35d 100644 --- a/nginx/ulicraft-caddy.conf.tmpl +++ b/nginx/ulicraft-caddy.conf.tmpl @@ -32,7 +32,7 @@ upstream ulicraft_caddy { server { listen 80; listen [::]:80; - server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN} avatar.${BASE_DOMAIN}; + server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN} avatar.${BASE_DOMAIN} status.${BASE_DOMAIN}; return 301 https://$host$request_uri; } @@ -145,3 +145,26 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } } + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name status.${BASE_DOMAIN}; + + ssl_certificate ${APP_DIR}/certs/status.${BASE_DOMAIN}/cert.pem; + ssl_certificate_key ${APP_DIR}/certs/status.${BASE_DOMAIN}/key.pem; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location / { + proxy_pass http://ulicraft_caddy; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Uptime Kuma uses websockets (socket.io) for live status updates. + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/tooling/issue-letsencrypt.sh b/tooling/issue-letsencrypt.sh index 01b4933..6ee422e 100755 --- a/tooling/issue-letsencrypt.sh +++ b/tooling/issue-letsencrypt.sh @@ -17,7 +17,7 @@ # OVH_CK OVH consumer key (obtained on first run — see below) # OVH_END_POINT OVH API endpoint (e.g. ovh-eu) # optional: -# LE_SUBDOMAINS space-separated (default: "auth pack distribution www avatar") +# LE_SUBDOMAINS space-separated (default: "auth pack distribution www avatar status") # LE_STAGING=1 use the LE staging CA (untrusted, for dry runs) # # First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK @@ -53,7 +53,7 @@ ACME="${ACME_SH:-$HOME/.acme.sh/acme.sh}" then re-run (or set ACME_SH=/path/to/acme.sh)." CERT_DIR="${CERT_DIR:-certs}" -read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution www avatar}" +read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution www avatar status}" if [ "${LE_STAGING:-0}" = "1" ]; then SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers"