283 lines
14 KiB
Bash
Executable File
283 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# build.sh — Build the M-Archy Arch Linux ISO + netboot artifacts
|
|
#
|
|
# PURPOSE:
|
|
# This is the primary build entry point for the M-Archy custom Arch Linux
|
|
# installer ISO. It takes the official archiso "releng" base profile and
|
|
# merges the M-Archy overlay on top of it, embedding custom installer
|
|
# scripts and an optional pre-filled answerfile for fully automated
|
|
# unattended installation. It also generates iPXE scripts for netboot-based
|
|
# deployment (PXE/WDS environments).
|
|
#
|
|
# WORKFLOW OVERVIEW:
|
|
# 1. Parse arguments (optional answerfile path, netboot URL, output dir).
|
|
# 2. Install archiso if missing.
|
|
# 3. Copy the upstream releng profile to a working directory.
|
|
# 4. Apply the M-Archy overlay (custom scripts, profiledef, mkinitcpio config).
|
|
# 5. Merge extra packages (packages.extra) into the releng package list.
|
|
# 6. Embed the installer shell scripts into the ISO's /root/installer/.
|
|
# 7. Optionally embed an answerfile.json for automated installs.
|
|
# 8. Run mkarchiso to produce the final .iso and netboot tarball.
|
|
# 9. Optionally write an iPXE chainload script for netboot.xyz / WDS.
|
|
#
|
|
# USAGE:
|
|
# bash build.sh [--preconf [FILE]] [--netboot-url URL] [OUT_DIR]
|
|
#
|
|
# --preconf Embed ~/answerfile.json into the ISO at /answerfile.json
|
|
# --preconf FILE Embed the specified answerfile instead
|
|
# --netboot-url URL Base URL where netboot artifacts will be served
|
|
# (generates m-archy-netboot.ipxe in OUT_DIR)
|
|
# OUT_DIR Output directory (default: ~/m-archy-out, or $OUT_DIR env)
|
|
# =============================================================================
|
|
|
|
set -euo pipefail
|
|
# set -e → abort on any command that exits non-zero
|
|
# set -u → abort if an unset variable is referenced (catches typos)
|
|
# set -o pipefail → a pipe fails if any command in it fails (not just the last)
|
|
|
|
# Resolve this script's directory and the dotfiles root, regardless of where
|
|
# the caller's working directory is. Using BASH_SOURCE[0] rather than $0 is
|
|
# safer when the script is sourced or called with a relative path.
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
# ── Argument parsing ───────────────────────────────────────────────────────────
|
|
# We support both --flag value and --flag=value syntax.
|
|
# PRECONF_FILE → path to JSON answerfile to embed (empty = no answerfile)
|
|
# NETBOOT_URL → HTTP base URL for generated iPXE script (empty = skip)
|
|
# OUT_ARG → positional output directory override
|
|
PRECONF_FILE=""
|
|
NETBOOT_URL=""
|
|
OUT_ARG=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--preconf)
|
|
# If the next argument looks like a path (doesn't start with -),
|
|
# treat it as the answerfile path; otherwise use the default location.
|
|
if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then
|
|
PRECONF_FILE="$2"; shift
|
|
else
|
|
PRECONF_FILE="$HOME/answerfile.json"
|
|
fi
|
|
shift
|
|
;;
|
|
--preconf=*)
|
|
# Handle --preconf=/path/to/file syntax (strip prefix up to =)
|
|
PRECONF_FILE="${1#--preconf=}"
|
|
shift
|
|
;;
|
|
--netboot-url)
|
|
[[ $# -gt 1 ]] || { echo "ERROR: --netboot-url requires a URL argument" >&2; exit 1; }
|
|
NETBOOT_URL="$2"; shift 2
|
|
;;
|
|
--netboot-url=*)
|
|
NETBOOT_URL="${1#--netboot-url=}"
|
|
shift
|
|
;;
|
|
-*)
|
|
echo "Unknown flag: $1" >&2; exit 1
|
|
;;
|
|
*)
|
|
# Any non-flag argument is treated as the output directory
|
|
OUT_ARG="$1"; shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# WORK_DIR: scratch space where mkarchiso does its work (can be huge — squashfs
|
|
# compression of the full airootfs happens here).
|
|
WORK_DIR="${WORK_DIR:-$HOME/m-archy-build}"
|
|
|
|
# OUT_DIR: where the final .iso and netboot tarball are written.
|
|
# Priority: positional arg > $OUT_DIR env var > default ~/m-archy-out
|
|
OUT_DIR="${OUT_ARG:-${OUT_DIR:-$HOME/m-archy-out}}"
|
|
|
|
# PROFILE: the assembled profile directory passed to mkarchiso.
|
|
# We copy releng here and then apply our overlay on top.
|
|
PROFILE="$WORK_DIR/profile"
|
|
|
|
# RELENG: the official Arch Linux "release engineering" base profile, installed
|
|
# with the archiso package. It provides sane defaults for pacman.conf, EFI
|
|
# boot entries, and a working live environment.
|
|
RELENG="/usr/share/archiso/configs/releng"
|
|
|
|
# ── Ensure archiso is installed ───────────────────────────────────────────────
|
|
# mkarchiso is the tool that actually assembles the ISO from a profile directory.
|
|
# It is only available after installing the archiso package.
|
|
if ! command -v mkarchiso &>/dev/null; then
|
|
echo "Installing archiso..."
|
|
sudo pacman -S --noconfirm archiso
|
|
fi
|
|
|
|
# Sanity check: the releng base profile must exist.
|
|
# If archiso installed correctly, this path will always be present.
|
|
[[ -d "$RELENG" ]] || { echo "ERROR: $RELENG not found — is archiso installed?"; exit 1; }
|
|
|
|
# ── Validate answerfile early (fail fast before the long build) ───────────────
|
|
# If --preconf was given, verify the file exists and is valid JSON now rather
|
|
# than discovering the problem after a 10-minute build.
|
|
if [[ -n "$PRECONF_FILE" ]]; then
|
|
[[ -f "$PRECONF_FILE" ]] \
|
|
|| { echo "ERROR: answerfile not found: $PRECONF_FILE"; exit 1; }
|
|
# jq empty parses JSON and exits 0 if valid, non-zero if malformed.
|
|
# Fall through gracefully if jq is not installed.
|
|
command -v jq &>/dev/null \
|
|
&& jq empty "$PRECONF_FILE" \
|
|
|| echo "Warning: jq not available — skipping answerfile JSON validation"
|
|
echo "Answerfile to embed: $PRECONF_FILE"
|
|
fi
|
|
|
|
# ── Clean and create working directories ─────────────────────────────────────
|
|
# Remove any previous build artifacts to ensure a clean, reproducible build.
|
|
# mkarchiso can behave unexpectedly if the profile or work directory has stale state.
|
|
rm -rf "$WORK_DIR"
|
|
mkdir -p "$WORK_DIR" "$OUT_DIR"
|
|
|
|
# ── Assemble the profile from releng + M-Archy overlay ───────────────────────
|
|
echo "Copying releng base profile..."
|
|
# Start with a full copy of upstream releng so we inherit all its boot
|
|
# entries, pacman.conf, syslinux/systemd-boot configs, etc.
|
|
cp -r "$RELENG" "$PROFILE"
|
|
|
|
echo "Applying M-Archy overlay..."
|
|
# Merge our custom airootfs overlay ON TOP of the releng copy.
|
|
# Files in our overlay replace or extend the releng defaults.
|
|
# The trailing /. ensures we copy the directory contents, not the directory itself.
|
|
cp -r "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE/airootfs/"
|
|
|
|
echo "Replacing profiledef..."
|
|
# profiledef.sh controls ISO metadata (name, label, build modes, boot modes,
|
|
# compression settings, and file permissions in the final image).
|
|
# We replace the releng default entirely with our customized version.
|
|
cp "$SCRIPT_DIR/overlay/profiledef.sh" "$PROFILE/profiledef.sh"
|
|
|
|
echo "Adding extra packages..."
|
|
# packages.extra lists additional packages beyond releng's defaults.
|
|
# We merge them into packages.x86_64 (the file mkarchiso reads for pacman).
|
|
# Strategy: read line-by-line, skip blank lines and comments (#),
|
|
# and append each package only if it is not already in the list (idempotent).
|
|
while IFS= read -r pkg || [[ -n "$pkg" ]]; do
|
|
[[ -z "$pkg" || "$pkg" == \#* ]] && continue
|
|
grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64"
|
|
done < "$SCRIPT_DIR/overlay/packages.extra"
|
|
|
|
# ── Embed installer scripts ────────────────────────────────────────────────────
|
|
# These three scripts live in the main setup/ directory of the dotfiles repo
|
|
# and implement the actual Arch Linux installation logic. They are placed in
|
|
# /root/installer/ on the live ISO so the auto-launch scripts can find them.
|
|
echo "Embedding installer scripts..."
|
|
mkdir -p "$PROFILE/airootfs/root/installer"
|
|
# Guided interactive installer — walks the user through partitioning, locale, etc.
|
|
cp "$DOTFILES_DIR/setup/archbaseos-guided-install.sh" "$PROFILE/airootfs/root/installer/"
|
|
# Automated unattended installer — reads /answerfile.json and installs silently.
|
|
cp "$DOTFILES_DIR/setup/arch-autoinstall.sh" "$PROFILE/airootfs/root/installer/"
|
|
# Reset script — wipes and reinstalls the system while preserving /home.
|
|
cp "$DOTFILES_DIR/setup/reset-arch.sh" "$PROFILE/airootfs/root/installer/"
|
|
|
|
echo "Embedding resources (branding assets used by post-install modules)..."
|
|
# resources/ contains shared assets (SVGs, etc.) referenced by installer modules
|
|
# such as the Plymouth splash logo. Embedding them here means the ISO carries
|
|
# everything needed so post-install steps never require the user to supply files.
|
|
mkdir -p "$PROFILE/airootfs/root/installer/resources"
|
|
cp -r "$DOTFILES_DIR/resources/." "$PROFILE/airootfs/root/installer/resources/"
|
|
|
|
# Make all scripts executable. The archiso tool preserves these bits in the
|
|
# squashfs, so they will be executable on the live system too.
|
|
chmod 755 \
|
|
"$PROFILE/airootfs/root/launch.sh" \
|
|
"$PROFILE/airootfs/root/.automated_script.sh" \
|
|
"$PROFILE/airootfs/usr/local/bin/install-arch" \
|
|
"$PROFILE/airootfs/root/installer/"*.sh
|
|
|
|
# ── Embed answerfile (--preconf) ───────────────────────────────────────────────
|
|
# An answerfile baked into the ISO lets machines boot and install completely
|
|
# hands-free — useful for bulk deployment via PXE. Without it, the guided
|
|
# installer starts automatically instead.
|
|
if [[ -n "$PRECONF_FILE" ]]; then
|
|
echo "Embedding answerfile: $PRECONF_FILE → /answerfile.json"
|
|
# install -m 644 creates the destination with the given permissions,
|
|
# equivalent to cp + chmod but in one atomic step.
|
|
install -m 644 "$PRECONF_FILE" "$PROFILE/airootfs/answerfile.json"
|
|
fi
|
|
|
|
echo "Building ISO (this may take a while)..."
|
|
# mkarchiso needs root to mount loopback devices, write to /proc-style paths,
|
|
# and set file ownership inside the squashfs correctly.
|
|
# -v: verbose (shows progress)
|
|
# -w: work directory (large temporary build area)
|
|
# -o: output directory for the final .iso and netboot tarball
|
|
sudo mkarchiso -v -w "$WORK_DIR/mkarchiso" -o "$OUT_DIR" "$PROFILE"
|
|
|
|
# After the build, mkarchiso leaves files owned by root. Fix ownership so the
|
|
# calling user can manipulate the output without sudo.
|
|
sudo chmod -R 777 "$WORK_DIR" "$OUT_DIR"
|
|
sudo chown -R "$(id -u):$(id -g)" "$WORK_DIR" "$OUT_DIR"
|
|
|
|
echo
|
|
echo "Done."
|
|
# List the produced ISO file(s). The || true prevents the script from failing
|
|
# when no .iso files exist (e.g., if only netboot mode was built).
|
|
ls -lh "$OUT_DIR/"*.iso 2>/dev/null || true
|
|
|
|
# Inform the user whether an automated or guided install will start on boot.
|
|
if [[ -n "$PRECONF_FILE" ]]; then
|
|
echo "Answerfile embedded — automated install will activate on boot."
|
|
else
|
|
echo "No answerfile — guided (manual) installer will start automatically on boot."
|
|
fi
|
|
|
|
# ── Netboot artifacts ──────────────────────────────────────────────────────────
|
|
# mkarchiso's 'netboot' build mode (set in profiledef.sh) produces a tarball
|
|
# containing the kernel, initramfs, and airootfs.sfs ready for HTTP-based PXE
|
|
# boot. We detect it here and optionally generate an iPXE chainload script.
|
|
NETBOOT_TARBALL="$(ls "$OUT_DIR/"*-netboot-*.tar.gz 2>/dev/null | head -n1 || true)"
|
|
if [[ -n "$NETBOOT_TARBALL" ]]; then
|
|
echo
|
|
echo "Netboot artifact: $NETBOOT_TARBALL"
|
|
echo " Extract and serve its contents from an HTTP server, then boot via PXE."
|
|
echo " Internal layout (relative to tarball root):"
|
|
# Show the tarball contents indented for readability.
|
|
tar -tzf "$NETBOOT_TARBALL" | sed 's/^/ /'
|
|
|
|
if [[ -n "$NETBOOT_URL" ]]; then
|
|
# Strip trailing slash so our URL concatenations are consistent.
|
|
BASE_URL="${NETBOOT_URL%/}"
|
|
IPXE_FILE="$OUT_DIR/m-archy-netboot.ipxe"
|
|
|
|
# Generate an iPXE script that can be chainloaded from netboot.xyz or
|
|
# served directly from a WDS/TFTP server. The script tells iPXE where
|
|
# to find the kernel, initramfs, and squashfs root over HTTP.
|
|
cat > "$IPXE_FILE" <<IPXE
|
|
#!ipxe
|
|
# M-Archy Arch Linux Installer — PXE Boot
|
|
# Generated by build.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
#
|
|
# Deploy:
|
|
# 1. Extract the netboot tarball to the web root at: ${BASE_URL}/
|
|
# 2. Add this script as a custom entry in netboot.xyz, or chainload it directly.
|
|
#
|
|
# netboot.xyz custom menu entry (paste into your netboot.xyz config):
|
|
# item m-archy M-Archy Arch Linux Installer
|
|
# goto m-archy
|
|
# :m-archy
|
|
# chain ${BASE_URL}/m-archy-netboot.ipxe
|
|
|
|
set base-url ${BASE_URL}
|
|
kernel \${base-url}/arch/boot/x86_64/vmlinuz-linux \\
|
|
archiso_http_srv=\${base-url}/ \\
|
|
archisobasedir=arch \\
|
|
ip=dhcp
|
|
initrd \${base-url}/arch/boot/x86_64/initramfs-linux.img
|
|
boot
|
|
IPXE
|
|
echo
|
|
echo "iPXE script written to: $IPXE_FILE"
|
|
echo " Serve it alongside the netboot tarball contents at: ${BASE_URL}/"
|
|
else
|
|
echo
|
|
echo "Tip: rerun with --netboot-url <http://your-server/path> to generate an iPXE script."
|
|
fi
|
|
fi
|