diff --git a/deployments/openbao/deploy.sh b/deployments/openbao/deploy.sh index ee73647..8465f26 100644 --- a/deployments/openbao/deploy.sh +++ b/deployments/openbao/deploy.sh @@ -320,12 +320,6 @@ install_docker cd "$STACK_DIR" -# Record whether this is the first deploy (raft volume not yet created) BEFORE any -# `docker compose run` below instantiates the volume -- the first-run data chown -# gates on this. -FIRST_RUN=0 -docker volume inspect openbao_openbao-data >/dev/null 2>&1 || FIRST_RUN=1 - log "Pulling image..." docker compose pull @@ -334,24 +328,47 @@ docker compose pull # read-only bind mounts and writes the raft data volume. So those must be owned by # that UID. Detect it from the pulled image (-T: no TTY on the piped stdout) and # align ownership; a no-op when the image runs as root. -BAO_UID=$(docker compose run --rm --no-deps -T --entrypoint id openbao -u 2>/dev/null | tr -dc '0-9') +# Ask for the account the ENTRYPOINT switches to, not the one a probe starts as. +# The image's entrypoint runs as root and then does `su-exec openbao "$@"` before +# exec'ing the server, so `--entrypoint id -u` bypasses that drop and reports 0. +# Chowning to 0 on the strength of that leaves the server running as the +# unprivileged account with a root-owned raft volume, and it crash-loops on +# "failed to open bolt file: /openbao/data/vault.db: permission denied". +BAO_UID=$(docker compose run --rm --no-deps -T --entrypoint sh openbao \ + -c 'id -u openbao 2>/dev/null' 2>/dev/null | tr -dc '0-9') if [[ -z "$BAO_UID" ]]; then - # `id -u` always prints a number if it ran, so empty means detection failed -- - # do NOT silently assume root (that would re-create the unreadable-config - # crash-loop on a non-root image). Fall back to root but say so loudly. - warn "Could not detect the OpenBao container UID; assuming root. If OpenBao crash-loops on start, chown $STACK_DIR/config.hcl + $STACK_DIR/tls (and the openbao-data volume) to the container's UID (docker compose exec openbao id -u)." + # No such account: this image runs the server as whatever the entrypoint + # started as, so the older probe is the right answer here. + BAO_UID=$(docker compose run --rm --no-deps -T --entrypoint id openbao -u 2>/dev/null | tr -dc '0-9') +fi +if [[ -z "$BAO_UID" ]]; then + # Both probes failed. Falling back to root is the DANGEROUS direction -- it is + # exactly what produced the crash loop -- so say how to recover. + warn "Could not detect the OpenBao service account; assuming root." + warn "If OpenBao crash-loops with a permission error on /openbao/data, run:" + warn " cd ${STACK_DIR} && docker compose down" + warn " docker run --rm -v openbao_openbao-data:/data -v ${STACK_DIR}/tls:/tls \\" + warn " --entrypoint sh openbao/openbao:\${OPENBAO_TAG:-2.5.5} -c 'chown -R openbao:openbao /data /tls'" BAO_UID=0 fi -log "OpenBao container runs as UID ${BAO_UID}; aligning file/volume ownership." +log "OpenBao server runs as UID ${BAO_UID}; aligning file/volume ownership." chown -R "${BAO_UID}:${BAO_UID}" "$STACK_DIR/config.hcl" "$STACK_DIR/tls" # tls.key stays private to that UID; config.hcl + tls.crt are non-secret. chmod 0600 "$STACK_DIR/tls/tls.key" 2>/dev/null || true -# Raft data volume: created root-owned on first mount. Only the FIRST run needs -# the chown (idempotent re-runs would recursively re-chown a live raft dir). -if [[ "$FIRST_RUN" == "1" ]]; then +# Raft data volume. Gate on what the volume ACTUALLY is, not on whether this is +# the first run: a volume left root-owned by an earlier deploy (or by a botched +# UID detection) would otherwise never be repaired, since the first-run flag is +# false forever after. Reading the current owner costs one container start and +# still keeps the recursive chown off a healthy live raft dir. +_data_uid=$(docker compose run --rm --no-deps -T --user 0:0 --entrypoint stat openbao \ + -c '%u' /openbao/data 2>/dev/null | tr -dc '0-9') +if [[ -z "$_data_uid" ]]; then + warn "Could not read the raft volume's ownership; skipping the data chown." +elif [[ "$_data_uid" != "$BAO_UID" ]]; then + log "Raft volume is owned by UID ${_data_uid}; chowning to ${BAO_UID}..." docker compose run --rm --no-deps --user 0:0 --entrypoint chown openbao \ -R "${BAO_UID}:${BAO_UID}" /openbao/data 2>/dev/null || \ - warn "Could not pre-chown the raft data volume; if OpenBao fails to write storage, chown the openbao-data volume to UID ${BAO_UID}." + warn "Could not chown the raft data volume; OpenBao will fail to write storage. chown the openbao-data volume to UID ${BAO_UID}." fi log "Starting OpenBao..." @@ -362,6 +379,18 @@ docker compose up -d --remove-orphans sleep 5 docker compose ps +# OpenBao starts SEALED and therefore reports UNHEALTHY until it is initialised +# and unsealed -- that is expected and the summary below explains it. A +# RESTARTING container is a different thing entirely: it crashed, compose is +# looping it, and every command in that summary will fail against it. Say so +# plainly rather than printing an unqualified DEPLOYED. +BAO_STATE="$(docker inspect -f '{{.State.Status}}' openbao 2>/dev/null || echo unknown)" +if [[ "$BAO_STATE" != "running" ]]; then + warn "Container state is '${BAO_STATE}', not 'running' -- OpenBao is crash-looping, not merely sealed." + warn "Nothing below will work until that is fixed. Start with:" + warn " cd ${STACK_DIR} && docker compose logs --tail=60 openbao" +fi + cat <