134 lines
5.1 KiB
Bash
Executable File
134 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# build-iso.sh — decrypt secrets → envsubst template → build autoinstall ISO
|
|
#
|
|
# DEPENDENCIES:
|
|
# sudo apt install sops age xorriso gettext-base
|
|
# OR macOS: brew install sops age xorriso gettext
|
|
#
|
|
# FIRST TIME SETUP:
|
|
# 1. age-keygen -o ~/.config/sops/age/keys.txt
|
|
# 2. Paste the public key (age1...) into .sops.yaml
|
|
# 3. Fill in secrets.yaml, then encrypt:
|
|
# sops -e secrets.yaml > secrets.sops.yaml && rm secrets.yaml
|
|
# 4. git add .sops.yaml secrets.sops.yaml user-data.tmpl build-iso.sh .gitignore scripts/
|
|
#
|
|
# USAGE:
|
|
# ./build-iso.sh
|
|
# ./build-iso.sh --ubuntu-iso ~/Downloads/ubuntu-24.04-live-server-amd64.iso
|
|
|
|
set -euo pipefail
|
|
|
|
UBUNTU_ISO="${UBUNTU_ISO:-}"
|
|
UBUNTU_VERSION="24.04.4"
|
|
UBUNTU_ISO_URL="https://releases.ubuntu.com/${UBUNTU_VERSION}/ubuntu-${UBUNTU_VERSION}-live-server-amd64.iso"
|
|
WORK_DIR="$(mktemp -d /tmp/autoinstall-build.XXXXXX)"
|
|
OUTPUT_ISO="autoinstall-$(date +%Y%m%d-%H%M).iso"
|
|
SOPS_FILE="secrets.sops.yaml"
|
|
TEMPLATE_FILE="user-data.tmpl"
|
|
RENDERED_FILE="user-data.yaml"
|
|
POST_INSTALL_SCRIPT="scripts/post-install.sh"
|
|
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m'
|
|
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
error() {
|
|
echo -e "${RED}[✗]${NC} $*"
|
|
exit 1
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--ubuntu-iso)
|
|
UBUNTU_ISO="$2"
|
|
shift 2
|
|
;;
|
|
*) error "Unknown argument: $1" ;;
|
|
esac
|
|
done
|
|
|
|
cleanup() {
|
|
info "Cleaning up..."
|
|
rm -rf "$WORK_DIR"
|
|
if [[ -f "$RENDERED_FILE" ]]; then
|
|
rm -f "$RENDERED_FILE"
|
|
info "Deleted plaintext $RENDERED_FILE"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
for cmd in sops envsubst xorriso; do
|
|
command -v "$cmd" &>/dev/null || error "'$cmd' not found. Install it first."
|
|
done
|
|
[[ -f "$SOPS_FILE" ]] || error "Secrets file '$SOPS_FILE' not found."
|
|
[[ -f "$TEMPLATE_FILE" ]] || error "Template '$TEMPLATE_FILE' not found."
|
|
[[ -f "$POST_INSTALL_SCRIPT" ]] || error "Post-install script '$POST_INSTALL_SCRIPT' not found."
|
|
[[ -f ".sops.yaml" ]] || error ".sops.yaml not found in current directory."
|
|
|
|
export SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$HOME/.config/sops/age/keys.txt}"
|
|
[[ -f "$SOPS_AGE_KEY_FILE" ]] || error "age key not found at $SOPS_AGE_KEY_FILE"
|
|
|
|
# ── Decrypt secrets → render template ─────────────────────────────────────────
|
|
info "Decrypting secrets and rendering template..."
|
|
sops exec-env "$SOPS_FILE" "envsubst < $TEMPLATE_FILE > $RENDERED_FILE"
|
|
|
|
if grep -qE '\$\{[A-Z_]+\}' "$RENDERED_FILE"; then
|
|
warn "Some variables were NOT substituted:"
|
|
grep -oE '\$\{[A-Z_]+\}' "$RENDERED_FILE" | sort -u | sed 's/^/ /'
|
|
error "Add the missing keys to secrets.yaml and re-encrypt."
|
|
fi
|
|
info "Template rendered."
|
|
|
|
if [[ -n "$UBUNTU_ISO" ]]; then
|
|
# User provided a path — validate it exists
|
|
[[ -f "$UBUNTU_ISO" ]] || error "ISO not found: $UBUNTU_ISO"
|
|
info "Using provided ISO: $UBUNTU_ISO"
|
|
else
|
|
# Auto mode — use default name, download if missing
|
|
# UBUNTU_ISO="ubuntu-${UBUNTU_VERSION}-desktop-amd64.iso"
|
|
UBUNTU_ISO="ubuntu-${UBUNTU_VERSION}-live-server-amd64.iso"
|
|
if [[ ! -f "$UBUNTU_ISO" ]]; then
|
|
info "Downloading Ubuntu ${UBUNTU_VERSION} server ISO..."
|
|
curl -L --fail --progress-bar -o "$UBUNTU_ISO" "$UBUNTU_ISO_URL" ||
|
|
{
|
|
rm -f "$UBUNTU_ISO"
|
|
error "Download failed (check URL or network): $UBUNTU_ISO_URL"
|
|
}
|
|
else
|
|
info "Found cached ISO: $UBUNTU_ISO"
|
|
fi
|
|
fi
|
|
|
|
# ── Extract ISO + MBR template ────────────────────────────────────────────────
|
|
info "Extracting ISO..."
|
|
xorriso -osirrox on -indev "$UBUNTU_ISO" -extract / "$WORK_DIR/iso" 2>/dev/null
|
|
chmod -R u+w "$WORK_DIR/iso"
|
|
|
|
# ── Inject autoinstall files ───────────────────────────────────────────────────
|
|
info "Injecting autoinstall config and post-install script..."
|
|
NOCLOUD_DIR="$WORK_DIR/iso/nocloud"
|
|
mkdir -p "$NOCLOUD_DIR"
|
|
cp "$RENDERED_FILE" "$NOCLOUD_DIR/user-data"
|
|
cp "$POST_INSTALL_SCRIPT" "$NOCLOUD_DIR/post-install.sh"
|
|
touch "$NOCLOUD_DIR/meta-data"
|
|
|
|
# ── Patch GRUB ────────────────────────────────────────────────────────────────
|
|
GRUB_CFG="$WORK_DIR/iso/boot/grub/grub.cfg"
|
|
if [[ -f "$GRUB_CFG" ]]; then
|
|
info "Patching GRUB for unattended boot..."
|
|
sed -i 's|linux\s*/casper/vmlinuz\(.*\)|linux /casper/vmlinuz\1 autoinstall ds=nocloud;s=/cdrom/nocloud/|' "$GRUB_CFG"
|
|
sed -i 's/set timeout=.*/set timeout=0/' "$GRUB_CFG"
|
|
fi
|
|
|
|
# ── Repack ISO ─────────────────────────────────────────────────────────────────
|
|
info "Repacking ISO → $OUTPUT_ISO ..."
|
|
|
|
info "Done! ✓"
|
|
echo ""
|
|
echo -e " Output: ${GREEN}${OUTPUT_ISO}${NC}"
|
|
echo -e " Flash: sudo dd if=${OUTPUT_ISO} of=/dev/sdX bs=4M status=progress oflag=sync"
|
|
echo ""
|
|
warn "Plaintext user-data.yaml deleted. secrets.yaml never hit disk."
|