bigboy-alma-deploy/alma10-minimal-bigboy.ks

345 lines
14 KiB
Text

#version=DEVEL
# AlmaLinux 10.2 Unattended Installation Kickstart — BigBoy Sovereign AI Server
# Phase 1 (Base OS Installation & Foundation)
# 5-drive architecture: 1x NVMe (OS) + 4x SATA (data)
#
# Usage:
# USB/OEMDRV: label a second USB "OEMDRV", place this file at its root as ks.cfg
# HTTP: at boot, edit kernel line: inst.ks=http://<workbench_ip>:8000/alma10-minimal-bigboy.ks
#
# Reference: planning/alma-phase1-install-workflow.md
#
# BEFORE USE — fill in:
# BEFORE USE — fill in:
# <user-name> — administrator account username (appears 4x: user --name,
# sudoers filename in the cat> line, inside the sudoers
# file content, and the chmod line's filename)
# <sha512-hash> — output of `openssl passwd -6` (never a plaintext password)
#
# Disk targeting uses /dev/disk/by-id, not /dev/sdX — sdX assignment isn't
# guaranteed stable across boots. by-id values confirmed against drive
# labels (BACKUPDRV/RAGLIB/AILOGS/PROMPTLIB) via blkid on 2026-07-16.
#
# ============================================================================
text
firstboot --disable
keyboard --xlayouts='us'
lang en_US.UTF-8
timezone Europe/Rome --utc
network --bootproto=dhcp --device=link --activate --hostname=bigboy --noipv6
# ============================================================================
# SECURITY & AUTHENTICATION
# ============================================================================
rootpw --lock
# Administrator account. Password auth enabled (hash only — never plaintext,
# this file is committed to git). Phase 9 disables PasswordAuthentication
# and moves to key-only.
# WARNING: the line below MUST remain a single unbroken line, however long
# it looks in your editor. Kickstart has no line-continuation syntax — if
# an editor or copy/paste wraps the hash onto a second line, pykickstart
# reads the wrapped portion as a separate, unrecognized top-level command
# ("Unknown command: --iscrypted" is exactly this failure mode).
user --name=<user-name> --groups=wheel --shell=/bin/bash --password=<sha512-hash> --iscrypted
# Permissive during bring-up; Phase 9 sets Enforcing.
selinux --disabled
# Enabled from first boot (required since password auth is active — see
# %post below for the SSH-only rich rules that scope this).
firewall --enabled
# ============================================================================
# REPOSITORY CONFIGURATION
# ============================================================================
url --url="https://repo.almalinux.org/almalinux/10/BaseOS/x86_64/kickstart/"
repo --name="almalinux10-appstream" --mirrorlist="https://mirrors.almalinux.org/mirrorlist/10/appstream"
repo --name="almalinux10-crb" --mirrorlist="https://mirrors.almalinux.org/mirrorlist/10/crb/"
repo --name="epel10" --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=epel-10&arch=x86_64"
# ============================================================================
# BOOTLOADER
# ============================================================================
bootloader --location=mbr --boot-drive=/dev/disk/by-id/nvme-Samsung_SSD_980_500GB_S64DNL0TC07019Z
# ============================================================================
# DISK PARTITIONING
# ============================================================================
#
# NVMe (500GB): EFI (1GB) + xfs OS (~499GB)
# SATA x4: single xfs partition each, mounted by Phase 3 Ansible
# BACKUPDRV=Lexar ...1730S340, RAGLIB=Samsung EVO 500GB, AILOGS=Samsung EVO
# 250GB, PROMPTLIB=Lexar ...0370S340
#
# NOTE: fstype is xfs, not btrfs. RHEL10's kickstart removed btrfs support
# entirely from the part/logvol/autopart commands and retired the dedicated
# btrfs command — Anaconda cannot create btrfs volumes at install time on
# this OS version. Phase 3 Ansible reformats these to real btrfs
# (subvolumes, compression) post-install; this was already the plan for
# root regardless, this just extends the same pattern to the SATA drives.
clearpart --none --initlabel
# All five drives were fully zeroed (dd if=/dev/zero) on 2026-07-16 — no
# partition table exists on any of them. clearpart --initlabel is
# documented as only meaningful combined with --all; on a genuinely
# blank disk with no recognizable label, Anaconda can otherwise prompt
# interactively to ask whether to initialize it, which would stall an
# unattended install. zerombr removes that prompt for all such disks.
zerombr
part /boot/efi --fstype=efi --size=1024 --ondrive=/dev/disk/by-id/nvme-Samsung_SSD_980_500GB_S64DNL0TC07019Z
part / --fstype=xfs --size=1 --grow --ondrive=/dev/disk/by-id/nvme-Samsung_SSD_980_500GB_S64DNL0TC07019Z
part /srv/backup-raw --fstype=xfs --size=1 --grow --ondrive=/dev/disk/by-id/ata-Lexar_SSD_NS100_256GB_QM9370R0291730S340
part /srv/rag-raw --fstype=xfs --size=1 --grow --ondrive=/dev/disk/by-id/ata-Samsung_SSD_870_EVO_500GB_S6PYNL0T620757H
part /srv/ai-raw --fstype=xfs --size=1 --grow --ondrive=/dev/disk/by-id/ata-Samsung_SSD_870_EVO_250GB_S61WNJ0R458541M
part /srv/prompt-raw --fstype=xfs --size=1 --grow --ondrive=/dev/disk/by-id/ata-Lexar_SSD_NS100_256GB_QM9370R0290370S340
# ============================================================================
# PACKAGE SELECTION
# ============================================================================
%packages
@core
# Kernel + headers (Phase 4 GPU driver compilation)
kernel
kernel-devel
kernel-headers
# UEFI bootloader
grub2-efi-x64
shim-x64
efibootmgr
# Build essentials (Phase 4 NVIDIA driver)
gcc
make
patch
perl
# Utilities
curl
wget
git
tmux
htop
# vim intentionally omitted — hit a repo mirror inconsistency
# (vim-enhanced -> vim-common -> vim-data, "nothing provides vim-data")
# during install on 2026-07-17. @core already includes vim-minimal;
# install full vim post-install via dnf once on the network normally.
# SSH + sudo
openssh-clients
openssh-server
sudo
# Storage/monitoring (Phase 3, Phase 11)
btrfs-progs
smartmontools
util-linux
-kde-l10n-*
-kde-l10n-common
-network-manager-applet
-nm-connection-editor
%end
services --enabled=sshd,NetworkManager --disabled=avahi-daemon
# ============================================================================
# POST-INSTALL
# ============================================================================
%post --log=/root/anaconda-post.log
#!/bin/bash
mkdir -p /srv/deployment-log
chmod 0755 /srv/deployment-log
{
echo "=== Kickstart Installation Completed ==="
echo "Timestamp: $(date -Iseconds)"
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "AlmaLinux version: $(cat /etc/almalinux-release)"
echo ""
echo "Installed packages:"
rpm -qa | wc -l
echo ""
echo "Disk layout:"
lsblk
echo ""
echo "Network configuration:"
ip addr show enp4s0
echo ""
echo "Repositories:"
dnf repolist
} >> /srv/deployment-log/kickstart.log 2>&1
# Blacklist nouveau before Phase 4 NVIDIA driver install
cat >> /etc/modprobe.d/blacklist-nouveau.conf << 'EOF'
blacklist nouveau
options nouveau modeset=0
EOF
dracut --force 2>&1 >> /srv/deployment-log/kickstart.log
# ============================================================================
# TEMPORARY PASSWORDLESS SUDO — READ BEFORE ASSUMING THIS IS A BUG
# ============================================================================
#
# This grants <user-name> full NOPASSWD sudo, with no restriction on which
# commands. That is intentionally broad, and intentionally temporary — not
# an oversight and not a permanent security posture for this machine.
#
# WHY IT EXISTS: Phase 2 (Ansible) is what establishes this box's real,
# permanent sudoers policy. But Phase 2 itself needs privileged access to
# do that configuration in the first place — and immediately after first
# boot, there is no other path to privilege escalation at all (root is
# locked via rootpw --lock above, and no sudoers policy exists yet). This
# bootstrap grant is the only way to close that chicken-and-egg gap.
#
# WHAT MUST HAPPEN NEXT: Phase 2 must replace this file with a properly
# scoped sudoers policy before this system is considered done bootstrapping
# — not layer on top of it, not leave it in place indefinitely. If you are
# reading this file's content on a running system, confirm this file has
# actually been superseded (check /etc/sudoers.d/ for what Phase 2 left
# behind) rather than assuming Phase 2 handled it just because it ran.
cat > /etc/sudoers.d/00-bootstrap-<user-name> << 'EOF'
# Temporary bootstrap exception — superseded by Phase 2 Ansible sudoers policy.
# If you are reading this after Phase 2 has run, something did not get
# cleaned up correctly; check ansible/roles/phase-2-*/ for the real policy.
<user-name> ALL=(ALL) NOPASSWD: ALL
EOF
chmod 0440 /etc/sudoers.d/00-bootstrap-<user-name>
# Explicit password auth (Phase 9 sets to 'no' once key-based login works)
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl enable sshd
# NOTE: no systemctl start/restart here — systemd isn't running inside
# the kickstart %post chroot, so start/restart are silently ignored
# ("Running in chroot, ignoring request"). enable works fine (it's just
# a symlink), and sshd will actually start normally on first real boot.
{
echo "SSH daemon enabled, password auth explicitly set in sshd_config"
systemctl is-enabled sshd
} >> /srv/deployment-log/kickstart.log 2>&1
# Restrict SSH to LAN ranges only (home LAN + workbench bench LAN)
#
# NOTE: firewall-cmd is NOT used here deliberately. firewalld is not
# running inside the kickstart %post chroot, and firewall-cmd requires
# a live D-Bus connection to the daemon — it fails with "FirewallD is
# not running" in this exact context, silently leaving these rules
# never applied. Writing the zone XML directly is the reliable
# mechanism instead: no daemon needed, and firewalld reads this file
# normally the first time it actually starts after reboot.
# /etc/firewalld/zones/ takes precedence over the packaged default at
# /usr/lib/firewalld/zones/ for a same-named zone.
mkdir -p /etc/firewalld/zones
cat > /etc/firewalld/zones/public.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>Default zone. SSH restricted to home LAN and workbench bench LAN only.</description>
<rule family="ipv4">
<source address="192.168.1.0/24"/>
<service name="ssh"/>
<accept/>
</rule>
<rule family="ipv4">
<source address="192.168.0.0/24"/>
<service name="ssh"/>
<accept/>
</rule>
</zone>
EOF
{
echo "Firewalld zone file written: SSH restricted to 192.168.1.0/24, 192.168.0.0/24"
} >> /srv/deployment-log/kickstart.log 2>&1
{
echo ""
echo "=== Kickstart Post-Installation Complete ==="
echo "Timestamp: $(date -Iseconds)"
echo "Next phase: Ansible Phase 2 (System Configuration)"
} >> /srv/deployment-log/kickstart.log 2>&1
exit 0
%end
%addon com_redhat_kdump --disable
%end
reboot --eject
# ============================================================================
# Pre-boot checklist:
# [ ] <user-name> and <sha512-hash> replaced (4 occurrences of the
# former — user line, sudoers cat> line, sudoers content, chmod line)
# [ ] The user line (search "iscrypted") is one unbroken line — no wrap
# [ ] by-id paths still match — re-run `ls -la /dev/disk/by-id/` if any
# drive has been physically moved/replaced since 2026-07-16
# [ ] Network interface (enp4s0), hostname, timezone correct
# [ ] Repos reachable; real outbound internet confirmed (not just bench LAN)
#
# Issues found and fixed during 2026-07-17 install attempts (for reference —
# if a *new*, different error appears, it is likely unrelated to these):
# 1. %anaconda section — not recognized by this Anaconda version, removed
# 2. bootloader --location=partition — GRUB2 doesn't support this,
# changed to --location=mbr (correct even on UEFI/GPT)
# 3. --iscrypted "Unknown command" — the user line had wrapped across
# two lines; kickstart has no line-continuation syntax
# 4. --ipv6=off — not a valid value ('off' isn't an IPv6 address);
# changed to --noipv6
# 5. --fstype=btrfs on all partitions — RHEL10 kickstart removed btrfs
# support from part/logvol/autopart and retired the btrfs command
# entirely; changed to --fstype=xfs, Phase 3 Ansible converts to real
# btrfs post-install
# 6. vim-enhanced dependency chain unresolvable ("nothing provides
# vim-data") — repo mirror inconsistency between BaseOS/AppStream;
# dropped vim from %packages, @core's vim-minimal suffices for now
#
# Found on deeper review before a fresh attempt (not yet hit live, but
# confirmed against pykickstart docs and known chroot behavior):
# 7. firewall-cmd in %post cannot work — firewalld isn't running in
# the kickstart chroot, so --permanent calls silently fail
# ("FirewallD is not running"). Replaced with a direct write of
# /etc/firewalld/zones/public.xml, which needs no running daemon.
# 8. systemctl start/restart sshd in %post are no-ops in a chroot
# (systemd isn't running there either) — removed; enable alone is
# sufficient and reliable, sshd starts normally on first real boot.
# 9. Considered adding a second, mirrorlist-based BaseOS repo entry to
# match the other three (rather than the single static --url). On
# reflection, reverted this — it would create a second source
# serving the same package set as the existing --url source, risking
# a genuine duplicate/conflicting-provider error, without actually
# addressing the real cause (BaseOS and AppStream are always
# separate repos, and can legitimately sync errata at different
# moments regardless of how either is selected). The vim fix (#6)
# remains the correct, sufficient fix for the error actually hit.
# 10. zerombr added — the five drives are genuinely blank (dd-zeroed),
# and clearpart --initlabel alone is only documented to matter
# with --all, not --none; zerombr prevents a possible interactive
# "initialize this disk?" prompt that would stall the unattended
# install.
#
# Temporary states, both closed by Phase 9:
# 1. Password-based SSH (-> key-only)
# 2. Blanket NOPASSWD sudo bootstrap (-> Phase 2's real sudoers policy)
#
# Next: Phase 2 (System Configuration & Secondary Drive Preparation)
# ============================================================================