147 lines
4.9 KiB
Text
147 lines
4.9 KiB
Text
|
|
{%- 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"
|