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
This commit is contained in:
+128
-88
@@ -1,12 +1,39 @@
|
||||
# Reinstall Runbook
|
||||
# Provisioning Runbook
|
||||
|
||||
Follow these steps to provision a fresh server from scratch.
|
||||
Follow these steps to provision the server from scratch — whether setting it up
|
||||
for the first time or reinstalling after a wipe.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
On your local machine:
|
||||
### Local tools
|
||||
|
||||
- Ansible installed (`pip install ansible`)
|
||||
- SSH access to the server as root using `keys/root_cloud_ladkau_de`
|
||||
- `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
|
||||
|
||||
@@ -21,48 +48,41 @@ 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. Initial root login
|
||||
### 2. Configure DNS
|
||||
|
||||
Connect as root and verify the server is reachable:
|
||||
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.
|
||||
|
||||
```
|
||||
ssh -i keys/root_cloud_ladkau_de root@217.154.207.148
|
||||
```
|
||||
| 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 deploy user (one-time, manual)
|
||||
### 3. Create the vault and populate secrets
|
||||
|
||||
On the server as root:
|
||||
|
||||
```bash
|
||||
adduser deploy
|
||||
usermod -aG sudo deploy
|
||||
mkdir -p /home/deploy/.ssh
|
||||
cat >> /home/deploy/.ssh/authorized_keys <<'EOF'
|
||||
<paste contents of keys/notroot_cloud_ladkau_de.pub>
|
||||
EOF
|
||||
chown -R deploy:deploy /home/deploy/.ssh
|
||||
chmod 700 /home/deploy/.ssh
|
||||
chmod 600 /home/deploy/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
### 4. Set secrets (Ansible Vault)
|
||||
|
||||
Before running the playbook, populate secrets that must not be committed in plain text.
|
||||
Create `ansible/group_vars/vault.yml` (gitignored) and encrypt it with Ansible Vault:
|
||||
|
||||
```bash
|
||||
ansible-vault create ansible/group_vars/vault.yml
|
||||
```
|
||||
|
||||
Required secrets:
|
||||
Populate all required secrets:
|
||||
|
||||
```yaml
|
||||
# Traefik dashboard basic auth — generate with:
|
||||
# echo $(htpasswd -nB admin) | sed -e 's/\$/\$\$/g'
|
||||
# Traefik dashboard basic auth
|
||||
# Generate: echo $(htpasswd -nB admin) | sed -e 's/\$/\$\$/g'
|
||||
traefik_dashboard_users: "admin:$$2y$$05$$..."
|
||||
|
||||
# Gitea — generate secrets with: openssl rand -hex 32
|
||||
# Gitea
|
||||
# Generate secrets: openssl rand -hex 32
|
||||
gitea_db_password: ""
|
||||
gitea_secret_key: ""
|
||||
gitea_internal_token: ""
|
||||
@@ -75,83 +95,103 @@ keycloak_admin_password: ""
|
||||
nextcloud_db_password: ""
|
||||
nextcloud_admin_password: ""
|
||||
|
||||
# Roundcube — des_key must be exactly 24 characters: openssl rand -hex 12
|
||||
# Roundcube
|
||||
# des_key must be exactly 24 characters: openssl rand -hex 12
|
||||
roundcube_db_password: ""
|
||||
roundcube_des_key: ""
|
||||
|
||||
# Container registry — generate with:
|
||||
# docker run --entrypoint htpasswd httpd:2 -Bbn <user> <password>
|
||||
# Container registry
|
||||
# Generate: docker run --entrypoint htpasswd httpd:2 -Bbn <user> <password>
|
||||
registry_htpasswd: "user:$2y$05$..."
|
||||
```
|
||||
|
||||
**Registry usage after deploy:**
|
||||
Verify all secrets are present and non-empty:
|
||||
|
||||
```bash
|
||||
# Login
|
||||
docker login cr.ladkau.de
|
||||
|
||||
# Push an image
|
||||
docker tag myimage:latest cr.ladkau.de/myimage:latest
|
||||
docker push cr.ladkau.de/myimage:latest
|
||||
|
||||
# Pull an image
|
||||
docker pull cr.ladkau.de/myimage:latest
|
||||
bash scripts/check-vault.sh
|
||||
```
|
||||
|
||||
**Gitea first-run note:** `gitea_disable_registration` defaults to `true` in
|
||||
`group_vars/all.yml`. For the very first deploy, override it to `false` in
|
||||
`vault.yml` so the Gitea setup wizard can create the admin account. After
|
||||
the admin account exists, remove the override and redeploy.
|
||||
To edit the vault later:
|
||||
|
||||
**Keycloak first-run note:** The `KEYCLOAK_ADMIN` / `KEYCLOAK_ADMIN_PASSWORD`
|
||||
environment variables bootstrap the initial admin account on first start only.
|
||||
After logging in at `https://sso.ladkau.de`, change the admin password via the
|
||||
Keycloak UI and remove the `keycloak_admin_password` reference from vault (or
|
||||
rotate it). Keycloak ignores these env vars once the admin account already exists.
|
||||
```bash
|
||||
ansible-vault edit ansible/group_vars/vault.yml
|
||||
```
|
||||
|
||||
To run the playbook with vault:
|
||||
### 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
|
||||
```
|
||||
|
||||
### 5. Run the Ansible master playbook
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
ansible-playbook -i ansible/inventory.ini ansible/site.yml
|
||||
```
|
||||
|
||||
This runs all roles in order:
|
||||
1. `base` — OS hardening, SSH config, ufw firewall
|
||||
2. `docker` — Docker Engine + Compose plugin
|
||||
3. `traefik` — reverse proxy, TLS via Let's Encrypt
|
||||
4. `gitea` — self-hosted Git
|
||||
5. `nextcloud` — file storage
|
||||
6. `sso` — Single Sign-On
|
||||
7. `mail` — mail server
|
||||
8. `registry` — container registry
|
||||
9. `k8s` — k3s node
|
||||
|
||||
### 6. DNS
|
||||
| # | 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 |
|
||||
|
||||
Ensure the following DNS A records point to `217.154.207.148` before running:
|
||||
|
||||
- cloud.ladkau.de
|
||||
- gitea.ladkau.de
|
||||
- nextcloud.ladkau.de
|
||||
- sso.ladkau.de
|
||||
- mail.ladkau.de
|
||||
- cr.ladkau.de
|
||||
- k8s.ladkau.de
|
||||
|
||||
Traefik will attempt ACME certificate issuance on first start; DNS must resolve first.
|
||||
|
||||
## Re-running after changes
|
||||
|
||||
The playbook is idempotent. To apply a single role only:
|
||||
To apply a single role:
|
||||
|
||||
```bash
|
||||
ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags <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:
|
||||
|
||||
```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 |
|
||||
|
||||
Reference in New Issue
Block a user