diff --git a/infra/main.tf b/infra/main.tf index 210ec4f..9c529c2 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -27,10 +27,13 @@ locals { { zone = "luke-else.co.uk", name = "bitwarden", value = module.prod.ipv4 }, { zone = "luke-else.co.uk", name = "rd", value = module.prod.ipv4 }, { zone = "luke-else.co.uk", name = "home", value = module.prod.ipv4 }, + { zone = "luke-else.co.uk", name = "glances", value = module.prod.ipv4 }, { zone = "luke-else.co.uk", name = "git", value = module.dev.ipv4 }, { zone = "luke-else.co.uk", name = "traefik.cicd", value = module.dev.ipv4 }, + { zone = "luke-else.co.uk", name = "glances.cicd", value = module.dev.ipv4 }, { zone = "luke-else.co.uk", name = "vpn", value = module.vpn.ipv4 }, { zone = "luke-else.co.uk", name = "traefik.vpn", value = module.vpn.ipv4 }, + { zone = "luke-else.co.uk", name = "glances.vpn", value = module.vpn.ipv4 }, ] # Microsoft 365 mail records for luke-else.co.uk diff --git a/readme.md b/readme.md index 05f4578..042d5bb 100644 --- a/readme.md +++ b/readme.md @@ -102,7 +102,8 @@ Each of `services/dev`, `services/prod`, `services/vpn` follows the same convent - **DNS is provisioned, not just documented.** `module.dns` creates a Hetzner zone and every A/MX/TXT/CNAME record in `infra/main.tf` — see [Managing DNS](#managing-dns). - **Hosts are discovered dynamically** from the Hetzner API by role label, not a static inventory file — nothing in Ansible reads Terraform state. - **Gitea Actions runners are disposable.** Their `/data` is a local cache; runner identity re-registers with Gitea on every `spinup.sh`, so they're excluded from backup. -- **The `home` dashboard only gets Docker container widgets for `prod`.** It links to every service on all three hosts, but container-level widgets (CPU/memory) are wired up only for containers on `prod` itself (`services/prod/home/config/docker.yaml`) — reaching into `dev`'s or `vpn`'s Docker socket would mean opening a path across hosts, which cuts against `vpn`'s deliberate isolation above. The Traefik widgets for `dev`/`vpn` are the one exception: they hit those hosts' public, unauthenticated dashboard URLs (same as any browser could), not their Docker sockets, so they need no network changes. +- **The `home` dashboard only gets Docker container widgets for `prod`.** It links to every service on all three hosts, but container-level widgets (CPU/memory) are wired up only for containers on `prod` itself (`services/prod/home/config/docker.yaml`) — reaching into `dev`'s or `vpn`'s Docker socket would mean opening a path across hosts, which cuts against `vpn`'s deliberate isolation above. The Traefik and Glances widgets for `dev`/`vpn` are the exception: they hit those hosts' public, unauthenticated dashboard/panel URLs (same as any browser could), not their Docker sockets, so they need no network changes. +- **Each host runs its own Glances instance** (`glances-docker-compose.yml`) for performance monitoring (CPU/memory/disk/network/process), fronted by that host's own Traefik like everything else. `home`'s Glances widgets (`services/prod/home/config/services.yaml`) read all three over HTTPS, the same pattern as the Traefik widgets above. ## Prerequisites @@ -221,6 +222,7 @@ All three hosts run [Watchtower](https://containrrr.dev/watchtower/), polling ev | Service | Compose file | Domain(s) | |---|---|---| | Traefik | `traefik-docker-compose.yml` | `traefik.cicd.luke-else.co.uk` | +| Glances | `glances-docker-compose.yml` | `glances.cicd.luke-else.co.uk` | | Gitea | `gitea-docker-compose.yml` | `git.luke-else.co.uk` (HTTP), SSH on `222` | | Gitea Actions runner(s) | `Runners/docker-compose.yml` (generated, 3 by default) | N/A | | Watchtower | `watchtower-docker-compose.yml` | — | @@ -236,6 +238,7 @@ All three hosts run [Watchtower](https://containrrr.dev/watchtower/), polling ev | Bitwarden (Vaultwarden) | `bitwarden-docker-compose.yml` | `bitwarden.luke-else.co.uk` | | RustDesk relay (hbbs/hbbr) | `rd-docker-compose.yml` | `rd.luke-else.co.uk`, ports `21115-21119` | | Home dashboard (Homepage) | `home-docker-compose.yml` | `home.luke-else.co.uk` | +| Glances | `glances-docker-compose.yml` | `glances.luke-else.co.uk` | | Watchtower | `watchtower-docker-compose.yml` | — | | Backup/restore | `backup-docker-compose.yml` | — | @@ -244,6 +247,7 @@ All three hosts run [Watchtower](https://containrrr.dev/watchtower/), polling ev | Service | Compose file | Domain(s) | |---|---|---| | Traefik | `traefik-docker-compose.yml` | `traefik.vpn.luke-else.co.uk` | +| Glances | `glances-docker-compose.yml` | `glances.vpn.luke-else.co.uk` | | WireGuard (wg-easy) | `vpn-docker-compose.yml` | `vpn.luke-else.co.uk`, UDP `51820` | | Watchtower | `watchtower-docker-compose.yml` | — | | Backup/restore | `backup-docker-compose.yml` | — | diff --git a/services/dev/glances-docker-compose.yml b/services/dev/glances-docker-compose.yml new file mode 100644 index 0000000..6efbd98 --- /dev/null +++ b/services/dev/glances-docker-compose.yml @@ -0,0 +1,30 @@ +services: + glances: + image: nicolargo/glances:latest + container_name: glances + hostname: dev + pid: host + environment: + - GLANCES_OPT=-w + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - proxy + labels: + ## Expose Glances Through Trefik ## + - "traefik.enable=true" # <== Enable traefik to proxy this container + + - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https" + - "traefik.http.routers.glances-insecure.middlewares=redirect-web-secure" + - "traefik.http.routers.glances-insecure.rule=Host(`glances.cicd.luke-else.co.uk`)" + - "traefik.http.routers.glances-insecure.entrypoints=web" + + - "traefik.http.services.glances.loadbalancer.server.port=61208" + - "traefik.http.routers.glances.rule=Host(`glances.cicd.luke-else.co.uk`)" + - "traefik.http.routers.glances.entrypoints=websecure" + - "traefik.http.routers.glances.tls.certresolver=myresolver" + restart: unless-stopped + +networks: + proxy: + external: true diff --git a/services/dev/spinup.sh b/services/dev/spinup.sh index f25d991..2d207c5 100755 --- a/services/dev/spinup.sh +++ b/services/dev/spinup.sh @@ -13,6 +13,7 @@ docker compose -f backup-docker-compose.yml pull restore docker compose -f backup-docker-compose.yml run --rm restore docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d +docker compose -f glances-docker-compose.yml pull && docker compose -f glances-docker-compose.yml up -d docker compose -f gitea-docker-compose.yml pull && docker compose -f gitea-docker-compose.yml up -d diff --git a/services/prod/glances-docker-compose.yml b/services/prod/glances-docker-compose.yml new file mode 100644 index 0000000..5657130 --- /dev/null +++ b/services/prod/glances-docker-compose.yml @@ -0,0 +1,30 @@ +services: + glances: + image: nicolargo/glances:latest + container_name: glances + hostname: prod + pid: host + environment: + - GLANCES_OPT=-w + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - proxy + labels: + ## Expose Glances Through Trefik ## + - "traefik.enable=true" # <== Enable traefik to proxy this container + + - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https" + - "traefik.http.routers.glances-insecure.middlewares=redirect-web-secure" + - "traefik.http.routers.glances-insecure.rule=Host(`glances.luke-else.co.uk`)" + - "traefik.http.routers.glances-insecure.entrypoints=web" + + - "traefik.http.services.glances.loadbalancer.server.port=61208" + - "traefik.http.routers.glances.rule=Host(`glances.luke-else.co.uk`)" + - "traefik.http.routers.glances.entrypoints=websecure" + - "traefik.http.routers.glances.tls.certresolver=myresolver" + restart: unless-stopped + +networks: + proxy: + external: true diff --git a/services/prod/home/config/services.yaml b/services/prod/home/config/services.yaml index e30deb8..e0b0677 100644 --- a/services/prod/home/config/services.yaml +++ b/services/prod/home/config/services.yaml @@ -55,6 +55,34 @@ type: uptimekuma url: https://status.luke-else.co.uk slug: dashboard + - Glances (prod): + href: https://glances.luke-else.co.uk + description: Performance monitoring - prod + icon: mdi-chart-line + container: glances + widget: + type: glances + url: https://glances.luke-else.co.uk + metric: cpu + chart: false + - Glances (dev): + href: https://glances.cicd.luke-else.co.uk + description: Performance monitoring - dev + icon: mdi-chart-line + widget: + type: glances + url: https://glances.cicd.luke-else.co.uk + metric: cpu + chart: false + - Glances (vpn): + href: https://glances.vpn.luke-else.co.uk + description: Performance monitoring - vpn + icon: mdi-chart-line + widget: + type: glances + url: https://glances.vpn.luke-else.co.uk + metric: cpu + chart: false - Hetzner Cloud Console: href: https://console.hetzner.cloud/ description: Servers, networks, firewalls (OpenTofu-managed) diff --git a/services/prod/spinup.sh b/services/prod/spinup.sh index 3d4b377..5d7392b 100755 --- a/services/prod/spinup.sh +++ b/services/prod/spinup.sh @@ -17,6 +17,7 @@ docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-d sleep 20 # Allow Traefik + registry auth to settle before starting the rest of the services docker compose -f watchtower-docker-compose.yml pull && docker compose -f watchtower-docker-compose.yml up -d +docker compose -f glances-docker-compose.yml pull && docker compose -f glances-docker-compose.yml up -d docker compose -f status-docker-compose.yml pull && docker compose -f status-docker-compose.yml up -d docker compose -f web-docker-compose.yml pull && docker compose -f web-docker-compose.yml up -d docker compose -f bitwarden-docker-compose.yml pull && docker compose -f bitwarden-docker-compose.yml up -d diff --git a/services/vpn/glances-docker-compose.yml b/services/vpn/glances-docker-compose.yml new file mode 100644 index 0000000..2cd69fd --- /dev/null +++ b/services/vpn/glances-docker-compose.yml @@ -0,0 +1,30 @@ +services: + glances: + image: nicolargo/glances:latest + container_name: glances + hostname: vpn + pid: host + environment: + - GLANCES_OPT=-w + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - proxy + labels: + ## Expose Glances Through Trefik ## + - "traefik.enable=true" # <== Enable traefik to proxy this container + + - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https" + - "traefik.http.routers.glances-insecure.middlewares=redirect-web-secure" + - "traefik.http.routers.glances-insecure.rule=Host(`glances.vpn.luke-else.co.uk`)" + - "traefik.http.routers.glances-insecure.entrypoints=web" + + - "traefik.http.services.glances.loadbalancer.server.port=61208" + - "traefik.http.routers.glances.rule=Host(`glances.vpn.luke-else.co.uk`)" + - "traefik.http.routers.glances.entrypoints=websecure" + - "traefik.http.routers.glances.tls.certresolver=myresolver" + restart: unless-stopped + +networks: + proxy: + external: true diff --git a/services/vpn/spinup.sh b/services/vpn/spinup.sh index 1911994..c6653da 100755 --- a/services/vpn/spinup.sh +++ b/services/vpn/spinup.sh @@ -13,6 +13,7 @@ docker compose -f backup-docker-compose.yml pull restore docker compose -f backup-docker-compose.yml run --rm restore docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d +docker compose -f glances-docker-compose.yml pull && docker compose -f glances-docker-compose.yml up -d docker compose -f vpn-docker-compose.yml pull && docker compose -f vpn-docker-compose.yml up -d docker compose -f watchtower-docker-compose.yml pull && docker compose -f watchtower-docker-compose.yml up -d