Files
server_cloud_ladkau_de/docs/runbook.md
T
ml 03d2f6d57a Add bootstrap scripts and restructure runbook
- scripts/run-bootstrap.sh: copies and runs the deploy user bootstrap on a
  fresh server, verifies SSH login, checks vault as a preflight step
- scripts/bootstrap-deploy-user.sh: runs on the server as root, accepts the
  SSH public key as an argument (read from keys/ by run-bootstrap.sh)
- scripts/check-vault.sh: decrypts vault.yml and verifies all 11 required
  secrets are present and non-empty
- docs/runbook.md: restructured into correct order (collections → DNS → vault
  → bootstrap → playbook), added scripts reference table, moved first-run
  notes for Gitea/Keycloak/registry into their own section
- README.md: added scripts/ to repo layout
2026-06-28 05:03:05 +02:00

5.7 KiB

Provisioning Runbook

Follow these steps to provision the server from scratch — whether setting it up for the first time or reinstalling after a wipe.

Prerequisites

Local tools

  • Ansible installed (pip install ansible)
  • htpasswd available (apt install apache2-utils or brew install httpd)
  • docker available (for generating the registry htpasswd entry)
  • openssl available (for generating secrets)

SSH keys

Two SSH key pairs are required. Place them in the keys/ directory (private keys are gitignored; public keys are committed).

File Purpose
keys/root_cloud_ladkau_de Root access — used only during initial bootstrap
keys/root_cloud_ladkau_de.pub Public counterpart
keys/notroot_cloud_ladkau_de Deploy user — used by Ansible for all subsequent runs
keys/notroot_cloud_ladkau_de.pub Public counterpart — installed on server by bootstrap

Generate fresh key pairs if they do not already exist:

ssh-keygen -t ed25519 -f keys/root_cloud_ladkau_de -C "root@cloud.ladkau.de" -N ""
ssh-keygen -t ed25519 -f keys/notroot_cloud_ladkau_de -C "deploy@cloud.ladkau.de" -N ""

Upload the root public key to the server via the Strato control panel (or paste it during the initial OS install) so that root@217.154.207.148 is accessible before running anything.

Steps

1. Install Ansible collections

From the repo root:

ansible-galaxy collection install -r ansible/requirements.yml

Required collections:

  • community.general — ufw, timezone modules
  • ansible.posix — authorized_key module
  • community.docker — docker_network, docker_compose_v2 modules

2. Configure DNS

Ensure the following DNS A records point to 217.154.207.148 before running the playbook. Traefik requests Let's Encrypt certificates on first start and DNS must resolve at that point.

Domain Record
cloud.ladkau.de A → 217.154.207.148
gitea.ladkau.de A → 217.154.207.148
nextcloud.ladkau.de A → 217.154.207.148
sso.ladkau.de A → 217.154.207.148
mail.ladkau.de A → 217.154.207.148
cr.ladkau.de A → 217.154.207.148
k8s.ladkau.de A → 217.154.207.148

3. Create the vault and populate secrets

Create ansible/group_vars/vault.yml (gitignored) and encrypt it with Ansible Vault:

ansible-vault create ansible/group_vars/vault.yml

Populate all required secrets:

# Traefik dashboard basic auth
# Generate: echo $(htpasswd -nB admin) | sed -e 's/\$/\$\$/g'
traefik_dashboard_users: "admin:$$2y$$05$$..."

# Gitea
# Generate secrets: openssl rand -hex 32
gitea_db_password: ""
gitea_secret_key: ""
gitea_internal_token: ""

# Keycloak
keycloak_db_password: ""
keycloak_admin_password: ""

# Nextcloud
nextcloud_db_password: ""
nextcloud_admin_password: ""

# Roundcube
# des_key must be exactly 24 characters: openssl rand -hex 12
roundcube_db_password: ""
roundcube_des_key: ""

# Container registry
# Generate: docker run --entrypoint htpasswd httpd:2 -Bbn <user> <password>
registry_htpasswd: "user:$2y$05$..."

Verify all secrets are present and non-empty:

bash scripts/check-vault.sh

To edit the vault later:

ansible-vault edit ansible/group_vars/vault.yml

4. Bootstrap the deploy user

Copies the bootstrap script to the server, runs it as root, and verifies the deploy user can log in. Also runs check-vault.sh as a preflight check.

bash scripts/run-bootstrap.sh

5. Run the Ansible master playbook

ansible-playbook -i ansible/inventory.ini ansible/site.yml --ask-vault-pass

This runs all roles in order:

# Role What it does
1 base OS hardening, deploy user, SSH config, ufw firewall, fail2ban
2 docker Docker Engine + Compose plugin, shared Traefik network
3 traefik Reverse proxy, automatic TLS via Let's Encrypt
4 gitea Self-hosted Git with PostgreSQL
5 nextcloud File storage with PostgreSQL, Redis, cron sidecar
6 sso Keycloak single sign-on with PostgreSQL
7 mail Roundcube webmail client with PostgreSQL
8 registry Docker Registry v2 with htpasswd auth
9 k8s Placeholder page at k8s.ladkau.de

To apply a single role:

ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags <role> --ask-vault-pass

First-run notes

Gitea

gitea_disable_registration defaults to true. For the first deploy, override it to false in vault.yml so the setup wizard can create the admin account:

ansible-vault edit ansible/group_vars/vault.yml
# add: gitea_disable_registration: false
ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags gitea --ask-vault-pass

After the admin account is created at https://gitea.ladkau.de, remove the override and redeploy to close public registration.

Keycloak

KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD bootstrap the initial admin account on first start only — Keycloak ignores them once the account exists. After logging in at https://sso.ladkau.de, change the admin password via the UI.

Container registry

# Login
docker login cr.ladkau.de

# Push
docker tag myimage:latest cr.ladkau.de/myimage:latest
docker push cr.ladkau.de/myimage:latest

# Pull
docker pull cr.ladkau.de/myimage:latest

Scripts reference

Script Purpose
scripts/run-bootstrap.sh Copy and run the deploy user bootstrap on a fresh server
scripts/bootstrap-deploy-user.sh Runs on the server as root — creates the deploy user
scripts/check-vault.sh Verify vault.yml exists and all required secrets are non-empty