feat: add disk-deploy core, disk image builder, and installer
- deploy-disk.sh: the shared "write a finished system to a disk" step — partition (GPT bios/efi) -> btrfs layout via btrfs/init.sh -> rsync rootfs -> chroot (real fstab, GRUB, hostname). Carries the optional ARCLINE_SIGN hook; the signing tooling itself lands in a later commit. - apply-fstab.sh: renders the edition fstab template with real root/efi UUIDs and drops the swap line. - build-image.sh: rootfs -> bootable qcow2/raw disk image (sparse file + loop device + deploy), the cloud edition's primary output. - install.sh: scripted installer for a real disk, confirmation-gated. - Makefile: image-<edition> targets (+ minimal variants); build-edition.sh learns the "image" stage; cloud metadata now ships a disk image. - check-host-deps.sh / GitLab CI: add gdisk, parted, rsync, dosfstools, qemu-utils, dpkg, sbsigntool to the build image.
This commit is contained in:
16
Makefile
16
Makefile
@@ -35,7 +35,7 @@ export TOOLCHAIN_REPO
|
|||||||
ROOT := $(CURDIR)
|
ROOT := $(CURDIR)
|
||||||
EDITION := $(filter-out $@,$(MAKECMDGOALS))
|
EDITION := $(filter-out $@,$(MAKECMDGOALS))
|
||||||
|
|
||||||
.PHONY: help deps check toolchain test clean
|
.PHONY: help deps check toolchain vendor test clean
|
||||||
|
|
||||||
# ── help ────────────────────────────────────────────────────────────────────
|
# ── help ────────────────────────────────────────────────────────────────────
|
||||||
help:
|
help:
|
||||||
@@ -45,9 +45,11 @@ help:
|
|||||||
@echo " make check Validate manifests + shell syntax"
|
@echo " make check Validate manifests + shell syntax"
|
||||||
@echo " make rootfs-<edition> Build rootfs (server|workstation|cloud)"
|
@echo " make rootfs-<edition> Build rootfs (server|workstation|cloud)"
|
||||||
@echo " make iso-<edition> Build ISO (server|workstation|cloud)"
|
@echo " make iso-<edition> Build ISO (server|workstation|cloud)"
|
||||||
|
@echo " make image-<edition> Build disk image (qcow2; cloud edition)"
|
||||||
@echo " make iso Build ISO for every edition"
|
@echo " make iso Build ISO for every edition"
|
||||||
@echo " make iso-<edition>-minimal Build ISO WITHOUT the Arcline toolchain"
|
@echo " make iso-<edition>-minimal Build ISO WITHOUT the Arcline toolchain"
|
||||||
@echo " make toolchain Build the 11 Go tools into .deb"
|
@echo " make toolchain Build the 11 Go tools into .deb"
|
||||||
|
@echo " make vendor Build grafana/loki/promtail .debs"
|
||||||
@echo " make test Run smoke tests against a built image"
|
@echo " make test Run smoke tests against a built image"
|
||||||
@echo " make clean Remove build/ artifacts"
|
@echo " make clean Remove build/ artifacts"
|
||||||
@echo
|
@echo
|
||||||
@@ -70,19 +72,25 @@ check:
|
|||||||
# require fail if no .deb files are available
|
# require fail if no .deb files are available
|
||||||
# The *-minimal targets are a shortcut for ARCLINE_TOOLCHAIN=skip.
|
# The *-minimal targets are a shortcut for ARCLINE_TOOLCHAIN=skip.
|
||||||
define ISO_RULE
|
define ISO_RULE
|
||||||
.PHONY: rootfs-$(1) iso-$(1) clean-$(1) rootfs-$(1)-minimal iso-$(1)-minimal
|
.PHONY: rootfs-$(1) iso-$(1) image-$(1) clean-$(1) rootfs-$(1)-minimal iso-$(1)-minimal image-$(1)-minimal
|
||||||
rootfs-$(1):
|
rootfs-$(1):
|
||||||
@./scripts/build-edition.sh $(1) rootfs
|
@./scripts/build-edition.sh $(1) rootfs
|
||||||
|
|
||||||
iso-$(1):
|
iso-$(1):
|
||||||
@./scripts/build-edition.sh $(1) iso
|
@./scripts/build-edition.sh $(1) iso
|
||||||
|
|
||||||
|
image-$(1):
|
||||||
|
@./scripts/build-edition.sh $(1) image
|
||||||
|
|
||||||
rootfs-$(1)-minimal:
|
rootfs-$(1)-minimal:
|
||||||
@ARCLINE_TOOLCHAIN=skip ./scripts/build-edition.sh $(1) rootfs
|
@ARCLINE_TOOLCHAIN=skip ./scripts/build-edition.sh $(1) rootfs
|
||||||
|
|
||||||
iso-$(1)-minimal:
|
iso-$(1)-minimal:
|
||||||
@ARCLINE_TOOLCHAIN=skip ./scripts/build-edition.sh $(1) iso
|
@ARCLINE_TOOLCHAIN=skip ./scripts/build-edition.sh $(1) iso
|
||||||
|
|
||||||
|
image-$(1)-minimal:
|
||||||
|
@ARCLINE_TOOLCHAIN=skip ./scripts/build-edition.sh $(1) image
|
||||||
|
|
||||||
clean-$(1):
|
clean-$(1):
|
||||||
@rm -rf "$(ROOTFS_DIR)/$(1)" "$(IMAGE_DIR)/$(1)"
|
@rm -rf "$(ROOTFS_DIR)/$(1)" "$(IMAGE_DIR)/$(1)"
|
||||||
endef
|
endef
|
||||||
@@ -94,6 +102,10 @@ iso: $(foreach e,$(EDITIONS),iso-$(e))
|
|||||||
toolchain:
|
toolchain:
|
||||||
@./toolchain/build-tools.sh
|
@./toolchain/build-tools.sh
|
||||||
|
|
||||||
|
# ── vendor packaging (grafana / loki / promtail) ────────────────────────────
|
||||||
|
vendor:
|
||||||
|
@./toolchain/build-vendor.sh
|
||||||
|
|
||||||
# ── tests ───────────────────────────────────────────────────────────────────
|
# ── tests ───────────────────────────────────────────────────────────────────
|
||||||
test:
|
test:
|
||||||
@./tests/run-tests.sh
|
@./tests/run-tests.sh
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ validate:
|
|||||||
image: debian:bookworm
|
image: debian:bookworm
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update -qq
|
- apt-get update -qq
|
||||||
- apt-get install -y -qq debootstrap squashfs-tools grub2-common xorriso cpio curl git make bash
|
- apt-get install -y -qq debootstrap squashfs-tools grub2-common xorriso cpio curl git make bash gdisk parted rsync dosfstools qemu-utils unzip dpkg openssl sbsigntool
|
||||||
script:
|
script:
|
||||||
- make iso-${EDITION}
|
- make ${TARGET:-iso}-${EDITION}
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "arcline-${EDITION}-${VERSION}"
|
name: "arcline-${EDITION}-${VERSION}"
|
||||||
paths:
|
paths:
|
||||||
@@ -58,6 +58,7 @@ build-cloud:
|
|||||||
extends: .build
|
extends: .build
|
||||||
variables:
|
variables:
|
||||||
EDITION: cloud
|
EDITION: cloud
|
||||||
|
TARGET: image # cloud edition ships as a qcow2 disk image
|
||||||
|
|
||||||
# ── test ────────────────────────────────────────────────────────────────────
|
# ── test ────────────────────────────────────────────────────────────────────
|
||||||
test:
|
test:
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ description: >
|
|||||||
network stack. Same hardening baseline as server.
|
network stack. Same hardening baseline as server.
|
||||||
|
|
||||||
image:
|
image:
|
||||||
type: iso
|
type: disk # cloud edition ships as a bootable qcow2/raw disk image
|
||||||
boot: bios+efi
|
boot: bios # primary boot mode (UEFI via --boot efi)
|
||||||
filesystem: btrfs
|
filesystem: btrfs
|
||||||
compression: zstd:3
|
compression: zstd:3
|
||||||
|
default_size: 4G
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
extra_repos: []
|
extra_repos: []
|
||||||
|
|||||||
34
scripts/apply-fstab.sh
Executable file
34
scripts/apply-fstab.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Arcline OS — write a real /etc/fstab from an edition template
|
||||||
|
#
|
||||||
|
# scripts/apply-fstab.sh <target-root> <edition> <root-uuid> [efi-uuid]
|
||||||
|
#
|
||||||
|
# Renders editions/<edition>/fstab with the real root partition UUID, drops
|
||||||
|
# the template's swapfile line (no swap is created during deploy), and — in
|
||||||
|
# EFI installs — appends the EFI system partition mount.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||||
|
|
||||||
|
TARGET="${1:?usage: apply-fstab.sh <target-root> <edition> <root-uuid> [efi-uuid]}"
|
||||||
|
EDITION="${2:?usage: apply-fstab.sh <target-root> <edition> <root-uuid> [efi-uuid]}"
|
||||||
|
ROOT_UUID="${3:?usage: apply-fstab.sh <target-root> <edition> <root-uuid> [efi-uuid]}"
|
||||||
|
EFI_UUID="${4:-}"
|
||||||
|
|
||||||
|
validate_edition "$EDITION"
|
||||||
|
[[ -d "$TARGET" ]] || die "target '$TARGET' does not exist"
|
||||||
|
|
||||||
|
EDIR="$(edition_dir "$EDITION")"
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
trap 'rm -f "$tmp" "$tmp.clean"' EXIT
|
||||||
|
|
||||||
|
# template → real fstab; remove the swapfile line + its comment (no swap created on deploy)
|
||||||
|
sed "s/__ROOT_UUID__/$ROOT_UUID/g" "$EDIR/fstab" | grep -v -E '(swapfile|^[[:space:]]*#.*swap)' > "$tmp.clean"
|
||||||
|
|
||||||
|
if [[ -n "$EFI_UUID" ]]; then
|
||||||
|
printf 'UUID=%s /boot/efi vfat umask=0077 0 1\n' "$EFI_UUID" >> "$tmp.clean"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -Dm0644 "$tmp.clean" "$TARGET/etc/fstab"
|
||||||
|
log "wrote $TARGET/etc/fstab (root UUID $ROOT_UUID${EFI_UUID:+, efi UUID $EFI_UUID})"
|
||||||
@@ -21,7 +21,10 @@ case "$STAGE" in
|
|||||||
iso)
|
iso)
|
||||||
"$ROOT/scripts/build-iso.sh" "$EDITION"
|
"$ROOT/scripts/build-iso.sh" "$EDITION"
|
||||||
;;
|
;;
|
||||||
|
image)
|
||||||
|
"$ROOT/scripts/build-image.sh" "$EDITION"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
die "unknown stage '$STAGE' (expected: rootfs | iso)"
|
die "unknown stage '$STAGE' (expected: rootfs | iso | image)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
82
scripts/build-image.sh
Executable file
82
scripts/build-image.sh
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Arcline OS — disk image builder (qcow2 / raw)
|
||||||
|
#
|
||||||
|
# scripts/build-image.sh <edition> [--format qcow2|raw] [--size 4G] [--boot bios|efi]
|
||||||
|
#
|
||||||
|
# Produces a bootable disk image (the cloud edition's real output — and useful
|
||||||
|
# for testing server/workstation locally in a VM):
|
||||||
|
#
|
||||||
|
# 1. ensure the rootfs exists (build it if needed)
|
||||||
|
# 2. create a sparse raw file, attach a loop device
|
||||||
|
# 3. deploy-disk.sh writes the finished system (btrfs layout + GRUB + fstab)
|
||||||
|
# 4. optionally qemu-img convert to qcow2
|
||||||
|
# 5. artifact → build/artifacts/arcline-<edition>-<version>-<arch>.<ext> + sha256
|
||||||
|
#
|
||||||
|
# Requires root (for the loop device + deploy step).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||||
|
|
||||||
|
EDITION="${1:?usage: build-image.sh <edition> [--format qcow2|raw] [--size 4G] [--boot bios|efi]}"
|
||||||
|
FORMAT="qcow2"
|
||||||
|
SIZE="4G"
|
||||||
|
BOOT="bios"
|
||||||
|
|
||||||
|
shift || true
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--format) FORMAT="${2:?}"; shift 2 ;;
|
||||||
|
--size) SIZE="${2:?}"; shift 2 ;;
|
||||||
|
--boot) BOOT="${2:?}"; shift 2 ;;
|
||||||
|
*) die "unknown option '$1'" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
validate_edition "$EDITION"
|
||||||
|
[[ "$FORMAT" == "qcow2" || "$FORMAT" == "raw" ]] || die "--format must be qcow2|raw"
|
||||||
|
[[ "$BOOT" == "bios" || "$BOOT" == "efi" ]] || die "--boot must be bios|efi"
|
||||||
|
require_root "$0" "$@"
|
||||||
|
|
||||||
|
command -v qemu-img >/dev/null || die "missing 'qemu-img' (package: qemu-utils)"
|
||||||
|
command -v losetup >/dev/null || die "missing 'losetup' (package: util-linux)"
|
||||||
|
|
||||||
|
ROOTFS="$ROOTFS_DIR/$EDITION"
|
||||||
|
RAW="$IMAGE_DIR/$EDITION/arcline-$EDITION-$VERSION-$ARCH.raw"
|
||||||
|
EXT="$FORMAT"
|
||||||
|
ARTIFACT="$ARTIFACT_DIR/arcline-$EDITION-$VERSION-$ARCH.$EXT"
|
||||||
|
|
||||||
|
mkdir -p "$IMAGE_DIR/$EDITION" "$ARTIFACT_DIR"
|
||||||
|
rm -f "$RAW" "$ARTIFACT" "$ARTIFACT.sha256"
|
||||||
|
|
||||||
|
# ── 1. rootfs ───────────────────────────────────────────────────────────────
|
||||||
|
if [[ ! -d "$ROOTFS" ]]; then
|
||||||
|
log "rootfs missing — building it first"
|
||||||
|
"$ROOT/scripts/build-rootfs.sh" "$EDITION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 2. sparse raw file + loop device ────────────────────────────────────────
|
||||||
|
log "creating ${SIZE} sparse image (edition $EDITION, $BOOT boot)"
|
||||||
|
truncate -s "$SIZE" "$RAW"
|
||||||
|
LOOP="$(losetup --find --show --partscan "$RAW")"
|
||||||
|
cleanup() { losetup -d "$LOOP" 2>/dev/null || true; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
log "attached loop device: $LOOP"
|
||||||
|
|
||||||
|
# ── 3. deploy ───────────────────────────────────────────────────────────────
|
||||||
|
"$ROOT/scripts/deploy-disk.sh" "$LOOP" "$ROOTFS" "$EDITION" --boot "$BOOT"
|
||||||
|
|
||||||
|
# ── 4. convert ──────────────────────────────────────────────────────────────
|
||||||
|
losetup -d "$LOOP"
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
|
if [[ "$FORMAT" == "qcow2" ]]; then
|
||||||
|
log "converting to qcow2"
|
||||||
|
qemu-img convert -f raw -O qcow2 "$RAW" "$ARTIFACT"
|
||||||
|
rm -f "$RAW"
|
||||||
|
else
|
||||||
|
mv "$RAW" "$ARTIFACT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "image artifact: $ARTIFACT"
|
||||||
|
sha256sum "$ARTIFACT" | tee "$ARTIFACT.sha256"
|
||||||
@@ -18,6 +18,19 @@ NEEDED=(
|
|||||||
"xorriso|xorriso"
|
"xorriso|xorriso"
|
||||||
"cpio|cpio"
|
"cpio|cpio"
|
||||||
"bash|bash"
|
"bash|bash"
|
||||||
|
# disk images / installer
|
||||||
|
"sgdisk|gdisk"
|
||||||
|
"partprobe|parted"
|
||||||
|
"rsync|rsync"
|
||||||
|
"mkfs.vfat|dosfstools"
|
||||||
|
"qemu-img|qemu-utils"
|
||||||
|
"losetup|util-linux"
|
||||||
|
# vendor packaging + secure boot
|
||||||
|
"unzip|unzip"
|
||||||
|
"dpkg-deb|dpkg"
|
||||||
|
"openssl|openssl"
|
||||||
|
"sbsign|sbsigntool"
|
||||||
|
"sbverify|sbsigntool"
|
||||||
)
|
)
|
||||||
|
|
||||||
MISSING=()
|
MISSING=()
|
||||||
|
|||||||
139
scripts/deploy-disk.sh
Executable file
139
scripts/deploy-disk.sh
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Arcline OS — deploy a rootfs to a disk
|
||||||
|
#
|
||||||
|
# scripts/deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]
|
||||||
|
#
|
||||||
|
# The shared "write a finished Arcline system to a block device" step, used by
|
||||||
|
# both the disk image builder (scripts/build-image.sh) and the scripted
|
||||||
|
# installer (scripts/install.sh):
|
||||||
|
#
|
||||||
|
# 1. partition the device (GPT):
|
||||||
|
# bios: [1M BIOS boot] + [btrfs root]
|
||||||
|
# efi: [512M EFI] + [btrfs root]
|
||||||
|
# 2. mkfs.btrfs + btrfs/init.sh → @, @home, @log, @snapshots
|
||||||
|
# 3. rsync the rootfs into @
|
||||||
|
# 4. chroot: write real /etc/fstab (real UUIDs), install GRUB, update-grub
|
||||||
|
#
|
||||||
|
# DANGER: this wipes <device>. There is no confirmation — the callers are
|
||||||
|
# responsible for safety (build-image.sh always uses a throwaway loop device;
|
||||||
|
# install.sh asks for explicit confirmation).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||||
|
|
||||||
|
DEV="${1:?usage: deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]}"
|
||||||
|
ROOTFS="${2:?usage: deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]}"
|
||||||
|
EDITION="${3:?usage: deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]}"
|
||||||
|
BOOT="${4:-bios}"
|
||||||
|
|
||||||
|
require_root "$0" "$@"
|
||||||
|
validate_edition "$EDITION"
|
||||||
|
[[ "$BOOT" == "bios" || "$BOOT" == "efi" ]] || die "--boot must be bios|efi (got '$BOOT')"
|
||||||
|
[[ -d "$ROOTFS" ]] || die "rootfs '$ROOTFS' does not exist (build it first: make rootfs-$EDITION)"
|
||||||
|
[[ -e "$DEV" ]] || die "device '$DEV' does not exist"
|
||||||
|
|
||||||
|
for tool in sgdisk mkfs.btrfs rsync; do
|
||||||
|
command -v "$tool" >/dev/null || die "missing host tool '$tool' (run: scripts/check-host-deps.sh --install)"
|
||||||
|
done
|
||||||
|
[[ "$BOOT" == "efi" ]] && { command -v mkfs.vfat >/dev/null || die "missing 'mkfs.vfat' (package: dosfstools)"; }
|
||||||
|
|
||||||
|
# device + partition naming: /dev/sda → /dev/sda1, /dev/nvme0n1 → /dev/nvme0n1p1, /dev/loop0 → /dev/loop0p1
|
||||||
|
part_of() {
|
||||||
|
if [[ "$1" =~ [0-9]$ ]]; then printf '%sp%s' "$1" "$2"; else printf '%s%s' "$1" "$2"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
MNT="$(mktemp -d /run/arcline-deploy.XXXXXX)"
|
||||||
|
cleanup() {
|
||||||
|
umount -R "$MNT" 2>/dev/null || true
|
||||||
|
rmdir "$MNT" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# ── 1. partition ────────────────────────────────────────────────────────────
|
||||||
|
log "wiping and partitioning $DEV ($BOOT boot)"
|
||||||
|
wipefs -a "$DEV" >/dev/null 2>&1 || true
|
||||||
|
sgdisk --zap-all "$DEV" >/dev/null
|
||||||
|
case "$BOOT" in
|
||||||
|
bios)
|
||||||
|
sgdisk -n 1:0:+1M -t 1:ef02 -c 1:"BIOS boot" "$DEV" >/dev/null
|
||||||
|
sgdisk -n 2:0:0 -t 2:8300 -c 2:"Arcline root" "$DEV" >/dev/null
|
||||||
|
ROOT_PART="$(part_of "$DEV" 2)"
|
||||||
|
EFI_PART=""
|
||||||
|
;;
|
||||||
|
efi)
|
||||||
|
sgdisk -n 1:0:+512M -t 1:ef00 -c 1:"EFI System" "$DEV" >/dev/null
|
||||||
|
sgdisk -n 2:0:0 -t 2:8300 -c 2:"Arcline root" "$DEV" >/dev/null
|
||||||
|
ROOT_PART="$(part_of "$DEV" 2)"
|
||||||
|
EFI_PART="$(part_of "$DEV" 1)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
partprobe "$DEV" 2>/dev/null || udevadm settle
|
||||||
|
sleep 1
|
||||||
|
[[ -b "$ROOT_PART" ]] || die "root partition $ROOT_PART did not appear"
|
||||||
|
|
||||||
|
# ── 2. filesystems + btrfs layout ───────────────────────────────────────────
|
||||||
|
log "formatting root partition $ROOT_PART (btrfs)"
|
||||||
|
mkfs.btrfs -f -L arcline-root "$ROOT_PART" >/dev/null
|
||||||
|
"$ROOT/btrfs/init.sh" "$ROOT_PART"
|
||||||
|
if [[ -n "$EFI_PART" ]]; then
|
||||||
|
log "formatting EFI partition $EFI_PART (vfat)"
|
||||||
|
mkfs.vfat -F32 -n ARCLINE-EFI "$EFI_PART" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT_UUID="$(blkid -s UUID -o value "$ROOT_PART")"
|
||||||
|
EFI_UUID=""
|
||||||
|
[[ -n "$EFI_PART" ]] && EFI_UUID="$(blkid -s UUID -o value "$EFI_PART")"
|
||||||
|
|
||||||
|
# ── 3. mount subvolumes + copy rootfs ───────────────────────────────────────
|
||||||
|
log "mounting subvolumes and copying rootfs (this takes a while)"
|
||||||
|
mkdir -p "$MNT/root"
|
||||||
|
mount -o subvol=@,compress=zstd:3 "$ROOT_PART" "$MNT/root"
|
||||||
|
mkdir -p "$MNT/root/home" "$MNT/root/var/log" "$MNT/root/.snapshots"
|
||||||
|
mount -o subvol=@home,compress=zstd:3 "$ROOT_PART" "$MNT/root/home"
|
||||||
|
mount -o subvol=@log,compress=zstd:3 "$ROOT_PART" "$MNT/root/var/log"
|
||||||
|
mount -o subvol=@snapshots,compress=zstd:3 "$ROOT_PART" "$MNT/root/.snapshots"
|
||||||
|
if [[ -n "$EFI_PART" ]]; then
|
||||||
|
mkdir -p "$MNT/root/boot/efi"
|
||||||
|
mount "$EFI_PART" "$MNT/root/boot/efi"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rsync -aHAX --numeric-ids \
|
||||||
|
--exclude='/proc/*' --exclude='/sys/*' --exclude='/dev/*' \
|
||||||
|
--exclude='/run/*' --exclude='/tmp/*' --exclude='/boot/grub/grub.cfg' \
|
||||||
|
"$ROOTFS/" "$MNT/root/"
|
||||||
|
|
||||||
|
# ── 4. chroot: fstab + grub + hostname ──────────────────────────────────────
|
||||||
|
log "configuring system in chroot"
|
||||||
|
mount --bind /dev "$MNT/root/dev"
|
||||||
|
mount --bind /proc "$MNT/root/proc"
|
||||||
|
mount --bind /sys "$MNT/root/sys"
|
||||||
|
|
||||||
|
# /etc/fstab from the edition template, with real UUIDs (swap line dropped;
|
||||||
|
# add a swapfile later if you want one — btrfs swapfiles need nocow).
|
||||||
|
"$ROOT/scripts/apply-fstab.sh" "$MNT/root" "$EDITION" "$ROOT_UUID" "$EFI_UUID"
|
||||||
|
|
||||||
|
chroot "$MNT/root" /bin/bash -c '
|
||||||
|
set -e
|
||||||
|
case "$1" in
|
||||||
|
bios) grub-install --target=i386-pc --recheck "$0" ;;
|
||||||
|
efi) grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arcline --recheck ;;
|
||||||
|
esac
|
||||||
|
update-grub
|
||||||
|
' "$DEV" "$BOOT"
|
||||||
|
|
||||||
|
# ── secure boot (optional: ARCLINE_SIGN=1 + a MOK keypair) ──────────────────
|
||||||
|
if [[ "${ARCLINE_SIGN:-0}" == "1" ]]; then
|
||||||
|
log "secure boot: shipping MOK and signing installed boot chain"
|
||||||
|
[[ -f "$BUILD_DIR/keys/MOK.der" ]] || die "ARCLINE_SIGN=1 but no MOK keypair — run: scripts/secureboot/gen-keys.sh"
|
||||||
|
mkdir -p "$MNT/root/etc/arcline"
|
||||||
|
cp "$BUILD_DIR/keys/MOK.der" "$MNT/root/etc/arcline/MOK.der"
|
||||||
|
"$ROOT/scripts/secureboot/sign-image.sh" "$MNT/root" --keydir "$BUILD_DIR/keys"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$EDITION" != "cloud" ]]; then
|
||||||
|
echo "$RELEASE_NAME" > "$MNT/root/etc/hostname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "deploy complete: $DEV is a bootable $EDITION ($BOOT) system"
|
||||||
|
log "root UUID: $ROOT_UUID"
|
||||||
75
scripts/install.sh
Executable file
75
scripts/install.sh
Executable file
@@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Arcline OS — scripted installer
|
||||||
|
#
|
||||||
|
# scripts/install.sh <device> [--edition server] [--boot bios|efi]
|
||||||
|
# [--rootfs <dir> | --image <rootfs.tar.xz>]
|
||||||
|
#
|
||||||
|
# Installs an Arcline OS rootfs onto a real disk:
|
||||||
|
#
|
||||||
|
# sudo scripts/install.sh /dev/sda --edition server
|
||||||
|
# sudo scripts/install.sh /dev/nvme0n1 --edition cloud --image build/artifacts/arcline-cloud-0.1.0-amd64.tar.xz
|
||||||
|
#
|
||||||
|
# WARNING: this WIPES <device>. You are asked to confirm the exact device path
|
||||||
|
# before anything is written.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||||
|
|
||||||
|
DEV="${1:?usage: install.sh <device> [--edition server] [--boot bios|efi] [--rootfs <dir>|--image <tar.xz>]}"
|
||||||
|
EDITION="server"
|
||||||
|
BOOT="bios"
|
||||||
|
ROOTFS_SRC=""
|
||||||
|
|
||||||
|
shift || true
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--edition) EDITION="${2:?}"; shift 2 ;;
|
||||||
|
--boot) BOOT="${2:?}"; shift 2 ;;
|
||||||
|
--rootfs) ROOTFS_SRC="${2:?}"; shift 2 ;;
|
||||||
|
--image) ROOTFS_SRC="${2:?}"; shift 2 ;;
|
||||||
|
*) die "unknown option '$1'" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
require_root "$0" "$@"
|
||||||
|
validate_edition "$EDITION"
|
||||||
|
[[ "$BOOT" == "bios" || "$BOOT" == "efi" ]] || die "--boot must be bios|efi"
|
||||||
|
[[ -b "$DEV" ]] || die "'$DEV' is not a block device — refusing to install"
|
||||||
|
|
||||||
|
# ── resolve rootfs (dir or tarball) ─────────────────────────────────────────
|
||||||
|
TMPROOT=""
|
||||||
|
if [[ -n "$ROOTFS_SRC" ]]; then
|
||||||
|
if [[ -d "$ROOTFS_SRC" ]]; then
|
||||||
|
ROOTFS="$ROOTFS_SRC"
|
||||||
|
elif [[ -f "$ROOTFS_SRC" ]]; then
|
||||||
|
TMPROOT="$(mktemp -d /run/arcline-install.XXXXXX)"
|
||||||
|
log "extracting rootfs image: $ROOTFS_SRC"
|
||||||
|
tar -xJf "$ROOTFS_SRC" -C "$TMPROOT"
|
||||||
|
ROOTFS="$TMPROOT"
|
||||||
|
else
|
||||||
|
die "rootfs source '$ROOTFS_SRC' not found"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ROOTFS="$ROOTFS_DIR/$EDITION"
|
||||||
|
[[ -d "$ROOTFS" ]] || die "no rootfs for '$EDITION' at $ROOTFS (build it: make rootfs-$EDITION, or pass --rootfs/--image)"
|
||||||
|
fi
|
||||||
|
trap '[[ -n "$TMPROOT" ]] && { rm -rf "$TMPROOT"; }' EXIT
|
||||||
|
|
||||||
|
# ── safety confirmation ─────────────────────────────────────────────────────
|
||||||
|
echo
|
||||||
|
echo "Arcline OS installer"
|
||||||
|
echo " target device : $DEV"
|
||||||
|
echo " edition : $EDITION"
|
||||||
|
echo " boot : $BOOT"
|
||||||
|
echo
|
||||||
|
echo "!!! ALL DATA ON $DEV WILL BE DESTROYED !!!"
|
||||||
|
read -r -p "Type the device path to confirm ($DEV): " CONFIRM
|
||||||
|
[[ "$CONFIRM" == "$DEV" ]] || die "confirmation mismatch — aborting"
|
||||||
|
|
||||||
|
"$ROOT/scripts/deploy-disk.sh" "$DEV" "$ROOTFS" "$EDITION" --boot "$BOOT"
|
||||||
|
|
||||||
|
echo
|
||||||
|
log "install complete. Remove the installation media and reboot."
|
||||||
|
log "After reboot: set a root password and enroll your ssh key"
|
||||||
|
log " sudo passwd root"
|
||||||
Reference in New Issue
Block a user