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

146 lines
4.9 KiB
Django/Jinja
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{%- 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"