Skip to content

Inventory repository: moving lite-dev's secrets

How the live inventory moves out of octopodia-abos into its own private repository, octopodia-abos-inventory, and lite-dev's secrets out of group_vars/all, where every host would inherit them (OI-124, known trap 15). The design is ADR-0042 ยง1; this is phase 4, step 3. Done once, on octo-ctl, by hand. Afterwards a second host can be added safely.

flowchart LR
    A["Copy today's vault<br/>and hosts.yml aside"] --> B["Merge, pull main"]
    B --> C["Clone the inventory<br/>beside the code"]
    C --> D["hosts.yml in,<br/>abos_tenants added"]
    D --> E["vault_split.py"]
    E --> F["Check passes"]
    F --> G["Playbooks: nothing changes"]
    G --> H["Commit, push"]

Where things end up

File in the inventory repository Holds Vault ID
hosts.yml The hosts, each with the tenants it carries (abos_tenants) and its plain settings, such as console_tenants not secret
group_vars/all/main.yml Shared plain settings (admin keys, ports, domains) not secret
group_vars/all/vault.yml vault_tailscale_auth_key only: the one secret every host shares core
host_vars/lite-dev/vault.yml lite-dev's machine secrets: alerts client secret, tunnel token, registry passwords, and the Console's copies of the tenant's link and detail tokens core
tenants/lite-dev/vars.yml Tenant zero's settings: slug, name, region not secret
tenants/lite-dev/vault.yml Tenant zero's own secrets: the n8n encryption key, and its own copies of its link and detail tokens (vault_link_token, vault_detail_token) lite-dev

The Console's copies stay under core so a console playbook run needs only core; the tenant keeps its own (El, 16 October 2026). Every value is encrypted inline, one at a time, so a diff shows which secret changed without showing it.

Before you start

  • octopodia-abos-inventory exists with its first commit (README, group_vars/all/main.yml, tenants/lite-dev/vars.yml). Done 16 October 2026.
  • The lite-dev tenant vault password is in Bitwarden. Done 16 October 2026. You also need the core password.
  • octo-ctl can clone from GitHub as you (your SSH key or gh auth login).
  • No Junior test round running: step 7 runs the n8n playbook.

Steps

  1. Before merging, copy today's vault and hosts file aside, on octo-ctl. The merge removes infra/inventory/group_vars/all from the code repository, and hosts.yml is not in git at all:

    cd ~/octopodia-abos && git status --short      # must print nothing
    cp infra/inventory/group_vars/all/vault.yml ~/vault-before-split.yml
    cp infra/inventory/group_vars/all/main.yml ~/main-before-split.yml
    cp infra/inventory/hosts.yml ~/hosts-before-split.yml
    

    If git status shows the vault or main.yml as changed, the copy on octo-ctl is newer than the repository's: that is fine, the copies you just made are the ones used below. Then git stash so the pull can run.

  2. Merge to main and pull on octo-ctl: cd ~/octopodia-abos && git pull. From now on infra/ansible.cfg looks for ../../octopodia-abos-inventory/hosts.yml, so playbooks fail until step 4 is done. That is expected.

  3. Clone the inventory beside the code:

    cd ~ && git clone git@github.com:zurelc/octopodia-abos-inventory.git
    
  4. Put hosts.yml in, and name lite-dev's tenant:

    cp ~/hosts-before-split.yml ~/octopodia-abos-inventory/hosts.yml
    diff ~/main-before-split.yml ~/octopodia-abos-inventory/group_vars/all/main.yml
    

    In hosts.yml, under the lite-dev host, add abos_tenants: [lite-dev]. If diff printed anything, copy ~/main-before-split.yml over group_vars/all/main.yml: the octo-ctl copy wins.

  5. Split the vault. It asks for the core password and then the lite-dev one, and prints the names it wrote to each file, never a value:

    python3 ~/octopodia-abos/infra/scripts/vault_split.py --old ~/vault-before-split.yml \
      --inventory ~/octopodia-abos-inventory --host lite-dev --tenant lite-dev
    

    Expect three lines: group_vars/all/vault.yml (core) with the tailnet key, host_vars/lite-dev/vault.yml (core) with six names, and tenants/lite-dev/vault.yml (lite-dev) with three. If it says No place in the plan for: ..., stop and send me the names; nothing was written.

  6. Check the layout, and that Ansible sees it the same way:

    python3 ~/octopodia-abos/infra/scripts/inventory_check.py ~/octopodia-abos-inventory
    cd ~/octopodia-abos/infra
    ansible lite-dev  -m debug -a var=vault_abos_alerts_client_secret --vault-id core@prompt ; clear
    ansible localhost -m debug -a var=vault_abos_alerts_client_secret --vault-id core@prompt
    ansible localhost -m debug -a var=vault_tailscale_auth_key --vault-id core@prompt ; clear
    

    The check prints OK: ... a new host would see only vault_tailscale_auth_key. lite-dev shows a value. localhost stands in for a second host: Ansible gives it only what group_vars/all gives every host, so it must say VARIABLE IS NOT DEFINED! for the alerts secret and show a value for the tailnet key. That is known trap 15's check passing.

  7. Run the playbooks: nothing changes.

    ansible-playbook playbooks/console.yml --limit lite-dev --vault-id core@prompt
    ansible-playbook playbooks/n8n.yml --limit lite-dev --check --diff --vault-id core@prompt --vault-id lite-dev@prompt
    

    The console run passes its guards, including Check n8n did not restart. In the n8n check run, Write the n8n environment file and Write the Compose file must both say ok, not changed, and there must be no RUNNING HANDLER [n8n : Recreate n8n]: the encryption key and everything else came out the same as before. (The env file is no_log, so --diff shows nothing for it; ok is the proof.) Only if both are ok, run it for real, the same command without --check --diff, and confirm n8n's start time is unchanged:

    sudo docker inspect -f '{{ .State.StartedAt }}' n8n    # on lite-dev, before and after
    
  8. Commit and push the inventory, as yourself:

    cd ~/octopodia-abos-inventory
    python3 ~/octopodia-abos/infra/scripts/inventory_check.py .
    git add -A && git status --short       # hosts.yml and three vault.yml files, nothing else
    git commit -m "feat: lite-dev's hosts and secrets split per ADR-0042 (OI-124)" && git push
    
  9. Tidy up, deleting nothing. Move the old, ignored hosts.yml out of the code checkout, so nothing reads it by mistake:

    mkdir -p ~/_to_delete && mv ~/octopodia-abos/infra/inventory/hosts.yml ~/_to_delete/hosts-old-code-checkout.yml
    

    Keep ~/vault-before-split.yml, ~/main-before-split.yml and ~/hosts-before-split.yml where they are until phase 4 step 4's runner is working from the new inventory. They are the way back if anything in the new layout turns out wrong. Then move them into ~/_to_delete/ the same way. The vault copy is still encrypted with core; the old vault also stays in the code repository's git history.

From now on, hand runs name the vault IDs they need: core for host and Console work, plus the tenant's for anything that touches that tenant's n8n.

If something goes wrong

Symptom Cause Fix
Unable to parse .../octopodia-abos-inventory/hosts.yml or no hosts matched The inventory is not cloned beside the code, or hosts.yml is not in it yet Steps 3 and 4
vault_split.py: already exists: nothing written It ran before Move host_vars/lite-dev/vault.yml and tenants/lite-dev/vault.yml aside and run it again
Decryption failed Wrong password, or the old file was encrypted with another ID Try again; the old file needs core
The check: encrypted as a whole file A vault.yml made with ansible-vault create or encrypt Rebuild it with encrypt_string, value by value (the inventory README)
Tenant lite-dev is listed ... but tenants/lite-dev/vars.yml or vault.yml is missing Step 5 not done, or run against another folder Step 5
vault_n8n_encryption_key is not set from the n8n playbook Run without --vault-id lite-dev@prompt Add it
The n8n check run says changed on the env file The key in tenants/lite-dev/vault.yml differs from the one n8n started with Do not run it for real. Stop and compare with Bitwarden; n8n cannot read its credentials with another key