198 lines
5.7 KiB
Bash
198 lines
5.7 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# Build NixOS image on remote Hetzner server (cross-platform builds)
|
||
|
|
# Usage: ./build-nixos-image-remote.sh [role] [location] [project_path]
|
||
|
|
# Output: SNAPSHOT_ID written to stdout on success
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
ROLE="${1:-cp}"
|
||
|
|
LOCATION="${2:-nbg1}"
|
||
|
|
PROJECT_PATH="${3:-.}"
|
||
|
|
SSH_KEY="${SSH_KEY:-htz_ops}"
|
||
|
|
HCLOUD_TOKEN="${HCLOUD_TOKEN:?HCLOUD_TOKEN required}"
|
||
|
|
|
||
|
|
# Derived
|
||
|
|
TEMP_NAME="build-nixos-${ROLE}-$$"
|
||
|
|
FLAKE_DIR="workspaces/librecloud_hetzner/nixos"
|
||
|
|
TIMESTAMP=$(date -u +%Y-%m-%dT%H%M%SZ)
|
||
|
|
DESCRIPTION="nixos-${ROLE}-aarch64-${TIMESTAMP}"
|
||
|
|
|
||
|
|
echo "=== Building NixOS ${ROLE} image on Hetzner ==="
|
||
|
|
echo "Temp server: $TEMP_NAME | Role: $ROLE | Location: $LOCATION"
|
||
|
|
|
||
|
|
# Create temporary build server
|
||
|
|
echo "=== 1. Creating temp server $TEMP_NAME ==="
|
||
|
|
hcloud server create \
|
||
|
|
--name "$TEMP_NAME" \
|
||
|
|
--type cax11 \
|
||
|
|
--location "$LOCATION" \
|
||
|
|
--image debian-12 \
|
||
|
|
--ssh-key "$SSH_KEY" > /dev/null
|
||
|
|
|
||
|
|
SERVER_ID=$(hcloud server describe "$TEMP_NAME" -o format='{{.ID}}')
|
||
|
|
SERVER_IP=$(hcloud server describe "$TEMP_NAME" -o format='{{.PublicNet.IPv4.IP}}')
|
||
|
|
echo "Created: $TEMP_NAME (ID=$SERVER_ID, IP=$SERVER_IP)"
|
||
|
|
|
||
|
|
cleanup() {
|
||
|
|
echo "=== Cleanup: deleting server ==="
|
||
|
|
hcloud server delete "$SERVER_ID" 2>/dev/null || true
|
||
|
|
rm -f /tmp/build-remote-*.sh /tmp/project-build.tar.gz
|
||
|
|
}
|
||
|
|
trap cleanup EXIT
|
||
|
|
|
||
|
|
# Wait for SSH
|
||
|
|
echo "=== 2. Waiting for SSH connectivity ==="
|
||
|
|
SSH_READY=0
|
||
|
|
for i in $(seq 1 60); do
|
||
|
|
if ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes root@"${SERVER_IP}" true 2>/dev/null; then
|
||
|
|
echo "SSH ready after $((i*5)) seconds"
|
||
|
|
SSH_READY=1
|
||
|
|
break
|
||
|
|
fi
|
||
|
|
printf "."
|
||
|
|
sleep 5
|
||
|
|
done
|
||
|
|
|
||
|
|
if [ "$SSH_READY" -eq 0 ]; then
|
||
|
|
echo ""
|
||
|
|
echo "ERROR: SSH timeout after 300 seconds"
|
||
|
|
echo "Server: $SERVER_IP"
|
||
|
|
echo "Check: ssh -o StrictHostKeyChecking=no root@${SERVER_IP}"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Transfer project
|
||
|
|
echo "=== 3. Transferring project ==="
|
||
|
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=10"
|
||
|
|
|
||
|
|
tar -czf /tmp/project-build.tar.gz \
|
||
|
|
--exclude='.git/objects' \
|
||
|
|
--exclude='.git/logs' \
|
||
|
|
--exclude='.nix' \
|
||
|
|
--exclude='result*' \
|
||
|
|
--exclude='*.img' \
|
||
|
|
--exclude='target' \
|
||
|
|
--exclude='.coder' \
|
||
|
|
-C "$PROJECT_PATH" .
|
||
|
|
|
||
|
|
SIZE=$(ls -lh /tmp/project-build.tar.gz | awk '{print $5}')
|
||
|
|
echo "Uploading $SIZE..."
|
||
|
|
scp $SSH_OPTS /tmp/project-build.tar.gz "root@${SERVER_IP}:/tmp/" || {
|
||
|
|
echo "ERROR: Failed to upload project"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
ssh $SSH_OPTS root@"${SERVER_IP}" "cd /tmp && tar -xzf project-build.tar.gz && rm project-build.tar.gz && echo 'Project extracted'" || {
|
||
|
|
echo "ERROR: Failed to extract project"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
echo "Project transferred"
|
||
|
|
|
||
|
|
# Install Nix and build
|
||
|
|
echo "=== 4. Installing Nix on server ==="
|
||
|
|
cat > /tmp/build-remote-install.sh << 'INSTALL_NIX'
|
||
|
|
#!/bin/bash
|
||
|
|
set -euo pipefail
|
||
|
|
apt-get update -qq
|
||
|
|
apt-get install -y -qq curl xz-utils
|
||
|
|
curl -L https://nixos.org/nix/install | bash -s -- --no-daemon --yes 2>/dev/null
|
||
|
|
export PATH="${HOME}/.nix-profile/bin:$PATH"
|
||
|
|
nix --version
|
||
|
|
INSTALL_NIX
|
||
|
|
|
||
|
|
scp $SSH_OPTS /tmp/build-remote-install.sh "root@${SERVER_IP}:/tmp/"
|
||
|
|
ssh $SSH_OPTS root@"${SERVER_IP}" bash /tmp/build-remote-install.sh
|
||
|
|
|
||
|
|
echo "=== 5. Building image ==="
|
||
|
|
cat > /tmp/build-remote-build.sh << BUILD_IMAGE
|
||
|
|
#!/bin/bash
|
||
|
|
set -euo pipefail
|
||
|
|
export PATH="\${HOME}/.nix-profile/bin:\$PATH"
|
||
|
|
export NIX_CONFIG="experimental-features = nix-command flakes"
|
||
|
|
|
||
|
|
cd /tmp
|
||
|
|
echo "Building ${ROLE} image..."
|
||
|
|
nix build "${FLAKE_DIR}#packages.aarch64-linux.${ROLE}-image" \
|
||
|
|
--out-link "/tmp/nixos-${ROLE}-image" \
|
||
|
|
--print-build-logs 2>&1 | tail -20
|
||
|
|
|
||
|
|
IMG=\$(find /tmp/nixos-${ROLE}-image -name "*.img" | head -1)
|
||
|
|
if [ -z "\$IMG" ]; then
|
||
|
|
echo "ERROR: image not found"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
ls -lh "\$IMG"
|
||
|
|
echo "SUCCESS: Image built"
|
||
|
|
BUILD_IMAGE
|
||
|
|
|
||
|
|
scp $SSH_OPTS /tmp/build-remote-build.sh "root@${SERVER_IP}:/tmp/"
|
||
|
|
ssh $SSH_OPTS root@"${SERVER_IP}" bash /tmp/build-remote-build.sh
|
||
|
|
|
||
|
|
# Fetch image
|
||
|
|
echo "=== 6. Fetching image back ==="
|
||
|
|
mkdir -p /tmp/nixos-build
|
||
|
|
scp $SSH_OPTS "root@${SERVER_IP}:/tmp/nixos-${ROLE}-image/*.img" /tmp/nixos-build/ 2>/dev/null || {
|
||
|
|
echo "ERROR: Failed to fetch image"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
IMAGE_LOCAL=$(find /tmp/nixos-build -name "*.img" | head -1)
|
||
|
|
echo "Image: $(ls -lh "$IMAGE_LOCAL" | awk '{print $5, $9}')"
|
||
|
|
|
||
|
|
# Reboot and deploy
|
||
|
|
echo "=== 7. Rebooting into rescue ==="
|
||
|
|
hcloud server reboot "$SERVER_ID" --force
|
||
|
|
sleep 15
|
||
|
|
|
||
|
|
hcloud server enable-rescue "$SERVER_ID" --type linux64 --ssh-key "$SSH_KEY" > /dev/null
|
||
|
|
hcloud server reboot "$SERVER_ID"
|
||
|
|
|
||
|
|
echo "Waiting for rescue SSH..."
|
||
|
|
RESCUE_READY=0
|
||
|
|
for i in $(seq 1 60); do
|
||
|
|
if ssh $SSH_OPTS -o ConnectTimeout=3 -o BatchMode=yes root@"${SERVER_IP}" true 2>/dev/null; then
|
||
|
|
echo "Rescue ready"
|
||
|
|
RESCUE_READY=1
|
||
|
|
break
|
||
|
|
fi
|
||
|
|
printf "."
|
||
|
|
sleep 5
|
||
|
|
done
|
||
|
|
|
||
|
|
if [ "$RESCUE_READY" -eq 0 ]; then
|
||
|
|
echo ""
|
||
|
|
echo "ERROR: Rescue SSH timeout"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Write image to disk
|
||
|
|
echo "=== 8. Writing image to /dev/sda ==="
|
||
|
|
gzip -dc "$IMAGE_LOCAL" | ssh $SSH_OPTS root@"${SERVER_IP}" \
|
||
|
|
"dd of=/dev/sda bs=4M conv=fsync status=progress"
|
||
|
|
|
||
|
|
echo "=== 9. Powering off ==="
|
||
|
|
hcloud server poweroff "$SERVER_ID"
|
||
|
|
sleep 15
|
||
|
|
|
||
|
|
echo "=== 10. Creating snapshot ==="
|
||
|
|
SNAPSHOT_ID=$(hcloud server create-image "$SERVER_ID" \
|
||
|
|
--type snapshot \
|
||
|
|
--description "$DESCRIPTION" \
|
||
|
|
-o format='{{.ID}}')
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "════════════════════════════════════════"
|
||
|
|
echo "✓ BUILD SUCCESS"
|
||
|
|
echo "════════════════════════════════════════"
|
||
|
|
echo "SNAPSHOT_ID=$SNAPSHOT_ID"
|
||
|
|
echo ""
|
||
|
|
echo "Next: Update servers.ncl for role '$ROLE':"
|
||
|
|
echo " image = \"$SNAPSHOT_ID\""
|
||
|
|
echo "════════════════════════════════════════"
|
||
|
|
|
||
|
|
# Keep snapshot, delete server
|
||
|
|
trap - EXIT
|
||
|
|
hcloud server delete "$SERVER_ID"
|