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

147 lines
4.9 KiB
Text
Raw Permalink Normal View History

{%- set role = image_role -%}
#!/bin/bash
# base-{{ role.name }} — build Debian base image with custom partition layout
# provisioning {{ provisioning_version }} — {{ now }}
# Output: SNAPSHOT_ID written to stdout on success
set -euo pipefail
HCLOUD_TOKEN="${HCLOUD_TOKEN:?HCLOUD_TOKEN required}"
SSH_KEY="{{ ssh_key }}"
LOCATION="{{ location | default(value='nbg1') }}"
ROLE="{{ role.name }}"
BUILD_TYPE="{{ role.build_server_type | default(value='cx22') }}"
ROOT_SIZE_GB="{{ role.partition_layout.root_size_gb | default(value=5) }}"
DATA_FMT="{{ role.partition_layout.data_part | default(value='ext4') }}"
# Create temporary x86_64 builder server (cx22: 2 vCPU, 4GB RAM, 40GB disk)
TEMP_NAME="basebuild-${ROLE}-$$"
echo "=== Creating builder $TEMP_NAME (${BUILD_TYPE}, location=${LOCATION}) ==="
hcloud server create \
--name "$TEMP_NAME" \
--type "$BUILD_TYPE" \
--location "$LOCATION" \
--image debian-12 \
--ssh-key "$SSH_KEY" \
--output json > /tmp/basebuild-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 (id=$SERVER_ID) ==="
hcloud server delete "$SERVER_ID" || true
rm -f /tmp/basebuild-create.json
}
trap cleanup EXIT
# Enable rescue mode and reboot
echo "=== Enabling rescue mode ==="
hcloud server enable-rescue "$SERVER_ID" --type linux64 --ssh-key "$SSH_KEY"
hcloud server reboot "$SERVER_ID"
# Wait for rescue SSH (30 attempts × 10s = 300s timeout)
echo "Waiting for rescue SSH..."
for i in $(seq 1 30); 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 300s"
exit 1
}
# Execute partitioning + Debian install in rescue mode
# ROOT_GB and DATA_FMT are passed as env vars; script is quoted to prevent local expansion
echo "=== Running rescue setup script ==="
ssh -o StrictHostKeyChecking=no root@"$SERVER_IP" \
ROOT_GB="$ROOT_SIZE_GB" DATA_FMT="$DATA_FMT" \
bash -s <<'RESCUE'
set -euo pipefail
# Hetzner rescue may not include debootstrap
apt-get install -y debootstrap 2>&1 | tail -10
# GPT partition layout:
# sda1 1MiB 2MiB bios_grub (required for GRUB on GPT+BIOS)
# sda2 2MiB ${ROOT_GB}GiB ext4 root (boot flag set)
# sda3 ${ROOT_GB}GiB 100% ext4 data (or left unformatted)
parted -s /dev/sda mklabel gpt
parted -s /dev/sda mkpart bios_boot 1MiB 2MiB
parted -s /dev/sda set 1 bios_grub on
parted -s /dev/sda mkpart primary ext4 2MiB "${ROOT_GB}GiB"
parted -s /dev/sda set 2 boot on
parted -s /dev/sda mkpart primary ext4 "${ROOT_GB}GiB" 100%
partprobe /dev/sda
sleep 2
# Format root; data partition formatted only when requested
mkfs.ext4 -L root /dev/sda2
[ "$DATA_FMT" = "ext4" ] && mkfs.ext4 -L data /dev/sda3 || true
# Bootstrap minimal Debian bookworm onto root partition
mount /dev/sda2 /mnt
debootstrap --arch amd64 bookworm /mnt https://deb.debian.org/debian
# Bind mounts required for chroot operations (grub-install needs /dev)
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys
# Configure base system inside chroot
chroot /mnt bash <<'CHROOT'
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
# Filesystem table: root on sda2 (LABEL=root), data on sda3 (LABEL=data)
printf 'LABEL=root / ext4 defaults 0 1\nLABEL=data /data ext4 defaults 0 2\n' > /etc/fstab
mkdir -p /data
# Minimal loopback config; cloud-init manages real network interfaces
printf 'auto lo\niface lo inet loopback\n' > /etc/network/interfaces
apt-get update
apt-get install -y --no-install-recommends \
linux-image-amd64 \
grub-pc \
openssh-server \
cloud-init \
cloud-guest-utils \
isc-dhcp-client \
net-tools
# grub-install targets the whole disk; sda1 (bios_grub) provides the embedding area
grub-install /dev/sda
update-grub
systemctl enable ssh
apt-get clean
rm -rf /var/lib/apt/lists/*
CHROOT
# Unmount all bind mounts before poweroff
umount -l /mnt/dev /mnt/proc /mnt/sys /mnt
RESCUE
# Power off before snapshot to ensure disk consistency
echo "=== Powering off builder ==="
hcloud server poweroff "$SERVER_ID"
sleep 15
# Create snapshot from powered-off server
DESCRIPTION="base-{{ role.name }}-debian12-{{ 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 images.ncl for role '{{ role.name }}': snapshot_id = \"$SNAPSHOT_ID\""
trap - EXIT
hcloud server delete "$SERVER_ID"