Files
homelab-config/virtual-machines/build-image.sh
T

184 lines
4.1 KiB
Bash
Raw Normal View History

2026-09-27 02:06:13 -05:00
#!/usr/bin/env bash
2026-10-16 00:09:34 -05:00
set -Eeuo pipefail
2026-09-27 02:06:13 -05:00
2026-09-27 02:11:09 -05:00
# Usage:
2026-10-16 00:09:34 -05:00
# ./build-image.sh <vm-folder> <alias> [vmname]
2026-09-27 02:11:09 -05:00
#
2026-10-16 00:09:34 -05:00
# Example:
# ./build-image.sh vm-test nixos/custom/jellybean
#
# Run from anywhere; all project operations use virtual-machines/.
2026-09-27 02:11:09 -05:00
2026-10-16 00:09:34 -05:00
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
if [[ $# -lt 2 || $# -gt 3 ]]; then
echo "Usage: $0 <vm-folder> <alias> [vmname]"
2026-09-27 02:11:09 -05:00
exit 1
fi
2026-10-16 00:09:34 -05:00
VM_FOLDER="$1"
ALIAS="$2"
VMNAME="${3:-vm}"
VM_DIR="$SCRIPT_DIR/$VM_FOLDER"
SOPS_CONFIG="$SCRIPT_DIR/.sops.yaml"
AGE_DIR="/run/sops-virtual"
AGE_KEY="$AGE_DIR/virtual-key.txt"
SANDBOX_KEY="/run/sops-virtual-key.txt"
for cmd in age-keygen sops yq nix incus sudo; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "Missing required command: $cmd" >&2
exit 1
}
done
[[ -d "$VM_DIR" ]] || {
echo "VM directory not found: $VM_DIR" >&2
exit 1
}
[[ -f "$VM_DIR/flake.nix" ]] || {
echo "No flake.nix found in $VM_DIR" >&2
exit 1
}
[[ -f "$SOPS_CONFIG" ]] || {
echo "SOPS config not found: $SOPS_CONFIG" >&2
exit 1
}
[[ -d "$VM_DIR/secrets" ]] || {
echo "Secrets directory not found: $VM_DIR/secrets" >&2
exit 1
}
# Refuse to overwrite a key that may belong to another running build.
if sudo test -e "$AGE_KEY"; then
echo "Temporary key already exists: $AGE_KEY" >&2
echo "Remove it or investigate the previous build before continuing." >&2
exit 1
fi
SOPS_BACKUP="$(mktemp)"
AGE_TEMP="$(mktemp)"
2026-10-16 00:52:44 -05:00
rm -f "$AGE_TEMP"
2026-10-16 00:09:34 -05:00
cp -p "$SOPS_CONFIG" "$SOPS_BACKUP"
CLEANED_UP=0
cleanup() {
local status=$?
if [[ "$CLEANED_UP" -eq 1 ]]; then
return
fi
CLEANED_UP=1
# Restore the repository's original SOPS configuration.
cp "$SOPS_BACKUP" "$SOPS_CONFIG"
rm -f "$SOPS_BACKUP" "$AGE_TEMP"
# Remove the temporary private key, even if the build failed.
sudo rm -f "$AGE_KEY"
sudo rmdir "$AGE_DIR" 2>/dev/null || true
echo "Temporary age key removed."
return "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
echo "Generating temporary age identity..."
age-keygen -o "$AGE_TEMP"
AGE_PUBLIC_KEY="$(age-keygen -y "$AGE_TEMP")"
# Make the key readable by Nix build users, but not ordinary users.
sudo install -d -o root -g nixbld -m 0750 "$AGE_DIR"
sudo install -o root -g nixbld -m 0440 \
"$AGE_TEMP" "$AGE_KEY"
# Update age recipients in the SOPS creation rules temporarily.
# Requires mikefarah/yq v4 and simple comma-separated age fields.
export AGE_PUBLIC_KEY
if ! yq -e \
'.creation_rules | any(.[]; has("age"))' \
"$SOPS_CONFIG" >/dev/null; then
echo "No creation_rules with an age field found in .sops.yaml" >&2
exit 1
fi
yq -i '
(.creation_rules[] | select(has("age")) | .age) |= (
split(",")
| map(trim)
| map(select(. != ""))
| . + [strenv(AGE_PUBLIC_KEY)]
| unique
| join(",")
)
' "$SOPS_CONFIG"
echo "Updating SOPS recipients for secrets in $VM_FOLDER..."
shopt -s nullglob
SECRET_FILES=("$VM_DIR"/secrets/*)
shopt -u nullglob
if [[ ${#SECRET_FILES[@]} -eq 0 ]]; then
echo "No secret files found in $VM_DIR/secrets" >&2
exit 1
fi
for secret in "${SECRET_FILES[@]}"; do
[[ -f "$secret" ]] || continue
echo "Updating recipients: ${secret#"$SCRIPT_DIR"/}"
sops updatekeys --yes "$secret"
done
echo "Building VM metadata..."
2026-09-27 02:06:13 -05:00
TARBALL_DIR="$(nix build \
2026-10-16 00:09:34 -05:00
"./$VM_FOLDER#nixosConfigurations.${VMNAME}.config.system.build.metadata" \
2026-09-27 02:11:09 -05:00
--print-out-paths)"
2026-09-27 02:06:13 -05:00
TARBALL="$(find "$TARBALL_DIR" -type f -name '*.tar.xz' -print -quit)"
if [[ -z "$TARBALL" ]]; then
2026-10-16 00:09:34 -05:00
echo "Could not find a .tar.xz tarball in: $TARBALL_DIR" >&2
2026-09-27 02:06:13 -05:00
exit 1
fi
2026-09-27 02:11:09 -05:00
echo "Metadata tarball: $TARBALL"
2026-09-27 02:06:13 -05:00
2026-10-16 00:09:34 -05:00
echo "Building QEMU disk image..."
2026-09-27 02:11:09 -05:00
QCOW2_DIR="$(nix build \
2026-10-16 00:09:34 -05:00
"./$VM_FOLDER#nixosConfigurations.${VMNAME}.config.system.build.qemuImage" \
2026-09-27 02:11:09 -05:00
--print-out-paths)"
QCOW2="$QCOW2_DIR/nixos.qcow2"
2026-09-27 02:06:13 -05:00
if [[ ! -f "$QCOW2" ]]; then
2026-10-16 00:09:34 -05:00
echo "QEMU image not found: $QCOW2" >&2
2026-09-27 02:06:13 -05:00
exit 1
fi
2026-10-16 00:09:34 -05:00
echo "QEMU image: $QCOW2"
2026-09-27 02:11:09 -05:00
echo "Importing image into Incus..."
2026-09-27 02:06:13 -05:00
incus image import \
--alias "$ALIAS" \
"$TARBALL" \
"$QCOW2"
2026-09-27 02:11:09 -05:00
echo "Successfully imported image: $ALIAS"
2026-09-27 02:06:13 -05:00