provisioning-catalog/providers/hetzner/templates/hetzner_build_image.j2

106 lines
3.4 KiB
Text
Raw Normal View History

{%- set role = image_role -%}
#!/bin/bash
# nixos-{{ role.name }} — build image and create Hetzner snapshot
# provisioning {{ provisioning_version }} — {{ now }}
# Output: SNAPSHOT_ID written to stdout on success
set -euo pipefail
{%- if debug is defined and debug == "yes" %}
{%- endif %}
HCLOUD_TOKEN="${HCLOUD_TOKEN:?HCLOUD_TOKEN required}"
PROVISIONING="${PROVISIONING:-/usr/local/provisioning}"
FLAKE_DIR="{{ flake_dir }}"
SSH_KEY="{{ ssh_key }}"
LOCATION="{{ location | default(value='nbg1') }}"
ROLE="{{ role.name }}"
# Build the raw-efi image via nix
# Requires: nix with aarch64-linux builder (Nix Desktop on Apple Silicon, or remote builder)
echo "=== Building NixOS $ROLE image ==="
# Find nix executable (handle both installed and path variations)
NIX_BIN=$(which nix 2>/dev/null || find /nix/store -name "nix" -type f 2>/dev/null | head -1)
if [ -z "$NIX_BIN" ]; then
echo "ERROR: nix not found in PATH or /nix/store"
exit 1
fi
export PROVISIONING
$NIX_BIN build "${FLAKE_DIR}#packages.aarch64-linux.${ROLE}-image" \
--out-link "/tmp/nixos-${ROLE}-image" \
--print-build-logs \
--impure
IMAGE_STORE=$(readlink -f "/tmp/nixos-${ROLE}-image")
# nixos-generators raw-efi output is a .img file inside the store path
IMAGE_PATH=$(find "$IMAGE_STORE" -name "*.img" | head -1)
if [ -z "$IMAGE_PATH" ]; then
echo "ERROR: no .img found in $IMAGE_STORE"
exit 1
fi
echo "Image: $IMAGE_PATH ($(du -sh "$IMAGE_PATH" | cut -f1))"
# Create temporary build server (cheapest ARM64 on Hetzner)
TEMP_NAME="imgbuild-${ROLE}-$$"
echo "=== Creating temp server $TEMP_NAME ==="
hcloud server create \
--name "$TEMP_NAME" \
--type cax11 \
--location "$LOCATION" \
--image debian-12 \
--ssh-key "$SSH_KEY" \
--output json > /tmp/imgbuild-create.json
SERVER_ID=$(hcloud server describe "$TEMP_NAME" -o format='{{ "{{.ID}}" }}')
SERVER_IP=$(hcloud server describe "$TEMP_NAME" -o format='{{ "{{.PublicNet.IPv4.IP}}" }}')
echo "Server $TEMP_NAME: id=$SERVER_ID ip=$SERVER_IP"
cleanup() {
echo "=== Cleanup: deleting $TEMP_NAME ==="
hcloud server delete "$SERVER_ID" || true
rm -f /tmp/imgbuild-create.json "/tmp/nixos-${ROLE}-image"
}
trap cleanup EXIT
# Enable rescue and reboot
echo "=== Booting into rescue ==="
hcloud server enable-rescue "$SERVER_ID" --type linux64 --ssh-key "$SSH_KEY"
hcloud server reboot "$SERVER_ID"
echo "Waiting for rescue SSH..."
for i in $(seq 1 24); do
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
-o BatchMode=yes root@"$SERVER_IP" true 2>/dev/null && break
sleep 10
done
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
-o BatchMode=yes root@"$SERVER_IP" true || {
echo "ERROR: rescue SSH unreachable after 240s"
exit 1
}
# Write image to disk
echo "=== Writing image to /dev/sda ==="
gzip -dc "$IMAGE_PATH" | ssh -o StrictHostKeyChecking=no root@"$SERVER_IP" \
"dd of=/dev/sda bs=4M conv=fsync status=progress"
echo "=== Powering off ==="
hcloud server poweroff "$SERVER_ID"
sleep 15
# Create snapshot
DESCRIPTION="nixos-${ROLE}-aarch64-{{ now | replace(from=' ', to='T') | replace(from=':', to='') }}"
echo "=== Creating snapshot: $DESCRIPTION ==="
SNAPSHOT_ID=$(hcloud server create-image "$SERVER_ID" \
--type snapshot \
--description "$DESCRIPTION" \
-o format='{{ "{{.ID}}" }}')
echo "=== Done ==="
echo "SNAPSHOT_ID=$SNAPSHOT_ID"
echo "Update servers.ncl for role '$ROLE': image = \"$SNAPSHOT_ID\""
# Disable cleanup trap (keep snapshot, delete server still happens)
trap - EXIT
hcloud server delete "$SERVER_ID"