Compare commits

...

2 commits

Author SHA1 Message Date
796428cbda Extract restic repo init to shared task file
Deduplicate nearly identical init logic from backend.yml and
backend_sftp.yml into init.yml. Also fixes missing set -euo pipefail
in the sftp backend variant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 21:24:45 -07:00
16da843131 Fix storage_box SSH key installation and deploy ordering
- 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 <noreply@anthropic.com>
2026-03-01 21:14:45 -07:00
8 changed files with 31 additions and 37 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -8,12 +8,5 @@
mode: "0700"
when: restic_repo is defined and restic_repo.startswith('/') # only local path
- 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
- ansible.builtin.include_tasks: init.yml

View file

@ -20,11 +20,5 @@
dest: /root/.ssh/config
mode: "0644"
- 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
- ansible.builtin.include_tasks: init.yml

View file

@ -0,0 +1,9 @@
---
- 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

View file

@ -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:

View file

@ -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"