fix(openbao): detect the account the entrypoint drops to, not the probe's
The image's entrypoint starts as root and then runs `su-exec openbao "$@"` before exec'ing the server. deploy.sh probed with `--entrypoint id -u`, which BYPASSES the entrypoint, so it reported UID 0 and chowned config.hcl, tls/ and the raft volume to root -- while the server ran as uid 100 and could write none of it: error initializing storage of type raft: failed to create fsm: failed to open bolt file: open /openbao/data/vault.db: permission denied The container crash-looped on that, and tls.key (0600 root:root) would have failed the listener straight afterwards. Confirmed on the affected host: the probe reports 0, `id -u openbao` reports 100, and the entrypoint's line 92 is `set -- su-exec openbao "$@"`. Ask for the account the entrypoint switches to, falling back to the old probe when the image has no such account (then the server really does run as whatever the entrypoint started as). Preferring the service account is also the safe direction to be wrong in: root ignores file permissions, so chowning to the unprivileged uid still works if the server turns out to run as root, whereas the reverse is fatal. Gate the raft chown on the volume's ACTUAL ownership rather than on a first-run flag. The flag was false forever after the first deploy, so a volume left root-owned by an earlier run -- exactly the state this bug created -- could never be repaired by re-running; the operator had to chown it by hand. Reading the owner costs one container start and still keeps the recursive chown off a healthy live raft dir. FIRST_RUN is now unused, so it is gone. Also from the same run, two reporting failures: - `docker compose ps` printed "Restarting (1)" and the script went on to print an unqualified DEPLOYED banner. It now inspects the container state and, when it is not `running`, says plainly that this is a crash loop rather than the expected sealed-and-unhealthy state -- before and after the banner, with the logs command to run. - Every `docker compose` command in that banner assumed the project directory. deploy.sh runs them from $STACK_DIR itself, so the omission only bit the operator afterwards, with "no configuration file provided: not found". The banner now says to cd there first and quotes that error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <<EOF
|
||||
|
||||
================================================================
|
||||
@@ -369,6 +398,10 @@ DEPLOYED (OpenBao starts SEALED -- finish setup below)
|
||||
|
||||
Address: https://${OPENBAO_ADDR}:8200
|
||||
Stack dir: ${STACK_DIR}
|
||||
^ run every 'docker compose' command below from there:
|
||||
cd ${STACK_DIR}
|
||||
From anywhere else compose reports "no configuration file
|
||||
provided: not found".
|
||||
TLS: ${STACK_DIR}/tls/tls.crt (give this to the Kanrisha daemon as
|
||||
[encryption.openbao].ca_cert)
|
||||
|
||||
@@ -411,6 +444,10 @@ Re-running this script is idempotent (it won't re-init or touch .env / tls).
|
||||
================================================================
|
||||
EOF
|
||||
|
||||
if [[ "$BAO_STATE" != "running" ]]; then
|
||||
warn "Reminder: the container is '${BAO_STATE}'. Fix that before step 1 above."
|
||||
fi
|
||||
|
||||
# IMPORTANT: nothing executable below this line. Everything after
|
||||
# __ARCHIVE_BELOW__ is the embedded tar.gz payload (base64), added by build.sh.
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user