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.
191 lines
5.6 KiB
Bash
Executable file
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"
|