reference-ansible/scripts/bao-seed.sh
Simon Bärlocher 2ba0c07cd3
docs(reference-ansible): add docs/ tree and document repo, playbooks, Makefile
Addresses the WKS PoC review (Notion 2026-05-26). All docs in English.
- README: purpose, docs table of contents, annotated repo tree
- docs/getting_started.md: prerequisites (WKS account, OIDC, SSH, VPN) + first deploy
- docs/ansible.md: playbook table, "Running Ansible", service parameters, cheatsheet
- docs/secrets.md: canonical Bao login (moved out of README) + demo defaults
- docs/operations.md: full Makefile reference
- docs/inventories.md: repo layout, topology, standard folder structure, walkthrough
- docs/testing.md: static checks, inventory resolution, smoke test / dry run
- remove ARCHITECTURE.md (architecture docs live externally)

Also includes the gymburgdorf inventory build-out (bookstack, homarr,
opnform, send) and scripts/bao-seed.sh. site.yml keeps a third traefik
play (traefik_servers minus the vagrant _dmz/_backend split) so the demo
inventories still configure their reverse proxy after the rebase onto main.
2026-05-28 11:20:54 +02:00

191 lines
5.6 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Seed OpenBao secrets for a demo inventory. Merge semantics: existing
# keys at a given path are kept; only missing keys are generated. OIDC
# client secrets are synced between <mount>/data/authentik and the
# per-service secret so authentik and the service agree on the same
# value.
#
# Usage:
# scripts/bao-seed.sh demo-gymburgdorf
#
# Requirements:
# - bao CLI in $PATH, authenticated (make bao)
# - jq in $PATH
# - openssl in $PATH
#
# Environment overrides:
# BAO_ADDR default https://bao.digitalboard.ch
# DRY_RUN=1 print what would change without writing
set -eu
MOUNT="${1:-}"
if [[ -z "$MOUNT" ]]; then
echo "usage: $0 <bao-mount> (e.g. demo-gymburgdorf)" >&2
exit 1
fi
: "${BAO_ADDR:=https://bao.digitalboard.ch}"
export BAO_ADDR
for cmd in bao jq openssl mktemp; do
command -v "$cmd" >/dev/null || { echo "missing: $cmd" >&2; exit 1; }
done
if ! bao token lookup >/dev/null 2>&1; then
echo "not authenticated — run 'make bao' first" >&2
exit 1
fi
DRY_RUN="${DRY_RUN:-0}"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
# Read current data for a KV-v2 secret into $WORKDIR/<path>.json.
# Writes {} if the secret does not exist or has no data yet.
read_secret() {
local path="$1"
local out="$WORKDIR/$path.json"
local raw="$WORKDIR/$path.raw"
if bao kv get -format=json "$MOUNT/$path" >"$raw" 2>/dev/null; then
if jq -e -c '.data.data // {}' "$raw" >"$out" 2>/dev/null; then
return 0
fi
fi
echo '{}' >"$out"
}
# Write the in-memory secret back to bao using `bao kv put @file` so
# values containing `=` (e.g. base64 padding) survive intact.
write_secret() {
local path="$1"
local src="$WORKDIR/$path.json"
if [[ "$DRY_RUN" == "1" ]]; then
echo " [dry-run] would write $MOUNT/$path"
return
fi
bao kv put "$MOUNT/$path" "@$src" >/dev/null
}
# Print the current value of a key from a secret file, empty string if
# absent.
get_key() {
local file="$1"
local key="$2"
jq -r --arg k "$key" '.[$k] // ""' "$file"
}
# Set a key inside the secret file to the given literal value.
set_key() {
local file="$1"
local key="$2"
local value="$3"
local tmp="$file.tmp"
jq --arg k "$key" --arg v "$value" '.[$k] = $v' "$file" >"$tmp"
mv "$tmp" "$file"
}
# Generate a key in the secret file if missing.
ensure_key() {
local path="$1"
local key="$2"
local generator="$3"
local file="$WORKDIR/$path.json"
if [[ -n "$(get_key "$file" "$key")" ]]; then
return
fi
local value
value="$($generator)"
set_key "$file" "$key" "$value"
echo " + $key"
}
# Force a key on path2 to match path1's value for the named key.
# Prints when a change happens.
sync_key_from() {
local src_path="$1"
local src_key="$2"
local dst_path="$3"
local dst_key="$4"
local src_file="$WORKDIR/$src_path.json"
local dst_file="$WORKDIR/$dst_path.json"
local src_val
src_val="$(get_key "$src_file" "$src_key")"
if [[ -z "$src_val" ]]; then
echo " ! $src_path/$src_key missing — cannot sync to $dst_path/$dst_key" >&2
return
fi
local dst_val
dst_val="$(get_key "$dst_file" "$dst_key")"
if [[ "$src_val" == "$dst_val" ]]; then
return
fi
set_key "$dst_file" "$dst_key" "$src_val"
echo " = $dst_key (synced from $src_path/$src_key)"
}
gen_hex32() { openssl rand -hex 32; }
gen_hex64() { openssl rand -hex 64; }
gen_pass() { openssl rand -base64 24 | tr -d '/+='; }
gen_long_pass() { openssl rand -base64 32 | tr -d '/+='; }
gen_app_key() { echo "base64:$(openssl rand -base64 32)"; }
# OpnForm requires: min 8 chars, a letter, a digit, AND one of the
# special chars @$!%*#?&-_+=.,:;<>^()[]{}|~. The base64 generator alone
# only produces [A-Za-z0-9], so we append a fixed-position special char
# and digit to guarantee the rule passes regardless of entropy outcome.
gen_opnform_pass() { echo "$(openssl rand -base64 18 | tr -d '/+=')!1Aa"; }
# ---------------------------------------------------------------- main
echo "==> seeding $MOUNT (dry-run=$DRY_RUN)"
echo "-> authentik"
read_secret authentik
ensure_key authentik secret_key gen_hex64
ensure_key authentik postgres_password gen_pass
ensure_key authentik admin_password gen_pass
ensure_key authentik ldap_outpost_token gen_hex32
ensure_key authentik nextcloud_oidc_secret gen_hex32
ensure_key authentik opnform_oidc_secret gen_hex32
ensure_key authentik homarr_oidc_secret gen_hex32
ensure_key authentik bookstack_oidc_secret gen_hex32
write_secret authentik
echo "-> opnform"
read_secret opnform
ensure_key opnform app_key gen_app_key
ensure_key opnform jwt_secret gen_hex32
ensure_key opnform front_api_secret gen_hex32
ensure_key opnform db_password gen_long_pass
ensure_key opnform admin_password gen_opnform_pass
ensure_key opnform oidc_client_secret gen_hex32
sync_key_from authentik opnform_oidc_secret opnform oidc_client_secret
write_secret opnform
echo "-> homarr"
read_secret homarr
ensure_key homarr secret_encryption_key gen_hex32
ensure_key homarr admin_password gen_pass
ensure_key homarr oidc_client_secret gen_hex32
sync_key_from authentik homarr_oidc_secret homarr oidc_client_secret
write_secret homarr
echo "-> bookstack"
read_secret bookstack
ensure_key bookstack db_root_password gen_long_pass
ensure_key bookstack db_password gen_long_pass
ensure_key bookstack admin_password gen_pass
ensure_key bookstack oidc_client_secret gen_hex32
sync_key_from authentik bookstack_oidc_secret bookstack oidc_client_secret
write_secret bookstack
echo "==> done"