diff --git a/stacks/scripts/bootstrap.sh b/stacks/scripts/bootstrap.sh new file mode 100755 index 0000000..0612352 --- /dev/null +++ b/stacks/scripts/bootstrap.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { printf '%s\n' "$*"; } +warn() { printf 'WARN: %s\n' "$*" >&2; } + +ensure_network() { + local name=$1 + if docker network inspect "$name" >/dev/null 2>&1; then + log "network exists: $name" + else + log "creating overlay network: $name" + docker network create --driver overlay --attachable "$name" + fi +} + +secret_exists() { docker secret inspect "$1" >/dev/null 2>&1; } + +create_secret_from_value() { + local name=$1; local value=$2 + printf '%s' "$value" | docker secret create "$name" - >/dev/null 2>&1 && log "secret created: $name" || log "secret exists: $name" +} + +create_secret_from_file() { + local name=$1; local path=$2 + docker secret create "$name" "$path" >/dev/null 2>&1 && log "secret created: $name (from $path)" || log "secret exists: $name" +} + +rand_b64() { openssl rand -base64 32 2>/dev/null || head -c 24 /dev/urandom | base64; } + +get_from_env_or_file_or_default() { + local name=$1 envvar=$2 default_gen=$3 + local file="stacks/secrets/${name}.txt" + local val="" + if [[ -n "${!envvar-}" ]]; then + val="${!envvar}" + elif [[ -f "$file" ]]; then + val=$(cat "$file") + elif [[ "$default_gen" == "gen" ]]; then + val=$(rand_b64) + warn "no env or file for $name; generating random" + else + val="" + fi + printf '%s' "$val" +} + +main() { + # Networks + ensure_network traefik-public + ensure_network database-network + ensure_network monitoring-network + + # Secrets map: name envvar gen-policy + declare -a items=( + "pg_root_password PG_ROOT_PASSWORD gen" + "mariadb_root_password MARIADB_ROOT_PASSWORD gen" + "gitea_db_password GITEA_DB_PASSWORD gen" + "nextcloud_db_password NEXTCLOUD_DB_PASSWORD gen" + "smtp_user SMTP_USER plain" + "smtp_pass SMTP_PASS gen" + "appflowy_db_url APPFLOWY_DB_URL plain" + "minio_access_key MINIO_ACCESS_KEY gen" + "minio_secret_key MINIO_SECRET_KEY gen" + ) + + for entry in "${items[@]}"; do + set -- $entry + name=$1; envvar=$2; policy=$3 + if secret_exists "$name"; then + log "secret exists: $name" + continue + fi + val=$(get_from_env_or_file_or_default "$name" "$envvar" "$policy") + if [[ -z "$val" ]]; then + warn "secret $name missing (env $envvar or stacks/secrets/${name}.txt). Creating placeholder; update ASAP." + case "$name" in + smtp_user) val="noreply@example.local" ;; + appflowy_db_url) val="postgres://user:pass@postgresql_primary:5432/appflowy" ;; + *) val=$(rand_b64) ;; + esac + fi + create_secret_from_value "$name" "$val" + done + + log "bootstrap complete" +} + +main "$@" diff --git a/stacks/secrets/templates/README.md b/stacks/secrets/templates/README.md new file mode 100644 index 0000000..6f313c8 --- /dev/null +++ b/stacks/secrets/templates/README.md @@ -0,0 +1,30 @@ +# Secrets Templates + +Provide secrets via environment variables or plain-text files in `stacks/secrets/` before running `stacks/scripts/bootstrap.sh`. + +Supported inputs: +- Environment variables (preferred for CI/sealed envs) +- Files: `stacks/secrets/.txt` +- If absent, bootstrap will generate random values for most; placeholders for structured values. + +Secrets used by stacks: +- `pg_root_password`: Postgres superuser password +- `mariadb_root_password`: MariaDB root password +- `gitea_db_password`: Gitea DB user password +- `nextcloud_db_password`: Nextcloud DB user password +- `smtp_user`, `smtp_pass`: Vaultwarden SMTP creds +- `appflowy_db_url`: e.g., `postgres://user:pass@postgresql_primary:5432/appflowy` +- `minio_access_key`, `minio_secret_key`: MinIO root creds + +Example files: +``` +stacks/secrets/pg_root_password.txt +stacks/secrets/mariadb_root_password.txt +stacks/secrets/gitea_db_password.txt +stacks/secrets/nextcloud_db_password.txt +stacks/secrets/smtp_user.txt +stacks/secrets/smtp_pass.txt +stacks/secrets/appflowy_db_url.txt +stacks/secrets/minio_access_key.txt +stacks/secrets/minio_secret_key.txt +```