diff --git a/CLAUDE.md b/CLAUDE.md index ca4de24..b94f76c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,10 +51,9 @@ Note: Inventory and vault password are set via `ANSIBLE_INVENTORY` and `ANSIBLE_ **What `deploy.yml` runs internally:** 1. `provision.yml` - create server, auto-writes IP to hosts.yml and config.yml 2. `dns.yml` - create DNS records -3. `bootstrap.yml` - users, SSH hardening, packages, Docker (connects as root) -4. `site.yml` - deploy all services - -**Note:** `storage_box.yml` must be run before `deploy.yml` when `enable_restic: true` — Ansible loads group_vars at startup, so `storagebox.yml` must exist before the playbook begins. +3. `storage_box.yml` - generate SSH key, configure storage box, writes storagebox.yml to stack config +4. `bootstrap.yml` - users, SSH hardening, packages, Docker (connects as root) +5. `site.yml` - deploy all services **Playbook Execution Order** (via `site.yml`): 1. networks.yml - Pre-create all Docker networks (must run before any service) diff --git a/README.md b/README.md index 1fd1094..258c741 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,7 @@ ansible-galaxy collection install -r requirements.yml full deployment order for a fresh server: ```bash -ansible-playbook playbooks/storage_box.yml # if using restic: create storage box and install SSH key (run before deploy.yml) -ansible-playbook playbooks/deploy.yml # provision → dns → bootstrap → site (all-in-one) +ansible-playbook playbooks/deploy.yml # provision → dns → storage_box → bootstrap → site (all-in-one) ansible-playbook playbooks/dkim_sync.yml # generate DKIM keys and publish to DNS (run once after mail is up) ``` @@ -79,7 +78,7 @@ or step by step: ```bash ansible-playbook playbooks/provision.yml # create server, writes IP to stack config ansible-playbook playbooks/dns.yml # create DNS zones and records -ansible-playbook playbooks/storage_box.yml # if using restic: create storage box and install SSH key +ansible-playbook playbooks/storage_box.yml # create storage box and install SSH key (if using restic) ansible-playbook playbooks/site.yml --tags bootstrap # users, SSH hardening, packages, Docker ansible-playbook playbooks/site.yml # deploy all services ansible-playbook playbooks/dkim_sync.yml # generate DKIM keys and publish to DNS diff --git a/playbooks/deploy.yml b/playbooks/deploy.yml index 56634c3..003c438 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -2,10 +2,7 @@ # Full first-time deployment — provisions and deploys everything in one shot. # Usage: ansible-playbook playbooks/deploy.yml # -# Prerequisites: -# 1. run setup.sh and review config.yml, vault.yml, dns.yml -# 2. if enable_restic: run storage_box.yml first so storagebox.yml exists -# before this playbook starts (Ansible loads group_vars at startup) +# Prerequisites: run setup.sh first, then review config.yml, vault.yml, dns.yml # # This playbook is intended for initial deployments only. After the first run, # bootstrap will fail (root SSH is disabled) — use site.yml for subsequent deploys. @@ -16,6 +13,7 @@ - import_playbook: provision.yml - import_playbook: dns.yml +- import_playbook: storage_box.yml # Refresh inventory so the newly provisioned server IP is visible to subsequent plays - name: Refresh inventory diff --git a/roles/restic/tasks/backend.yml b/roles/restic/tasks/backend.yml index 187aa2b..d392f9f 100644 --- a/roles/restic/tasks/backend.yml +++ b/roles/restic/tasks/backend.yml @@ -8,5 +8,12 @@ mode: "0700" when: restic_repo is defined and restic_repo.startswith('/') # only local path -- ansible.builtin.include_tasks: init.yml +- name: Ensure restic repo is initialized + ansible.builtin.shell: | + set -euo pipefail + source /etc/restic/restic.env + restic snapshots > /dev/null 2>&1 || restic init + touch /etc/restic/.initialized + args: + creates: /etc/restic/.initialized diff --git a/roles/restic/tasks/backend_sftp.yml b/roles/restic/tasks/backend_sftp.yml index 7f38c48..2795f1d 100644 --- a/roles/restic/tasks/backend_sftp.yml +++ b/roles/restic/tasks/backend_sftp.yml @@ -20,5 +20,11 @@ dest: /root/.ssh/config mode: "0644" -- ansible.builtin.include_tasks: init.yml +- name: Initialize restic repo on Storage Box (if needed) + ansible.builtin.shell: | + source /etc/restic/restic.env + restic snapshots > /dev/null 2>&1 || restic init + touch /etc/restic/.initialized + args: + creates: /etc/restic/.initialized diff --git a/roles/restic/tasks/init.yml b/roles/restic/tasks/init.yml deleted file mode 100644 index 83f65b0..0000000 --- a/roles/restic/tasks/init.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- name: Initialize restic repo (if needed) - ansible.builtin.shell: | - set -euo pipefail - source /etc/restic/restic.env - restic snapshots > /dev/null 2>&1 || restic init - touch /etc/restic/.initialized - args: - creates: /etc/restic/.initialized diff --git a/roles/storage_box/tasks/main.yml b/roles/storage_box/tasks/main.yml index b731be1..ed7e943 100644 --- a/roles/storage_box/tasks/main.yml +++ b/roles/storage_box/tasks/main.yml @@ -34,22 +34,30 @@ api_token: "{{ hcloud_token }}" access_settings: ssh_enabled: true - reachable_externally: true state: present register: storagebox_result when: ssh_pub_key_stat.stat.exists +- name: Check SSH key auth on Storage Box + ansible.builtin.shell: | + echo "bye" | sftp -i {{ restic_local_key_path }} \ + -o BatchMode=yes -o StrictHostKeyChecking=no \ + -P 23 \ + {{ storagebox_result.hcloud_storage_box.username }}@{{ storagebox_result.hcloud_storage_box.server }} + register: ssh_key_check + failed_when: false + changed_when: false + when: ssh_pub_key_stat.stat.exists + - name: Install SSH public key on Storage Box ansible.builtin.shell: | cat {{ restic_local_key_path }}.pub | \ - sshpass -e \ - ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -p 23 \ + sshpass -p "{{ restic_storagebox_password }}" \ + ssh -o StrictHostKeyChecking=no -p 23 \ {{ storagebox_result.hcloud_storage_box.username }}@{{ storagebox_result.hcloud_storage_box.server }} \ install-ssh-key - environment: - SSHPASS: "{{ restic_storagebox_password }}" no_log: true - when: ssh_pub_key_stat.stat.exists + when: ssh_pub_key_stat.stat.exists and ssh_key_check.rc != 0 - name: Write storagebox.yml to stack config directory ansible.builtin.copy: diff --git a/setup.sh b/setup.sh index 1d285f5..11cb54e 100755 --- a/setup.sh +++ b/setup.sh @@ -247,9 +247,7 @@ 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 " 4. 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"