From 16da8431310c4e49586841f62461296719d55cb5 Mon Sep 17 00:00:00 2001 From: Matthias Johnson Date: Sun, 1 Mar 2026 21:14:45 -0700 Subject: [PATCH] Fix storage_box SSH key installation and deploy ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Always run install-ssh-key (drop unreliable sftp idempotency check that was bypassed by SSH agent forwarding) - Use sshpass -e (env var) instead of -p to avoid shell quoting issues with special characters in passwords - Add -o IdentitiesOnly=yes to prevent agent keys interfering - Add reachable_externally: true to access_settings (was being reset to false on every run) - Remove storage_box.yml from deploy.yml chain — Ansible loads group_vars at startup so storagebox.yml must exist before deploy.yml - Document storage_box.yml as a prerequisite step in README, CLAUDE.md, and setup.sh next steps Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 7 ++++--- README.md | 5 +++-- playbooks/deploy.yml | 6 ++++-- roles/storage_box/tasks/main.yml | 20 ++++++-------------- setup.sh | 4 +++- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b94f76c..ca4de24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,9 +51,10 @@ 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. `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 +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. **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 258c741..1fd1094 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,8 @@ ansible-galaxy collection install -r requirements.yml full deployment order for a fresh server: ```bash -ansible-playbook playbooks/deploy.yml # provision → dns → storage_box → bootstrap → site (all-in-one) +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/dkim_sync.yml # generate DKIM keys and publish to DNS (run once after mail is up) ``` @@ -78,7 +79,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 # create storage box and install SSH key (if using restic) +ansible-playbook playbooks/storage_box.yml # if using restic: create storage box and install SSH key 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 003c438..56634c3 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -2,7 +2,10 @@ # Full first-time deployment — provisions and deploys everything in one shot. # Usage: ansible-playbook playbooks/deploy.yml # -# Prerequisites: run setup.sh first, then review config.yml, vault.yml, dns.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) # # 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. @@ -13,7 +16,6 @@ - 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/storage_box/tasks/main.yml b/roles/storage_box/tasks/main.yml index ed7e943..b731be1 100644 --- a/roles/storage_box/tasks/main.yml +++ b/roles/storage_box/tasks/main.yml @@ -34,30 +34,22 @@ 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 -p "{{ restic_storagebox_password }}" \ - ssh -o StrictHostKeyChecking=no -p 23 \ + sshpass -e \ + ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -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 and ssh_key_check.rc != 0 + when: ssh_pub_key_stat.stat.exists - name: Write storagebox.yml to stack config directory ansible.builtin.copy: diff --git a/setup.sh b/setup.sh index 11cb54e..1d285f5 100755 --- a/setup.sh +++ b/setup.sh @@ -247,7 +247,9 @@ echo "Next steps:" echo " 1. Review $CONFIG" echo " 2. Review $VAULT (ansible-vault edit)" echo " 3. Review $DNS_CONFIG" -echo " 4. Deploy: ansible-playbook playbooks/deploy.yml" +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 "" echo " If mail is enabled, sync DKIM keys once the server is up:" echo " ansible-playbook playbooks/dkim_sync.yml"