railiance-infra/docs/provisioning.md
codex b93af8cc78
Some checks failed
CI Smoke / source-contract (push) Failing after 2s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Implement reproducible S1 handoff contracts
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
2026-08-23 12:02:23 +02:00

4.5 KiB
Raw Blame History

🚀 Provisioning Servers with railiance-infra

This guide explains how adopted hosts and provider-managed Hetzner hosts share an inventory without sharing lifecycle behavior.


🚀 Fast Path: Using the Helper Script

Instead of manually editing inventory/servers.yaml and running make tf-apply, you can use the convenience script scripts/hcloud_new_server.sh.
This script will:

  1. Add the new host entry to inventory/servers.yaml
  2. Decrypt your Hetzner API token with SOPS
  3. Run Terraform init and plan
  4. Apply only when the operator supplies the explicit --apply flag after reviewing the plan

Example:

scripts/hcloud_new_server.sh core-01 --type cpx11 --region nbg1 --role core
# after review and the required approval:
scripts/hcloud_new_server.sh core-01 --type cpx11 --region nbg1 --role core --apply

This will create a small cpx11 instance in the Nuremberg (nbg1) region, tagged with the role core. You can then connect directly:

ssh admin@<printed-ip>

👉 The script is optional. You can always manage servers by editing inventory/servers.yaml and running make tf-apply instead.

1) Where you define servers

All host identities live in inventory/servers.yaml. Run make validate-inventory before Terraform or Ansible. Adopted hosts have a provider address but no provisioning block. Provider-managed Hetzner hosts have no committed IP and use this shape:

servers:
  - name: core-01
    provider: hetzner
    lifecycle_mode: provider-managed
    ssh_user: admin
    baseline_profile: ufw-managed
    provisioning:
      server_type: cpx21
      region: nbg1
      image: ubuntu-24.04
      role: core
      labels: [core, wireguard, git]

Tip: Keep names stable. Renaming a server in this file makes Terraform think the old one was destroyed and a new one should be created.


2) Two ways to add a server

A) Edit YAML by hand (simple)

Open inventory/servers.yaml, add a new entry, save, commit.

B) Use the helper script (safe & quick)

# requires scripts/new_host.py
make new-host NAME=web-01 TYPE=cpx21 REGION=nbg1 ROLE=web
# or directly:
python3 scripts/new_host.py --name web-01 --type cpx21 --region nbg1 --role web

You can also do add + provision in one step:

scripts/hcloud_new_server.sh web-01 --type cpx21 --region nbg1 --role web

3) How Terraform uses your declaration

The module at terraform/hetzner/:

  • Selects only records with provider: hetzner and lifecycle_mode: provider-managed
  • Registers your SSH key from keys/admin_ssh.pub
  • Injects cloud-init that sets up the admin user and basic hardening
  • Creates/updates/destroys servers to match the YAML

Outputs include a map of server names → IPv4 addresses.


4) Provision (create/update)

The Hetzner API token is SOPS-decryptable at secrets/hetzner-token.yaml, field hetzner.token. It is decrypted only into the invoking process environment.

Then run either:

# plan and apply in separate steps
make tf-plan
APPROVE_TF_APPLY=YES make tf-apply

or the end-to-end convenience:

APPROVE_TF_APPLY=YES make apply    # terraform apply + Ansible bootstrap

If you used the one-shot script:

scripts/hcloud_new_server.sh web-01 --type cpx21 --region nbg1 --role web

Terraform will print the new servers IPv4 addresses at the end.


5) Connect & converge

Connect via SSH:

ssh admin@<server-ip>

Run Ansible base bootstrap (if not using make apply):

make ansible-bootstrap

6) Destroy (tear down)

To remove all servers managed by this repo:

APPROVE_TF_DESTROY=DESTROY-MANAGED-HETZNER make tf-destroy

To remove one provider-managed Hetzner server, delete its entry from inventory/servers.yaml, review the plan, obtain the required approval, then:

APPROVE_TF_APPLY=YES make tf-apply

Terraform will destroy the missing managed Hetzner server and leave others intact. Adopted Host Europe records are never Terraform resources.


7) Notes & conventions

  • Idempotent: You can run make apply repeatedly; Terraform converges infra, Ansible converges config.
  • SSH keys: Ensure keys/admin_ssh.pub exists before provisioning.
  • Secret token: The Hetzner API token is secrets/hetzner-token.yaml, field hetzner.token, and must remain SOPS-encrypted.
  • Cloud-init delay: Allow ~3060s after creation for first-boot tasks before first SSH.
  • Labels & role: labels are freeform tags; role can drive Ansible plays as you grow.