stacks/auth/bootstrap.sh

#!/usr/bin/env bash
# stacks/auth/bootstrap.sh
# One-off bootstrap for the auth stack. Generates secrets, creates the
# admin user in lldap, and writes the Authelia totp bootstrap hash so
# the admin can log in for the first time.
#
# Idempotent: existing secrets are kept. Run as root.
#
# Docs: mercemay.top/src/homelab-compose/

set -euo pipefail

STACK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SECRETS_DIR="${SECRETS_DIR:-/srv/homelab/secrets/auth}"

say() { printf '[bootstrap] %s\n' "$*" >&2; }

gen_secret() {
    local name="$1" bytes="${2:-48}"
    local path="${SECRETS_DIR}/${name}"
    if [[ -s "${path}" ]]; then
        say "keep ${name}"
        return 0
    fi
    install -d -m 0700 "${SECRETS_DIR}"
    openssl rand -hex "${bytes}" > "${path}"
    chmod 0600 "${path}"
    say "wrote ${name}"
}

require() {
    command -v "$1" >/dev/null 2>&1 || { say "missing dep: $1"; exit 1; }
}

main() {
    require docker
    require openssl

    gen_secret authelia_jwt 48
    gen_secret authelia_session 48
    gen_secret authelia_storage 48
    gen_secret lldap_jwt 48
    gen_secret lldap_key_seed 48
    gen_secret lldap_admin_pass 24
    gen_secret redis_password 32
    gen_secret slack_webhook 0 2>/dev/null || true

    say "starting lldap + redis"
    (cd "${STACK_DIR}" && docker compose up -d redis lldap)

    say "waiting for lldap health"
    for _ in $(seq 1 30); do
        if docker compose -f "${STACK_DIR}/docker-compose.yml" \
            exec -T lldap wget -q -O - http://localhost:17170/health >/dev/null 2>&1; then
            break
        fi
        sleep 2
    done

    if ! docker compose -f "${STACK_DIR}/docker-compose.yml" \
        exec -T lldap sh -c 'lldap_set_password --help' >/dev/null 2>&1; then
        say "lldap CLI unavailable, skipping admin reset"
    else
        local admin_pass
        admin_pass=$(cat "${SECRETS_DIR}/lldap_admin_pass")
        docker compose -f "${STACK_DIR}/docker-compose.yml" \
            exec -T lldap lldap_set_password \
                --base-url http://localhost:17170 \
                --username admin \
                --password "${admin_pass}" \
            || say "failed to set admin password (maybe already set)"
    fi

    say "starting authelia"
    (cd "${STACK_DIR}" && docker compose up -d authelia)

    say "done. Admin password is in ${SECRETS_DIR}/lldap_admin_pass"
}

main "$@"