diff --git a/ansible/roles/healthcheck/templates/health-common.sh.j2 b/ansible/roles/healthcheck/templates/health-common.sh.j2 index a88f660..54b11a5 100644 --- a/ansible/roles/healthcheck/templates/health-common.sh.j2 +++ b/ansible/roles/healthcheck/templates/health-common.sh.j2 @@ -31,9 +31,27 @@ compose_ps() { } check_compose() { - local output - output="$(compose_ps)" || { record critical 'compose_ps_failed'; return; } - if grep -qiE 'Exited|Restarting|[[:space:]]Dead[[:space:]]' <<<"$output"; then + local output services bad + # Only flag containers of *active* services (config --services excludes + # debug/profile-gated services such as vaultwarden's pgweb, which is + # intentionally stopped unless started with --profile debug). + services="$(docker compose --project-directory '{{ compose_project_dir }}' config --services 2>/dev/null)" || { record critical 'compose_ps_failed'; return; } + output="$(docker compose --project-directory '{{ compose_project_dir }}' ps --all --format json 2>&1)" || { record critical 'compose_ps_failed'; return; } + bad="$(printf '%s\n' "$output" | python3 -c ' +import json, sys +services = set(sys.argv[1].split()) +for line in sys.stdin: + line = line.strip() + if not line: + continue + try: + c = json.loads(line) + except Exception: + continue + if c.get("Service") in services and c.get("State") in ("exited", "restarting", "dead"): + print(c.get("Service")) +' "$services")" + if [[ -n "$bad" ]]; then record critical 'compose_unhealthy_container' else record ok 'compose_ok' diff --git a/ansible/roles/healthcheck/templates/healthcheck-runner.sh.j2 b/ansible/roles/healthcheck/templates/healthcheck-runner.sh.j2 index 76ac750..3079c09 100644 --- a/ansible/roles/healthcheck/templates/healthcheck-runner.sh.j2 +++ b/ansible/roles/healthcheck/templates/healthcheck-runner.sh.j2 @@ -14,5 +14,11 @@ rm -f '{{ healthcheck_state_dir }}/latest-{{ healthcheck_profile_scripts[profile this_rc="${PIPESTATUS[0]}" [ "$this_rc" -gt "$rc" ] && rc="$this_rc" {% endfor %} -aggregate_result{% for profile in healthcheck_profiles %} {{ healthcheck_profile_scripts[profile] | replace('.sh', '') }}{% endfor %} +# Collect profile check names line-by-line (robust against Jinja trim_blocks +# whitespace control, which would otherwise merge this into one line). +aggregate_args="" +{% for profile in healthcheck_profiles %} +aggregate_args="$aggregate_args {{ healthcheck_profile_scripts[profile] | replace('.sh', '') }}" +{% endfor %} +aggregate_result $aggregate_args exit "$rc" diff --git a/ansible/roles/healthcheck/templates/vaultwarden.sh.j2 b/ansible/roles/healthcheck/templates/vaultwarden.sh.j2 index 631fe36..bd46c2e 100644 --- a/ansible/roles/healthcheck/templates/vaultwarden.sh.j2 +++ b/ansible/roles/healthcheck/templates/vaultwarden.sh.j2 @@ -15,11 +15,12 @@ grep -Fq 'vw-db' <<<"$health" || record critical 'postgres_missing' check_https 'https://auth.wsvc.info/' '^200$' check_tls_days auth.wsvc.info 443 -# Read effective config only inside the service and report booleans/fingerprints, -# never its SMTP password or other secret fields. -smtp_result="$(docker compose --project-directory '{{ compose_project_dir }}' exec -T vaultwarden python3 - <<'PY' 2>&1 +# Read effective config from the mounted vw-data dir on the host and run the +# SMTP AUTH probe from the host (the vaultwarden image has no python3; the +# host does). Never print the SMTP password. +smtp_result="$(python3 - <<'PY' 2>&1 import json, pathlib, smtplib, ssl -cfg=json.loads(pathlib.Path('/data/config.json').read_text()) +cfg=json.loads(pathlib.Path('{{ compose_project_dir }}/vw-data/config.json').read_text()) host=cfg.get('smtp_host'); port=int(cfg.get('smtp_port') or 0) user=cfg.get('smtp_username') smtp_secret=cfg.get('smtp_password') diff --git a/compose/vaultwarden/compose.yml b/compose/vaultwarden/compose.yml index 09c6be2..6ea44b5 100644 --- a/compose/vaultwarden/compose.yml +++ b/compose/vaultwarden/compose.yml @@ -1,6 +1,6 @@ services: vaultwarden: - image: vaultwarden/server:1.37.1 + image: vaultwarden/server:1.37.2 container_name: vaultwarden restart: unless-stopped env_file: ".env" diff --git a/hosts/us2.wsvc.info.md b/hosts/us2.wsvc.info.md index af7614e..0ee6dd5 100644 --- a/hosts/us2.wsvc.info.md +++ b/hosts/us2.wsvc.info.md @@ -10,7 +10,7 @@ ## Vaultwarden (Bitwarden-compatible) -**Status: operational** (Postgres live, HTTPS 200, healthy containers, SMTP AUTH OK — last probe 2026-08-01 18:55 CST). +**Status: operational** (Postgres live, HTTPS 200, healthy containers, SMTP AUTH OK — last probe 2026-08-29). Upstream docs: [docs/vaultwarden-upstream.md](../docs/vaultwarden-upstream.md) @@ -21,9 +21,9 @@ Upstream docs: [docs/vaultwarden-upstream.md](../docs/vaultwarden-upstream.md) | Env file | `/opt/vaultwarden/.env` | | Admin overrides | `/opt/vaultwarden/vw-data/config.json` (**wins over env**) | | Public URL / `DOMAIN` | `https://auth.wsvc.info` | -| Image | `vaultwarden/server:1.37.1` (pinned) | +| Image | `vaultwarden/server:1.37.2` (pinned) | | Live DB | **Postgres 16** (`vw-db` / service `pg`) via compose `DATABASE_URL` | -| Data (probe) | users=1, ciphers=1327 | +| Data (probe) | users=1, ciphers=1360 | | Cold SQLite | `backups/sqlite-cold/db.sqlite3.pre-pg-20260801` (not used live) | | Pre-migrate backup | `backups/pre-pg-migrate-20260801_161204/` | | Data dir | `./vw-data` → `/data` (attachments, rsa keys, `config.json`) | @@ -50,7 +50,7 @@ Upstream docs: [docs/vaultwarden-upstream.md](../docs/vaultwarden-upstream.md) | Container | Status | |-----------|--------| -| `vaultwarden` | Up (healthy), `vaultwarden/server:1.37.1` | +| `vaultwarden` | Up (healthy), `vaultwarden/server:1.37.2` | | `vw-db` | Up (healthy) — **live** Postgres | | `vaultwarden-backup` | Up (`pg_dump`) | | `vaultwarden-pgweb` | Exited (profile `debug`) | @@ -88,11 +88,11 @@ Directories for `authelia`, `conduit`, `dendrite`, `mastodon`, `rustdesk`, `zita ## Verified -Last checked: **2026-08-01 18:55 CST** — operational. +Last checked: **2026-08-29** — operational. - `vaultwarden` + `vw-db` healthy; `DATABASE_URL` → `pg:5432/vaultwarden` - `https://auth.wsvc.info/` **200**, `/admin` **200**, `/api/config` OK (`disableUserRegistration: true`) -- Identity wrong-password → **400** business error (DB readable, not 500) -- SMTP: container → `mx2:587` OK; STARTTLS cert CN=`mx2.windy.me`; **AUTH OK** with effective `config.json` password (synced with `.env` / `.smtp-credentials`) -- LE cert CN=`auth.wsvc.info` -- PG counts: users=1, ciphers=1327 +- SMTP: container → `mx2:587` OK; **AUTH OK** with effective `config.json` password (synced with `.env` / `.smtp-credentials`, fingerprint match) +- PG counts: users=1, ciphers=1360 +- Image `vaultwarden/server:1.37.2` (**upgraded 2026-08-29** from 1.37.1; required for Bitwarden clients 2026.8.0+); post-upgrade 404 fixed by Traefik restart, then 200 +- vps-health local check **installed 2026-08-29** (`vps-healthcheck.timer` daily 06:15 + `/usr/local/lib/vps-health/run`); `health-report.yml --limit vaultwarden` now passes (**ok**, was failing due to missing check infra + script bugs fixed: trim_blocks render, pgweb debug-profile false positive, SMTP probe moved host-side since image lacks python3)