|
38 | 38 | set -o allexport; source "$ROOT_DIR/.env"; set +o allexport |
39 | 39 |
|
40 | 40 | missing=() |
41 | | - for var in VAULT_EMAIL VAULT_PASSWORD PAGES_HOST VAULT_HOST_URL VAULT_HOST_PORT; do |
| 41 | + for var in \ |
| 42 | + VAULT_EMAIL VAULT_PASSWORD \ |
| 43 | + PAGES_HOST VAULT_HOST_URL VAULT_HOST_PORT \ |
| 44 | + CLI_SERVE_HOST CLI_SERVE_PORT \ |
| 45 | + EXTENSION_BUILD_PATH \ |
| 46 | + BW_DOMAIN \ |
| 47 | + BW_DB_PROVIDER BW_DB_SERVER BW_DB_DATABASE \ |
| 48 | + BW_DB_USERNAME BW_DB_PASSWORD BW_DB_PORT \ |
| 49 | + BW_ENABLE_SSL BW_SSL_CERT BW_SSL_KEY; do |
42 | 50 | val=$(eval "echo \"\${${var}:-}\"") |
43 | 51 | if [ -z "$val" ] || [[ "$val" == *"<"* ]]; then |
44 | 52 | missing+=("$var") |
|
53 | 61 | fi |
54 | 62 | fi |
55 | 63 |
|
| 64 | +# ── Vault import file (optional) ────────────────────────────────────────────── |
| 65 | +if [ -n "${VAULT_IMPORT_FILE:-}" ]; then |
| 66 | + if [ -f "$ROOT_DIR/$VAULT_IMPORT_FILE" ]; then |
| 67 | + row "$OK" "Vault import file" "$VAULT_IMPORT_FILE" |
| 68 | + else |
| 69 | + row "$ERR" "Vault import file" "not found ($VAULT_IMPORT_FILE)" |
| 70 | + errors=$((errors + 1)) |
| 71 | + fi |
| 72 | +else |
| 73 | + row "$WARN" "Vault import file" "not set (optional)" |
| 74 | + warnings=$((warnings + 1)) |
| 75 | +fi |
| 76 | + |
56 | 77 | # ── SSL certificates ────────────────────────────────────────────────────────── |
57 | 78 | if [ -f "$ROOT_DIR/ssl.crt" ] && [ -f "$ROOT_DIR/ssl.key" ]; then |
58 | 79 | row "$OK" "SSL certificates" "ssl.crt / ssl.key" |
@@ -119,20 +140,34 @@ else |
119 | 140 | if [ -n "$BW_ID" ]; then |
120 | 141 | BW_HEALTH=$(docker inspect --format "{{.State.Health.Status}}" "$BW_ID" 2>/dev/null || true) |
121 | 142 | ACTUAL_IMAGE=$(docker inspect --format "{{.Config.Image}}" "$BW_ID" 2>/dev/null || true) |
| 143 | + BW_PROJECT=$(docker inspect --format \ |
| 144 | + "{{index .Config.Labels \"com.docker.compose.project\"}}" "$BW_ID" 2>/dev/null || true) |
| 145 | + BW_NAME=$(docker inspect --format "{{.Name}}" "$BW_ID" 2>/dev/null | sed 's|^/||' || true) |
| 146 | + BW_PORTS=$(docker ps --filter "id=$BW_ID" --format "{{.Ports}}" 2>/dev/null || true) |
| 147 | + DB_IMAGE=$(docker ps \ |
| 148 | + --filter "label=com.docker.compose.project=${BW_PROJECT}" \ |
| 149 | + --filter "label=com.docker.compose.service=db" \ |
| 150 | + --format "{{.Image}}" 2>/dev/null | head -1 || true) |
| 151 | + |
| 152 | + show_container_details() { |
| 153 | + hint "name: ${BW_NAME}${DB_IMAGE:+ | db: ${DB_IMAGE}}${BW_PORTS:+ | ports: ${BW_PORTS}}" |
| 154 | + } |
122 | 155 |
|
123 | 156 | if [ "$BW_HEALTH" = "unhealthy" ]; then |
124 | 157 | row "$ERR" "Docker" "bitwarden unhealthy (${ACTUAL_IMAGE})" |
| 158 | + show_container_details |
125 | 159 | hint "docker compose logs bitwarden" |
126 | 160 | errors=$((errors + 1)) |
127 | 161 | elif [ "$BW_HEALTH" = "starting" ]; then |
128 | 162 | row "$WARN" "Docker" "bitwarden still starting up... (${ACTUAL_IMAGE})" |
| 163 | + show_container_details |
129 | 164 | warnings=$((warnings + 1)) |
130 | 165 | elif [ "$ACTUAL_IMAGE" = "$EXPECTED_IMAGE" ]; then |
131 | 166 | row "$OK" "Docker" "running (${ACTUAL_IMAGE})" |
| 167 | + show_container_details |
132 | 168 | else |
133 | | - BW_PROJECT=$(docker inspect --format \ |
134 | | - "{{index .Config.Labels \"com.docker.compose.project\"}}" "$BW_ID" 2>/dev/null || true) |
135 | 169 | row "$WARN" "Docker" "running wrong version (got: ${ACTUAL_IMAGE}, want: ${EXPECTED_IMAGE})" |
| 170 | + show_container_details |
136 | 171 | if [ -n "$BW_PROJECT" ]; then |
137 | 172 | hint "docker compose -p ${BW_PROJECT} down && docker compose up -d --build --wait" |
138 | 173 | else |
@@ -208,24 +243,82 @@ if curl -sf $CACERT_OPT --max-time 3 "${VAULT_URL}/alive" > /dev/null 2>&1; then |
208 | 243 | hint "npm run setup:vault" |
209 | 244 | errors=$((errors + 1)) |
210 | 245 | fi |
| 246 | + |
| 247 | + # Check feature flags |
| 248 | + if [ -n "${REMOTE_VAULT_CONFIG_MATCH:-}" ]; then |
| 249 | + row "$OK" "Feature flags" "synced from remote (${REMOTE_VAULT_CONFIG_MATCH})" |
| 250 | + else |
| 251 | + CONFIG_JSON=$(curl -s $CACERT_OPT --max-time 5 "${VAULT_URL}/api/config" 2>/dev/null || true) |
| 252 | + FLAG_COUNT=$(python3 -c " |
| 253 | +import json, sys |
| 254 | +try: |
| 255 | + d = json.loads(sys.argv[1]) |
| 256 | + print(len(d.get('featureStates', {}))) |
| 257 | +except: |
| 258 | + print(-1) |
| 259 | +" "$CONFIG_JSON" 2>/dev/null || echo -1) |
| 260 | + |
| 261 | + if [ "$FLAG_COUNT" -gt 0 ] 2>/dev/null; then |
| 262 | + row "$OK" "Feature flags" "${FLAG_COUNT} flags loaded" |
| 263 | + elif [ "$FLAG_COUNT" -eq 0 ] 2>/dev/null; then |
| 264 | + row "$WARN" "Feature flags" "featureStates is empty — flags may not be configured" |
| 265 | + hint "npm run setup:flags # sync from a remote config source" |
| 266 | + warnings=$((warnings + 1)) |
| 267 | + else |
| 268 | + row "$WARN" "Feature flags" "could not read /api/config" |
| 269 | + warnings=$((warnings + 1)) |
| 270 | + fi |
| 271 | + fi |
211 | 272 | else |
212 | 273 | row "$WARN" "Vault" "not reachable (${VAULT_URL})" |
213 | 274 | warnings=$((warnings + 1)) |
214 | 275 | fi |
215 | 276 |
|
216 | 277 | # ── Test site ───────────────────────────────────────────────────────────────── |
| 278 | +# Playwright's webServer starts/stops the test site automatically. |
| 279 | +# Probe that it's serveable: start in background, hit the endpoint, then stop. |
217 | 280 | PAGES_URL="${PAGES_HOST:-https://127.0.0.1}${PAGES_HOST_PORT:+:${PAGES_HOST_PORT}}" |
218 | | -PID_FILE="$ROOT_DIR/.test-site.pid" |
219 | 281 |
|
220 | | -if curl -sf $CACERT_OPT --max-time 3 "${PAGES_URL}" > /dev/null 2>&1; then |
221 | | - row "$OK" "Test site" "running (${PAGES_URL})" |
222 | | -elif [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then |
223 | | - row "$WARN" "Test site" "process alive but not yet responding" |
224 | | - warnings=$((warnings + 1)) |
| 282 | +_probe_test_site() { |
| 283 | + local pid log i ok=0 |
| 284 | + log=$(mktemp) |
| 285 | + |
| 286 | + # If something is already serving on this port, the server is clearly up |
| 287 | + if curl -sf $CACERT_OPT --max-time 2 "${PAGES_URL}" > /dev/null 2>&1; then |
| 288 | + rm -f "$log"; return 0 |
| 289 | + fi |
| 290 | + |
| 291 | + # exec replaces the subshell with node directly, so $pid == node PID |
| 292 | + ( |
| 293 | + export SSL_CERT=${BW_SSL_CERT:-ssl.crt} |
| 294 | + export SSL_KEY=${BW_SSL_KEY:-ssl.key} |
| 295 | + export SERVE_PORT=$PAGES_HOST_PORT |
| 296 | + export SERVE_INSECURE_PORT=$PAGES_HOST_INSECURE_PORT |
| 297 | + cd "$ROOT_DIR/test-site/api" |
| 298 | + exec node build/app.js |
| 299 | + ) > "$log" 2>&1 & |
| 300 | + pid=$! |
| 301 | + |
| 302 | + for i in $(seq 1 15); do |
| 303 | + sleep 0.5 |
| 304 | + if ! kill -0 "$pid" 2>/dev/null; then break; fi |
| 305 | + if curl -sf $CACERT_OPT --max-time 2 "${PAGES_URL}" > /dev/null 2>&1; then |
| 306 | + ok=1; break |
| 307 | + fi |
| 308 | + done |
| 309 | + |
| 310 | + kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null |
| 311 | + rm -f "$log" |
| 312 | + return $((1 - ok)) |
| 313 | +} |
| 314 | + |
| 315 | +if _probe_test_site; then |
| 316 | + row "$OK" "Test site" "serveable (${PAGES_URL})" |
225 | 317 | else |
226 | | - row "$WARN" "Test site" "not running (${PAGES_URL})" |
227 | | - hint "npm run start:test-site" |
228 | | - warnings=$((warnings + 1)) |
| 318 | + row "$ERR" "Test site" "failed to serve" |
| 319 | + hint "cd test-site && npm run build:client # rebuild if not yet built" |
| 320 | + hint "cd test-site/api && npm run build # rebuild API if needed" |
| 321 | + errors=$((errors + 1)) |
229 | 322 | fi |
230 | 323 |
|
231 | 324 | # ── Summary ─────────────────────────────────────────────────────────────────── |
|
0 commit comments