feat(dev-deploy): one /_gm gate for console + Grafana + Mailpit
Tests · Go / test (push) Successful in 1m59s

Consolidate the operator console and the observability / captured-mail
UIs behind a single Basic Auth gate, so one password (the admin-console
account, dev: gm/gm-dev-password) unlocks all three, with links in the
console nav:

- Caddyfile.dev: a single basic_auth on /_gm/* fronts nested routes —
  /_gm/grafana/ -> Grafana, /_gm/mailpit/ -> Mailpit, catch-all -> the
  gateway/backend console. Caddy forwards the same Authorization header,
  which the backend console also accepts, so there is one prompt. The
  former top-level /grafana/ and /mailpit/ routes are removed.
- Grafana: served under /_gm/grafana/ (sub-path) as anonymous Admin with
  the login form and basic auth disabled, so it relies solely on the
  /_gm gate and ignores the forwarded credentials.
- Mailpit: MP_WEBROOT=/_gm/mailpit (and the healthcheck path) so its UI
  lives under the gate.
- Operator console: add Grafana and Mailpit links to the nav.
This commit is contained in:
Ilia Denisov
2026-06-01 06:30:15 +02:00
parent 45815c27d9
commit cb8491c200
3 changed files with 38 additions and 22 deletions
@@ -17,6 +17,8 @@
<a href="/_gm/games"{{if eq .ActiveNav "games"}} class="active"{{end}}>Games</a> <a href="/_gm/games"{{if eq .ActiveNav "games"}} class="active"{{end}}>Games</a>
<a href="/_gm/operators"{{if eq .ActiveNav "operators"}} class="active"{{end}}>Operators</a> <a href="/_gm/operators"{{if eq .ActiveNav "operators"}} class="active"{{end}}>Operators</a>
<a href="/_gm/mail"{{if eq .ActiveNav "mail"}} class="active"{{end}}>Mail</a> <a href="/_gm/mail"{{if eq .ActiveNav "mail"}} class="active"{{end}}>Mail</a>
<a href="/_gm/grafana/" target="_blank" rel="noopener">Grafana</a>
<a href="/_gm/mailpit/" target="_blank" rel="noopener">Mailpit</a>
</nav> </nav>
<span class="who">{{.Username}}</span> <span class="who">{{.Username}}</span>
</header> </header>
+23 -17
View File
@@ -29,28 +29,34 @@
reverse_proxy galaxy-api:8080 reverse_proxy galaxy-api:8080
} }
# Operator console. Shares the gateway public listener with `/api`; the # Operator console + observability behind one Basic Auth gate. The gate
# gateway applies the admin anti-abuse class and reverse-proxies to the # credential equals the admin-console account (dev: gm / gm-dev-password),
# backend `/_gm` surface, which enforces Basic Auth and renders the pages. # so Caddy forwards the same Authorization header to the backend `/_gm`
# surface (its own Basic Auth) and to Grafana/Mailpit — one prompt covers
# all three. The gateway applies the admin anti-abuse class to the console.
@gm path /_gm /_gm/* @gm path /_gm /_gm/*
handle @gm { handle @gm {
reverse_proxy galaxy-api:8080
}
# Grafana (observability UI) under /grafana/ — Caddy sub-path mode
# (Grafana set with GF_SERVER_SERVE_FROM_SUB_PATH); its own login.
handle /grafana/* {
reverse_proxy galaxy-grafana:3000
}
# Mailpit captured-mail UI under /mailpit/. Shows every message the
# backend sent (relayed or not); basic-auth (dev: gm / gm-dev-password)
# guards the OTP codes it exposes. Mailpit runs with MP_WEBROOT=/mailpit.
handle /mailpit/* {
basic_auth { basic_auth {
gm "$2a$14$xVh1TLaZxh8fazlKrI9Mx.NQMQlMarYWtr3FRELmZIXuac/DeeTRO" gm "$2a$14$xVh1TLaZxh8fazlKrI9Mx.NQMQlMarYWtr3FRELmZIXuac/DeeTRO"
} }
reverse_proxy galaxy-mailpit:8025
# Grafana under /_gm/grafana/ (sub-path mode; anonymous Admin, so the
# /_gm gate is the only barrier — GF_AUTH_BASIC_ENABLED=false makes it
# ignore the forwarded Authorization header).
handle /_gm/grafana/* {
reverse_proxy galaxy-grafana:3000
}
# Mailpit captured-mail UI under /_gm/mailpit/ (MP_WEBROOT). Shows
# every message the backend sent, relayed or not.
handle /_gm/mailpit/* {
reverse_proxy galaxy-mailpit:8025
}
# The operator console itself (gateway -> backend /_gm surface).
handle {
reverse_proxy galaxy-api:8080
}
} }
# Bare `/game` (no trailing slash) -> `/game/` so the SPA root # Bare `/game` (no trailing slash) -> `/game/` so the SPA root
+13 -5
View File
@@ -74,9 +74,10 @@ services:
command: command:
- "--smtp-relay-config=/etc/mailpit/relay.conf" - "--smtp-relay-config=/etc/mailpit/relay.conf"
- "--smtp-relay-matching=${GALAXY_DEV_MAIL_RELAY_MATCH:-nobody@invalid.example}" - "--smtp-relay-matching=${GALAXY_DEV_MAIL_RELAY_MATCH:-nobody@invalid.example}"
# Serve the capture UI under /mailpit so the host Caddy can expose it # Serve the capture UI under /_gm/mailpit so the host Caddy can expose
# at https://galaxy.lan/mailpit/ (behind basic-auth); SMTP is unaffected. # it at https://galaxy.lan/_gm/mailpit/ behind the shared /_gm gate;
- "--webroot=/mailpit" # SMTP is unaffected.
- "--webroot=/_gm/mailpit"
labels: labels:
galaxy.stack: dev-deploy galaxy.stack: dev-deploy
networks: networks:
@@ -84,7 +85,7 @@ services:
volumes: volumes:
- galaxy-dev-mailpit-config:/etc/mailpit:ro - galaxy-dev-mailpit-config:/etc/mailpit:ro
healthcheck: healthcheck:
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8025/mailpit/livez"] test: ["CMD", "wget", "-q", "-O-", "http://localhost:8025/_gm/mailpit/livez"]
interval: 3s interval: 3s
timeout: 3s timeout: 3s
retries: 30 retries: 30
@@ -412,8 +413,15 @@ services:
- galaxy-tempo - galaxy-tempo
environment: environment:
GF_SECURITY_ADMIN_PASSWORD: ${GALAXY_DEV_GRAFANA_ADMIN_PASSWORD:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GALAXY_DEV_GRAFANA_ADMIN_PASSWORD:-admin}
GF_SERVER_ROOT_URL: https://galaxy.lan/grafana/ GF_SERVER_ROOT_URL: https://galaxy.lan/_gm/grafana/
GF_SERVER_SERVE_FROM_SUB_PATH: "true" GF_SERVER_SERVE_FROM_SUB_PATH: "true"
# No own login: the /_gm Basic Auth gate is the only barrier, so
# serve everyone as anonymous Admin and ignore the forwarded
# Authorization header (basic auth off, login form off).
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
GF_AUTH_DISABLE_LOGIN_FORM: "true"
GF_AUTH_BASIC_ENABLED: "false"
GF_USERS_ALLOW_SIGN_UP: "false" GF_USERS_ALLOW_SIGN_UP: "false"
GF_ANALYTICS_REPORTING_ENABLED: "false" GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_ANALYTICS_CHECK_FOR_UPDATES: "false" GF_ANALYTICS_CHECK_FOR_UPDATES: "false"