#!/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 /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 (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/.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"