feat: scaffold Arcline OS build system
Scaffold the Arcline OS build system ("the wires"): a transparent,
auditable pipeline that turns a Debian bookworm base into hardened OS
images for the server, workstation, and cloud editions.
- Makefile orchestrates everything (make iso-<edition>, check, test,
toolchain, clean); versions.mk is the single source of truth for
versions and paths.
- scripts/ is the plain-bash pipeline: debootstrap -> install packages
-> apply overlays -> in-chroot configure -> live ISO, plus a rootfs
archive along the way.
- ARCLINE_TOOLCHAIN=auto|skip|require controls whether the 11 Go tools
are bundled into an image (auto by default; minimal builds available
via make iso-<edition>-minimal).
- GPL-3.0 licensed, sponsored by Arcline IT LLC.
This commit is contained in:
36
scripts/apply-overlays.sh
Executable file
36
scripts/apply-overlays.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — overlay application
|
||||
#
|
||||
# scripts/apply-overlays.sh <rootfs> <edition>
|
||||
#
|
||||
# overlays/ is organised as layered filesystem trees:
|
||||
# overlays/base/<abs path> → every edition
|
||||
# overlays/<edition>/<abs path> → that edition only
|
||||
#
|
||||
# Files are copied preserving structure and permissions. Later layers win.
|
||||
# overlays/base is applied first, then the edition overlay.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||
|
||||
TARGET="${1:?usage: apply-overlays.sh <rootfs> <edition>}"
|
||||
EDITION="${2:?usage: apply-overlays.sh <rootfs> <edition>}"
|
||||
validate_edition "$EDITION"
|
||||
|
||||
[[ -d "$TARGET" ]] || die "rootfs '$TARGET' does not exist"
|
||||
|
||||
apply_layer() {
|
||||
local layer="$1"
|
||||
local src="$ROOT/overlays/$layer"
|
||||
[[ -d "$src" ]] || { warn "overlay layer '$layer' not present, skipping"; return 0; }
|
||||
log "applying overlay layer: $layer"
|
||||
# Copy the *contents* of the layer dir into the rootfs, keeping dotfiles,
|
||||
# symlinks, and permissions. Never clobber across layers silently — but
|
||||
# cp -a with overwrite is the desired "later layer wins" behaviour here.
|
||||
cp -a "$src/." "$TARGET/"
|
||||
}
|
||||
|
||||
apply_layer base
|
||||
apply_layer "$EDITION"
|
||||
log "overlays applied ✓"
|
||||
27
scripts/build-edition.sh
Executable file
27
scripts/build-edition.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — edition build orchestrator
|
||||
#
|
||||
# scripts/build-edition.sh <edition> <stage>
|
||||
# stage: rootfs | iso
|
||||
#
|
||||
# Thin dispatcher so the Makefile can stay dumb.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||
|
||||
EDITION="${1:?usage: build-edition.sh <edition> <rootfs|iso>}"
|
||||
STAGE="${2:?usage: build-edition.sh <edition> <rootfs|iso>}"
|
||||
validate_edition "$EDITION"
|
||||
|
||||
case "$STAGE" in
|
||||
rootfs)
|
||||
"$ROOT/scripts/build-rootfs.sh" "$EDITION"
|
||||
;;
|
||||
iso)
|
||||
"$ROOT/scripts/build-iso.sh" "$EDITION"
|
||||
;;
|
||||
*)
|
||||
die "unknown stage '$STAGE' (expected: rootfs | iso)"
|
||||
;;
|
||||
esac
|
||||
81
scripts/build-iso.sh
Executable file
81
scripts/build-iso.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — live ISO builder
|
||||
#
|
||||
# scripts/build-iso.sh <edition>
|
||||
#
|
||||
# Wraps a rootfs into a hybrid (BIOS+UEFI) live ISO:
|
||||
# 1. ensure the rootfs exists (build it with live-boot support if needed)
|
||||
# 2. stage kernel + initramfs + squashfs in isofiles/live
|
||||
# 3. write the grub boot config (live-boot: boot=live)
|
||||
# 4. grub-mkrescue → build/artifacts/arcline-<edition>-<version>-<arch>.iso
|
||||
#
|
||||
# Requires root for the rootfs stage; the ISO assembly itself runs unprivileged.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||
|
||||
EDITION="${1:?usage: build-iso.sh <edition>}"
|
||||
validate_edition "$EDITION"
|
||||
|
||||
ROOTFS="$ROOTFS_DIR/$EDITION"
|
||||
ISOFILES="$IMAGE_DIR/$EDITION/isofiles"
|
||||
ARTIFACT="$ARTIFACT_DIR/arcline-$EDITION-$VERSION-$ARCH.iso"
|
||||
|
||||
# ── 1. rootfs ───────────────────────────────────────────────────────────────
|
||||
if [[ ! -d "$ROOTFS" ]]; then
|
||||
log "rootfs missing — building with live-boot support"
|
||||
ARCLINE_LIVE=1 "$ROOT/scripts/build-rootfs.sh" "$EDITION"
|
||||
fi
|
||||
|
||||
# live-boot must be present in the rootfs for the ISO to boot
|
||||
if [[ ! -d "$ROOTFS/lib/live" && ! -d "$ROOTFS/usr/lib/live" ]]; then
|
||||
warn "rootfs has no live-boot support; rebuilding with ARCLINE_LIVE=1"
|
||||
ARCLINE_LIVE=1 "$ROOT/scripts/build-rootfs.sh" "$EDITION"
|
||||
fi
|
||||
|
||||
# ── 2. stage files ──────────────────────────────────────────────────────────
|
||||
log "staging ISO files for edition '$EDITION'"
|
||||
rm -rf "$ISOFILES"
|
||||
mkdir -p "$ISOFILES/live" "$ISOFILES/boot/grub"
|
||||
|
||||
KERNEL="$(find "$ROOTFS/boot" -maxdepth 1 -name 'vmlinuz-*' | sort -V | tail -1)"
|
||||
INITRD="$(find "$ROOTFS/boot" -maxdepth 1 -name 'initrd.img-*' | sort -V | tail -1)"
|
||||
[[ -n "$KERNEL" && -n "$INITRD" ]] || die "kernel or initramfs not found in rootfs"
|
||||
cp -L "$KERNEL" "$ISOFILES/live/vmlinuz"
|
||||
cp -L "$INITRD" "$ISOFILES/live/initrd.img"
|
||||
|
||||
log "compressing rootfs → squashfs (this takes a while)"
|
||||
# The squashfs is the live root. Keep it complete — offline man pages and
|
||||
# docs are a product promise (see the landing page), so nothing is excluded.
|
||||
mksquashfs "$ROOTFS" "$ISOFILES/live/arcline.squashfs" -noappend -comp zstd -Xcompression-level 15 2>/dev/null || \
|
||||
mksquashfs "$ROOTFS" "$ISOFILES/live/arcline.squashfs" -noappend -comp xz
|
||||
|
||||
# ── 3. grub boot config ─────────────────────────────────────────────────────
|
||||
log "writing grub config"
|
||||
KCMD="$(tr '\n' ' ' < "$(edition_dir "$EDITION")/kernel.cmdline" | sed 's/ */ /g')"
|
||||
cat > "$ISOFILES/boot/grub/grub.cfg" <<EOF
|
||||
set timeout=5
|
||||
set default=0
|
||||
|
||||
menuentry "Arcline $EDITION ($VERSION)" {
|
||||
linux /live/vmlinuz boot=live config quiet $KCMD
|
||||
initrd /live/initrd.img
|
||||
}
|
||||
|
||||
menuentry "Arcline $EDITION ($VERSION) — safe mode (no mitigations)" {
|
||||
linux /live/vmlinuz boot=live config quiet nomodeset
|
||||
initrd /live/initrd.img
|
||||
}
|
||||
EOF
|
||||
|
||||
# ── 4. assemble ─────────────────────────────────────────────────────────────
|
||||
log "assembling ISO with grub-mkrescue"
|
||||
command -v grub-mkrescue >/dev/null || die "grub-mkrescue not found (run scripts/check-host-deps.sh --install)"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
grub-mkrescue -o "$ARTIFACT" "$ISOFILES" -- \
|
||||
-volume-label "ARCLINE_${EDITION^^}" 2>/dev/null || \
|
||||
grub-mkrescue -o "$ARTIFACT" "$ISOFILES"
|
||||
|
||||
log "ISO artifact: $ARTIFACT"
|
||||
sha256sum "$ARTIFACT" | tee "$ARTIFACT.sha256"
|
||||
143
scripts/build-rootfs.sh
Executable file
143
scripts/build-rootfs.sh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — rootfs builder
|
||||
#
|
||||
# scripts/build-rootfs.sh <edition>
|
||||
#
|
||||
# Pipeline:
|
||||
# 1. debootstrap a minimal Debian base
|
||||
# 2. configure apt sources (main + security)
|
||||
# 3. install the edition package set
|
||||
# 4. apply overlays (hardening, configs, service units)
|
||||
# 5. run configure-system.sh inside the chroot
|
||||
# 6. clean and tar the result → build/artifacts/arcline-<edition>-<version>.tar.xz
|
||||
#
|
||||
# Root is required (re-execs under sudo when needed).
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||
|
||||
EDITION="${1:?usage: build-rootfs.sh <edition>}"
|
||||
validate_edition "$EDITION"
|
||||
|
||||
# Re-exec under sudo if we aren't root (debootstrap/chroot/mount need it).
|
||||
require_root "$0" "$@"
|
||||
|
||||
# Fail fast on a bad toolchain policy before debootstrap does any work.
|
||||
case "$ARCLINE_TOOLCHAIN" in
|
||||
auto|skip|require) ;;
|
||||
*) die "ARCLINE_TOOLCHAIN must be auto|skip|require (got '$ARCLINE_TOOLCHAIN')" ;;
|
||||
esac
|
||||
|
||||
EDIR="$(edition_dir "$EDITION")"
|
||||
ROOTFS="$ROOTFS_DIR/$EDITION"
|
||||
ARTIFACT="$ARTIFACT_DIR/arcline-$EDITION-$VERSION-$ARCH.tar.xz"
|
||||
LIVE="${ARCLINE_LIVE:-0}"
|
||||
|
||||
mkdir -p "$ROOTFS_DIR" "$LOG_DIR" "$ARTIFACT_DIR"
|
||||
rm -rf "$ROOTFS" "$ARTIFACT"
|
||||
|
||||
log "═══ building Arcline $EDITION rootfs ($DISTRO_NAME $VERSION) ═══"
|
||||
|
||||
# ── 1. bootstrap ────────────────────────────────────────────────────────────
|
||||
log "[1/6] debootstrap $DEBIAN_SUITE ($ARCH)"
|
||||
debootstrap \
|
||||
--arch="$ARCH" \
|
||||
--variant=minbase \
|
||||
--include=apt-transport-https,ca-certificates,gnupg,curl \
|
||||
"$DEBIAN_SUITE" "$ROOTFS" "$DEBIAN_MIRROR" \
|
||||
| tee "$LOG_DIR/bootstrap-$EDITION.log"
|
||||
|
||||
# ── 2. apt sources ──────────────────────────────────────────────────────────
|
||||
log "[2/6] configuring apt sources"
|
||||
cat > "$ROOTFS/etc/apt/sources.list" <<EOF
|
||||
deb $DEBIAN_MIRROR $DEBIAN_SUITE main contrib non-free-firmware
|
||||
deb $DEBIAN_MIRROR $DEBIAN_SUITE-updates main contrib non-free-firmware
|
||||
deb $SECURITY_MIRROR $DEBIAN_SUITE-security main contrib non-free-firmware
|
||||
EOF
|
||||
|
||||
# ── chroot helpers (bind-mount pseudo-fs, run in chroot, unmount on exit) ──
|
||||
mount_pseudo() {
|
||||
mount --bind /dev "$ROOTFS/dev"
|
||||
mount --bind /proc "$ROOTFS/proc"
|
||||
mount --bind /sys "$ROOTFS/sys"
|
||||
mount --bind /dev/pts "$ROOTFS/dev/pts" 2>/dev/null || true
|
||||
}
|
||||
unmount_pseudo() {
|
||||
umount -l "$ROOTFS/dev/pts" 2>/dev/null || true
|
||||
umount -l "$ROOTFS/sys" 2>/dev/null || true
|
||||
umount -l "$ROOTFS/proc" 2>/dev/null || true
|
||||
umount -l "$ROOTFS/dev" 2>/dev/null || true
|
||||
}
|
||||
trap 'unmount_pseudo' EXIT
|
||||
chroot_run() { chroot "$ROOTFS" /bin/bash -c "$*"; }
|
||||
|
||||
# ── 3. install edition packages ─────────────────────────────────────────────
|
||||
log "[3/6] installing edition packages (${EDITION})"
|
||||
PKGS="$(grep -vE '^\s*(#|$)' "$EDIR/packages.list" | tr '\n' ' ')"
|
||||
mount_pseudo
|
||||
chroot_run "export DEBIAN_FRONTEND=noninteractive; apt-get update -qq && apt-get install -y --no-install-recommends $PKGS" \
|
||||
| tee "$LOG_DIR/packages-$EDITION.log"
|
||||
unmount_pseudo
|
||||
|
||||
# ── 4. overlays ─────────────────────────────────────────────────────────────
|
||||
log "[4/6] applying overlays"
|
||||
"$ROOT/scripts/apply-overlays.sh" "$ROOTFS" "$EDITION"
|
||||
|
||||
# copy btrfs snapshot/rollback tooling into the image
|
||||
install -Dm0755 "$ROOT/btrfs/snapshot.sh" "$ROOTFS/usr/local/sbin/arcline-snapshot"
|
||||
install -Dm0755 "$ROOT/btrfs/rollback.sh" "$ROOTFS/usr/local/sbin/arcline-rollback"
|
||||
|
||||
# ── 5. configure system in chroot ───────────────────────────────────────────
|
||||
log "[5/6] configuring system in chroot"
|
||||
install -m0755 "$ROOT/scripts/configure-system.sh" "$ROOTFS/root/configure-system.sh"
|
||||
# Stage the edition manifest INSIDE the chroot (the hook runs in there and
|
||||
# cannot see host paths).
|
||||
mkdir -p "$ROOTFS/root/arcline-edition"
|
||||
cp "$EDIR/kernel.cmdline" "$EDIR/metadata.yaml" "$ROOTFS/root/arcline-edition/"
|
||||
|
||||
# Toolchain policy — the Arcline tools are optional in an image build. See
|
||||
# ARCLINE_TOOLCHAIN in scripts/common.sh (auto | skip | require).
|
||||
HAVE_DEBS=0
|
||||
if [[ -d "$DEB_DIR" ]] && ls "$DEB_DIR"/*.deb >/dev/null 2>&1; then
|
||||
HAVE_DEBS=1
|
||||
fi
|
||||
case "$ARCLINE_TOOLCHAIN" in
|
||||
require)
|
||||
[[ $HAVE_DEBS -eq 1 ]] || die "ARCLINE_TOOLCHAIN=require but no .deb files in $DEB_DIR (run: make toolchain)"
|
||||
mkdir -p "$ROOTFS/arcline-debs"
|
||||
cp "$DEB_DIR"/*.deb "$ROOTFS/arcline-debs/"
|
||||
log "toolchain: staging $(ls "$DEB_DIR"/*.deb | wc -l) packages (required)"
|
||||
;;
|
||||
skip)
|
||||
log "toolchain: skipped (ARCLINE_TOOLCHAIN=skip) — building without Arcline tools"
|
||||
;;
|
||||
auto)
|
||||
if [[ $HAVE_DEBS -eq 1 ]]; then
|
||||
mkdir -p "$ROOTFS/arcline-debs"
|
||||
cp "$DEB_DIR"/*.deb "$ROOTFS/arcline-debs/"
|
||||
log "toolchain: staging $(ls "$DEB_DIR"/*.deb | wc -l) packages"
|
||||
else
|
||||
warn "no toolchain .debs in $DEB_DIR — building WITHOUT Arcline tools (run: make toolchain)"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
die "ARCLINE_TOOLCHAIN must be auto|skip|require (got '$ARCLINE_TOOLCHAIN')"
|
||||
;;
|
||||
esac
|
||||
|
||||
mount_pseudo
|
||||
chroot_run "ARCLINE_EDITION_DIR='/root/arcline-edition' ARCLINE_LIVE='$LIVE' ARCLINE_LOCK_ROOT='${ARCLINE_LOCK_ROOT:-0}' ARCLINE_EXTRA_REPOS='${ARCLINE_EXTRA_REPOS:-0}' /root/configure-system.sh '$EDITION'"
|
||||
rm -f "$ROOTFS/root/configure-system.sh"
|
||||
rm -rf "$ROOTFS/root/arcline-edition" "$ROOTFS/arcline-debs"
|
||||
unmount_pseudo
|
||||
|
||||
# ── 6. clean + archive ──────────────────────────────────────────────────────
|
||||
log "[6/6] cleaning and archiving"
|
||||
chroot_run "apt-get clean 2>/dev/null; rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* /root/.bash_history"
|
||||
rm -f "$ROOTFS/etc/machine-id"
|
||||
: > "$ROOTFS/etc/machine-id"
|
||||
|
||||
tar -C "$ROOTFS" -cJf "$ARTIFACT" .
|
||||
log "rootfs artifact: $ARTIFACT"
|
||||
sha256sum "$ARTIFACT" | tee "$ARTIFACT.sha256"
|
||||
53
scripts/check-host-deps.sh
Executable file
53
scripts/check-host-deps.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — host build-dependency check
|
||||
#
|
||||
# scripts/check-host-deps.sh → report what's missing
|
||||
# scripts/check-host-deps.sh --install → apt-get install what's missing
|
||||
#
|
||||
# Builds run debootstrap (rootfs), copy overlays (standard tools), and produce
|
||||
# a hybrid ISO via grub-mkrescue + mksquashfs.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||
|
||||
NEEDED=(
|
||||
"debootstrap|debootstrap"
|
||||
"grub-mkrescue|grub2-common"
|
||||
"mksquashfs|squashfs-tools"
|
||||
"xorriso|xorriso"
|
||||
"cpio|cpio"
|
||||
"bash|bash"
|
||||
)
|
||||
|
||||
MISSING=()
|
||||
for entry in "${NEEDED[@]}"; do
|
||||
bin="${entry%%|*}"; pkg="${entry##*|}"
|
||||
if ! command -v "$bin" >/dev/null 2>&1; then
|
||||
MISSING+=("$pkg")
|
||||
warn "missing: $bin (package: $pkg)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#MISSING[@]} -eq 0 ]]; then
|
||||
log "all host build dependencies present ✓"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "--install" ]]; then
|
||||
log "installing: ${MISSING[*]}"
|
||||
if [[ $EUID -ne 0 ]] && command -v sudo >/dev/null; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y "${MISSING[@]}"
|
||||
else
|
||||
apt-get update
|
||||
apt-get install -y "${MISSING[@]}"
|
||||
fi
|
||||
log "dependencies installed ✓"
|
||||
else
|
||||
echo
|
||||
echo "Run the following to install them:"
|
||||
echo " sudo apt-get install -y ${MISSING[*]}"
|
||||
echo "or: scripts/check-host-deps.sh --install"
|
||||
exit 1
|
||||
fi
|
||||
67
scripts/common.sh
Executable file
67
scripts/common.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — shared build environment + helpers
|
||||
#
|
||||
# Every script under scripts/ sources this first. Values mirror versions.mk;
|
||||
# when invoked through the Makefile the exported variables take precedence, so
|
||||
# versions.mk stays the single source of truth.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
# ── identity ────────────────────────────────────────────────────────────────
|
||||
: "${DISTRO_NAME:=Arcline OS}"
|
||||
: "${DISTRO_ID:=arclineos}"
|
||||
: "${VERSION:=0.1.0}"
|
||||
: "${RELEASE_NAME:=arclines}"
|
||||
: "${DEBIAN_SUITE:=bookworm}"
|
||||
: "${DEBIAN_MIRROR:=http://deb.debian.org/debian}"
|
||||
: "${SECURITY_MIRROR:=http://security.debian.org/debian-security}"
|
||||
: "${ARCH:=amd64}"
|
||||
: "${KERNEL_PACKAGE:=linux-image-amd64}"
|
||||
: "${KERNEL_VERSION:=6.1}"
|
||||
|
||||
# Toolchain policy for image builds:
|
||||
# auto (default) install the Arcline tools if build/debs/*.deb exist,
|
||||
# otherwise build without them (with a warning)
|
||||
# skip never install the tools, even if debs are present
|
||||
# require fail the build if no toolchain debs are available
|
||||
: "${ARCLINE_TOOLCHAIN:=auto}"
|
||||
|
||||
# ── paths (relative to the repo root) ───────────────────────────────────────
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
: "${BUILD_DIR:=$ROOT/build}"
|
||||
: "${ROOTFS_DIR:=$BUILD_DIR/rootfs}"
|
||||
: "${IMAGE_DIR:=$BUILD_DIR/iso}"
|
||||
: "${DEB_DIR:=$BUILD_DIR/debs}"
|
||||
: "${LOG_DIR:=$BUILD_DIR/logs}"
|
||||
: "${ARTIFACT_DIR:=$BUILD_DIR/artifacts}"
|
||||
|
||||
# Valid edition names, mirroring versions.mk.
|
||||
EDITIONS=(server workstation cloud)
|
||||
|
||||
# ── output helpers ──────────────────────────────────────────────────────────
|
||||
log() { printf '\033[1;34m[arcline]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[arcline]\033[0m warning: %s\n' "$*" >&2; }
|
||||
die() { printf '\033[1;31m[arcline]\033[0m error: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# ── path helpers ────────────────────────────────────────────────────────────
|
||||
edition_dir() { printf '%s/editions/%s' "$ROOT" "$1"; }
|
||||
|
||||
validate_edition() {
|
||||
local e
|
||||
for e in "${EDITIONS[@]}"; do [[ "$e" == "$1" ]] && return 0; done
|
||||
die "unknown edition '$1' (expected: ${EDITIONS[*]})"
|
||||
}
|
||||
|
||||
# ── privilege helper ────────────────────────────────────────────────────────
|
||||
# Builds need root (debootstrap / chroot / mount). If we are not root and sudo
|
||||
# is present, re-exec under sudo so the rest of the script can assume root.
|
||||
require_root() {
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
return 0
|
||||
elif command -v sudo >/dev/null; then
|
||||
exec sudo -E "$0" "$@"
|
||||
else
|
||||
die "build requires root: run with sudo or as root (debootstrap + chroot need it)"
|
||||
fi
|
||||
}
|
||||
112
scripts/configure-system.sh
Executable file
112
scripts/configure-system.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — in-chroot system configuration
|
||||
#
|
||||
# scripts/configure-system.sh <edition>
|
||||
#
|
||||
# This script is executed INSIDE the chroot (build-rootfs.sh copies it in and
|
||||
# runs it via chroot). It turns a raw debootstrap tree into an Arcline system:
|
||||
# hostname, locale, kernel cmdline, enabled/masked services, optional live-boot,
|
||||
# optional Arcline toolchain install, optional upstream repos (grafana/loki).
|
||||
#
|
||||
# It is idempotent and safe to re-run.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
EDITION="${1:?usage: configure-system.sh <edition>}"
|
||||
DISTRO_NAME="${DISTRO_NAME:-Arcline OS}"
|
||||
VERSION="${VERSION:-0.1.0}"
|
||||
RELEASE_NAME="${RELEASE_NAME:-arclines}"
|
||||
LIVE="${ARCLINE_LIVE:-0}"
|
||||
LOCK_ROOT="${ARCLINE_LOCK_ROOT:-0}"
|
||||
EXTRA_REPOS="${ARCLINE_EXTRA_REPOS:-0}"
|
||||
|
||||
# Absolute path of the edition dir on the host is injected by build-rootfs.sh.
|
||||
EDIR="${ARCLINE_EDITION_DIR:?ARCLINE_EDITION_DIR must be set}"
|
||||
|
||||
log() { printf '\033[1;34m[arcline:chroot]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[arcline:chroot]\033[0m %s\n' "$*" >&2; }
|
||||
|
||||
# Read a flat YAML list (key: then indented "- item" lines). Keys may be
|
||||
# nested under a section (e.g. services: → enabled:).
|
||||
yaml_list() {
|
||||
awk -v key="$2" '
|
||||
$0 ~ "^[[:space:]]*" key ":" { insec=1; next }
|
||||
insec && /^[[:space:]]*-/ { sub(/^[[:space:]]*-[[:space:]]*/, ""); print; next }
|
||||
insec && !/^[[:space:]]*-/ && !/^[[:space:]]*$/ { exit }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# ── identity ────────────────────────────────────────────────────────────────
|
||||
echo "$RELEASE_NAME" > /etc/hostname
|
||||
cat > /etc/arcline-release <<EOF
|
||||
$DISTRO_NAME $VERSION ($RELEASE_NAME)
|
||||
Edition: $EDITION
|
||||
Debian base: $(. /etc/os-release && echo "$PRETTY_NAME")
|
||||
EOF
|
||||
ln -sf /etc/arcline-release /etc/os-release-arcline
|
||||
|
||||
# ── locale / timezone ───────────────────────────────────────────────────────
|
||||
sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
|
||||
locale-gen >/dev/null 2>&1 || true
|
||||
echo "LANG=en_US.UTF-8" > /etc/default/locale
|
||||
ln -sf /usr/share/zoneinfo/UTC /etc/localtime
|
||||
|
||||
# ── kernel cmdline (used when this rootfs is installed to disk) ─────────────
|
||||
KCMD="$(tr '\n' ' ' < "$EDIR/kernel.cmdline" | sed 's/ */ /g; s/^ *//; s/ *$//')"
|
||||
cat > /etc/default/grub <<EOF
|
||||
GRUB_DEFAULT=0
|
||||
GRUB_TIMEOUT=5
|
||||
GRUB_DISTRIBUTOR="$DISTRO_NAME"
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="$KCMD"
|
||||
GRUB_CMDLINE_LINUX=""
|
||||
GRUB_DISABLE_OS_PROBER=false
|
||||
EOF
|
||||
|
||||
# ── systemd services ────────────────────────────────────────────────────────
|
||||
META="$EDIR/metadata.yaml"
|
||||
while IFS= read -r svc; do
|
||||
[[ -n "$svc" ]] && systemctl enable "$svc" 2>/dev/null || true
|
||||
done < <(yaml_list "$META" "enabled" || true)
|
||||
|
||||
while IFS= read -r svc; do
|
||||
[[ -n "$svc" ]] && systemctl mask "$svc" 2>/dev/null || true
|
||||
done < <(yaml_list "$META" "masked" || true)
|
||||
|
||||
# Zero-telemetry: apt's automatic update calls are masked in metadata; also
|
||||
# ensure no package telemetry survives.
|
||||
rm -f /var/log/apt/*.log /var/cache/apt/archives/*.deb
|
||||
|
||||
# ── live-boot (ISO builds) ──────────────────────────────────────────────────
|
||||
if [[ "$LIVE" == "1" ]]; then
|
||||
log "installing live-boot support"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y --no-install-recommends live-boot live-config-systemd live-tools || warn "live-boot install failed"
|
||||
fi
|
||||
|
||||
# ── Arcline toolchain (host-built .debs) ────────────────────────────────────
|
||||
if [[ -d /arcline-debs ]] && ls /arcline-debs/*.deb >/dev/null 2>&1; then
|
||||
log "installing Arcline toolchain packages"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y /arcline-debs/*.deb 2>/dev/null \
|
||||
|| dpkg -i /arcline-debs/*.deb 2>/dev/null \
|
||||
|| warn "toolchain install incomplete (fix with: apt-get -f install)"
|
||||
fi
|
||||
|
||||
# ── optional upstream repos (grafana, loki) ─────────────────────────────────
|
||||
if [[ "$EXTRA_REPOS" == "1" ]]; then
|
||||
log "adding upstream observability repos (grafana, loki)"
|
||||
install -d /usr/share/keyrings
|
||||
curl -fsSL https://apt.grafana.com/gpg.key -o /usr/share/keyrings/grafana.asc 2>/dev/null || warn "grafana key fetch failed"
|
||||
echo "deb [signed-by=/usr/share/keyrings/grafana.asc] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list
|
||||
# Loki ships as a static binary tarball; the packaging lives in toolchain/.
|
||||
# (No-op here; documented in docs/observability.md.)
|
||||
fi
|
||||
|
||||
# ── root account policy ─────────────────────────────────────────────────────
|
||||
if [[ "$LOCK_ROOT" == "1" ]]; then
|
||||
passwd -l root
|
||||
log "root account locked (sudo/ssh-key access only)"
|
||||
fi
|
||||
|
||||
log "configure-system.sh complete for edition '$EDITION'"
|
||||
64
scripts/validate.sh
Executable file
64
scripts/validate.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — fast, offline validation of the build tree.
|
||||
#
|
||||
# scripts/validate.sh
|
||||
#
|
||||
# Runs on any machine (no root, no network). Checks:
|
||||
# * every shell script parses (bash -n)
|
||||
# * every edition has a complete manifest (metadata, packages, cmdline, fstab)
|
||||
# * package lists reference no obvious duplicate lines
|
||||
# * the Makefile + versions.mk parse
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||
|
||||
fail=0
|
||||
note_fail() { warn "$1"; fail=1; }
|
||||
|
||||
log "validating shell scripts…"
|
||||
while IFS= read -r -d '' s; do
|
||||
if ! bash -n "$s"; then
|
||||
note_fail "syntax error in $s"
|
||||
fi
|
||||
done < <(find "$ROOT/scripts" "$ROOT/btrfs" "$ROOT/toolchain" "$ROOT/tests" \
|
||||
-name '*.sh' -type f -print0 2>/dev/null)
|
||||
|
||||
log "validating editions…"
|
||||
for e in "${EDITIONS[@]}"; do
|
||||
ed="$(edition_dir "$e")"
|
||||
for f in metadata.yaml packages.list kernel.cmdline fstab; do
|
||||
[[ -f "$ed/$f" ]] || note_fail "edition '$e' missing $f"
|
||||
done
|
||||
|
||||
# package list sanity: no blank-or-comment duplicates
|
||||
if [[ -f "$ed/packages.list" ]]; then
|
||||
dup="$(grep -vE '^\s*(#|$)' "$ed/packages.list" | sort | uniq -d)"
|
||||
[[ -z "$dup" ]] || note_fail "edition '$e' has duplicate package entries: $(echo "$dup" | tr '\n' ' ')"
|
||||
fi
|
||||
done
|
||||
|
||||
log "validating top-level Makefile / versions.mk…"
|
||||
make -C "$ROOT" -n check >/dev/null 2>&1 || note_fail "Makefile dry-run failed (make -n check)"
|
||||
|
||||
log "validating YAML manifests (if python3 + pyyaml available)…"
|
||||
if python3 -c 'import yaml' 2>/dev/null; then
|
||||
python3 - "$ROOT" <<'PYEOF' || note_fail "YAML manifest parse failed"
|
||||
import sys, glob, yaml
|
||||
root = sys.argv[1]
|
||||
for f in sorted(glob.glob(root + "/editions/*/metadata.yaml")):
|
||||
d = yaml.safe_load(open(f))
|
||||
assert d.get("edition") and d.get("codename"), f"{f}: missing edition/codename"
|
||||
assert d.get("image", {}).get("type"), f"{f}: missing image.type"
|
||||
assert "services" in d, f"{f}: missing services"
|
||||
assert isinstance(d["services"].get("enabled", []), list)
|
||||
PYEOF
|
||||
else
|
||||
warn "python3/pyyaml not available — skipping YAML parse (structural checks above still apply)"
|
||||
fi
|
||||
|
||||
if [[ $fail -eq 0 ]]; then
|
||||
log "validation passed ✓"
|
||||
else
|
||||
die "validation found problems"
|
||||
fi
|
||||
Reference in New Issue
Block a user