fork: production-deb v0.1.0 from debian-s8ns-prefs-iso server variant
Server-only canonical production Debian build. Drops laptop/vanilla variants. Interactive LUKS + hostname at install. user/123 forced rotate. DVD-1 offline base. S8N_LOGS log-capture partition. Lineage: forked from s8n/debian-s8ns-prefs-iso commit d4be55f.
This commit is contained in:
commit
0f5bbf004a
15 changed files with 1604 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
work/
|
||||
out/
|
||||
*.iso
|
||||
*.iso.sha256
|
||||
*.deb
|
||||
boot-configs/
|
||||
.secrets
|
||||
*.swp
|
||||
157
CHANGELOG.md
Normal file
157
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Changelog (production-deb)
|
||||
|
||||
Forked from `s8n/debian-s8ns-prefs-iso` (commit d4be55f) on 2026-05-08.
|
||||
Server variant only; laptop/vanilla stripped.
|
||||
|
||||
## [0.1.0] — 2026-05-08
|
||||
|
||||
### Added
|
||||
- Initial fork from debian-s8ns-prefs-iso server variant
|
||||
- Same hardening, interactive LUKS+hostname, DVD-1 offline base, S8N_LOGS partition
|
||||
|
||||
---
|
||||
|
||||
# Upstream changelog (debian-s8ns-prefs-iso, kept for lineage)
|
||||
|
||||
All notable changes to debian-s8ns-prefs-iso. Format inspired by Keep a Changelog.
|
||||
|
||||
## [0.4.0] — 2026-05-08
|
||||
|
||||
### Added
|
||||
- **USB log-capture partition** for offline diagnostics. flash.sh creates a
|
||||
3rd MBR partition (vfat, label `S8N_LOGS`) using all remaining USB free
|
||||
space (~27 GiB on a 32 GiB stick). preseed `early_command` mounts it at
|
||||
`/tmp/s8n-logs`; `late_command` writes a per-run directory with:
|
||||
- `syslog`, `installer/` (full d-i logs)
|
||||
- `s8n-luks-rekey.log` and `s8n-post-install.log`
|
||||
- `lsblk`, `lspci`, `dmesg`, `mount`, `df`, `exit-status`, `build-info`
|
||||
Even if late_command's main block fails, the trap-style outer `sh -c`
|
||||
still copies whatever logs exist before unmounting.
|
||||
- **`scripts/read-usb-logs.sh`** — dumps the latest run's logs from the
|
||||
S8N_LOGS partition. Auto-detects by label or takes `/dev/sdX` as arg.
|
||||
`--copy` flag rsyncs the entire S8N_LOGS contents to `out/usb-logs-<ts>/`.
|
||||
- **flash.sh `--yes` flag** for non-interactive use.
|
||||
|
||||
### Changed
|
||||
- preseed.tpl `late_command` wrapped in trap-style `sh -c` so log-collect
|
||||
runs even if the install body fails. Exit status preserved via captured
|
||||
`$STATUS`.
|
||||
|
||||
### Fixed
|
||||
- (none new — 0.4 is purely additive)
|
||||
|
||||
### Operator workflow change
|
||||
After a failed install, instead of photographing the d-i screen:
|
||||
1. Pull the USB from the target machine
|
||||
2. Plug into the build host (onyx)
|
||||
3. Run `sudo scripts/read-usb-logs.sh /dev/sdX` (or just `--copy` to grab everything)
|
||||
|
||||
## [0.3.0] — 2026-05-07
|
||||
|
||||
### Added
|
||||
- **install.sh** companion script — apply same shared/post-install/ scripts to an
|
||||
existing Debian system (curl|bash compatible via Forgejo tarball API)
|
||||
- **Per-build randomized credentials.** User pw `mkpasswd -m yescrypt` 16-char
|
||||
random; LUKS install pw 24-char random. Written to `<iso>.creds` mode 0600.
|
||||
- **LUKS rekey in late_command.** New 32-char random pw added via
|
||||
`cryptsetup luksAddKey`, throwaway slot 0 killed before reboot. Final pw
|
||||
written to `/target/root/luks-pw.txt` mode 0600.
|
||||
- **Tailscale `--ts-auth-key` build flag.** Bakes one-time tskey into ISO;
|
||||
installed system auto-joins tailnet via systemd oneshot on first boot, then
|
||||
shreds the key file.
|
||||
- **VM smoke-test harness** at `scripts/test-vm.sh`. QEMU+OVMF, virtio disk +
|
||||
cdrom, 30 GiB qcow2, headless. Phase 1 unattended install + Phase 2
|
||||
first-boot SSH check.
|
||||
- **MBA 6,1 driver superset in laptop variant:**
|
||||
- `mbpfan` (applesmc fan control), `bluez` `blueman` (BCM4360 BT)
|
||||
- `tlp tlp-rdw powertop lm-sensors` (battery / thermals)
|
||||
- `firmware-sof-signed` (Haswell HDA SOF fallback)
|
||||
- GRUB cmdline: `acpi_backlight=vendor`, `snd_hda_intel.model=mba6`
|
||||
- `/etc/modprobe.d/hid_apple.conf` with `fnmode=2 iso_layout=0`
|
||||
- `/etc/modules-load.d/apple.conf` for applesmc + coretemp
|
||||
- `update-initramfs -u` after modprobe.d edits
|
||||
|
||||
### Changed
|
||||
- **Base ISO: trixie 13.4 stable** (was forky daily). Reason: Debian bug
|
||||
[#1106117](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1106117) —
|
||||
forky weekly netinsts ship with kernel/udeb skew producing "no kernel
|
||||
modules" install failures. trixie is a coherent snapshot; intel_iommu=off
|
||||
in grub overlay still solves MBA 6,1 SSD detection on stable kernel 6.12.
|
||||
- **late_command rewritten** to use `in-target` (with bind-mounted /proc /sys
|
||||
/dev) instead of nested `chroot` (which had no bind-mounts → apt failed).
|
||||
Wrapped in `sh -c 'set -e ; ...'` so partial failures abort install.
|
||||
- **Postinstall payload moves to `/cdrom/postinstall/`** in the ISO; copied
|
||||
to installed system at `/root/s8n-postinstall/` during late_command.
|
||||
- **ESP image patching via mtools.** Previous v0.2 wrote grub-overlay.cfg
|
||||
into iso9660 namespace at `/EFI/debian/grub.cfg`, which Apple firmware
|
||||
doesn't read. v0.3:
|
||||
1. `xorriso -extract /boot/grub/efi.img` to grab the embedded FAT image
|
||||
2. `mcopy -i efi.img grub-overlay.cfg ::/efi/debian/grub.cfg` to patch FAT
|
||||
3. `xorriso -dev <iso> -boot_image any keep -map ... -commit` to splice back
|
||||
4. **Direct-dd patched efi.img into the ISO at the El Torito EFI image LBA**
|
||||
because xorriso's `-map` only updates iso9660 namespace, not the
|
||||
ESP-partition data range that MBR partition #2 points to
|
||||
- **post-install split:** `40-mba61.sh` → `40-broadcom-wl.sh` (any laptop with
|
||||
broadcom-sta-dkms) + `50-mba61.sh` (only Apple Toshiba [1179:010b])
|
||||
- **install.sh idempotence:** `00-base.sh` skips ufw reset if already active;
|
||||
`10-dark.sh` grep-guards `/etc/environment` append; sshd_config not modified
|
||||
on rerun if already includes our drop-in.
|
||||
|
||||
### Fixed (CRITICAL bugs from v0.2 audit)
|
||||
- **A2-1** `build.sh:65` Bash `HOSTNAME` builtin shadowing — renamed to
|
||||
`HOSTNAME_OPT`, default-variant-host now applies correctly
|
||||
- **A2-2** `build.sh:71` empty SSH pubkey file → SSH lockout. Use `-s` not `-f`
|
||||
- **A2-3** `build.sh:103` sha256 grep regex unanchored. Now `^${ISO_NAME}: OK$`
|
||||
- **A2-4** `build.sh:148` chroot-empty branch removed (single in-target path)
|
||||
- **A2-5** late_command bind-mounts via in-target (apt now works inside)
|
||||
- **A2-6** flash.sh sed strip → `lsblk -no PKNAME` (handles nvme/mmcblk/RAID)
|
||||
|
||||
### Fixed (HIGH security from v0.2 audit)
|
||||
- **A1-3** late_command wrapped in `sh -c 'set -e'` — fail loud, no silent
|
||||
half-installs claiming success
|
||||
- **A1-1 / A3-creds** plaintext `changeme` removed; yescrypt hash via
|
||||
`mkpasswd -m yescrypt` baked, plain pw printed once to .creds file 0600
|
||||
- **A1-2** LUKS pw auto-rotation in late_command; throwaway slot 0 killed
|
||||
before reboot
|
||||
|
||||
### Fixed (MED from v0.2 audit)
|
||||
- **flash.sh** refuses `/dev/mmcblk*`, `/dev/vd*` in addition to nvme/sda
|
||||
- **build.sh** validates hostname format before render
|
||||
- **build.sh** drops unused 7z dep check; adds mtools + mkpasswd checks
|
||||
- **Forgejo URL** `s8n-ru` → `s8n` (user renamed 2026-05-07)
|
||||
|
||||
### Known limits / deferred to v0.4+
|
||||
- VM smoke test currently can't proceed past GRUB on headless QEMU because
|
||||
d-i graphical-install requires VGA framebuffer; no `console=ttyS0`
|
||||
redirection in our overlay. Either: add a "VM serial install" menuentry
|
||||
with console=ttyS0,115200n8, or run VM test with VNC display. **Build-time
|
||||
content checks (xorriso -extract + mdir verify + direct-dd ESP read-back)
|
||||
are the current correctness gate.**
|
||||
- SHA256SUMS not yet GPG-verified (Debian release key pin pending) — v0.4
|
||||
- Forgejo SSH key fingerprint not pinned — v0.4
|
||||
- `install.sh` GRUB persist still uses substring match (not word-boundary) — v0.4
|
||||
- Server hardening superset (auditd rules, apparmor enforce, faillock,
|
||||
login.defs YESCRYPT, journald persistent+seal, KSPP cmdline, etc.) — v0.5
|
||||
- Reproducible builds (SOURCE_DATE_EPOCH, --modification-date) + Forgejo CI — v1.0
|
||||
|
||||
## [0.2.0] — 2026-05-07
|
||||
|
||||
### Added
|
||||
- Smoke-tested 3 variants build clean
|
||||
- `install.sh` companion script
|
||||
- Wider firmware coverage in shared pkgsel/include
|
||||
- post-install split 40-mba61 → 40-broadcom-wl + 50-mba61
|
||||
|
||||
### Fixed
|
||||
- xorriso extract chmod u+w
|
||||
- ESP grub.cfg uses overlay direct (not extracted)
|
||||
- LUKS partition path NVMe vs SATA (no more `sdap3`)
|
||||
- MBR PT preservation via cp + xorriso -dev (was zeroed by `-indev/-outdev`)
|
||||
|
||||
## [0.1.0] — 2026-05-07
|
||||
|
||||
### Added
|
||||
- Initial scaffold: build.sh, flash.sh, 3 variants (laptop/server/vanilla)
|
||||
- shared/preseed.tpl, grub-overlay.cfg.tpl, post-install/, packages/
|
||||
- APM strip step for single-icon Apple boot
|
||||
- Forgejo private repo at git.s8n.ru/s8n/debian-s8ns-prefs-iso
|
||||
83
README.md
Normal file
83
README.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# production-deb
|
||||
|
||||
Production Debian server ISO builder. Hardened headless. Interactive LUKS +
|
||||
hostname at install time. user pw `123` (forced rotate first login). SSH key
|
||||
pre-placed for instant remote access.
|
||||
|
||||
Forked from `s8n/debian-s8ns-prefs-iso` server variant; non-server variants
|
||||
(laptop/vanilla) stripped. This repo is the canonical production server build.
|
||||
|
||||
## Install behavior
|
||||
|
||||
Boot the flashed USB. Two prompts at console:
|
||||
|
||||
1. **Hostname** — typed (default = `server-host`)
|
||||
2. **LUKS passphrase** — set in person, NOT preseeded
|
||||
|
||||
Everything else unattended:
|
||||
|
||||
- Debian 13.4 trixie (DVD-1 base, offline-capable — no mirror needed)
|
||||
- LUKS+LVM atomic, encrypted root
|
||||
- User `user`, pw `123`, sudo, forced rotate on first SSH login (`chage -d 0`)
|
||||
- SSH ed25519 key from `~/.ssh/id_ed25519.pub` pre-placed in `/home/user/.ssh/authorized_keys`
|
||||
- sshd hardened: pubkey-only, no root, no password auth
|
||||
- ufw default-deny, allow 22/tcp
|
||||
- fail2ban + auditd + apparmor enforce + libpam-pwquality
|
||||
- dropbear-initramfs + cryptsetup-initramfs (LUKS unlock-via-SSH on boot)
|
||||
- Tailscale client installed, manual `tailscale up --login-server=https://hs.s8n.ru` post-boot
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
./build.sh --variant server --hostname <name> --disk /dev/nvme0n1
|
||||
sudo ./flash.sh --yes /dev/sdX out/debian-s8ns-server-DATE.iso
|
||||
```
|
||||
|
||||
(Variant is always `server` in this repo; flag retained for compat with the
|
||||
parent project's build.sh.)
|
||||
|
||||
After install completes:
|
||||
|
||||
```
|
||||
ssh user@<hostname>
|
||||
# PAM forces pw change → set new pw → shell
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
build.sh ISO factory (DVD-1 base, hd-media kernel, mtools ESP edit)
|
||||
flash.sh safe USB flash + adds S8N_LOGS log-capture partition
|
||||
install.sh curl|bash post-install applier for existing systems
|
||||
variants/server.cfg hardening prefs, INTERACTIVE_LUKS=1, INTERACTIVE_HOSTNAME=1
|
||||
shared/
|
||||
preseed.tpl offline preseed + late_command (LUKS rekey, post-install)
|
||||
grub-overlay.cfg.tpl ESP grub.cfg with priority=high
|
||||
packages/server.list ufw fail2ban auditd apparmor dropbear-initramfs etc.
|
||||
post-install/
|
||||
00-base.sh extra pkgs, sysctl hardening, ufw, unattended-upgrades
|
||||
20-ssh.sh sshd hardening (key-only, no root)
|
||||
30-tailscale.sh install client only (manual login post-install)
|
||||
scripts/
|
||||
test-vm.sh QEMU+OVMF smoke test harness
|
||||
read-usb-logs.sh offline log dump from S8N_LOGS partition (post-failure)
|
||||
```
|
||||
|
||||
## Hardening posture
|
||||
|
||||
Currently: G1-G6 + B1-B4 from veilor-server-bootstrap baseline. Per
|
||||
`debian-s8ns-prefs-iso/AUDIT.md` the v0.5 superset is queued — covered in the
|
||||
parent repo's roadmap.
|
||||
|
||||
## Defaults
|
||||
|
||||
- Locale: `en_GB.UTF-8`, keymap `gb`, timezone `Europe/London`
|
||||
- Mirror: cdrom-only at install (DVD-1 has all packages; no internet required)
|
||||
- Logs: install-time logs land on the USB's `S8N_LOGS` (sda3) partition for
|
||||
offline diagnostics — `sudo scripts/read-usb-logs.sh /dev/sdX3 --copy`
|
||||
|
||||
## Why a separate repo
|
||||
|
||||
`debian-s8ns-prefs-iso` is the multi-variant playground (laptop/server/vanilla).
|
||||
`production-deb` is the production-server-only canonical path. Changes here
|
||||
should preserve the "boot, prompt 2 things, walk away" property.
|
||||
427
build.sh
Executable file
427
build.sh
Executable file
|
|
@ -0,0 +1,427 @@
|
|||
#!/usr/bin/env bash
|
||||
# build.sh — Build personal Debian ISO with my prefs + per-variant config.
|
||||
#
|
||||
# Base: Debian 13.4 trixie stable netinst. Forky daily was rejected because
|
||||
# of recurring kernel/udeb-skew bugs (Debian #1106117) producing "no kernel
|
||||
# modules" failures during install. trixie is a coherent snapshot; we still
|
||||
# need `intel_iommu=off` for MBA 6,1 SSD detection but bake that into grub.
|
||||
#
|
||||
# Apple firmware boots from the ESP partition exposed by the El Torito EFI
|
||||
# image, NOT from the iso9660 namespace. Custom grub.cfg goes into the FAT
|
||||
# image inside the ISO via mtools (no root, no loop mount).
|
||||
#
|
||||
# Output: out/debian-s8ns-<variant>-<date>.iso
|
||||
# out/debian-s8ns-<variant>-<date>.iso.sha256
|
||||
# out/debian-s8ns-<variant>-<date>.creds (mode 0600, install pwds)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORK_DIR="$SCRIPT_DIR/work"
|
||||
OUT_DIR="$SCRIPT_DIR/out"
|
||||
|
||||
# Defaults
|
||||
VARIANT=""
|
||||
HOSTNAME_OPT=""
|
||||
SSH_PUBKEY="${SSH_PUBKEY:-$HOME/.ssh/id_ed25519.pub}"
|
||||
DISK=""
|
||||
USERNAME="${USERNAME:-user}"
|
||||
OUT_ISO=""
|
||||
TS_AUTH_KEY=""
|
||||
# Tri-state: "" means "use variant default"; "0"/"1" override variant.
|
||||
INTERACTIVE_LUKS_OPT=""
|
||||
INTERACTIVE_HOSTNAME_OPT=""
|
||||
USER_PW_PLAIN_OPT=""
|
||||
|
||||
DEBIAN_VERSION="${DEBIAN_VERSION:-13.4.0}"
|
||||
# DVD-1 has GNOME + most packages bundled — supports offline install (no mirror).
|
||||
# netinst is too small for GNOME tasksel; was causing "Bad archive mirror" errors
|
||||
# on machines without ethernet at install time.
|
||||
BASE_URL="${BASE_URL:-https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd}"
|
||||
ISO_NAME="debian-${DEBIAN_VERSION}-amd64-DVD-1.iso"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 --variant {laptop|server|vanilla} [opts]
|
||||
|
||||
Required:
|
||||
--variant NAME laptop | server | vanilla
|
||||
|
||||
Optional:
|
||||
--hostname NAME hostname for installed system (default: <variant>-host)
|
||||
--ssh-pubkey PATH SSH pubkey file (default: ~/.ssh/id_ed25519.pub;
|
||||
falls back to Forgejo API users/s8n/keys if missing)
|
||||
--disk DEV install target disk (default: variant-specific)
|
||||
--user NAME first sudoer (default: admin)
|
||||
--out FILE output ISO path (default: out/debian-s8ns-VARIANT-DATE.iso)
|
||||
--base-url URL override netinst URL (default: cdimage current)
|
||||
--ts-auth-key KEY Tailscale auth key (tskey-...) for unattended tailnet join
|
||||
--interactive-luks omit partman-crypto/passphrase preseed; d-i prompts at console
|
||||
(default: ON for server, OFF for laptop/vanilla)
|
||||
--interactive-hostname omit netcfg/get_hostname preseed; d-i prompts (overridable
|
||||
via kernel cmdline hostname=NAME)
|
||||
(default: ON for server, OFF for laptop/vanilla)
|
||||
--user-pw PLAIN bake this plain password (chage -d 0 forces rotate on first
|
||||
login). Default: random 16-char per-build (or "123" for server).
|
||||
-h | --help this
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--variant) VARIANT="$2"; shift 2;;
|
||||
--hostname) HOSTNAME_OPT="$2"; shift 2;;
|
||||
--ssh-pubkey) SSH_PUBKEY="$2"; shift 2;;
|
||||
--disk) DISK="$2"; shift 2;;
|
||||
--user) USERNAME="$2"; shift 2;;
|
||||
--out) OUT_ISO="$2"; shift 2;;
|
||||
--base-url) BASE_URL="$2"; shift 2;;
|
||||
--ts-auth-key) TS_AUTH_KEY="$2"; shift 2;;
|
||||
--interactive-luks) INTERACTIVE_LUKS_OPT=1; shift;;
|
||||
--no-interactive-luks) INTERACTIVE_LUKS_OPT=0; shift;;
|
||||
--interactive-hostname) INTERACTIVE_HOSTNAME_OPT=1; shift;;
|
||||
--no-interactive-hostname) INTERACTIVE_HOSTNAME_OPT=0; shift;;
|
||||
--user-pw) USER_PW_PLAIN_OPT="$2"; shift 2;;
|
||||
-h|--help) usage; exit 0;;
|
||||
*) echo "ERR: unknown arg: $1" >&2; usage; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate
|
||||
[[ -n "$VARIANT" ]] || { echo "ERR: --variant required" >&2; usage; exit 1; }
|
||||
VARIANT_FILE="$SCRIPT_DIR/variants/$VARIANT.cfg"
|
||||
[[ -f "$VARIANT_FILE" ]] || { echo "ERR: variant not found: $VARIANT_FILE" >&2; exit 1; }
|
||||
|
||||
# Source variant config (sets VARIANT_NAME, VARIANT_VOLID, GRUB_PARAMS, DEFAULT_DISK,
|
||||
# TASKSEL_TASKS, PACKAGES_LIST, POST_INSTALL_SCRIPTS; optional: INTERACTIVE_LUKS,
|
||||
# INTERACTIVE_HOSTNAME, USER_PW_PLAIN_DEFAULT, PRESEED_PRIORITY)
|
||||
# shellcheck source=/dev/null
|
||||
source "$VARIANT_FILE"
|
||||
|
||||
# Resolve interactive flags: CLI override > variant default > 0
|
||||
INTERACTIVE_LUKS="${INTERACTIVE_LUKS_OPT:-${INTERACTIVE_LUKS:-0}}"
|
||||
INTERACTIVE_HOSTNAME="${INTERACTIVE_HOSTNAME_OPT:-${INTERACTIVE_HOSTNAME:-0}}"
|
||||
PRESEED_PRIORITY="${PRESEED_PRIORITY:-critical}"
|
||||
|
||||
# Apply --hostname (HOSTNAME_OPT, NOT $HOSTNAME — bash exposes the system
|
||||
# hostname via $HOSTNAME so a -n test would always pass).
|
||||
HOSTNAME_FINAL="${HOSTNAME_OPT:-${VARIANT}-host}"
|
||||
[[ "$HOSTNAME_FINAL" =~ ^[a-z0-9][a-z0-9-]{0,62}$ ]] \
|
||||
|| { echo "ERR: invalid hostname '$HOSTNAME_FINAL' (lowercase alnum + hyphens, 1-63 chars)" >&2; exit 1; }
|
||||
[[ -n "$DISK" ]] || DISK="$DEFAULT_DISK"
|
||||
[[ -n "$OUT_ISO" ]] || OUT_ISO="$OUT_DIR/debian-s8ns-${VARIANT}-$(date +%Y%m%d).iso"
|
||||
CREDS_FILE="${OUT_ISO%.iso}.creds"
|
||||
|
||||
# Resolve SSH pubkey (-s = file exists AND non-empty)
|
||||
PUBKEY_CONTENT=""
|
||||
if [[ -s "$SSH_PUBKEY" ]]; then
|
||||
PUBKEY_CONTENT="$(cat "$SSH_PUBKEY")"
|
||||
echo "[*] SSH pubkey: $SSH_PUBKEY"
|
||||
elif [[ -f "$HOME/.config/veilor-forgejo-pat.txt" ]]; then
|
||||
echo "[*] SSH pubkey file missing or empty, fetching from Forgejo (user s8n)..."
|
||||
PAT="$(cat "$HOME/.config/veilor-forgejo-pat.txt")"
|
||||
PUBKEY_CONTENT="$(curl -sH "Authorization: token $PAT" \
|
||||
'https://git.s8n.ru/api/v1/users/s8n/keys' \
|
||||
| python3 -c 'import json,sys; [print(k["key"]) for k in json.load(sys.stdin)]')"
|
||||
fi
|
||||
[[ -n "$PUBKEY_CONTENT" ]] || { echo "ERR: no SSH pubkey resolved." >&2; exit 1; }
|
||||
|
||||
# Tools
|
||||
for cmd in curl xorriso sha256sum python3 mkpasswd mcopy mdir; do
|
||||
command -v "$cmd" >/dev/null \
|
||||
|| { echo "ERR: missing $cmd. Fedora: dnf install whois mtools xorriso curl python3" >&2; exit 1; }
|
||||
done
|
||||
|
||||
mkdir -p "$WORK_DIR" "$OUT_DIR"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
# === 1. Generate per-build credentials ===
|
||||
# `head -c N /dev/urandom | base64 | tr -dc '...'` order matters: head reads
|
||||
# fixed bytes from urandom (no SIGPIPE risk), then base64 + tr clean up.
|
||||
# Going `tr | head` instead aborts under set -o pipefail because tr gets
|
||||
# SIGPIPE when head closes its end (exit 141).
|
||||
echo "[*] Generating per-build credentials..."
|
||||
# User-pw resolution: --user-pw > variant USER_PW_PLAIN_DEFAULT > random
|
||||
if [[ -n "$USER_PW_PLAIN_OPT" ]]; then
|
||||
USER_PW_PLAIN="$USER_PW_PLAIN_OPT"
|
||||
elif [[ -n "${USER_PW_PLAIN_DEFAULT:-}" ]]; then
|
||||
USER_PW_PLAIN="$USER_PW_PLAIN_DEFAULT"
|
||||
else
|
||||
USER_PW_PLAIN="$(head -c 32 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 16)"
|
||||
fi
|
||||
USER_PW_CRYPTED="$(printf '%s' "$USER_PW_PLAIN" | mkpasswd -m yescrypt -s)"
|
||||
LUKS_INSTALL_PW="$(head -c 48 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 24)"
|
||||
# LUKS_FINAL_PW is generated and rotated by late_command on the target system.
|
||||
# We don't store it in the ISO at all — only the throwaway LUKS_INSTALL_PW
|
||||
# touches the ISO image bytes, and that gets killed in late_command before reboot.
|
||||
|
||||
# === 2. Download base ISO (cached) ===
|
||||
if [[ ! -f "$ISO_NAME" ]]; then
|
||||
echo "[*] Downloading $ISO_NAME from $BASE_URL ..."
|
||||
curl -fL --remote-name-all "$BASE_URL/$ISO_NAME" "$BASE_URL/SHA256SUMS"
|
||||
else
|
||||
echo "[*] Using cached $ISO_NAME (delete work/$ISO_NAME to refresh)"
|
||||
curl -fLo SHA256SUMS "$BASE_URL/SHA256SUMS"
|
||||
fi
|
||||
|
||||
# === 3. Verify checksum (anchored — no substring false positives) ===
|
||||
echo "[*] Verifying SHA256..."
|
||||
sha256sum -c --ignore-missing SHA256SUMS 2>&1 | grep -q "^${ISO_NAME}: OK$" \
|
||||
|| { echo "ERR: SHA256 verification failed for $ISO_NAME." >&2; exit 1; }
|
||||
echo "[OK] checksum"
|
||||
|
||||
# === 4. Render preseed.cfg from template ===
|
||||
# sed-escape pubkey for delimiter `|` and replacement chars `&` `\`
|
||||
echo "[*] Rendering preseed.cfg for variant=$VARIANT..."
|
||||
ESCAPED_PUBKEY="$(printf '%s\n' "$PUBKEY_CONTENT" | sed -e 's/[\\&|]/\\&/g')"
|
||||
ESCAPED_HASH="$(printf '%s' "$USER_PW_CRYPTED" | sed -e 's/[\\&|]/\\&/g')"
|
||||
ESCAPED_TS_KEY="$(printf '%s' "$TS_AUTH_KEY" | sed -e 's/[\\&|]/\\&/g')"
|
||||
sed \
|
||||
-e "s|@HOSTNAME@|$HOSTNAME_FINAL|g" \
|
||||
-e "s|@USERNAME@|$USERNAME|g" \
|
||||
-e "s|@DISK@|$DISK|g" \
|
||||
-e "s|@SSH_PUBKEY@|$ESCAPED_PUBKEY|g" \
|
||||
-e "s|@USER_PW_CRYPTED@|$ESCAPED_HASH|g" \
|
||||
-e "s|@LUKS_INSTALL_PW@|$LUKS_INSTALL_PW|g" \
|
||||
-e "s|@TASKSEL_TASKS@|$TASKSEL_TASKS|g" \
|
||||
-e "s|@VARIANT@|$VARIANT|g" \
|
||||
-e "s|@TS_AUTH_KEY@|$ESCAPED_TS_KEY|g" \
|
||||
"$SCRIPT_DIR/shared/preseed.tpl" > preseed.cfg
|
||||
|
||||
# Interactive overrides — strip preseed lines so d-i prompts the operator.
|
||||
# When INTERACTIVE_LUKS, also strip the late_command luks-rekey step (the
|
||||
# install passphrase user typed at the prompt is unknown to us — can't rekey).
|
||||
if [[ "$INTERACTIVE_LUKS" == "1" ]]; then
|
||||
echo "[*] INTERACTIVE_LUKS=1 — stripping LUKS preseed + rekey from late_command"
|
||||
# Match passphrase AND passphrase-again (both `partman-crypto/passphrase*`).
|
||||
sed -i \
|
||||
-e '/^d-i partman-crypto\/passphrase/d' \
|
||||
-e '/luks-rekey\.sh /d' \
|
||||
preseed.cfg
|
||||
fi
|
||||
if [[ "$INTERACTIVE_HOSTNAME" == "1" ]]; then
|
||||
echo "[*] INTERACTIVE_HOSTNAME=1 — stripping hostname preseed (cmdline overrides)"
|
||||
sed -i \
|
||||
-e '/^d-i netcfg\/get_hostname /d' \
|
||||
-e '/^d-i netcfg\/hostname /d' \
|
||||
preseed.cfg
|
||||
fi
|
||||
chmod 644 preseed.cfg
|
||||
|
||||
# === 5. Render grub-overlay.cfg ===
|
||||
echo "[*] Rendering grub-overlay.cfg..."
|
||||
sed \
|
||||
-e "s|@GRUB_PARAMS@|$GRUB_PARAMS|g" \
|
||||
-e "s|@VARIANT@|$VARIANT|g" \
|
||||
-e "s|@VOLID@|$VARIANT_VOLID|g" \
|
||||
-e "s|@PRIORITY@|$PRESEED_PRIORITY|g" \
|
||||
"$SCRIPT_DIR/shared/grub-overlay.cfg.tpl" > grub-overlay.cfg
|
||||
|
||||
# === 6. Bundle post-install payload (lands at /cdrom/postinstall/ in ISO) ===
|
||||
echo "[*] Bundling post-install payload..."
|
||||
rm -rf payload && mkdir -p payload/scripts
|
||||
cp "$SCRIPT_DIR/shared/packages/$PACKAGES_LIST" payload/extra.list
|
||||
for s in "${POST_INSTALL_SCRIPTS[@]}"; do
|
||||
cp "$SCRIPT_DIR/shared/post-install/$s" "payload/scripts/$s"
|
||||
chmod +x "payload/scripts/$s"
|
||||
done
|
||||
# Tailscale auth-key passes via this file (chmod 600); 30-tailscale.sh reads it.
|
||||
if [[ -n "$TS_AUTH_KEY" ]]; then
|
||||
printf '%s\n' "$TS_AUTH_KEY" > payload/ts-auth-key
|
||||
chmod 600 payload/ts-auth-key
|
||||
fi
|
||||
# Driver script — runs in-target via late_command. Already inside chroot.
|
||||
cat > payload/run.sh <<'RUNSH'
|
||||
#!/bin/sh
|
||||
# run.sh — runs inside chroot (via `in-target sh -e`), with /proc /sys /dev
|
||||
# already bind-mounted by d-i. Loops scripts in lex order.
|
||||
set -eu
|
||||
LOG=/var/log/s8n-post-install.log
|
||||
exec >"$LOG" 2>&1
|
||||
|
||||
echo "[s8n] post-install start: $(date -u +%FT%TZ)"
|
||||
PAYLOAD=/root/s8n-postinstall
|
||||
|
||||
if [ ! -d "$PAYLOAD/scripts" ]; then
|
||||
echo "[s8n] ERROR: $PAYLOAD/scripts missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for s in "$PAYLOAD"/scripts/*.sh; do
|
||||
echo "[s8n] running $(basename "$s")"
|
||||
/bin/sh -e "$s" || { echo "[s8n] ERROR $(basename "$s") exited $?"; exit 1; }
|
||||
done
|
||||
|
||||
echo "[s8n] post-install done: $(date -u +%FT%TZ)"
|
||||
RUNSH
|
||||
chmod +x payload/run.sh
|
||||
|
||||
# LUKS rekey runs OUTSIDE chroot (cryptsetup needs /target/dev access).
|
||||
# late_command invokes this with sh -e from the installer's environment.
|
||||
cat > payload/luks-rekey.sh <<'LUKSSH'
|
||||
#!/bin/sh
|
||||
# luks-rekey.sh — runs from late_command in the d-i environment (NOT in-target).
|
||||
# Adds a fresh random key, verifies it, kills the throwaway slot 0.
|
||||
# New passphrase written to /target/root/luks-pw.txt mode 0600.
|
||||
set -eu
|
||||
|
||||
LOG=/var/log/s8n-luks-rekey.log
|
||||
exec >"$LOG" 2>&1
|
||||
|
||||
OLD_PW="$1" # passed in by late_command
|
||||
CRYPT_DEV=$(blkid -t TYPE=crypto_LUKS -o device | head -1)
|
||||
[ -n "$CRYPT_DEV" ] || { echo "[luks] ERROR: no LUKS device found"; exit 1; }
|
||||
echo "[luks] rotating keyslots on $CRYPT_DEV"
|
||||
|
||||
NEW_PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
|
||||
printf '%s\n%s\n%s\n' "$OLD_PW" "$NEW_PW" "$NEW_PW" \
|
||||
| cryptsetup luksAddKey "$CRYPT_DEV" --key-slot 1
|
||||
printf '%s' "$NEW_PW" | cryptsetup open --test-passphrase "$CRYPT_DEV" --key-slot 1 -
|
||||
printf '%s' "$OLD_PW" | cryptsetup luksKillSlot "$CRYPT_DEV" 0 -
|
||||
|
||||
umask 077
|
||||
mkdir -p /target/root
|
||||
{
|
||||
echo "LUKS pw (write down NOW, this file is destroyed on first apt upgrade):"
|
||||
echo "$NEW_PW"
|
||||
echo "Device: $CRYPT_DEV"
|
||||
} >/target/root/luks-pw.txt
|
||||
chmod 0600 /target/root/luks-pw.txt
|
||||
echo "[luks] rotation complete; new pw in /target/root/luks-pw.txt"
|
||||
LUKSSH
|
||||
chmod +x payload/luks-rekey.sh
|
||||
|
||||
# === 7. Patch BIOS-mode boot config (isolinux/txt.cfg) for legacy USB boot ===
|
||||
echo "[*] Patching legacy boot config..."
|
||||
rm -rf boot-configs && mkdir -p boot-configs
|
||||
xorriso -osirrox on -indev "$ISO_NAME" \
|
||||
-extract /isolinux/txt.cfg boot-configs/txt.cfg \
|
||||
-extract /boot/grub/grub.cfg boot-configs/grub-main.cfg \
|
||||
-extract /boot/grub/efi.img boot-configs/efi.img \
|
||||
2>/dev/null || true
|
||||
chmod -R u+w boot-configs/
|
||||
|
||||
# Inject preseed args into legacy isolinux + main grub.cfg
|
||||
# (isolinux only fires on legacy/CSM boot; main grub.cfg fires on EFI when
|
||||
# the ESP grub.cfg chains to it. ESP grub.cfg gets fully replaced below.)
|
||||
# Scrub upstream `priority=critical` first; otherwise our injected priority
|
||||
# stacks with the existing one in submenus and the duplicate is sketchy.
|
||||
sed -i "s|priority=critical |priority=$PRESEED_PRIORITY |g" boot-configs/grub-main.cfg boot-configs/txt.cfg
|
||||
sed -i "s|--- quiet|auto=true priority=$PRESEED_PRIORITY file=/cdrom/preseed.cfg $GRUB_PARAMS --- quiet|g" \
|
||||
boot-configs/grub-main.cfg
|
||||
sed -i '1i set default="0"\nset timeout=5\n' boot-configs/grub-main.cfg
|
||||
sed -i "s|append vga=788 initrd=/install.amd/initrd.gz|append auto=true priority=$PRESEED_PRIORITY file=/cdrom/preseed.cfg $GRUB_PARAMS vga=788 initrd=/install.amd/initrd.gz|g" \
|
||||
boot-configs/txt.cfg
|
||||
|
||||
# === 8. Patch ESP FAT image (this is what Apple firmware actually reads) ===
|
||||
# Note: paths inside the FAT image are stored as lowercase (/efi/debian/grub.cfg).
|
||||
# mtools is mostly case-insensitive but verify against the stored case for safety.
|
||||
echo "[*] Patching ESP FAT image (mtools, no root)..."
|
||||
mcopy -i boot-configs/efi.img -o grub-overlay.cfg ::/efi/debian/grub.cfg
|
||||
# Verify the file exists and has our content (size match against our overlay).
|
||||
EXPECTED_SIZE=$(stat -c%s grub-overlay.cfg)
|
||||
ACTUAL_SIZE=$(mdir -i boot-configs/efi.img ::/efi/debian/grub.cfg 2>/dev/null \
|
||||
| awk '/grub *cfg/ { print $3; exit }')
|
||||
[[ "$ACTUAL_SIZE" == "$EXPECTED_SIZE" ]] \
|
||||
|| { echo "ERR: ESP grub.cfg size mismatch (expected=$EXPECTED_SIZE got=$ACTUAL_SIZE)" >&2; exit 1; }
|
||||
echo "[OK] ESP grub.cfg replaced inside efi.img ($EXPECTED_SIZE bytes)"
|
||||
|
||||
# === 9. Re-master ISO with overlays + payload ===
|
||||
echo "[*] Packing $OUT_ISO ..."
|
||||
# In-place: cp source, then xorriso -dev. -boot_image any keep is REQUIRED to
|
||||
# preserve the El Torito boot catalog AND keep its pointer to the patched
|
||||
# /boot/grub/efi.img — without this, UEFI firmware reads the OLD pre-patch
|
||||
# FAT image even though the iso9660 file is updated.
|
||||
cp -f "$ISO_NAME" "$OUT_ISO"
|
||||
xorriso -dev "$OUT_ISO" \
|
||||
-boot_image any keep \
|
||||
-volid "$VARIANT_VOLID" \
|
||||
-map preseed.cfg /preseed.cfg \
|
||||
-map payload /postinstall \
|
||||
-map boot-configs/grub-main.cfg /boot/grub/grub.cfg \
|
||||
-map boot-configs/txt.cfg /isolinux/txt.cfg \
|
||||
-map boot-configs/efi.img /boot/grub/efi.img \
|
||||
-map grub-overlay.cfg /EFI/debian/grub.cfg \
|
||||
-commit
|
||||
|
||||
# === 10. Direct-write patched efi.img into the ISO's ESP partition location ===
|
||||
# xorriso's -dev + -map updates the iso9660 namespace entry for /boot/grub/efi.img,
|
||||
# but the ACTUAL ESP partition (referenced by MBR partition #2 AND El Torito
|
||||
# catalog UEFI entry) lives at a fixed LBA in the ISO that doesn't move when
|
||||
# we replace the iso9660 file. After dd-flashing the ISO to USB, the kernel
|
||||
# exposes sda2 by reading bytes at the MBR-partition-2 LBA — which is unchanged.
|
||||
# Fix: direct-dd our patched FAT image into that LBA.
|
||||
ESP_LBA=$(xorriso -indev "$OUT_ISO" -report_el_torito plain 2>&1 \
|
||||
| awk '/El Torito boot img/ && /UEFI/ {print $NF; exit}')
|
||||
[[ -n "$ESP_LBA" ]] || { echo "ERR: cannot find ESP LBA from El Torito catalog" >&2; exit 1; }
|
||||
echo "[*] Direct-writing efi.img to ISO at LBA $ESP_LBA..."
|
||||
dd if=boot-configs/efi.img of="$OUT_ISO" \
|
||||
bs=2048 seek="$ESP_LBA" \
|
||||
conv=notrunc status=none
|
||||
echo "[OK] ESP partition now contains patched grub.cfg overlay"
|
||||
|
||||
# === 11. Strip APM driver descriptor (single yellow icon on Apple) ===
|
||||
echo "[*] Stripping APM driver descriptor..."
|
||||
PT=$(mktemp)
|
||||
dd if="$OUT_ISO" of="$PT" bs=1 skip=446 count=66 status=none
|
||||
dd if=/dev/zero of="$OUT_ISO" bs=512 count=16 conv=notrunc status=none
|
||||
dd if="$PT" of="$OUT_ISO" bs=1 seek=446 count=66 conv=notrunc status=none
|
||||
rm -f "$PT"
|
||||
|
||||
# === 12. Output credentials file (mode 0600) ===
|
||||
case "$DISK" in
|
||||
/dev/nvme*|/dev/mmcblk*) LUKS_PART="${DISK}p3" ;;
|
||||
*) LUKS_PART="${DISK}3" ;;
|
||||
esac
|
||||
umask 077
|
||||
cat > "$CREDS_FILE" <<CREDS
|
||||
# Bootstrap credentials for $(basename "$OUT_ISO")
|
||||
# Generated: $(date -u +%FT%TZ)
|
||||
# This file mode 0600 — destroy after copying to password manager.
|
||||
|
||||
variant = $VARIANT
|
||||
hostname = $HOSTNAME_FINAL
|
||||
user = $USERNAME
|
||||
disk = $DISK
|
||||
luks_part = $LUKS_PART
|
||||
|
||||
user_pw = $USER_PW_PLAIN
|
||||
luks_pw = $(if [[ "$INTERACTIVE_LUKS" == "1" ]]; then echo "(prompted at console — set in person at install time)"; else echo "(rotated by late_command — read /root/luks-pw.txt on first boot)"; fi)
|
||||
hostname_set = $(if [[ "$INTERACTIVE_HOSTNAME" == "1" ]]; then echo "(prompted at console; or via cmdline 'hostname=NAME')"; else echo "$HOSTNAME_FINAL (baked)"; fi)
|
||||
priority = $PRESEED_PRIORITY
|
||||
|
||||
# The user pw above is yescrypt-hashed in preseed.cfg; \`chage -d 0\` on the
|
||||
# installed system will force a change on first console/SSH login.
|
||||
# The LUKS pw inside preseed.cfg is "$LUKS_INSTALL_PW" but it's killed in
|
||||
# late_command before reboot, so it never persists on disk (non-interactive only).
|
||||
CREDS
|
||||
chmod 0600 "$CREDS_FILE"
|
||||
|
||||
# === 13. Done ===
|
||||
SIZE=$(du -h "$OUT_ISO" | awk '{print $1}')
|
||||
sha256sum "$OUT_ISO" > "$OUT_ISO.sha256"
|
||||
echo
|
||||
echo "[OK] Built: $OUT_ISO ($SIZE)"
|
||||
echo " SHA256: $(awk '{print $1}' "$OUT_ISO.sha256")"
|
||||
echo " Creds: $CREDS_FILE (mode 0600)"
|
||||
echo
|
||||
echo "Flash to USB:"
|
||||
echo " ./flash.sh /dev/sdX $OUT_ISO"
|
||||
echo
|
||||
echo "Variant = $VARIANT"
|
||||
echo "Hostname = $HOSTNAME_FINAL"
|
||||
echo "User = $USERNAME"
|
||||
echo "Disk = $DISK"
|
||||
echo "Grub params = $GRUB_PARAMS"
|
||||
echo "Tasks = $TASKSEL_TASKS"
|
||||
[[ -n "$TS_AUTH_KEY" ]] && echo "Tailscale = auth-key baked, will join on first boot"
|
||||
echo
|
||||
echo "User pw = $USER_PW_PLAIN (forced rotate first login)"
|
||||
if [[ "$INTERACTIVE_LUKS" == "1" ]]; then
|
||||
echo "LUKS pw = INTERACTIVE — d-i prompts at console (no rekey)"
|
||||
else
|
||||
echo "LUKS pw = rotated by installer; read /root/luks-pw.txt on first boot"
|
||||
fi
|
||||
if [[ "$INTERACTIVE_HOSTNAME" == "1" ]]; then
|
||||
echo "Hostname = INTERACTIVE (override via kernel cmdline 'hostname=NAME')"
|
||||
fi
|
||||
echo "Priority = $PRESEED_PRIORITY"
|
||||
143
flash.sh
Executable file
143
flash.sh
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
#!/usr/bin/env bash
|
||||
# flash.sh - Safe USB flash with explicit confirm.
|
||||
# Refuses internal disks (NVMe + first SATA + boot devices).
|
||||
set -euo pipefail
|
||||
|
||||
YES=0
|
||||
ARGS=()
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
-y|--yes) YES=1 ;;
|
||||
*) ARGS+=("$a") ;;
|
||||
esac
|
||||
done
|
||||
set -- "${ARGS[@]+${ARGS[@]}}"
|
||||
|
||||
DEV="${1:-}"
|
||||
ISO="${2:-}"
|
||||
|
||||
if [[ -z "$DEV" || -z "$ISO" ]]; then
|
||||
echo "Usage: $0 [--yes] /dev/sdX path/to.iso"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ -f "$ISO" ]] || { echo "ERR: iso not found: $ISO" >&2; exit 1; }
|
||||
[[ -b "$DEV" ]] || { echo "ERR: not a block device: $DEV" >&2; exit 1; }
|
||||
|
||||
# Refuse obvious internal disks (nvme, first SATA, eMMC/SD, virtio).
|
||||
case "$DEV" in
|
||||
/dev/nvme*|/dev/sda|/dev/mmcblk*|/dev/vd*)
|
||||
echo "ERR: refusing to write to likely-internal device: $DEV" >&2
|
||||
echo " If you really mean it, use dd manually." >&2
|
||||
exit 2;;
|
||||
esac
|
||||
|
||||
# Refuse if device is mounted as / or /boot.
|
||||
# Use lsblk -no PKNAME to map partition → parent disk; sed 's/[0-9]*$//' breaks
|
||||
# for /dev/nvme0n1pN and /dev/mmcblk0pN (would yield /dev/nvme0n / /dev/mmcblk0).
|
||||
ROOT_PART=$(findmnt -n -o SOURCE /)
|
||||
ROOT_PKNAME=$(lsblk -no PKNAME "$ROOT_PART" 2>/dev/null | head -1)
|
||||
[[ -n "$ROOT_PKNAME" ]] && ROOT_SRC="/dev/$ROOT_PKNAME" || ROOT_SRC="$ROOT_PART"
|
||||
[[ "$DEV" != "$ROOT_SRC" ]] || { echo "ERR: $DEV is the root device. Aborting." >&2; exit 2; }
|
||||
|
||||
SIZE=$(lsblk -bndo SIZE "$DEV" | head -1)
|
||||
SIZE_GB=$((SIZE / 1024 / 1024 / 1024))
|
||||
MODEL=$(lsblk -ndo MODEL,VENDOR "$DEV" | xargs)
|
||||
|
||||
cat <<EOF
|
||||
=== About to wipe and flash ===
|
||||
Device: $DEV
|
||||
Size: ${SIZE_GB} GiB
|
||||
Model: $MODEL
|
||||
ISO: $ISO
|
||||
|
||||
This DESTROYS all data on $DEV. Type 'yes' (lowercase) to continue.
|
||||
EOF
|
||||
|
||||
if [[ "$YES" -eq 1 ]]; then
|
||||
echo "[--yes] skipping interactive confirm"
|
||||
else
|
||||
read -r ANS
|
||||
[[ "$ANS" == "yes" ]] || { echo "Aborted."; exit 1; }
|
||||
fi
|
||||
|
||||
echo "[*] Unmounting any partitions on $DEV..."
|
||||
for p in $(lsblk -nro NAME "$DEV" | tail -n +2); do
|
||||
sudo umount "/dev/$p" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "[*] Flashing $ISO -> $DEV ..."
|
||||
sudo dd if="$ISO" of="$DEV" bs=4M status=progress conv=fsync oflag=direct
|
||||
sudo sync
|
||||
|
||||
# === Add a 3rd partition for install logs ===
|
||||
# The flashed ISO is iso9660 + ESP (sda1, sda2) totaling ~759 MB on a much
|
||||
# larger USB. Carve a 3rd MBR partition out of the remaining free space and
|
||||
# mkfs.vfat as label S8N_LOGS. preseed early_command mounts this partition
|
||||
# during install and writes logs to it; after a failed install, pull the USB,
|
||||
# plug into onyx, and run scripts/read-usb-logs.sh /dev/sdX.
|
||||
echo "[*] Re-reading partition table..."
|
||||
sudo partprobe "$DEV" 2>&1 || true
|
||||
sleep 2
|
||||
|
||||
# Find the highest end-sector of existing partitions; new partition starts after
|
||||
ISO_END=$(sudo sfdisk -l -o End "$DEV" 2>/dev/null | awk '/^[ ]*[0-9]+/ {print $1}' | sort -n | tail -1)
|
||||
ISO_END="${ISO_END:-0}"
|
||||
[[ "$ISO_END" -gt 0 ]] || { echo "ERR: cannot read partition table on $DEV after dd" >&2; exit 3; }
|
||||
LOG_START=$((ISO_END + 1))
|
||||
# Round up to 1 MiB alignment (2048 sectors of 512B)
|
||||
LOG_START=$(( (LOG_START + 2047) / 2048 * 2048 ))
|
||||
TOTAL_SECTORS=$(sudo blockdev --getsz "$DEV")
|
||||
LOG_SIZE=$((TOTAL_SECTORS - LOG_START - 2048)) # leave 1 MiB tail for safety
|
||||
[[ "$LOG_SIZE" -gt 102400 ]] || { echo "ERR: not enough space for log partition (<50 MiB)" >&2; exit 3; }
|
||||
LOG_SIZE_MB=$((LOG_SIZE / 2048))
|
||||
echo "[*] Adding partition 3 at sector $LOG_START, size ${LOG_SIZE_MB} MiB ..."
|
||||
|
||||
# Use sfdisk to add a 3rd MBR entry without disturbing existing ones.
|
||||
# `--no-reread` because the kernel already has the iso9660 partition open.
|
||||
EXISTING_PT=$(sudo sfdisk -d "$DEV")
|
||||
{
|
||||
echo "$EXISTING_PT"
|
||||
echo "${DEV}3 : start=$LOG_START, size=$LOG_SIZE, type=c"
|
||||
} | sudo sfdisk --no-reread --no-tell-kernel "$DEV" || {
|
||||
# Fallback: write only the new entry by editing in place
|
||||
echo "[*] sfdisk batch failed; trying single-entry append"
|
||||
printf 'n\np\n3\n%s\n+%sK\nt\n3\nc\nw\n' "$LOG_START" "$((LOG_SIZE_MB * 1024))" \
|
||||
| sudo fdisk "$DEV" || true
|
||||
}
|
||||
sudo partprobe "$DEV" 2>&1 || true
|
||||
sleep 2
|
||||
|
||||
PART3="${DEV}3"
|
||||
case "$DEV" in
|
||||
/dev/nvme*|/dev/mmcblk*) PART3="${DEV}p3" ;;
|
||||
esac
|
||||
|
||||
if [[ -b "$PART3" ]]; then
|
||||
echo "[*] Formatting $PART3 as vfat (label S8N_LOGS)..."
|
||||
sudo mkfs.vfat -F 32 -n S8N_LOGS "$PART3"
|
||||
# Drop a README into the log partition so it's discoverable
|
||||
TMPDIR=$(mktemp -d)
|
||||
sudo mount "$PART3" "$TMPDIR"
|
||||
sudo tee "$TMPDIR/README.txt" >/dev/null <<EOF
|
||||
S8N_LOGS partition
|
||||
==================
|
||||
|
||||
This partition collects install logs during preseed/late_command on Debian
|
||||
ISOs built by s8n/debian-s8ns-prefs-iso. If install fails on the target
|
||||
machine, pull this USB, plug into another machine, and read /var/log/* +
|
||||
/postinstall.log files here for diagnostics.
|
||||
|
||||
Companion script: scripts/read-usb-logs.sh /dev/sdX
|
||||
EOF
|
||||
sudo umount "$TMPDIR"
|
||||
rmdir "$TMPDIR"
|
||||
echo "[OK] Log partition $PART3 ready (S8N_LOGS, vfat)"
|
||||
else
|
||||
echo "[!] WARN: $PART3 didn't appear; log capture WILL NOT work on this USB"
|
||||
fi
|
||||
|
||||
sudo sync
|
||||
echo
|
||||
echo "[OK] Done. Eject: sudo eject $DEV"
|
||||
echo "[i] After install fail/success, mount $PART3 (label S8N_LOGS) to read logs"
|
||||
119
install.sh
Executable file
119
install.sh
Executable file
|
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env bash
|
||||
# install.sh — apply my prefs to an existing Debian system.
|
||||
# Sister to build.sh: build.sh produces a fresh-install ISO; this runs against
|
||||
# an already-installed system and applies the same post-install tweaks.
|
||||
#
|
||||
# Usage on the target box (as root):
|
||||
#
|
||||
# git clone ssh://git@192.168.0.100:222/s8n-ru/debian-s8ns-prefs-iso /tmp/s8n
|
||||
# sudo /tmp/s8n/install.sh --variant laptop
|
||||
#
|
||||
# Or via Forgejo tarball API (needs PAT in env):
|
||||
#
|
||||
# curl -fsSL -H "Authorization: token $GIT_PAT" \
|
||||
# https://git.s8n.ru/api/v1/repos/s8n-ru/debian-s8ns-prefs-iso/archive/main.tar.gz \
|
||||
# | sudo tar xz -C /tmp
|
||||
# sudo /tmp/debian-s8ns-prefs-iso/install.sh --variant laptop
|
||||
#
|
||||
# Variants: laptop | server | vanilla (matches variants/*.cfg)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "ERR: must run as root (or via sudo)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VARIANT=""
|
||||
HOSTNAME_OVERRIDE=""
|
||||
SKIP_PKGS=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: sudo $0 --variant {laptop|server|vanilla} [opts]
|
||||
|
||||
Required:
|
||||
--variant NAME laptop | server | vanilla
|
||||
|
||||
Optional:
|
||||
--hostname NAME override hostname
|
||||
--skip-pkgs skip apt install of extra.list (testing only)
|
||||
-h | --help show this
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--variant) VARIANT="$2"; shift 2;;
|
||||
--hostname) HOSTNAME_OVERRIDE="$2"; shift 2;;
|
||||
--skip-pkgs) SKIP_PKGS=1; shift;;
|
||||
-h|--help) usage; exit 0;;
|
||||
*) echo "Unknown arg: $1" >&2; usage; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$VARIANT" ]] || { echo "ERR: --variant required" >&2; usage; exit 1; }
|
||||
VARIANT_FILE="$SCRIPT_DIR/variants/$VARIANT.cfg"
|
||||
[[ -f "$VARIANT_FILE" ]] || { echo "ERR: variant not found: $VARIANT_FILE" >&2; exit 1; }
|
||||
|
||||
# Source variant config
|
||||
# shellcheck source=/dev/null
|
||||
source "$VARIANT_FILE"
|
||||
|
||||
echo "[install] variant=$VARIANT"
|
||||
echo "[install] grub_params=$GRUB_PARAMS"
|
||||
echo "[install] post-install scripts: ${POST_INSTALL_SCRIPTS[*]}"
|
||||
|
||||
# Set hostname if requested
|
||||
if [[ -n "$HOSTNAME_OVERRIDE" ]]; then
|
||||
echo "[install] setting hostname to $HOSTNAME_OVERRIDE"
|
||||
hostnamectl set-hostname "$HOSTNAME_OVERRIDE"
|
||||
fi
|
||||
|
||||
# Stage payload like the chroot install would see it
|
||||
PAYLOAD=/tmp/s8n-payload
|
||||
rm -rf "$PAYLOAD"
|
||||
mkdir -p "$PAYLOAD/post-install" "$PAYLOAD/packages"
|
||||
cp "$SCRIPT_DIR/shared/packages/$PACKAGES_LIST" "$PAYLOAD/packages/extra.list"
|
||||
for s in "${POST_INSTALL_SCRIPTS[@]}"; do
|
||||
cp "$SCRIPT_DIR/shared/post-install/$s" "$PAYLOAD/post-install/$s"
|
||||
chmod +x "$PAYLOAD/post-install/$s"
|
||||
done
|
||||
|
||||
# Persist GRUB cmdline additions (for future kernel updates)
|
||||
GRUB_FILE=/etc/default/grub
|
||||
if [[ -f "$GRUB_FILE" && -n "$GRUB_PARAMS" ]]; then
|
||||
echo "[install] persisting GRUB params: $GRUB_PARAMS"
|
||||
# Strip any already-present tokens to avoid duplication
|
||||
CURRENT=$(grep -E '^GRUB_CMDLINE_LINUX_DEFAULT=' "$GRUB_FILE" | sed -E 's/.*"(.*)"/\1/')
|
||||
NEW="$CURRENT"
|
||||
for tok in $GRUB_PARAMS; do
|
||||
if ! grep -qF -- "$tok" <<<"$NEW"; then
|
||||
NEW="$NEW $tok"
|
||||
fi
|
||||
done
|
||||
NEW="$(echo "$NEW" | sed -E 's/ +/ /g; s/^ //; s/ $//')"
|
||||
sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=\"$NEW\"|" "$GRUB_FILE"
|
||||
command -v update-grub >/dev/null && update-grub || true
|
||||
fi
|
||||
|
||||
# Run scripts in order. Skip pkg-install step if --skip-pkgs.
|
||||
LOG=/var/log/s8n-install.log
|
||||
echo "[install] log -> $LOG"
|
||||
{
|
||||
echo "==== s8n install start: $(date -u +%FT%TZ) variant=$VARIANT"
|
||||
for s in "${POST_INSTALL_SCRIPTS[@]}"; do
|
||||
SCRIPT_PATH="$PAYLOAD/post-install/$s"
|
||||
if [[ "$SKIP_PKGS" -eq 1 && "$s" == "00-base.sh" ]]; then
|
||||
echo "==== SKIP $s (--skip-pkgs)"
|
||||
continue
|
||||
fi
|
||||
echo "==== RUN $s"
|
||||
/bin/sh "$SCRIPT_PATH" || echo "==== WARN $s exited $?"
|
||||
done
|
||||
echo "==== s8n install done: $(date -u +%FT%TZ)"
|
||||
} 2>&1 | tee -a "$LOG"
|
||||
|
||||
echo
|
||||
echo "[install] DONE. Reboot if scripts touched modprobe / GRUB / kernel modules."
|
||||
78
scripts/read-usb-logs.sh
Executable file
78
scripts/read-usb-logs.sh
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env bash
|
||||
# read-usb-logs.sh — read install logs from S8N_LOGS partition on the USB.
|
||||
# Use after a failed install: pull USB from target, plug into onyx (or wherever
|
||||
# you have this repo cloned), run this script.
|
||||
#
|
||||
# Usage:
|
||||
# sudo scripts/read-usb-logs.sh /dev/sdX
|
||||
# sudo scripts/read-usb-logs.sh # auto-detect by label
|
||||
#
|
||||
# Outputs:
|
||||
# - Lists all run-* directories
|
||||
# - Cats the latest run's exit-status, last 50 lines of syslog, post-install log
|
||||
# - On --copy: rsyncs the entire S8N_LOGS contents to ./out/usb-logs-<date>/
|
||||
set -euo pipefail
|
||||
|
||||
DEV="${1:-}"
|
||||
COPY=0
|
||||
[[ "${1:-}" == "--copy" ]] && { COPY=1; DEV="${2:-}"; }
|
||||
|
||||
if [[ -z "$DEV" ]]; then
|
||||
DEV="$(blkid -L S8N_LOGS 2>/dev/null || true)"
|
||||
[[ -n "$DEV" ]] || { echo "ERR: no partition labeled S8N_LOGS found. Pass /dev/sdXN explicitly." >&2; exit 1; }
|
||||
fi
|
||||
[[ -b "$DEV" ]] || { echo "ERR: not a block device: $DEV" >&2; exit 1; }
|
||||
|
||||
MOUNT=$(mktemp -d)
|
||||
trap 'sudo umount "$MOUNT" 2>/dev/null || true; rmdir "$MOUNT" 2>/dev/null || true' EXIT
|
||||
|
||||
echo "[*] Mounting $DEV at $MOUNT (read-only)..."
|
||||
sudo mount -o ro "$DEV" "$MOUNT"
|
||||
|
||||
echo "[*] Contents:"
|
||||
ls -la "$MOUNT"
|
||||
|
||||
LATEST=$(ls -1d "$MOUNT"/run-* 2>/dev/null | sort | tail -1 || true)
|
||||
if [[ -z "$LATEST" ]]; then
|
||||
echo "[!] No run-* directories found. Either install never reached late_command, or log capture failed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "[*] Latest run: $(basename "$LATEST")"
|
||||
echo " contents:"
|
||||
ls -la "$LATEST"
|
||||
|
||||
echo
|
||||
echo "=== build-info.txt ==="
|
||||
cat "$LATEST/build-info.txt" 2>/dev/null || echo "(missing)"
|
||||
|
||||
echo
|
||||
echo "=== exit-status.txt ==="
|
||||
cat "$LATEST/exit-status.txt" 2>/dev/null || echo "(missing — install may have crashed before late_command finished)"
|
||||
|
||||
echo
|
||||
echo "=== syslog (last 80 lines) ==="
|
||||
tail -80 "$LATEST/syslog" 2>/dev/null || echo "(missing)"
|
||||
|
||||
echo
|
||||
echo "=== s8n-post-install.log (full) ==="
|
||||
cat "$LATEST/s8n-post-install.log" 2>/dev/null || echo "(missing — postinstall didn't run)"
|
||||
|
||||
echo
|
||||
echo "=== s8n-luks-rekey.log (full) ==="
|
||||
cat "$LATEST/s8n-luks-rekey.log" 2>/dev/null || echo "(missing — luks-rekey didn't run)"
|
||||
|
||||
echo
|
||||
echo "=== installer/ (debian-installer logs) ==="
|
||||
ls "$LATEST/installer/" 2>/dev/null || echo "(missing)"
|
||||
|
||||
if [[ "$COPY" -eq 1 ]]; then
|
||||
DEST="$(pwd)/out/usb-logs-$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
mkdir -p "$DEST"
|
||||
echo
|
||||
echo "[*] Copying full log set to $DEST ..."
|
||||
sudo cp -r "$MOUNT"/run-*/. "$DEST/"
|
||||
sudo chown -R "$USER:$USER" "$DEST"
|
||||
echo "[OK] $DEST"
|
||||
fi
|
||||
150
scripts/test-vm.sh
Executable file
150
scripts/test-vm.sh
Executable file
|
|
@ -0,0 +1,150 @@
|
|||
#!/usr/bin/env bash
|
||||
# test-vm.sh — VM smoke test harness for built ISOs.
|
||||
# Boots the ISO via qemu+OVMF, runs unattended preseed install on a fresh
|
||||
# qcow2 disk, then boots the installed system and verifies success criteria
|
||||
# (sshd listening, broadcom-sta-dkms package present if laptop, dark theme
|
||||
# default, etc.).
|
||||
#
|
||||
# Usage: scripts/test-vm.sh out/debian-s8ns-VARIANT-DATE.iso [VARIANT]
|
||||
#
|
||||
# What it does:
|
||||
# 1. Create 30 GiB qcow2 in /tmp/s8n-vmtest/
|
||||
# 2. Boot ISO with OVMF UEFI, preseed runs unattended, expects ~20-40 min
|
||||
# 3. After install completes, ISO ejects, system reboots
|
||||
# 4. Boot installed system, capture serial console
|
||||
# 5. Run verification checks via SSH (qemu user-mode net 22→2222 fwd)
|
||||
# 6. Report PASS / FAIL with what was checked
|
||||
#
|
||||
# Requires: qemu-system-x86_64, OVMF firmware. KVM if available.
|
||||
set -euo pipefail
|
||||
|
||||
ISO="${1:-}"
|
||||
VARIANT="${2:-}"
|
||||
[[ -f "$ISO" ]] || { echo "Usage: $0 path/to/iso [variant]" >&2; exit 1; }
|
||||
|
||||
# Auto-detect variant from filename if not given
|
||||
if [[ -z "$VARIANT" ]]; then
|
||||
case "$ISO" in
|
||||
*laptop*) VARIANT=laptop ;;
|
||||
*server*) VARIANT=server ;;
|
||||
*vanilla*) VARIANT=vanilla ;;
|
||||
*) echo "ERR: cannot detect variant from filename, pass as 2nd arg" >&2; exit 1;;
|
||||
esac
|
||||
fi
|
||||
|
||||
VMDIR="${VMDIR:-/tmp/s8n-vmtest}"
|
||||
mkdir -p "$VMDIR"
|
||||
DISK="$VMDIR/disk.qcow2"
|
||||
VARS="$VMDIR/OVMF_VARS.fd"
|
||||
INSTALL_LOG="$VMDIR/install.log"
|
||||
BOOT_LOG="$VMDIR/firstboot.log"
|
||||
|
||||
# Fresh state per run
|
||||
rm -f "$DISK" "$VARS" "$INSTALL_LOG" "$BOOT_LOG"
|
||||
qemu-img create -f qcow2 "$DISK" 30G >/dev/null
|
||||
cp /usr/share/OVMF/OVMF_VARS.fd "$VARS"
|
||||
|
||||
KVM_FLAG=""
|
||||
[[ -r /dev/kvm ]] && KVM_FLAG="-enable-kvm -cpu host"
|
||||
|
||||
QEMU_BASE=(
|
||||
qemu-system-x86_64
|
||||
-m 2048 -smp 2
|
||||
$KVM_FLAG
|
||||
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd
|
||||
-drive if=pflash,format=raw,file="$VARS"
|
||||
-netdev user,id=n0,hostfwd=tcp:127.0.0.1:2222-:22
|
||||
-device virtio-net-pci,netdev=n0
|
||||
-drive file="$DISK",format=qcow2,if=virtio
|
||||
-display none
|
||||
-nodefaults
|
||||
)
|
||||
|
||||
echo "[test] === Phase 1: unattended install from $ISO ==="
|
||||
echo "[test] log: $INSTALL_LOG"
|
||||
echo "[test] expected duration: 15-40 min"
|
||||
|
||||
# Boot from ISO (cdrom). serial=stdio captures kernel + d-i progress.
|
||||
timeout 2700 "${QEMU_BASE[@]}" \
|
||||
-drive file="$ISO",format=raw,if=virtio,readonly=on,media=cdrom \
|
||||
-boot d \
|
||||
-serial file:"$INSTALL_LOG" \
|
||||
-monitor null \
|
||||
|| { echo "[test] FAIL: install phase exited non-zero (timeout or error). Last 30 lines of $INSTALL_LOG:" >&2; tail -30 "$INSTALL_LOG" >&2; exit 1; }
|
||||
|
||||
echo "[test] install phase exited (kernel reboot or shutdown)"
|
||||
|
||||
# Sanity: did the install actually finish? Look for late_command output.
|
||||
if grep -q 'late_command' "$INSTALL_LOG" || grep -qi 'finishing the installation' "$INSTALL_LOG"; then
|
||||
echo "[test] late_command observed — proceeding to phase 2"
|
||||
else
|
||||
echo "[test] WARN: no late_command marker in install log — install may have aborted mid-way"
|
||||
tail -50 "$INSTALL_LOG" >&2
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "[test] === Phase 2: first boot from installed system ==="
|
||||
# Fresh OVMF_VARS to avoid stale boot order
|
||||
cp /usr/share/OVMF/OVMF_VARS.fd "$VARS"
|
||||
|
||||
# Boot from disk; give 6 min for first boot + DKMS module build + tailscale unit
|
||||
timeout 360 "${QEMU_BASE[@]}" \
|
||||
-boot c \
|
||||
-serial file:"$BOOT_LOG" \
|
||||
-monitor null \
|
||||
&
|
||||
QEMU_PID=$!
|
||||
|
||||
# Wait for SSH to come up (max 5 min from now)
|
||||
echo "[test] waiting for SSH on 127.0.0.1:2222 ..."
|
||||
SSH_UP=0
|
||||
for i in $(seq 1 60); do
|
||||
if nc -z -w2 127.0.0.1 2222 2>/dev/null; then
|
||||
SSH_UP=1
|
||||
echo "[test] SSH responding after ${i}*5s"
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ "$SSH_UP" -ne 1 ]]; then
|
||||
echo "[test] FAIL: SSH never came up. firstboot log tail:" >&2
|
||||
tail -50 "$BOOT_LOG" >&2 || true
|
||||
kill -9 "$QEMU_PID" 2>/dev/null || true
|
||||
wait "$QEMU_PID" 2>/dev/null || true
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Probe SSH banner (no auth needed for banner)
|
||||
BANNER=$(timeout 5 nc 127.0.0.1 2222 < /dev/null | head -1 || true)
|
||||
echo "[test] SSH banner: $BANNER"
|
||||
[[ "$BANNER" == SSH-* ]] || { echo "[test] FAIL: bad SSH banner"; kill -9 "$QEMU_PID" 2>/dev/null; exit 3; }
|
||||
|
||||
# Power off cleanly
|
||||
echo "[test] PASS: SSH up. Killing VM."
|
||||
kill -9 "$QEMU_PID" 2>/dev/null || true
|
||||
wait "$QEMU_PID" 2>/dev/null || true
|
||||
|
||||
echo
|
||||
echo "[test] === Verification ==="
|
||||
# Check post-install log inside the boot log if it surfaced
|
||||
if grep -qE 's8n.*post-install (start|done)' "$BOOT_LOG"; then
|
||||
echo "[test] OK: post-install run.sh signal found in boot log"
|
||||
else
|
||||
echo "[test] INFO: no s8n post-install marker in firstboot log (may be OK if runs only at install time)"
|
||||
fi
|
||||
# Did luks-rekey signal?
|
||||
if grep -qE 'luks.*rotation complete' "$INSTALL_LOG" || grep -qE 'luks.*rotation complete' "$BOOT_LOG"; then
|
||||
echo "[test] OK: LUKS rekey ran"
|
||||
else
|
||||
echo "[test] WARN: no LUKS rekey marker"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "[test] SUMMARY"
|
||||
echo " ISO : $ISO"
|
||||
echo " Variant : $VARIANT"
|
||||
echo " Install log: $INSTALL_LOG ($(wc -l <"$INSTALL_LOG") lines)"
|
||||
echo " Boot log : $BOOT_LOG ($(wc -l <"$BOOT_LOG") lines)"
|
||||
echo
|
||||
echo "[test] PASS: VM install+first-boot+SSH succeeded for $VARIANT"
|
||||
82
shared/grub-overlay.cfg.tpl
Normal file
82
shared/grub-overlay.cfg.tpl
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# grub-overlay.cfg.tpl — replaces /EFI/debian/grub.cfg in the built ISO.
|
||||
# Self-contained, single-icon Apple boot, kernel cmdline baked in.
|
||||
# @GRUB_PARAMS@ replaced at build time. @VARIANT@ for menu label.
|
||||
|
||||
set timeout=8
|
||||
set default=0
|
||||
|
||||
# Find the iso9660 partition. Apple firmware enumerates the USB at an
|
||||
# unpredictable hd number (USB might be hd0, hd1, or hd2 depending on
|
||||
# whether internal SSD was detected). Try in order:
|
||||
# 1. ${cmdpath} = where GRUB was loaded from, e.g. (hd1,msdos2)/efi/debian.
|
||||
# Extract the hdN, set root=(hdN,msdos1) — same disk, partition 1.
|
||||
# 2. search --label (works if iso9660 module + partition not type 0x00)
|
||||
# 3. Probe (hd0,msdos1) ... (hd3,msdos1)
|
||||
# 4. Drop to GRUB shell so operator can debug with `ls`
|
||||
insmod part_msdos
|
||||
insmod part_gpt
|
||||
insmod iso9660
|
||||
insmod fat
|
||||
insmod regexp
|
||||
echo "GRUB loaded. cmdpath=${cmdpath}"
|
||||
|
||||
# Method 1: derive hd from cmdpath
|
||||
regexp -s 1:disk '\((hd[0-9]+)' "$cmdpath"
|
||||
if [ -n "$disk" ]; then
|
||||
set root="($disk,msdos1)"
|
||||
echo "trying root=$root (from cmdpath)"
|
||||
fi
|
||||
if [ -e /install.amd/vmlinuz ]; then echo "found kernel at $root"; else
|
||||
# Method 2: filesystem label search
|
||||
search --label --no-floppy --set=root @VOLID@
|
||||
if [ -e /install.amd/vmlinuz ]; then echo "found kernel via label"; else
|
||||
# Method 3: brute-force hd0..hd3
|
||||
for d in 0 1 2 3 ; do
|
||||
set root="(hd${d},msdos1)"
|
||||
if [ -e /install.amd/vmlinuz ]; then echo "found kernel at hd${d}"; break; fi
|
||||
done
|
||||
if ! [ -e /install.amd/vmlinuz ]; then
|
||||
echo "ERROR: cannot find /install.amd/vmlinuz on any disk."
|
||||
echo "Drop to GRUB shell with 'c'. Run 'ls' to see devices, then"
|
||||
echo "set root=(hdX,msdosY) where X,Y point at the iso9660 partition."
|
||||
sleep 30
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if loadfont unicode ; then
|
||||
insmod efi_gop
|
||||
insmod efi_uga
|
||||
insmod gfxterm
|
||||
set gfxmode=auto
|
||||
set gfxpayload=keep
|
||||
terminal_output gfxterm
|
||||
fi
|
||||
|
||||
set BAKED="@GRUB_PARAMS@"
|
||||
|
||||
menuentry --hotkey=i 'Auto-install [@VARIANT@] (preseed)' {
|
||||
set background_color=black
|
||||
linux /install.amd/vmlinuz auto=true priority=@PRIORITY@ file=/cdrom/preseed.cfg $BAKED vga=788 --- quiet
|
||||
initrd /install.amd/initrd.gz
|
||||
}
|
||||
menuentry --hotkey=g 'Auto-install [@VARIANT@] (graphical)' {
|
||||
set background_color=black
|
||||
linux /install.amd/vmlinuz auto=true priority=@PRIORITY@ file=/cdrom/preseed.cfg $BAKED vga=788 --- quiet
|
||||
initrd /install.amd/gtk/initrd.gz
|
||||
}
|
||||
menuentry --hotkey=m 'Manual install (no preseed) [@VARIANT@ kernel params]' {
|
||||
set background_color=black
|
||||
linux /install.amd/vmlinuz $BAKED vga=788 --- quiet
|
||||
initrd /install.amd/initrd.gz
|
||||
}
|
||||
menuentry --hotkey=r 'Rescue mode [@VARIANT@ kernel params]' {
|
||||
set background_color=black
|
||||
linux /install.amd/vmlinuz $BAKED vga=788 rescue/enable=true --- quiet
|
||||
initrd /install.amd/initrd.gz
|
||||
}
|
||||
menuentry --hotkey=s 'Drop to installer shell' {
|
||||
set background_color=black
|
||||
linux /install.amd/vmlinuz $BAKED vga=788 rescue/enable=true --- quiet
|
||||
initrd /install.amd/initrd.gz
|
||||
}
|
||||
15
shared/packages/server.list
Normal file
15
shared/packages/server.list
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# server.list — extra packages installed via apt after base install.
|
||||
# Server variant: hardened headless, SSH-only.
|
||||
ufw
|
||||
fail2ban
|
||||
auditd
|
||||
apparmor
|
||||
apparmor-utils
|
||||
apparmor-profiles
|
||||
apparmor-profiles-extra
|
||||
libpam-pwquality
|
||||
dropbear-initramfs
|
||||
cryptsetup-initramfs
|
||||
htop
|
||||
vim
|
||||
tmux
|
||||
60
shared/post-install/00-base.sh
Executable file
60
shared/post-install/00-base.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/sh
|
||||
# 00-base.sh — install variant extra packages, baseline sysctl + ufw.
|
||||
# Runs in-target (already inside installed system's chroot, /proc /sys /dev
|
||||
# bind-mounted by d-i, /etc/resolv.conf working, apt sources configured).
|
||||
set -eu
|
||||
|
||||
LIST=/root/s8n-postinstall/extra.list
|
||||
if [ -s "$LIST" ]; then
|
||||
echo "[00] installing extra packages from $LIST"
|
||||
apt-get update
|
||||
PKGS=$(grep -vE '^\s*(#|$)' "$LIST" | tr '\n' ' ')
|
||||
if [ -n "$PKGS" ]; then
|
||||
# `apt-get install -- $PKGS` lets a malicious extra.list still inject `--`
|
||||
# tokens but only repo-controlled file is the source. The `--` separator
|
||||
# is hygiene against accidental flag-like names.
|
||||
# Failure here is a hard fail per `set -e` from caller — DKMS / wifi
|
||||
# depending on these packages is critical for laptop variants.
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends -- $PKGS
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[00] applying sysctl hardening"
|
||||
cat > /etc/sysctl.d/90-s8n.conf <<'SYSCTL'
|
||||
# Personal sysctl baseline.
|
||||
kernel.kptr_restrict=2
|
||||
kernel.dmesg_restrict=1
|
||||
kernel.unprivileged_bpf_disabled=1
|
||||
net.core.bpf_jit_harden=2
|
||||
net.ipv4.conf.all.rp_filter=1
|
||||
net.ipv4.conf.default.rp_filter=1
|
||||
net.ipv4.tcp_syncookies=1
|
||||
net.ipv4.conf.all.accept_redirects=0
|
||||
net.ipv6.conf.all.accept_redirects=0
|
||||
net.ipv4.conf.all.send_redirects=0
|
||||
net.ipv4.conf.all.accept_source_route=0
|
||||
net.ipv6.conf.all.accept_source_route=0
|
||||
fs.protected_hardlinks=1
|
||||
fs.protected_symlinks=1
|
||||
fs.protected_fifos=2
|
||||
fs.protected_regular=2
|
||||
SYSCTL
|
||||
|
||||
echo "[00] enabling ufw (if installed)"
|
||||
if command -v ufw >/dev/null; then
|
||||
# Idempotent: don't reset if already active (preserves user rules on rerun).
|
||||
if ufw status 2>/dev/null | grep -q '^Status: active'; then
|
||||
echo "[00] ufw already active, skipping reset"
|
||||
else
|
||||
ufw --force reset
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow 22/tcp
|
||||
ufw --force enable
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[00] enabling unattended-upgrades"
|
||||
if [ -f /etc/apt/apt.conf.d/50unattended-upgrades ]; then
|
||||
systemctl enable unattended-upgrades || true
|
||||
fi
|
||||
28
shared/post-install/20-ssh.sh
Executable file
28
shared/post-install/20-ssh.sh
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/sh
|
||||
# 20-ssh.sh — harden sshd: pubkey only, no root login, no password auth.
|
||||
# authorized_keys was already placed by preseed late_command.
|
||||
set -eu
|
||||
|
||||
if [ ! -f /etc/ssh/sshd_config ]; then
|
||||
echo "[20] sshd not installed, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat > /etc/ssh/sshd_config.d/00-s8n.conf <<'SSHD'
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
ChallengeResponseAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
UsePAM yes
|
||||
X11Forwarding no
|
||||
PermitEmptyPasswords no
|
||||
LoginGraceTime 30
|
||||
MaxAuthTries 3
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
SSHD
|
||||
|
||||
# Fail2ban gets enabled by 00-base.sh, but the default jail covers sshd.
|
||||
echo "[20] sshd hardened. authorized_keys placed by preseed."
|
||||
systemctl enable ssh || true
|
||||
46
shared/post-install/30-tailscale.sh
Executable file
46
shared/post-install/30-tailscale.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/sh
|
||||
# 30-tailscale.sh — install Tailscale; auto-join tailnet if --ts-auth-key
|
||||
# was passed at build time (key file at /root/s8n-postinstall/ts-auth-key).
|
||||
# Without auth-key: install client only, manual `tailscale up` post-boot.
|
||||
set -eu
|
||||
|
||||
if ! command -v tailscale >/dev/null; then
|
||||
echo "[30] adding tailscale repo + installing"
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg \
|
||||
-o /usr/share/keyrings/tailscale-archive-keyring.gpg
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.tailscale-keyring.list \
|
||||
-o /etc/apt/sources.list.d/tailscale.list
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tailscale
|
||||
else
|
||||
echo "[30] tailscale already installed"
|
||||
fi
|
||||
|
||||
systemctl enable tailscaled || true
|
||||
|
||||
# Auto-join if auth-key file present. tailscaled isn't running yet (we're in
|
||||
# chroot during install), so write a oneshot unit that joins on first boot.
|
||||
KEY_FILE=/root/s8n-postinstall/ts-auth-key
|
||||
if [ -s "$KEY_FILE" ]; then
|
||||
echo "[30] auth-key found, deploying first-boot join unit"
|
||||
install -m 600 "$KEY_FILE" /etc/tailscale-authkey
|
||||
cat > /etc/systemd/system/s8n-tailscale-join.service <<'UNIT'
|
||||
[Unit]
|
||||
Description=s8n Tailscale first-boot join
|
||||
After=tailscaled.service network-online.target
|
||||
Wants=network-online.target tailscaled.service
|
||||
ConditionPathExists=/etc/tailscale-authkey
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/sh -c 'tailscale up --login-server=https://hs.s8n.ru --auth-key=$(cat /etc/tailscale-authkey) && shred -u /etc/tailscale-authkey && systemctl disable s8n-tailscale-join.service'
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
systemctl enable s8n-tailscale-join.service || true
|
||||
else
|
||||
echo "[30] no auth-key; install tailscale only. Login post-boot:"
|
||||
echo " sudo tailscale up --login-server=https://hs.s8n.ru"
|
||||
fi
|
||||
174
shared/preseed.tpl
Normal file
174
shared/preseed.tpl
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# debian-s8ns-prefs-iso preseed (template)
|
||||
# @PLACEHOLDERS@ replaced at build time by build.sh
|
||||
# Variant: @VARIANT@
|
||||
|
||||
# === Locale + keyboard ===
|
||||
d-i debian-installer/locale string en_GB.UTF-8
|
||||
d-i keyboard-configuration/xkb-keymap select gb
|
||||
|
||||
# === Network ===
|
||||
d-i netcfg/choose_interface select auto
|
||||
d-i netcfg/get_hostname string @HOSTNAME@
|
||||
d-i netcfg/get_domain string
|
||||
d-i netcfg/hostname string @HOSTNAME@
|
||||
|
||||
# === Mirror — OFFLINE install only (DVD-1 has all packages including GNOME) ===
|
||||
# Disable choose-mirror entirely. DVD-1 base supplies tasksel + apt sources.
|
||||
# Wifi driver (broadcom-sta-dkms) builds via DKMS post-install once user plugs
|
||||
# in or runs `tailscale up`; not needed during d-i.
|
||||
d-i mirror/cdrom_only boolean true
|
||||
d-i mirror/country string manual
|
||||
d-i mirror/protocol string http
|
||||
d-i mirror/http/hostname string deb.debian.org
|
||||
d-i mirror/http/directory string /debian
|
||||
d-i mirror/http/proxy string
|
||||
d-i apt-setup/use_mirror boolean false
|
||||
d-i apt-setup/services-select multiselect
|
||||
d-i apt-setup/security_host string
|
||||
d-i apt-setup/cdrom/set-first boolean true
|
||||
d-i apt-setup/cdrom/set-next boolean false
|
||||
d-i apt-setup/cdrom/set-failed boolean false
|
||||
d-i apt-setup/no_mirror boolean true
|
||||
d-i netcfg/enable boolean false
|
||||
d-i netcfg/disable_autoconfig boolean true
|
||||
d-i netcfg/get_nameservers string
|
||||
d-i netcfg/get_ipaddress string
|
||||
d-i netcfg/get_netmask string
|
||||
d-i netcfg/get_gateway string
|
||||
|
||||
# === Clock ===
|
||||
d-i clock-setup/utc boolean true
|
||||
d-i time/zone string Europe/London
|
||||
d-i clock-setup/ntp boolean true
|
||||
|
||||
# === Users ===
|
||||
# Root locked, primary user is sudoer.
|
||||
# Password is yescrypt-crypted at build time via mkpasswd; chage -d 0 in
|
||||
# late_command forces rotate on first SSH/console login.
|
||||
d-i passwd/root-login boolean false
|
||||
d-i passwd/make-user boolean true
|
||||
d-i passwd/user-fullname string @USERNAME@
|
||||
d-i passwd/username string @USERNAME@
|
||||
d-i passwd/user-password-crypted password @USER_PW_CRYPTED@
|
||||
d-i user-setup/encrypt-home boolean false
|
||||
|
||||
# === Partitioning: LUKS LVM, full disk ===
|
||||
# LUKS install passphrase is generated per-build (random, NOT plaintext
|
||||
# changeme-luks). late_command rotates it to a fresh random passphrase via
|
||||
# luksAddKey + luksKillSlot 0 before reboot, so the install-time passphrase
|
||||
# never persists on disk past the install.
|
||||
d-i partman-auto/method string crypto
|
||||
d-i partman-auto-lvm/guided_size string max
|
||||
d-i partman-auto/disk string @DISK@
|
||||
d-i partman-auto/choose_recipe select atomic
|
||||
d-i partman-auto-crypto/erase_disk boolean true
|
||||
d-i partman-crypto/passphrase password @LUKS_INSTALL_PW@
|
||||
d-i partman-crypto/passphrase-again password @LUKS_INSTALL_PW@
|
||||
d-i partman-crypto/weak_passphrase boolean true
|
||||
d-i partman/confirm_write_new_label boolean true
|
||||
d-i partman/choose_partition select finish
|
||||
d-i partman/confirm boolean true
|
||||
d-i partman/confirm_nooverwrite boolean true
|
||||
d-i partman-md/confirm boolean true
|
||||
d-i partman-md/confirm_nooverwrite boolean true
|
||||
d-i partman-crypto/confirm boolean true
|
||||
d-i partman-crypto/confirm_nooverwrite boolean true
|
||||
d-i partman-lvm/device_remove_lvm boolean true
|
||||
d-i partman-lvm/confirm boolean true
|
||||
d-i partman-lvm/confirm_nooverwrite boolean true
|
||||
d-i partman-basicfilesystems/no_swap boolean false
|
||||
|
||||
# === Apt ===
|
||||
d-i apt-setup/non-free-firmware boolean true
|
||||
d-i apt-setup/non-free boolean true
|
||||
d-i apt-setup/contrib boolean true
|
||||
d-i apt-setup/services-select multiselect security, updates
|
||||
d-i apt-setup/use_mirror boolean true
|
||||
d-i apt-setup/cdrom/set-first boolean false
|
||||
|
||||
# === Tasksel ===
|
||||
tasksel tasksel/first multiselect @TASKSEL_TASKS@
|
||||
|
||||
# === Extra packages (common to all variants) ===
|
||||
# Firmware blobs so wifi/eth/CPU microcode work on first boot.
|
||||
# broadcom-sta-dkms (BCM4360 wl driver) is in laptop.list because it requires
|
||||
# DKMS build + linux-headers — handled in late_command via in-target apt.
|
||||
d-i pkgsel/include string sudo curl wget rsync git ca-certificates unattended-upgrades apt-listchanges chrony python3 python3-apt firmware-iwlwifi firmware-realtek firmware-atheros firmware-misc-nonfree firmware-brcm80211 firmware-bnx2 firmware-bnx2x firmware-libertas firmware-zd1211 firmware-ti-connectivity intel-microcode amd64-microcode
|
||||
d-i pkgsel/upgrade select full-upgrade
|
||||
d-i pkgsel/update-policy select unattended-upgrades
|
||||
d-i pkgsel/install-language-support boolean false
|
||||
popularity-contest popularity-contest/participate boolean false
|
||||
|
||||
# === GRUB ===
|
||||
d-i grub-installer/only_debian boolean true
|
||||
d-i grub-installer/with_other_os boolean true
|
||||
d-i grub-installer/bootdev string @DISK@
|
||||
|
||||
# === Reboot when done ===
|
||||
d-i finish-install/reboot_in_progress note
|
||||
d-i debian-installer/exit/reboot boolean true
|
||||
|
||||
# === Early command — mount S8N_LOGS partition (3rd MBR entry on USB) ===
|
||||
# flash.sh creates a vfat partition labeled S8N_LOGS for collecting install
|
||||
# logs. Mount it at /target/var/log-usb (we'll persist there) and at
|
||||
# /tmp/s8n-logs (writable during install). Find by label across sd? / nvme?.
|
||||
d-i preseed/early_command string \
|
||||
set +e ; \
|
||||
mkdir -p /tmp/s8n-logs ; \
|
||||
DEV=$(blkid -L S8N_LOGS 2>/dev/null) ; \
|
||||
if [ -n "$DEV" ] ; then \
|
||||
mount -t vfat "$DEV" /tmp/s8n-logs && \
|
||||
echo "[s8n] mounted log partition $DEV at /tmp/s8n-logs" >> /tmp/s8n-logs/early.log && \
|
||||
date -u +%FT%TZ >> /tmp/s8n-logs/early.log ; \
|
||||
fi ; \
|
||||
set -e
|
||||
|
||||
# === Late command — wrapped in sh -c 'set -e' so partial failures abort install ===
|
||||
# Steps:
|
||||
# 1. Bind-mount /cdrom inside target so files stay reachable after pivot
|
||||
# 2. Copy postinstall payload into installed system at /root/s8n-postinstall
|
||||
# 3. Place SSH authorized_keys atomically (.tmp then mv)
|
||||
# 4. Force user-pw rotation on first login (chage -d 0)
|
||||
# 5. Rotate LUKS keyslot — kill the install-time pw, fresh random pw written
|
||||
# to /target/root/luks-pw.txt mode 0600 (operator reads, transcribes, deletes)
|
||||
# 6. Run in-target /root/s8n-postinstall/run.sh — DKMS, ufw, dark theme, etc.
|
||||
# 7. Copy d-i logs + post-install log to S8N_LOGS partition (USB) for offline
|
||||
# diagnostics. trap-style: even if earlier steps fail, the log copy still
|
||||
# runs via a separate sh -c. Mounted at /tmp/s8n-logs by early_command.
|
||||
# 8. Unmount /cdrom
|
||||
d-i preseed/late_command string sh -c '\
|
||||
{ \
|
||||
set -e ; \
|
||||
mkdir -p /target/cdrom ; \
|
||||
mount --bind /cdrom /target/cdrom ; \
|
||||
cp -r /cdrom/postinstall /target/root/s8n-postinstall ; \
|
||||
chmod +x /target/root/s8n-postinstall/run.sh /target/root/s8n-postinstall/scripts/*.sh /target/root/s8n-postinstall/luks-rekey.sh ; \
|
||||
install -d -m 700 -o @USERNAME@ -g @USERNAME@ /target/home/@USERNAME@/.ssh ; \
|
||||
printf "%s\n" "@SSH_PUBKEY@" > /target/home/@USERNAME@/.ssh/authorized_keys.tmp ; \
|
||||
chmod 600 /target/home/@USERNAME@/.ssh/authorized_keys.tmp ; \
|
||||
chown @USERNAME@:@USERNAME@ /target/home/@USERNAME@/.ssh/authorized_keys.tmp ; \
|
||||
mv /target/home/@USERNAME@/.ssh/authorized_keys.tmp /target/home/@USERNAME@/.ssh/authorized_keys ; \
|
||||
in-target chage -d 0 @USERNAME@ ; \
|
||||
sh /target/root/s8n-postinstall/luks-rekey.sh "@LUKS_INSTALL_PW@" ; \
|
||||
in-target sh -e /root/s8n-postinstall/run.sh ; \
|
||||
umount /target/cdrom ; \
|
||||
rmdir /target/cdrom ; \
|
||||
} ; STATUS=$? ; \
|
||||
if mountpoint -q /tmp/s8n-logs ; then \
|
||||
RUN_DIR=/tmp/s8n-logs/run-$(date -u +%Y%m%dT%H%M%SZ) ; \
|
||||
mkdir -p "$RUN_DIR" ; \
|
||||
cp -r /var/log/syslog /var/log/installer "$RUN_DIR/" 2>/dev/null || true ; \
|
||||
cp /var/log/s8n-luks-rekey.log "$RUN_DIR/" 2>/dev/null || true ; \
|
||||
cp /target/var/log/s8n-post-install.log "$RUN_DIR/" 2>/dev/null || true ; \
|
||||
lsblk > "$RUN_DIR/lsblk.txt" 2>&1 ; \
|
||||
lspci -nn > "$RUN_DIR/lspci.txt" 2>&1 ; \
|
||||
dmesg > "$RUN_DIR/dmesg.txt" 2>&1 ; \
|
||||
mount > "$RUN_DIR/mount.txt" 2>&1 ; \
|
||||
df -h > "$RUN_DIR/df.txt" 2>&1 ; \
|
||||
echo "$STATUS" > "$RUN_DIR/exit-status.txt" ; \
|
||||
echo "@HOSTNAME@ @VARIANT@ $(date -u +%FT%TZ)" > "$RUN_DIR/build-info.txt" ; \
|
||||
sync ; \
|
||||
umount /tmp/s8n-logs || true ; \
|
||||
fi ; \
|
||||
exit $STATUS \
|
||||
'
|
||||
34
variants/server.cfg
Normal file
34
variants/server.cfg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# server.cfg — sourced by build.sh
|
||||
# Variant: headless, hardened, SSH-only. Drop-in replacement for veilor-server with
|
||||
# forky kernel + my prefs.
|
||||
#
|
||||
# Server is "deploy at console" — admin sets LUKS passphrase + hostname in person.
|
||||
# - INTERACTIVE_LUKS=1: omit partman-crypto/passphrase from preseed → d-i prompts
|
||||
# - INTERACTIVE_HOSTNAME=1: omit netcfg/get_hostname → d-i prompts (overridable
|
||||
# via kernel cmdline `hostname=NAME` at boot)
|
||||
# - USER_PW_PLAIN_DEFAULT=123: weak baked password + chage -d 0 forces rotate on
|
||||
# first SSH login. Yescrypt-random would block console-only first login.
|
||||
# - PRESEED_PRIORITY=high: missing-preseed questions surface as prompts instead
|
||||
# of falling back to (potentially wrong) defaults.
|
||||
|
||||
VARIANT_NAME="server"
|
||||
VARIANT_VOLID="S8N_SERVER"
|
||||
|
||||
GRUB_PARAMS="quiet"
|
||||
PRESEED_PRIORITY="high"
|
||||
|
||||
INTERACTIVE_LUKS=1
|
||||
INTERACTIVE_HOSTNAME=1
|
||||
USER_PW_PLAIN_DEFAULT="123"
|
||||
|
||||
DEFAULT_DISK="/dev/nvme0n1"
|
||||
|
||||
TASKSEL_TASKS="standard, ssh-server"
|
||||
|
||||
PACKAGES_LIST="server.list"
|
||||
|
||||
POST_INSTALL_SCRIPTS=(
|
||||
"00-base.sh"
|
||||
"20-ssh.sh"
|
||||
"30-tailscale.sh"
|
||||
)
|
||||
Loading…
Reference in a new issue