terraform: IaC-of-record for the cc-ci Hetzner host (salvaged from PR#2)
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
The cc-ci server already runs on Hetzner (migration done; nix/hosts/cc-ci-hetzner landed directly on main 2026-05-31). PR#2's host config was superseded by newer main commits, but its terraform/ provisioning scaffolding (cpx32 + nixos-infect) was never preserved. Add it here as the infrastructure-of-record so the box is reproducible. .gitignore keeps tfstate + secret tfvars out; HCLOUD_TOKEN is an env var at apply time (no secrets committed). PR#2 closed as superseded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
19
terraform/.gitignore
vendored
Normal file
19
terraform/.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Terraform state — may contain secrets; NEVER commit
|
||||||
|
*.tfstate
|
||||||
|
*.tfstate.*
|
||||||
|
*.tfstate.backup
|
||||||
|
|
||||||
|
# Variable files with secret values — NEVER commit
|
||||||
|
*.auto.tfvars
|
||||||
|
*.auto.tfvars.json
|
||||||
|
terraform.tfvars
|
||||||
|
|
||||||
|
# Terraform working directory (downloaded providers, modules)
|
||||||
|
.terraform/
|
||||||
|
|
||||||
|
# Crash logs
|
||||||
|
crash.log
|
||||||
|
crash.*.log
|
||||||
|
|
||||||
|
# NOTE: .terraform.lock.hcl (provider lock file) IS committed — it pins provider SHAs
|
||||||
|
# for reproducibility, analogous to flake.lock.
|
||||||
23
terraform/.terraform.lock.hcl
generated
Normal file
23
terraform/.terraform.lock.hcl
generated
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# This file is maintained automatically by "tofu init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/hetznercloud/hcloud" {
|
||||||
|
version = "1.64.0"
|
||||||
|
constraints = "1.64.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:FUkTfFrWlmv0JhsbjQvTk3zY7A2Q0LuoSs0PKEzaLpk=",
|
||||||
|
"zh:5bf7f8f429b1a8f485988d199f46295676a6cdf7d84ad11f1f4613faecfa89d5",
|
||||||
|
"zh:63b3d182474dd5afd0d5ab3f5f66228b752504436bcb2f4721bd6f1233d0f2ae",
|
||||||
|
"zh:6867da2d89d297b6760d80dde373e74df511bea72f7daccf6a944a9de4b4d4ed",
|
||||||
|
"zh:766fdcea1b03038a92414eafaa430b9ac0c57b36ce4c1573e6e291431659d528",
|
||||||
|
"zh:7f3186dfcae4028eac4f2c9c2c382b49c1fad0b63d0471b50748ee6817fbd8d2",
|
||||||
|
"zh:bb8a33b6ff9a4d3bce87628c49b08a4780e2c034762f40112058d96f5a4e52bd",
|
||||||
|
"zh:cc93751c7c90a37f180cf3e5439ed34f3154e60de5920a13d153d93954938239",
|
||||||
|
"zh:d6e2abf05a0eb8fe0544eb099960a4962db61532e7757016ccacbf0b83bcd1ae",
|
||||||
|
"zh:da9e3adedd8d33623aac4929fa8b1210f98d2931d5737c201da0dda992dd25ab",
|
||||||
|
"zh:dffc931aec4d7b0733690e115b1aabdf5c157b7d347a09a9d149ee6b7e9d8ce3",
|
||||||
|
"zh:e565dea4f28182099a271f794e3b781f069ea54976f5f05dbb79a1c2b6627459",
|
||||||
|
"zh:e79411287af28ccf6187bd418b7ea2ee217e642026392ddc8027bf3e3287fb80",
|
||||||
|
"zh:f5102d7141a04c193dffbb5cbc3f7e3588c41b87e11877d2e20d57ea5ef64123",
|
||||||
|
]
|
||||||
|
}
|
||||||
100
terraform/README.md
Normal file
100
terraform/README.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# cc-ci Hetzner Cloud Terraform
|
||||||
|
|
||||||
|
Provisions the cc-ci NixOS server on Hetzner Cloud (cpx32, 4 vCPU / 8 GB, x86 AMD, nbg1).
|
||||||
|
Stage 1 (Terraform): creates the server, runs nixos-infect to convert Debian 12 → NixOS.
|
||||||
|
Stage 2 (manual): clone the flake + apply the cc-ci config.
|
||||||
|
|
||||||
|
## Prerequisites (Class-A1 inputs — provide at apply time, NEVER commit)
|
||||||
|
|
||||||
|
| Input | How to provide |
|
||||||
|
|---|---|
|
||||||
|
| `HCLOUD_TOKEN` | `export HCLOUD_TOKEN=<token>` in shell before `tofu apply` |
|
||||||
|
| SSH key pair | Generate once: `ssh-keygen -t ed25519 -f ~/.ssh/cc-ci-hetzner`; pass pubkey via `TF_VAR_ssh_public_key="$(cat ~/.ssh/cc-ci-hetzner.pub)"` |
|
||||||
|
| Bootstrap age key | Provision to `/var/lib/sops-nix/key.txt` on the server (Stage 2; see `docs/install.md`) |
|
||||||
|
|
||||||
|
## Stage 1 — Provision server + nixos-infect
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd terraform/
|
||||||
|
|
||||||
|
# Provide secrets via environment
|
||||||
|
export HCLOUD_TOKEN=<your-token>
|
||||||
|
export TF_VAR_ssh_public_key="$(cat ~/.ssh/cc-ci-hetzner.pub)"
|
||||||
|
|
||||||
|
# Download providers (uses .terraform.lock.hcl — pinned, reproducible)
|
||||||
|
tofu init # or: terraform init
|
||||||
|
|
||||||
|
# Preview
|
||||||
|
tofu plan
|
||||||
|
|
||||||
|
# Apply — creates cpx31 server in nbg1, runs nixos-infect on first boot
|
||||||
|
tofu apply
|
||||||
|
|
||||||
|
# Note the output IP:
|
||||||
|
# server_ipv4 = "x.x.x.x"
|
||||||
|
# ssh_connect = "ssh root@x.x.x.x"
|
||||||
|
```
|
||||||
|
|
||||||
|
nixos-infect runs on first boot and **reboots the server** into NixOS (~5 min total).
|
||||||
|
Wait for the reboot to complete, then verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check NixOS is up:
|
||||||
|
ssh root@<ip> 'nixos-version'
|
||||||
|
|
||||||
|
# Inspect infect log if needed:
|
||||||
|
ssh root@<ip> 'cat /var/log/nixos-infect.log'
|
||||||
|
```
|
||||||
|
|
||||||
|
After the reboot the server runs bare NixOS (infect-generated config). Proceed to Stage 2.
|
||||||
|
|
||||||
|
## Stage 2 — Apply the cc-ci flake config
|
||||||
|
|
||||||
|
Follows the D8 install flow documented in `docs/install.md` exactly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On the Hetzner server (ssh root@<ip>):
|
||||||
|
|
||||||
|
# 1. Clone the flake (--recursive brings cc-ci-secrets submodule)
|
||||||
|
git clone --recursive https://git.autonomic.zone/recipe-maintainers/cc-ci.git /etc/cc-ci
|
||||||
|
cd /etc/cc-ci
|
||||||
|
|
||||||
|
# 2. Provision the bootstrap age key (the one irreducible out-of-band secret)
|
||||||
|
mkdir -p /var/lib/sops-nix
|
||||||
|
install -m 0600 /dev/stdin /var/lib/sops-nix/key.txt <<'EOF'
|
||||||
|
<paste bootstrap age private key here — see docs/install.md>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 3. Apply the cc-ci Hetzner host config
|
||||||
|
nixos-rebuild switch --flake .#cc-ci-hetzner
|
||||||
|
|
||||||
|
# 4. Verify (all units green, reconcile oneshots converged)
|
||||||
|
systemctl --failed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `server_type` | `cpx31` | x86 only. `cpx31`=AMD 4vCPU/8GB, `cx33`=Intel 4vCPU/8GB. Never `cax*` (ARM). |
|
||||||
|
| `location` | `nbg1` | Hetzner datacenter. |
|
||||||
|
| `image` | `debian-12` | Base image; nixos-infect converts it to NixOS. debian-12 preferred. |
|
||||||
|
| `server_name` | `cc-ci` | Hetzner server name. |
|
||||||
|
| `ssh_public_key` | (required) | Public key registered for root access. |
|
||||||
|
|
||||||
|
Override via env: `TF_VAR_location=hel1 tofu apply`.
|
||||||
|
|
||||||
|
## Teardown (throwaway verification run)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu destroy # removes server + SSH key; billing stops immediately
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `.terraform.lock.hcl` is committed (pins provider SHAs — analogous to flake.lock).
|
||||||
|
- `*.tfstate`, `*.tfvars`, `.terraform/` are gitignored — never commit state or secrets.
|
||||||
|
- `cpx31` is retired in some Hetzner DCs; `cpx32` (equivalent AMD, 4 vCPU / 8 GB) is the default.
|
||||||
|
`cx33` (Intel, same spec) is also available. Both are x86_64 — compatible with the `x86_64-linux` flake.
|
||||||
|
- The Hetzner server has a public IPv4 — future: point `*.ci.commoninternet.net` A record directly
|
||||||
|
at it and drop the gateway/MagicDNS path (see plan §6 + `DECISIONS.md`).
|
||||||
32
terraform/main.tf
Normal file
32
terraform/main.tf
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
resource "hcloud_ssh_key" "cc_ci" {
|
||||||
|
name = "cc-ci-deploy"
|
||||||
|
public_key = var.ssh_public_key
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
project = "cc-ci"
|
||||||
|
managed = "terraform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server" "cc_ci" {
|
||||||
|
name = var.server_name
|
||||||
|
server_type = var.server_type
|
||||||
|
image = var.image
|
||||||
|
location = var.location
|
||||||
|
ssh_keys = [hcloud_ssh_key.cc_ci.id]
|
||||||
|
|
||||||
|
# Stage 1: cloud-init runs nixos-infect on first boot, converting Ubuntu to NixOS,
|
||||||
|
# then reboots. See user-data.sh for the pinned infect revision.
|
||||||
|
user_data = file("${path.module}/user-data.sh")
|
||||||
|
|
||||||
|
public_net {
|
||||||
|
ipv4_enabled = true
|
||||||
|
ipv6_enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
project = "cc-ci"
|
||||||
|
managed = "terraform"
|
||||||
|
stage = "infect"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
terraform/outputs.tf
Normal file
19
terraform/outputs.tf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
output "server_ipv4" {
|
||||||
|
description = "Public IPv4 address of the cc-ci Hetzner server"
|
||||||
|
value = hcloud_server.cc_ci.ipv4_address
|
||||||
|
}
|
||||||
|
|
||||||
|
output "server_id" {
|
||||||
|
description = "Hetzner internal server ID"
|
||||||
|
value = hcloud_server.cc_ci.id
|
||||||
|
}
|
||||||
|
|
||||||
|
output "ssh_connect" {
|
||||||
|
description = "SSH command to connect as root"
|
||||||
|
value = "ssh root@${hcloud_server.cc_ci.ipv4_address}"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "nixos_infect_log" {
|
||||||
|
description = "Path on the server where nixos-infect logs are written"
|
||||||
|
value = "ssh root@${hcloud_server.cc_ci.ipv4_address} 'cat /var/log/nixos-infect.log'"
|
||||||
|
}
|
||||||
25
terraform/user-data.sh
Normal file
25
terraform/user-data.sh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Stage 1 — convert Debian 12 → NixOS via nixos-infect (pinned revision).
|
||||||
|
#
|
||||||
|
# nixos-infect generates /etc/nixos/{configuration.nix,hardware-configuration.nix,networking.nix}
|
||||||
|
# with Hetzner-correct bootloader (GRUB, not systemd-boot) and networking, then reboots into NixOS.
|
||||||
|
#
|
||||||
|
# After the reboot:
|
||||||
|
# - SSH as root is available (key registered with Hetzner survives infect)
|
||||||
|
# - Run Stage 2 per terraform/README.md: clone cc-ci + cc-ci-secrets, provision the bootstrap
|
||||||
|
# age key, then `nixos-rebuild switch --flake .#cc-ci-hetzner`
|
||||||
|
#
|
||||||
|
# Logs are written to /var/log/nixos-infect.log on the server for post-mortem inspection.
|
||||||
|
# The server reboots automatically at the end of infect — wait ~5 min before sshing in.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Pinned nixos-infect revision (2026-03-22: "fixes errors for non efi systems").
|
||||||
|
# Update deliberately; verify Hetzner still supported before bumping.
|
||||||
|
INFECT_SHA="40f62a680bb0e8f2f607d79abfaaecd99d59401c"
|
||||||
|
|
||||||
|
export NIX_CHANNEL="nixos-24.11"
|
||||||
|
export PROVIDER="hetzner" # tells nixos-infect to use GRUB + Hetzner networking
|
||||||
|
export NIXOS_IMPORT="" # no extra imports at infect time; we apply the real flake in Stage 2
|
||||||
|
|
||||||
|
curl -fsSL "https://raw.githubusercontent.com/elitak/nixos-infect/${INFECT_SHA}/nixos-infect" \
|
||||||
|
| bash -x 2>&1 | tee /var/log/nixos-infect.log
|
||||||
37
terraform/variables.tf
Normal file
37
terraform/variables.tf
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
variable "location" {
|
||||||
|
description = "Hetzner datacenter (nbg1=Nuremberg, fsn1=Falkenstein, hel1=Helsinki, ash=Ashburn, hil=Hillsboro)"
|
||||||
|
type = string
|
||||||
|
default = "nbg1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "server_type" {
|
||||||
|
description = <<-EOT
|
||||||
|
Hetzner server type. Must be x86 — the flake is x86_64-linux; NEVER use cax* (ARM).
|
||||||
|
cpx32 = AMD 4 vCPU / 8 GB (default; replaces cpx31 which is retired in some DCs).
|
||||||
|
cx33 = Intel 4 vCPU / 8 GB (alternative).
|
||||||
|
EOT
|
||||||
|
type = string
|
||||||
|
default = "cpx32"
|
||||||
|
|
||||||
|
validation {
|
||||||
|
condition = !startswith(var.server_type, "cax")
|
||||||
|
error_message = "ARM server types (cax*) are not supported — the cc-ci flake is x86_64-linux only."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image" {
|
||||||
|
description = "Base OS image. nixos-infect supports debian-12 and ubuntu-24.04. debian-12 preferred."
|
||||||
|
type = string
|
||||||
|
default = "debian-12"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ssh_public_key" {
|
||||||
|
description = "SSH public key content (the full line, e.g. 'ssh-ed25519 AAAA... comment'). Registered with Hetzner for root access post-infect. Pass via TF_VAR_ssh_public_key or terraform.tfvars (gitignored)."
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "server_name" {
|
||||||
|
description = "Hetzner server name and initial NixOS hostname"
|
||||||
|
type = string
|
||||||
|
default = "cc-ci"
|
||||||
|
}
|
||||||
14
terraform/versions.tf
Normal file
14
terraform/versions.tf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.0"
|
||||||
|
required_providers {
|
||||||
|
hcloud = {
|
||||||
|
source = "hetznercloud/hcloud"
|
||||||
|
version = "1.64.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The hcloud provider reads HCLOUD_TOKEN from the environment automatically.
|
||||||
|
# Never put the token value in any .tf file or .tfvars — keep it in the shell
|
||||||
|
# environment (export HCLOUD_TOKEN=...) or pass via TF_VAR_hcloud_token.
|
||||||
|
provider "hcloud" {}
|
||||||
Reference in New Issue
Block a user