initial commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
75891c3271
129 changed files with 8046 additions and 0 deletions
243
setup.sh
Executable file
243
setup.sh
Executable file
|
|
@ -0,0 +1,243 @@
|
|||
#!/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"
|
||||
|
||||
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 (or TBD)" "0.0.0.0"
|
||||
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
|
||||
|
||||
export admin_user server_name server_ip domain hcloud_token
|
||||
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 admin_mail_password notifications_mail_password git_mail_password
|
||||
export grafana_admin_password rspamd_web_password goaccess_password rainloop_admin_password
|
||||
export tuwunel_registration_token restic_password
|
||||
export forgejo_secret_key forgejo_internal_token forgejo_jwt_secret
|
||||
|
||||
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)
|
||||
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"
|
||||
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' \
|
||||
< "$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_ip $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. Provision a server: ansible-playbook playbooks/provision.yml"
|
||||
echo " 5. Update DNS: ansible-playbook playbooks/dns.yml"
|
||||
echo " 6. Deploy: ansible-playbook playbooks/site.yml"
|
||||
echo " 7. After mail deploys, retrieve DKIM keys and add to vault.yml:"
|
||||
echo " docker exec mailserver cat /tmp/docker-mailserver/rspamd/dkim/$domain/mail.pub"
|
||||
Loading…
Add table
Add a link
Reference in a new issue