# 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: ```bash 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: ```bash 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: ```bash ansible-vault create ansible/group_vars/vault.yml ``` Populate all required secrets: ```yaml # 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 registry_htpasswd: "user:$2y$05$..." ``` Verify all secrets are present and non-empty: ```bash bash scripts/check-vault.sh ``` To edit the vault later: ```bash 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 bash scripts/run-bootstrap.sh ``` ### 5. Run the Ansible master playbook ```bash 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: ```bash ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags --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: ```bash 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 ```bash # 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 |