linderhof/setup.sh

256 lines
9.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TEMPLATES="$SCRIPT_DIR/inventory/group_vars/all"
# ── helpers ──────────────────────────────────────────────────
info() { printf '\033[1;34m::\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m::\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m::\033[0m %s\n' "$*"; }
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
prompt() {
local var="$1" msg="$2" default="$3"
printf '%s [%s]: ' "$msg" "$default"
read -r input
eval "$var=\"\${input:-$default}\""
}
prompt_secret() {
local var="$1" msg="$2"
printf '%s: ' "$msg"
read -rs input
echo
eval "$var=\"\$input\""
}
# ── 1. check prerequisites ──────────────────────────────────
info "checking prerequisites..."
missing=()
for cmd in ansible ansible-galaxy ssh-keygen openssl envsubst; do
command -v "$cmd" &>/dev/null || missing+=("$cmd")
done
if (( ${#missing[@]} )); then
die "missing required commands: ${missing[*]}"
fi
ok "all prerequisites found"
# ── 2. install ansible collections ──────────────────────────
info "installing ansible collections..."
ansible-galaxy collection install -r "$SCRIPT_DIR/requirements.yml"
ok "collections installed"
# ── 3. stack name ────────────────────────────────────────────
echo
info "stack setup"
prompt stack_name "Stack name" "home"
export stack_name
LINDERHOF_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/linderhof"
STACK_DIR="$LINDERHOF_CONFIG_DIR/$stack_name"
GROUP_VARS="$STACK_DIR/group_vars/all"
CONFIG="$GROUP_VARS/config.yml"
VAULT="$GROUP_VARS/vault.yml"
HOSTS="$STACK_DIR/hosts.yml"
STACK_ENV="$STACK_DIR/stack.env"
VAULT_PASS_FILE="$STACK_DIR/vault-pass"
info "stack directory: $STACK_DIR"
mkdir -p "$GROUP_VARS"
# ── 4. SSH key ───────────────────────────────────────────────
prompt ssh_key_path "SSH key path" "$HOME/.ssh/id_ed25519"
if [[ -f "$ssh_key_path" ]]; then
ok "SSH key already exists at $ssh_key_path"
else
printf 'No key at %s. Generate one? [Y/n]: ' "$ssh_key_path"
read -r yn
if [[ "${yn,,}" != "n" ]]; then
ssh-keygen -t ed25519 -f "$ssh_key_path"
ok "SSH key generated"
else
warn "skipping SSH key generation"
fi
fi
# ── 5. vault password ───────────────────────────────────────
if [[ -f "$VAULT_PASS_FILE" ]]; then
ok "vault password file already exists at $VAULT_PASS_FILE"
else
info "generating vault password..."
openssl rand -base64 32 > "$VAULT_PASS_FILE"
chmod 600 "$VAULT_PASS_FILE"
ok "vault password saved to $VAULT_PASS_FILE"
fi
export ANSIBLE_VAULT_PASSWORD_FILE="$VAULT_PASS_FILE"
# ── 6. server settings ───────────────────────────────────────
echo
info "configure your server"
prompt admin_user "Admin username" "$USER"
prompt server_name "Server hostname" "$stack_name"
prompt server_ip "Server IP (leave TBD if provisioning via Hetzner)" "TBD"
prompt domain "Domain" "example.com"
prompt_secret hcloud_token "Hetzner API token (leave blank to skip)"
if [[ -z "$hcloud_token" ]]; then
warn "no Hetzner token provided — add it to vault.yml manually if needed"
fi
echo
info "configure restic backups (optional — leave blank to skip)"
prompt restic_storagebox_name "Storage box name" "${server_name}-backup"
prompt_secret restic_storagebox_password "Storage box password (leave blank to skip)"
export admin_user server_name server_ip domain hcloud_token restic_storagebox_name restic_storagebox_password
export ssh_key_pub="${ssh_key_path}.pub"
echo
info "using domain: $domain"
info " mail: mail.$domain"
info " forgejo: code.$domain"
info " grafana: watch.$domain"
info " tuwunel: chat.$domain"
info " webmail: webmail.$domain"
info " rspamd: rspamd.$domain"
# ── 7. generate secrets ─────────────────────────────────────
info "generating secrets..."
export root_password admin_password
export admin_mail_password notifications_mail_password git_mail_password
export grafana_admin_password rspamd_web_password goaccess_password rainloop_admin_password radicale_password
export tuwunel_registration_token restic_password restic_storagebox_password
export forgejo_secret_key forgejo_internal_token forgejo_jwt_secret
root_password=$(openssl rand -base64 32)
admin_password=$(openssl rand -base64 32)
admin_mail_password=$(openssl rand -base64 32)
notifications_mail_password=$(openssl rand -base64 32)
git_mail_password=$(openssl rand -base64 32)
grafana_admin_password=$(openssl rand -base64 32)
rspamd_web_password=$(openssl rand -base64 32)
goaccess_password=$(openssl rand -base64 32)
radicale_password=$(openssl rand -base64 32)
rainloop_admin_password=$(openssl rand -base64 32)
tuwunel_registration_token=$(openssl rand -base64 32)
restic_password=$(openssl rand -base64 32)
forgejo_secret_key=$(openssl rand -hex 32)
forgejo_internal_token=$(openssl rand -hex 32)
forgejo_jwt_secret=$(openssl rand -hex 32)
ok "secrets generated"
# ── 8. write hosts.yml ───────────────────────────────────────
if [[ -f "$HOSTS" ]]; then
warn "hosts.yml already exists — skipping (not overwriting)"
else
info "writing hosts.yml..."
cat > "$HOSTS" <<HOSTS_EOF
---
# ============================================================
# Linderhof Inventory — stack: $stack_name
# ============================================================
all:
hosts:
$server_name:
ansible_host: $server_ip
ansible_user: $admin_user
ansible_become: true
ansible_become_method: sudo
HOSTS_EOF
ok "hosts.yml created"
fi
# ── 9. write stack.env ───────────────────────────────────────
if [[ -f "$STACK_ENV" ]]; then
warn "stack.env already exists — skipping (not overwriting)"
else
info "writing stack.env..."
cat > "$STACK_ENV" <<ENV_EOF
# Per-stack environment variables — loaded by .envrc
export DOCKER_HOST="ssh://$admin_user@$server_name.$domain"
ENV_EOF
ok "stack.env created"
fi
# ── 10. write config.yml ──────────────────────────────────────
if [[ -f "$CONFIG" ]]; then
warn "config.yml already exists — skipping (not overwriting)"
else
info "writing config.yml..."
envsubst '$admin_user $server_name $server_ip $domain $ssh_key_pub $stack_name $restic_storagebox_name' \
< "$TEMPLATES/config.yml.setup" > "$CONFIG"
ok "config.yml created"
fi
# ── 10b. write dns.yml ────────────────────────────────────────
DNS_CONFIG="$GROUP_VARS/dns.yml"
if [[ -f "$DNS_CONFIG" ]]; then
warn "dns.yml already exists — skipping (not overwriting)"
else
info "writing dns.yml..."
envsubst '$domain $server_name' \
< "$TEMPLATES/dns.yml.setup" > "$DNS_CONFIG"
ok "dns.yml created (uncomment DKIM records after first mail deployment)"
fi
# ── 11. write vault.yml ───────────────────────────────────────
if [[ -f "$VAULT" ]]; then
warn "vault.yml already exists — skipping (not overwriting)"
else
info "writing vault.yml..."
envsubst < "$TEMPLATES/vault.yml.setup" > "$VAULT"
ansible-vault encrypt "$VAULT"
ok "vault.yml created and encrypted"
fi
# ── 12. write .stack file ─────────────────────────────────────
if [[ -f "$SCRIPT_DIR/.stack" ]]; then
warn ".stack already exists — skipping (not overwriting)"
else
printf '%s\n' "$stack_name" > "$SCRIPT_DIR/.stack"
ok ".stack file written ($stack_name)"
fi
# ── 13. summary ───────────────────────────────────────────────
echo
echo "============================================================"
ok "linderhof setup complete! (stack: $stack_name)"
echo "============================================================"
echo
echo " stack dir: $STACK_DIR"
echo " vault password: $VAULT_PASS_FILE"
echo " SSH key: $ssh_key_path"
echo " inventory: $HOSTS"
echo " config: $CONFIG"
echo " dns zones: $DNS_CONFIG"
echo " vault: $VAULT"
echo
echo "to override any variable (e.g. mail_hostname during migration):"
echo " vi $GROUP_VARS/overrides.yml"
echo
echo "activate the stack (if not already done):"
echo " direnv allow # reads .stack file automatically"
echo " # or: export LINDERHOF_STACK=$stack_name"
echo
echo "config.yml is plain text — edit it directly:"
echo " vi $CONFIG"
echo
echo "vault.yml is encrypted. to view or edit it:"
echo " ansible-vault view $VAULT"
echo " ansible-vault edit $VAULT"
echo
echo "Next steps:"
echo " 1. Review $CONFIG"
echo " 2. Review $VAULT (ansible-vault edit)"
echo " 3. Review $DNS_CONFIG"
echo " 4. If restic is enabled, set up the storage box first:"
echo " ansible-playbook playbooks/storage_box.yml"
echo " 5. Deploy: ansible-playbook playbooks/deploy.yml"
echo ""
echo " If mail is enabled, sync DKIM keys once the server is up:"
echo " ansible-playbook playbooks/dkim_sync.yml"