`BAO_TOKEN=<root> bash update.sh update` -- which the previous commit and the pre-existing DR runbook both recommended -- leaks the token twice over. It goes into the shell history, and `docker compose exec -e BAO_TOKEN=<value>` puts it in the docker process's argv, where /proc/<pid>/cmdline makes it readable by every user on the host. The second is the worse one and is not fixed by anything the operator does at their prompt. The token now reaches the container over STDIN and is never an argument to anything: the container's own shell reads one line, exports it, and execs bao. Verified locally that the child process sees the exact value -- including &, | and a backslash -- while its argv contains zero occurrences of it. How a token is supplied, in order: an already-exported BAO_TOKEN; a 0600 file named by the new BAO_TOKEN_FILE; otherwise an echo-off prompt. With no token and no terminal it dies telling the operator to use BAO_TOKEN_FILE rather than an inline assignment, and says why. Added a `snapshot` subcommand so the DR runbook no longer needs an inline pipeline at all. That runbook and the deploy banner both carried the -e form before this deployment had an updater, so both are corrected: taking a backup is now `bash update.sh snapshot`, and restore keeps the stdin shape rather than -e. The Kanrisha bootstrap lines say to export the token first instead of passing it inline. The conf file still refuses to hold a token, but now points at BAO_TOKEN_FILE as the unattended answer -- and suggests a snapshot-policy token rather than the root token for it. Not verified without a live host: that `docker compose exec -T` forwards stdin as expected. If it does not, the snapshot fails loudly with a permission error and the upgrade aborts before touching anything, which is the safe direction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
513 lines
20 KiB
Bash
513 lines
20 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# update.sh -- update the OpenBao container. Companion to deploy.sh, installed
|
|
# alongside the stack.
|
|
#
|
|
# THIS UPDATER IS DELIBERATELY UNLIKE THE OTHERS IN THIS REPO. copyparty and
|
|
# ergo come back by themselves after a recreate. OpenBao comes back SEALED: with
|
|
# the default Shamir seal, a restart needs three unseal keys typed in by a human
|
|
# before the vault serves anything again. A scheduled update at 03:00 would
|
|
# therefore take the vault offline until someone turns up with the keys.
|
|
#
|
|
# So the scheduled path NEVER changes the running version by default. It reports.
|
|
# UPDATE_POLICY=auto opts in, and even then it refuses unless auto-unseal is
|
|
# configured -- because only then does the vault come back on its own.
|
|
#
|
|
# It also takes a raft snapshot before touching anything, which neither of the
|
|
# other updaters needs to do. OpenBao's upgrade guide is explicit that reverting
|
|
# the image alone does NOT roll back the data store, so that snapshot is the
|
|
# rollback plan, not a formality. The snapshot is token-gated and cannot be taken
|
|
# from a sealed vault, both of which this script checks up front rather than
|
|
# failing halfway through.
|
|
#
|
|
# Subcommands:
|
|
# check (default) declared vs running vs latest, seal state, and whether
|
|
# an unattended update would be safe here. Changes nothing.
|
|
# snapshot take a raft snapshot and leave it in SNAPSHOT_DIR. Nothing else.
|
|
# update do it now: preflight -> snapshot -> down -> pin -> pull -> up
|
|
# run what the schedule invokes; obeys UPDATE_POLICY
|
|
# install schedule the daily `run`
|
|
# uninstall remove the schedule
|
|
#
|
|
# Policy (UPDATE_POLICY):
|
|
# notify (default) never change the running version; report only
|
|
# auto update when a newer release exists -- but ONLY if auto-unseal is
|
|
# configured. Without it, `run` reports and does nothing.
|
|
#
|
|
# Env (also read from /etc/openbao-update.conf; environment wins):
|
|
# STACK_DIR=/srv/openbao UPDATE_POLICY=notify
|
|
# BAO_TOKEN= required by `update`/`snapshot`. Prefer NOT to set
|
|
# this inline -- see "Supplying the token" below.
|
|
# BAO_TOKEN_FILE= read the token from a file (0600) instead
|
|
# SNAPSHOT_DIR=/var/backups/openbao
|
|
# TARGET_VERSION= pin a specific version instead of latest
|
|
# DRY_RUN=0 print what would happen, change nothing
|
|
# SKIP_SNAPSHOT=0 DANGEROUS: upgrade with no rollback plan
|
|
# GH_REPO=openbao/openbao
|
|
#
|
|
# Supplying the token:
|
|
# `BAO_TOKEN=<root> bash update.sh update` works, but DO NOT do it: the value
|
|
# lands in your shell history, and anything on the argv of a child process is
|
|
# readable by every user on the box via /proc/<pid>/cmdline. This script never
|
|
# puts the token on any command line -- it hands it to the container over
|
|
# stdin -- and it will prompt for it with echo off if you do not supply one.
|
|
# For unattended use, point BAO_TOKEN_FILE at a 0600 file.
|
|
#
|
|
# Usage:
|
|
# bash update.sh check # no token needed
|
|
# bash update.sh snapshot # prompts for the token
|
|
# bash update.sh update # prompts for the token
|
|
# BAO_TOKEN_FILE=/root/.bao-root bash update.sh update
|
|
# TARGET_VERSION=2.6.2 bash update.sh update
|
|
|
|
set -euo pipefail
|
|
|
|
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
|
|
|
: "${OPENBAO_UPDATE_CONF:=/etc/openbao-update.conf}"
|
|
if [[ -r "$OPENBAO_UPDATE_CONF" ]]; then
|
|
# Environment wins over the conf: only set what is not already set.
|
|
while IFS= read -r _line || [[ -n "$_line" ]]; do
|
|
[[ "$_line" =~ ^[[:space:]]*# || -z "${_line//[[:space:]]/}" ]] && continue
|
|
_k="${_line%%=*}"; _v="${_line#*=}"; _k="${_k//[[:space:]]/}"
|
|
[[ -n "$_k" ]] || continue
|
|
[[ -n "${!_k+x}" ]] || printf -v "$_k" '%s' "$_v"
|
|
done < "$OPENBAO_UPDATE_CONF"
|
|
fi
|
|
|
|
: "${STACK_DIR:=/srv/openbao}"
|
|
: "${UPDATE_POLICY:=notify}"
|
|
: "${BAO_TOKEN:=}"
|
|
: "${BAO_TOKEN_FILE:=}"
|
|
: "${SNAPSHOT_DIR:=/var/backups/openbao}"
|
|
: "${TARGET_VERSION:=}"
|
|
: "${DRY_RUN:=0}"
|
|
: "${SKIP_SNAPSHOT:=0}"
|
|
: "${GH_REPO:=openbao/openbao}"
|
|
|
|
ENV_FILE="$STACK_DIR/.env"
|
|
CONFIG_HCL="$STACK_DIR/config.hcl"
|
|
|
|
log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
# docker compose scoped to the stack dir, so ./config.hcl, ./tls and .env resolve.
|
|
dc() { ( cd "$STACK_DIR" && docker compose "$@" ); }
|
|
|
|
set_env() { # <KEY> <value>: update KEY in .env, or append if absent
|
|
# Value goes through the ENVIRONMENT, never interpolated into a sed script --
|
|
# see 947c899 / 185f404 for why.
|
|
local key="$1" val="$2" tmp
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE"
|
|
return 0
|
|
fi
|
|
tmp="$(mktemp)"
|
|
_SE_KEY="$key" _SE_VAL="$val" awk '
|
|
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
|
|
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
|
|
{ print }
|
|
END { if (!seen) print k "=" v }
|
|
' "$ENV_FILE" > "$tmp"
|
|
cat "$tmp" > "$ENV_FILE"
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
env_get() { # <KEY> -> value from .env ('' if absent)
|
|
[[ -f "$ENV_FILE" ]] || { printf ''; return 0; }
|
|
sed -n "s/^$1=//p" "$ENV_FILE" | tail -n1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Token handling
|
|
#
|
|
# The token never appears on a command line. `docker exec -e BAO_TOKEN=<value>`
|
|
# would put it in the docker process's argv, and /proc/<pid>/cmdline is
|
|
# world-readable -- so it would leak to every user on the host, not just to the
|
|
# shell history. Instead the value is written to the container's stdin and the
|
|
# container's own shell reads it into the environment it then execs `bao` with.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
acquire_token() {
|
|
[[ -n "$BAO_TOKEN" ]] && return 0
|
|
if [[ -n "$BAO_TOKEN_FILE" ]]; then
|
|
[[ -r "$BAO_TOKEN_FILE" ]] || die "BAO_TOKEN_FILE=${BAO_TOKEN_FILE} is not readable."
|
|
BAO_TOKEN="$(head -n1 "$BAO_TOKEN_FILE")"
|
|
[[ -n "$BAO_TOKEN" ]] || die "BAO_TOKEN_FILE=${BAO_TOKEN_FILE} is empty."
|
|
return 0
|
|
fi
|
|
if [[ -t 0 ]]; then
|
|
read -rs -p "OpenBao token (input hidden): " BAO_TOKEN
|
|
printf '\n' >&2
|
|
[[ -n "$BAO_TOKEN" ]] || die "No token entered."
|
|
return 0
|
|
fi
|
|
die "No token available and stdin is not a terminal. Set BAO_TOKEN_FILE=/path/to/a/0600/file, or run interactively to be prompted. Avoid BAO_TOKEN=... on the command line: it goes into your shell history and into /proc/<pid>/cmdline."
|
|
}
|
|
|
|
# Run `bao <args...>` inside the container with BAO_TOKEN set, passing the token
|
|
# over stdin so it never reaches an argv anywhere on the host.
|
|
bao_with_token() {
|
|
printf '%s\n' "$BAO_TOKEN" | dc exec -T openbao sh -c '
|
|
read -r _t
|
|
BAO_TOKEN="$_t"
|
|
export BAO_TOKEN
|
|
exec "$@"
|
|
' sh bao "$@"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# State
|
|
# ---------------------------------------------------------------------------
|
|
|
|
declared_version() { env_get OPENBAO_TAG; }
|
|
|
|
running_version() {
|
|
# `bao status` prints "Version 2.6.2". Works sealed or unsealed.
|
|
# `|| true` inside the substitution: status exits 2 when sealed, and this is
|
|
# a bare assignment at every call site.
|
|
dc exec -T openbao bao status -address=https://127.0.0.1:8200 -tls-skip-verify 2>/dev/null \
|
|
| awk '/^Version/ { print $2; exit }' || true
|
|
}
|
|
|
|
# 0 = unsealed, 2 = sealed, anything else = could not tell.
|
|
seal_state() {
|
|
local rc=0
|
|
dc exec -T openbao bao status -address=https://127.0.0.1:8200 -tls-skip-verify \
|
|
>/dev/null 2>&1 || rc=$?
|
|
printf '%s' "$rc"
|
|
}
|
|
|
|
container_running() {
|
|
local id
|
|
id="$(dc ps -q openbao 2>/dev/null || true)"
|
|
[[ -n "$id" ]] || return 1
|
|
[[ "$(docker inspect -f '{{.State.Status}}' "$id" 2>/dev/null || echo unknown)" == running ]]
|
|
}
|
|
|
|
# An uncommented `seal "..." {` stanza means the vault unseals itself, which is
|
|
# the only condition under which an unattended update is defensible.
|
|
auto_unseal_configured() {
|
|
[[ -f "$CONFIG_HCL" ]] || return 1
|
|
grep -qE '^[[:space:]]*seal[[:space:]]+"' "$CONFIG_HCL"
|
|
}
|
|
|
|
latest_version() {
|
|
# No jq on a stock Alpine host; parse the tag out with sed.
|
|
curl -fsSL --max-time 20 "https://api.github.com/repos/${GH_REPO}/releases/latest" 2>/dev/null \
|
|
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\{0,1\}\([^"]*\)".*/\1/p' \
|
|
| head -n1 || true
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Preflight + snapshot
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# The built-in pkcs11 seal is REMOVED in 2.7.0 (not merely deprecated), and the
|
|
# HSM distribution is discontinued. Crossing that line with the stanza active
|
|
# gives a vault that cannot unseal itself and cannot be unsealed by hand either.
|
|
check_pkcs11_cliff() { # <target-version>
|
|
local target="$1" major_minor
|
|
major_minor="${target%.*}"
|
|
case "$major_minor" in
|
|
2.7|2.8|2.9|3.*) ;;
|
|
*) return 0 ;;
|
|
esac
|
|
if [[ -f "$CONFIG_HCL" ]] && grep -qE '^[[:space:]]*seal[[:space:]]+"pkcs11"' "$CONFIG_HCL"; then
|
|
die "config.hcl has an active built-in seal \"pkcs11\" stanza and the target is ${target}. That stanza is REMOVED in 2.7.0 -- the vault would start with no way to unseal. Migrate to the external 'plugin \"kms\" \"pkcs11\"' first."
|
|
fi
|
|
}
|
|
|
|
take_snapshot() { # <target-version> -> echoes the snapshot path
|
|
local target="$1" stamp dest
|
|
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
dest="${SNAPSHOT_DIR}/openbao-pre-${target}-${stamp}.snap"
|
|
|
|
if [[ "$SKIP_SNAPSHOT" == "1" ]]; then
|
|
warn "SKIP_SNAPSHOT=1 -- upgrading with NO rollback plan. Reverting the image alone does not roll back the data store."
|
|
printf ''
|
|
return 0
|
|
fi
|
|
|
|
acquire_token
|
|
|
|
if [[ "$DRY_RUN" == "1" ]]; then
|
|
echo "DRY: snapshot -> ${dest}" >&2
|
|
printf '%s' "$dest"
|
|
return 0
|
|
fi
|
|
|
|
install -d -m 0700 "$SNAPSHOT_DIR"
|
|
log "Taking a raft snapshot to ${dest}..." >&2
|
|
# Write inside the container, then stream the RAW file out. `compose cp` is
|
|
# NOT usable here: it emits a TAR wrapper that will not restore.
|
|
bao_with_token operator raft snapshot save \
|
|
-address=https://127.0.0.1:8200 -tls-skip-verify /tmp/pre-upgrade.snap \
|
|
>/dev/null || die "Snapshot failed -- not upgrading. Check the token has sys/storage/raft/snapshot."
|
|
dc exec -T openbao cat /tmp/pre-upgrade.snap > "$dest" || die "Could not stream the snapshot out -- not upgrading."
|
|
dc exec -T openbao rm -f /tmp/pre-upgrade.snap >/dev/null 2>&1 || true
|
|
chmod 0600 "$dest"
|
|
|
|
# A snapshot is a gzip-wrapped tar. An empty or truncated file here means the
|
|
# rollback plan does not exist, so treat it as fatal rather than cosmetic.
|
|
[[ -s "$dest" ]] || die "Snapshot at ${dest} is empty -- not upgrading."
|
|
if command -v gzip >/dev/null 2>&1; then
|
|
gzip -t "$dest" 2>/dev/null || die "Snapshot at ${dest} is not a valid gzip archive -- not upgrading."
|
|
fi
|
|
log "Snapshot OK ($(wc -c < "$dest") bytes). Copy it OFF this host." >&2
|
|
printf '%s' "$dest"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Subcommands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
do_check() {
|
|
[[ -d "$STACK_DIR" ]] || die "No stack at $STACK_DIR (set STACK_DIR)."
|
|
local declared running latest state auto
|
|
declared="$(declared_version)"
|
|
latest="$(latest_version)"
|
|
|
|
if container_running; then
|
|
running="$(running_version)"
|
|
state="$(seal_state)"
|
|
else
|
|
running=""
|
|
state="down"
|
|
fi
|
|
auto_unseal_configured && auto=yes || auto=no
|
|
|
|
printf ' declared (.env OPENBAO_TAG): %s\n' "${declared:-<unset>}"
|
|
printf ' running (bao status): %s\n' "${running:-<not running>}"
|
|
printf ' latest (%s): %s\n' "$GH_REPO" "${latest:-<lookup failed>}"
|
|
case "$state" in
|
|
0) printf ' seal state: UNSEALED\n' ;;
|
|
2) printf ' seal state: SEALED (needs 3 keys)\n' ;;
|
|
down) printf ' seal state: container not running\n' ;;
|
|
*) printf ' seal state: unknown (bao status rc=%s)\n' "$state" ;;
|
|
esac
|
|
printf ' auto-unseal configured: %s\n' "$auto"
|
|
printf ' policy: %s\n' "$UPDATE_POLICY"
|
|
|
|
if [[ -z "$latest" ]]; then
|
|
warn "Could not reach the GitHub releases API; cannot say whether an update exists."
|
|
return 0
|
|
fi
|
|
if [[ "$declared" == "$latest" ]]; then
|
|
log "Up to date."
|
|
return 0
|
|
fi
|
|
|
|
log "Update available: ${declared:-?} -> ${latest}"
|
|
if [[ "$auto" == no ]]; then
|
|
warn "A restart will leave the vault SEALED until someone enters three unseal keys."
|
|
warn "Run it when you can do that: bash ${SELF} update (it will prompt for a token)"
|
|
fi
|
|
}
|
|
|
|
do_update() {
|
|
[[ $EUID -eq 0 ]] || die "Run as root."
|
|
[[ -d "$STACK_DIR" ]] || die "No stack at $STACK_DIR (set STACK_DIR)."
|
|
|
|
local from to snap
|
|
from="$(declared_version)"
|
|
to="${TARGET_VERSION:-$(latest_version)}"
|
|
[[ -n "$to" ]] || die "Could not determine a target version (GitHub lookup failed). Set TARGET_VERSION=x.y.z."
|
|
|
|
if [[ "$from" == "$to" ]]; then
|
|
log "Already pinned to ${to}; nothing to do."
|
|
return 0
|
|
fi
|
|
|
|
check_pkcs11_cliff "$to"
|
|
|
|
container_running || die "The openbao container is not running. Start it first: cd ${STACK_DIR} && docker compose up -d"
|
|
|
|
# A sealed vault cannot produce a snapshot, so there would be no rollback
|
|
# plan. Catch it here rather than after the container is already down.
|
|
local state; state="$(seal_state)"
|
|
case "$state" in
|
|
0) ;;
|
|
2) die "The vault is SEALED. A snapshot cannot be taken from a sealed vault, so there would be no rollback plan. Unseal first, then re-run." ;;
|
|
*) die "Could not read the seal state (bao status rc=${state}). Refusing to upgrade blind." ;;
|
|
esac
|
|
|
|
log "Updating OpenBao: ${from:-?} -> ${to}"
|
|
snap="$(take_snapshot "$to")"
|
|
|
|
if [[ "$DRY_RUN" == "1" ]]; then
|
|
echo "DRY: docker compose down; set OPENBAO_TAG=${to}; docker compose pull; docker compose up -d"
|
|
return 0
|
|
fi
|
|
|
|
# Clean shutdown before swapping the image, as the upgrade guide asks.
|
|
log "Stopping the stack (the vault will be sealed when it returns)..."
|
|
dc down
|
|
|
|
set_env OPENBAO_TAG "$to"
|
|
|
|
if ! dc pull; then
|
|
warn "Pull of ${to} failed; rolling the pin back to ${from:-<unset>}."
|
|
[[ -n "$from" ]] && set_env OPENBAO_TAG "$from"
|
|
dc up -d || true
|
|
die "Update aborted. The previous version is starting again; unseal it."
|
|
fi
|
|
|
|
if ! dc up -d --remove-orphans; then
|
|
warn "Starting ${to} failed; rolling the pin back to ${from:-<unset>}."
|
|
[[ -n "$from" ]] && set_env OPENBAO_TAG "$from"
|
|
dc up -d || true
|
|
die "Update aborted. If the data store is at fault, restore ${snap:-the snapshot} per the README's DR runbook."
|
|
fi
|
|
|
|
log "Started ${to}."
|
|
cat <<EOF
|
|
|
|
================================================================
|
|
UPDATED ${from:-?} -> ${to}
|
|
|
|
The vault is SEALED. Nothing works until you unseal it:
|
|
|
|
cd ${STACK_DIR}
|
|
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \\
|
|
bao operator unseal -tls-skip-verify # x3, three different keys
|
|
|
|
Then verify:
|
|
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \\
|
|
bao status -tls-skip-verify # Version ${to}, Sealed false
|
|
|
|
Rollback, if ${to} misbehaves: reverting the image alone does NOT roll back the
|
|
data store. Restore the pre-upgrade snapshot -- see the README's DR runbook.
|
|
snapshot: ${snap:-<none taken>}
|
|
================================================================
|
|
EOF
|
|
}
|
|
|
|
do_snapshot() {
|
|
[[ -d "$STACK_DIR" ]] || die "No stack at $STACK_DIR (set STACK_DIR)."
|
|
container_running || die "The openbao container is not running."
|
|
local state; state="$(seal_state)"
|
|
[[ "$state" == 0 ]] || die "The vault is SEALED (or unreadable): a snapshot can only be taken from an unsealed vault."
|
|
local dest; dest="$(take_snapshot manual)"
|
|
[[ -n "$dest" ]] || die "No snapshot was taken."
|
|
log "Snapshot written to ${dest} -- copy it OFF this host."
|
|
}
|
|
|
|
do_run() {
|
|
# What the schedule invokes. The whole point of this branch is that it is
|
|
# conservative: an unattended update of a manually-unsealed vault takes it
|
|
# offline until a human arrives, so that is never the default.
|
|
case "$UPDATE_POLICY" in
|
|
notify)
|
|
do_check
|
|
return 0 ;;
|
|
auto)
|
|
if ! auto_unseal_configured; then
|
|
do_check
|
|
warn "UPDATE_POLICY=auto but no seal stanza is configured, so the vault would stay SEALED after a restart with nobody present. Not updating."
|
|
return 0
|
|
fi
|
|
local from to
|
|
from="$(declared_version)"
|
|
to="${TARGET_VERSION:-$(latest_version)}"
|
|
if [[ -z "$to" || "$from" == "$to" ]]; then
|
|
do_check
|
|
return 0
|
|
fi
|
|
log "UPDATE_POLICY=auto and auto-unseal is configured; updating ${from:-?} -> ${to}."
|
|
do_update
|
|
return 0 ;;
|
|
*)
|
|
die "UPDATE_POLICY must be 'notify' or 'auto' (got '${UPDATE_POLICY}')." ;;
|
|
esac
|
|
}
|
|
|
|
write_conf() {
|
|
install -d -m 0755 "$(dirname "$OPENBAO_UPDATE_CONF")"
|
|
# BAO_TOKEN is deliberately NOT written here: a long-lived root token sitting
|
|
# in a conf file next to the vault it opens defeats the point of the vault.
|
|
cat > "$OPENBAO_UPDATE_CONF" <<EOF
|
|
# openbao-update.conf -- read by update.sh; the environment wins over this file.
|
|
STACK_DIR=${STACK_DIR}
|
|
UPDATE_POLICY=${UPDATE_POLICY}
|
|
SNAPSHOT_DIR=${SNAPSHOT_DIR}
|
|
GH_REPO=${GH_REPO}
|
|
# BAO_TOKEN is intentionally absent: a long-lived root token in a file next to
|
|
# the vault it opens defeats the vault. \`update\`/\`snapshot\` prompt for one.
|
|
# For unattended use set BAO_TOKEN_FILE below to a 0600 file holding a token
|
|
# with sys/storage/raft/snapshot -- not the root token.
|
|
#BAO_TOKEN_FILE=/root/.bao-snapshot-token
|
|
EOF
|
|
chmod 0600 "$OPENBAO_UPDATE_CONF"
|
|
}
|
|
|
|
do_install() {
|
|
[[ $EUID -eq 0 ]] || die "Run as root."
|
|
write_conf
|
|
if [[ "$UPDATE_POLICY" == notify ]]; then
|
|
log "Scheduling a daily CHECK (policy=notify: it will never change the running version)."
|
|
else
|
|
log "Scheduling a daily run (policy=${UPDATE_POLICY})."
|
|
fi
|
|
if [[ -r /etc/os-release ]] && grep -q '^ID=alpine' /etc/os-release; then
|
|
install -d -m 0755 /etc/periodic/daily
|
|
cat > /etc/periodic/daily/openbao-update <<EOF
|
|
#!/bin/sh
|
|
exec bash "$SELF" run
|
|
EOF
|
|
chmod +x /etc/periodic/daily/openbao-update
|
|
if command -v rc-update >/dev/null 2>&1; then
|
|
rc-update add crond default >/dev/null 2>&1 || true
|
|
rc-service crond start >/dev/null 2>&1 || true
|
|
fi
|
|
log "Installed /etc/periodic/daily/openbao-update."
|
|
else
|
|
cat > /etc/systemd/system/openbao-update.service <<EOF
|
|
[Unit]
|
|
Description=OpenBao container updater
|
|
After=docker.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/bin/env bash $SELF run
|
|
EOF
|
|
cat > /etc/systemd/system/openbao-update.timer <<EOF
|
|
[Unit]
|
|
Description=Daily OpenBao update check
|
|
|
|
[Timer]
|
|
OnCalendar=daily
|
|
Persistent=true
|
|
RandomizedDelaySec=1h
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
systemctl enable --now openbao-update.timer >/dev/null 2>&1 || true
|
|
log "Installed the openbao-update.timer systemd timer."
|
|
fi
|
|
}
|
|
|
|
do_uninstall() {
|
|
[[ $EUID -eq 0 ]] || die "Run as root."
|
|
rm -f /etc/periodic/daily/openbao-update
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl disable --now openbao-update.timer >/dev/null 2>&1 || true
|
|
rm -f /etc/systemd/system/openbao-update.timer /etc/systemd/system/openbao-update.service
|
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
|
fi
|
|
log "Schedule removed. ${OPENBAO_UPDATE_CONF} left in place."
|
|
}
|
|
|
|
case "${1:-check}" in
|
|
check) do_check ;;
|
|
snapshot) do_snapshot ;;
|
|
update) do_update ;;
|
|
run) do_run ;;
|
|
install) do_install ;;
|
|
uninstall) do_uninstall ;;
|
|
*) die "Usage: $(basename "$0") {check|snapshot|update|run|install|uninstall}" ;;
|
|
esac
|