SPS · log · files · refs · tags · releases · people · languages · GitHub · commit
243f4d5
setup: install Splux without killing live init
Parents
Stat
DESIGN.md | 18 +- MANIFESTO.md | 9 +- Makefile | 58 +++- PACKAGE-MAINTAINER.md | 13 +- README.md | 69 +++- bin/mkiso | 244 ++++++++++++-- bin/mkpkg | 4 +- bin/setup | 535 ++++++++++++++++++++++++++++-- bin/sget | 16 + lib/deps.awk | 42 ++- lib/index.awk | 8 +- lib/mkiso/initrd-init | 106 +++--- lib/mkiso/inittab | 12 + lib/mkiso/inittab-plasma | 8 + lib/mkiso/issue | 2 +- lib/mkiso/live-init | 83 ++--- lib/mkiso/live-plasma | 38 +++ lib/mkiso/live-rc | 90 +++++ lib/mkiso/motd | 29 +- lib/mkiso/plasma-bins | 33 ++ lib/mkiso/plasma-motd | 20 ++ lib/mkiso/plasma-trees | 39 +++ lib/mkiso/seed-bins | 4 + lib/mkiso/systemd/sps-live-plasma.service | 18 + lib/mkiso/systemd/sps-live-rc.service | 15 + lib/mkiso/systemd/sps-live-serial.service | 19 ++ lib/recipe.awk | 8 +- lib/relations.awk | 2 +- lib/repository.awk | 3 +- lib/setup/extras | 18 + lib/setup/hwdetect.sh | 103 ++++++ lib/setup/partition.sh | 282 ++++++++++++++++ lib/setup/sets/drivers.set | 9 + lib/setup/sets/firmware.set | 2 +- man/mkiso.1 | 33 +- man/setup.1 | 33 +- tests/run | 6 + tests/test_conflicts.sh | 66 ++++ tests/test_install.sh | 5 +- tests/test_mkiso.sh | 19 ++ tests/test_recipe_contract.sh | 2 +- tests/test_repository.sh | 2 +- tests/test_setup.sh | 12 +- 43 files changed, 1907 insertions(+), 230 deletions(-)
Diff
diff --git a/DESIGN.md b/DESIGN.md
index 560b6d0..f9e92b5 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -1,8 +1,10 @@
# SPS 1.0 formats and behavior
This file documents the interfaces that packages and distro tooling can depend
-on in SPS Package metadata,
-indexes, and installed records are meant to be inspectable and scriptable.
+on in SPS 1.0.
+
+Package metadata, indexes, and installed records are meant to be inspectable
+and scriptable.
The current package format is `format 1`. If a later SPS release needs an
incompatible package layout, it must use a new format number.
@@ -33,8 +35,15 @@ hash
depend
builddep
optional
+conflict
```
+`conflict` names packages that must not be installed together. `sget install`
+refuses a transaction that contains both a package and one of its conflicts,
+and also refuses to install a package that conflicts with something already
+in the database. `src update` still validates the whole graph; two packages
+that conflict may both exist in the repository.
+
`description` is a single-value field.
Build phases are repeatable command fields:
@@ -84,9 +93,12 @@ Indexes live under `$SPS_CACHE/indexes`. Each package record is tab-separated
and contains:
```text
-name version release arch depends builddeps optional repo priority recipe_path description definition_sha256
+name version release arch depends builddeps optional repo priority recipe_path description definition_sha256 conflicts
```
+`conflicts` is empty when the recipe has no `conflict` records. Older 12-field
+index files are still accepted; missing conflicts are treated as none.
+
Dependency lists are comma-separated. Field values may not contain tabs or
newlines. The definition digest covers the recipe content and the relative
paths, contents, and copied modes of its `files/`, `patches/`, and `hooks/`
diff --git a/MANIFESTO.md b/MANIFESTO.md
index 23e1cd7..61b9132 100644
--- a/MANIFESTO.md
+++ b/MANIFESTO.md
@@ -4,7 +4,10 @@
SPS is a source package system for Unix-like operating systems. It is meant for
small independent distributions and for administrators who want to understand
-how packages get from a recipe into the filesystem.
+how packages get from a recipe into the filesystem. SPS is not itself a
+distribution. **SPS Linux** (informally Splux) is one distro that uses it;
+Linux From Scratch and other trees can use the same tools without the Splux
+installer or live ISOs.
It is built from a few separate tools, plain text metadata, POSIX shell, POSIX
AWK, and standard Unix utilities. There is no package daemon, SQL database,
@@ -34,8 +37,8 @@ pkdel low-level package removal
pkstat installed package queries
pkcheck installed-state verification
pkmark explicit/dependency package marking
-setup dialog-based system installer
-mkiso live TTY ISO builder
+setup SPS Linux (Splux) dialog installer; optional, distro-specific
+mkiso SPS Linux live ISO builder; optional, distro-specific
```
Each tool has a boundary.
diff --git a/Makefile b/Makefile
index 58d79f1..9e1785a 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ PROGRAMS = src sget mkpkg pkin pkdel pkstat pkcheck pkmark setup mkiso
AWK_LIBS = common.awk config.awk db.awk deps.awk index.awk package.awk recipe.awk repository.awk relations.awk version.awk
.PHONY: all check lint check-version source-archive release-check \
- install install-config clean
+ install install-config clean iso iso-slim iso-plasma
all:
@printf '%s\n' "SPS is interpreted; run 'make check' or 'make install'."
@@ -25,6 +25,15 @@ lint:
@set -e; for library in lib/*.sh; do \
$(POSIX_SHELL) -n $$library; \
done
+ @set -e; for library in lib/setup/*.sh; do \
+ [ -f $$library ] || continue; \
+ $(POSIX_SHELL) -n $$library; \
+ done
+ @set -e; for script in lib/mkiso/initrd-init lib/mkiso/live-rc \
+ lib/mkiso/live-plasma lib/mkiso/live-init; do \
+ [ -f $$script ] || continue; \
+ $(POSIX_SHELL) -n $$script; \
+ done
@# Pure library modules can be parsed without an action or fixture. The
@# executable AWK modules validate their input in END rules, so the test
@# suite below is their syntax + behavior check rather than invoking them
@@ -163,12 +172,22 @@ install:
@set -e; for f in lib/mkiso/*; do \
[ -f "$$f" ] || continue; \
case $$f in \
- */live-init|*/initrd-init) \
+ */live-init|*/initrd-init|*/live-rc|*/live-plasma) \
install -m 0755 $$f "$(DESTDIR)$(LIBDIR)/mkiso/" ;; \
*) \
install -m 0644 $$f "$(DESTDIR)$(LIBDIR)/mkiso/" ;; \
esac; \
done
+ install -d "$(DESTDIR)$(LIBDIR)/mkiso/systemd"
+ @set -e; for f in lib/mkiso/systemd/*; do \
+ [ -f "$$f" ] || continue; \
+ install -m 0644 $$f "$(DESTDIR)$(LIBDIR)/mkiso/systemd/"; \
+ done
+ install -d "$(DESTDIR)$(LIBDIR)/setup"
+ @set -e; for f in lib/setup/*.sh; do \
+ [ -f "$$f" ] || continue; \
+ install -m 0644 $$f "$(DESTDIR)$(LIBDIR)/setup/"; \
+ done
# Configuration is separate so installation never overwrites administrator
# policy. Existing files are deliberately left untouched.
@@ -179,6 +198,41 @@ install-config:
@test -e "$(DESTDIR)$(SYSCONFDIR)/repos.conf" || \
install -m 0644 examples/repos.conf "$(DESTDIR)$(SYSCONFDIR)/repos.conf"
+# SPS Linux (Splux) live images. busybox must be a static binary; the host
+# copy is usually dynamically linked and will not survive in an initramfs.
+ISO_BUSYBOX ?= /tmp/sps-wave/busybox.static
+ISO_CORE ?= /usr/src/sps/core
+ISO_EXTRA ?= /usr/src/sps/extra
+ISO_DIR ?= dist
+
+iso:
+ @test -x "$(ISO_BUSYBOX)" || { \
+ printf '%s\n' "iso: static busybox not found: $(ISO_BUSYBOX)" >&2; \
+ exit 1; \
+ }
+ mkdir -p "$(ISO_DIR)"
+ bin/mkiso --output "$(ISO_DIR)/sps-live.iso" --init systemd \
+ --busybox "$(ISO_BUSYBOX)" --core "$(ISO_CORE)" --extra "$(ISO_EXTRA)"
+
+iso-slim:
+ @test -x "$(ISO_BUSYBOX)" || { \
+ printf '%s\n' "iso-slim: static busybox not found: $(ISO_BUSYBOX)" >&2; \
+ exit 1; \
+ }
+ mkdir -p "$(ISO_DIR)"
+ bin/mkiso --output "$(ISO_DIR)/sps-live-slim.iso" --init systemd \
+ --no-seed --no-modules --busybox "$(ISO_BUSYBOX)"
+
+iso-plasma:
+ @test -x "$(ISO_BUSYBOX)" || { \
+ printf '%s\n' "iso-plasma: static busybox not found: $(ISO_BUSYBOX)" >&2; \
+ exit 1; \
+ }
+ mkdir -p "$(ISO_DIR)"
+ bin/mkiso --output "$(ISO_DIR)/sps-live-plasma.iso" \
+ --session plasma --init systemd \
+ --busybox "$(ISO_BUSYBOX)" --core "$(ISO_CORE)" --extra "$(ISO_EXTRA)"
+
clean:
@find . -type f \( -name '*.pkg.tar' -o -name '*.pkg.tar.gz' -o \
-name '*.pkg.tar.xz' -o -name '*.pkg.tar.zst' \) -exec rm -f {} \;
diff --git a/PACKAGE-MAINTAINER.md b/PACKAGE-MAINTAINER.md
index e826418..4bbd677 100644
--- a/PACKAGE-MAINTAINER.md
+++ b/PACKAGE-MAINTAINER.md
@@ -4,6 +4,10 @@ An SPS package directory contains a line-oriented file named `recipe`. It may
also contain `files/`, `patches/`, and `hooks/`. Recipes are data, not shell
files: repository indexing parses them without executing build commands.
+The same recipe format works anywhere SPS runs, including Linux From Scratch
+and custom roots. `linux-desktop`, Plasma profiles, and `setup` defaults are
+**SPS Linux** (Splux) distro pieces; an LFS project can ignore them.
+
## Recipe records
Each logical line is a key, whitespace, and a non-empty value. Blank lines and
@@ -24,15 +28,20 @@ The complete key set is:
| `depend` | repeatable | Runtime dependency |
| `builddep` | repeatable | Build dependency |
| `optional` | repeatable | Informational optional dependency |
+| `conflict` | repeatable | Package names that cannot be co-installed |
| `prepare` | repeatable | Source preparation command |
| `configure` | repeatable | Configuration command |
| `build` | repeatable | Build command |
| `install` | one or more | Staged installation command |
Unknown keys are errors. SPS 1.0 has no recipe keys for homepage, license,
-maintainer, conflicts, replacements, provides, or package options. Keep that
+maintainer, replacements, provides, or package options. Keep that
information in repository documentation when needed; do not invent fields.
+`conflict` is supported: `sget` will not install a package next to a named
+conflict. Use it for init systems (`systemd` vs `openrc`) and for udev
+implementations (`systemd` vs `eudev`).
+
`name`, `version`, `release`, and at least one `install` record are required.
The install phase must produce a non-empty package. `name` must begin with an
alphanumeric character and may then contain letters, digits, `+`, `_`, `.`, or
@@ -71,7 +80,7 @@ not independently validate every tar link target before extraction.
## Dependencies
-Use the singular keys `depend`, `builddep`, and `optional`. A record may contain
+Use the singular keys `depend`, `builddep`, `optional`, and `conflict`. A record may contain
one or more whitespace- or comma-separated specifications. Version constraints
have no spaces around the operator:
diff --git a/README.md b/README.md
index 9ce792e..13c762c 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,26 @@ If you REALLY want the project rationale and the boundaries of the design, read
[MANIFESTO.md](MANIFESTO.md). The exact 1.0 file formats and behavior are in
[DESIGN.md](DESIGN.md).
+## SPS is not the distro
+
+SPS is the source package system. It is not locked to one operating system.
+You can run `src`, `sget`, `mkpkg`, `pkin`, and the rest on Linux From Scratch,
+on a custom chroot, or next to another distro's userland. Recipes are plain
+text; an LFS tree can consume sps-core and sps-extra the same way SPS Linux
+does.
+
+**SPS Linux** is a Linux distribution that *uses* SPS. Informally it is called
+**Splux**. Official installer flow, live ISOs, Plasma profiles, and the
+`linux-desktop` kernel are SPS Linux pieces. They live in this tree and in
+the core/extra collections because that is how Splux is built and installed.
+
+If you take SPS into an LFS (or any other) project, do not assume `setup`
+will do the right thing for you. The installer partitions disks, writes
+fstab, chooses systemd or OpenRC, copies a Splux hostname, and installs
+distro profiles. Your LFS book already has its own disk, boot, and
+configuration story. Use `sget` and `mkpkg` there; skip `setup` and `mkiso`
+unless you actually want SPS Linux on that machine.
+
## Tools
SPS is a set of tools rather than one large front end:
@@ -24,8 +44,10 @@ SPS is a set of tools rather than one large front end:
- `pkstat` reads the installed package database.
- `pkcheck` checks installed files and database consistency.
- `pkmark` changes whether an installed package is explicit or dependency-only.
-- `setup` is a dialog(1) system installer (Slackware/FreeBSD style).
-- `mkiso` builds a live TTY ISO you can flash and boot into `setup`.
+- `setup` is the SPS Linux (Splux) dialog(1) installer (Slackware/FreeBSD
+ style). Optional; other projects using SPS do not need it.
+- `mkiso` builds an SPS Linux live ISO (systemd by default; busybox init if
+ systemd is not on the build host) you can flash and boot into `setup`.
The split matters. `pkin` does not search repositories. `pkdel` does not solve
dependencies. `src` does not install anything. `sget` ties the lower-level tools
@@ -138,10 +160,10 @@ pkdel foo
This is useful for recovery work, distro installers, package development, and
cases where the administrator wants to bypass dependency policy on purpose.
-## Install a system
+## Install SPS Linux (Splux)
-`setup` is the distro installer. It uses `dialog` screens, or the same choices
-from flags for scripts:
+`setup` is the **SPS Linux** installer, not a generic SPS or LFS installer.
+It uses `dialog` screens, or the same choices from flags for scripts:
```sh
setup
@@ -156,33 +178,46 @@ SSH, and more. `setup --list-sets` prints them. There is also an extra-package
checklist (`--extra`) plus locale and login-shell screens.
The installer writes hostname, timezone, keymap, and a first user into the
-target, then runs `src update` and `sget install`. It does not partition disks
-and does not run `grub-install` unless you pass `--install-bootloader`, which
-still refuses to guess a disk.
+target, then runs `src update` and `sget install`. Guided partitioning is
+optional and waits until the last confirmation. Choose systemd or OpenRC
+as PID 1 (`--init`); those packages conflict. `grub-install` still needs
+`--install-bootloader` and `--disk`.
-## Live TTY ISO
+## SPS Linux live ISO
-`mkiso` writes a hybrid ISO. Flash it to USB, boot to a console, mount a
-destination filesystem on `/mnt`, and run `setup`. Ready-made images:
+`mkiso` writes a hybrid ISO for SPS Linux (Splux). Flash it to USB, boot to a
+console, mount a destination filesystem on `/mnt`, and run `setup`. Ready-made
+images:
-https://github.com/RobertFlexx/SPS/releases/tag/live-2026-08-31
+https://github.com/RobertFlexx/SPS/releases/tag/live-2026-08-31-2
```sh
-mkiso --output dist/sps-live.iso \
+mkiso --output dist/sps-live.iso --init systemd \
--busybox /path/to/busybox \
--core /usr/src/sps/core \
--extra /usr/src/sps/extra
+
+make iso
+make iso-slim
+make iso-plasma
```
+Live PID 1 is systemd when the build host has it, otherwise busybox init
+with gettys on tty1 and ttyS0. The earlier live TTY image exec'd a shell
+as PID 1 and panicked in QEMU (`Attempted to kill init`, exit 127) when
+that shell exited. `--session plasma` copies a host Plasma/Wayland stack;
+NVIDIA live graphics go through nouveau.
+
`--layout DIR` writes the live root without making an ISO. `--no-seed` skips
copying the host compiler. `--with-firmware` copies `/lib/firmware` (large;
published images omit it). The live image never runs `grub-install` on a disk.
Download a ready-made hybrid ISO from this repository's GitHub Releases, flash
-it to USB, boot the "SPS live TTY" entry, mount a destination filesystem on
-`/mnt`, and run `setup`. The image includes the installer, sps-core and
-sps-extra recipes, and a host seed compiler so `setup` can build packages.
-A full Plasma install compiles Qt and KDE from source and takes a long time.
+it to USB, boot the "SPS Linux live (Splux)" entry, mount a destination
+filesystem on `/mnt`, and run `setup`. The image includes the installer,
+sps-core and sps-extra recipes, and a host seed compiler so `setup` can build
+packages. A full Plasma install compiles Qt and KDE from source and takes a
+long time.
```sh
# flash (example)
diff --git a/bin/mkiso b/bin/mkiso
index aa0f479..5c41d50 100755
--- a/bin/mkiso
+++ b/bin/mkiso
@@ -1,7 +1,33 @@
#!/bin/sh
-# Build a bootable SPS live TTY ISO. Users flash it, boot to a console, mount
-# a destination filesystem, and run setup(1). It never runs grub-install on a
-# disk.
+# mkiso builds SPS Linux live ISOs (informally Splux). Maintainer tool;
+# not a desktop installer front-end. SPS the package system does not
+# require these images; they exist so Splux can be installed from USB.
+#
+# Default image: hybrid ISO, setup(1) on PATH, gettys on tty1 and ttyS0.
+# Flash it, boot it, run setup. Guided partitioning is in setup, not here.
+#
+# --session plasma copies Plasma/Wayland from this host. Qt is not compiled
+# during the ISO build. If startplasma-wayland is missing, the disc still
+# boots to a TTY. NVIDIA on the live image is nouveau; proprietary nvidia*
+# modules are blacklisted so they cannot load next to KWin. From Plasma,
+# open Konsole and run setup.
+#
+# --with-firmware copies all of /lib/firmware (often over a gigabyte).
+# Plasma sessions also copy nvidia/nouveau firmware without that flag;
+# TU-class cards need those blobs for nouveau.
+#
+# BusyBox --install -s writes host-absolute applet links. Those break
+# inside the initramfs. mkiso uses relative `ln -sf busybox applet`.
+#
+# grub-install is never run against a disk. The GRUB payload is El Torito
+# plus an ESP for this ISO only. grub-mkrescue wants unicode.pf2, which
+# this host does not ship, so the image is built with grub-mkimage and
+# xorriso.
+#
+# Seed toolchain (--no-seed to skip): gcc, headers, cmake, ninja, python.
+# Pass --core/--extra so the live image can build packages without cloning
+# the recipe repos. --with-go copies the official Go tree; gccgo on PATH
+# is too old for the Go recipes in extra.
sps_prog=mkiso
sps_bin_dir=$(CDPATH= cd -P "$(dirname "$0")" 2>/dev/null && pwd) || exit 1
@@ -23,12 +49,20 @@ mkiso_usage()
usage: mkiso --output FILE.iso [options]
mkiso --layout DIR [options]
-Build a live TTY ISO (or a layout directory) that boots to a console with
-setup(1) on PATH.
+Build a live ISO (or a layout directory) for SPS Linux, informally Splux.
+Flash it, boot it, run setup(1) on a spare disk.
+
+Sessions:
+ tty root shell on tty1 and ttyS0 (default; always works)
+ plasma copy host Plasma/Wayland into the squashfs; Konsole -> setup
+ NVIDIA live path is nouveau. Falls back to a TTY if Plasma
+ is missing on the build host.
options:
--output FILE write a hybrid ISO
--layout DIR populate a live root and stop (tests, inspection)
+ --session NAME tty or plasma (default: tty)
+ --init NAME systemd or openrc (default: systemd)
--work DIR working directory (default: a temp dir)
--kernel FILE vmlinuz (default: /boot/vmlinuz)
--modules DIR kernel modules (default: /lib/modules/RELEASE)
@@ -36,11 +70,13 @@ options:
--core DIR sps-core recipes (copied to /usr/src/sps/core)
--extra DIR sps-extra recipes (copied to /usr/src/sps/extra)
--with-rust copy rustc and cargo from the host
- --with-go copy the host Go toolchain
- --with-firmware copy /lib/firmware (large; omit from published ISOs)
+ --with-go copy the host Go toolchain (not gccgo)
+ --with-firmware copy all of /lib/firmware (large)
--no-modules do not copy /lib/modules (smaller, less hardware)
--no-seed do not copy host compiler tools (layout/tests)
-h, --help
+
+Makefile wrappers: make iso, make iso-slim, make iso-plasma.
EOF
}
@@ -58,6 +94,8 @@ mkiso_with_go=0
mkiso_with_firmware=0
mkiso_copy_modules=1
mkiso_copy_seed=1
+mkiso_session=tty
+mkiso_init=systemd
mkiso_libdir=$SPS_LIBDIR/mkiso
mkiso_need_value()
@@ -117,6 +155,16 @@ while [ "$#" -gt 0 ]; do
--with-firmware) mkiso_with_firmware=1; shift ;;
--no-modules) mkiso_copy_modules=0; shift ;;
--no-seed) mkiso_copy_seed=0; shift ;;
+ --session)
+ mkiso_need_value "$@"
+ mkiso_session=$2
+ shift 2
+ ;;
+ --init)
+ mkiso_need_value "$@"
+ mkiso_init=$2
+ shift 2
+ ;;
-*)
sps_die "$SPS_EX_USAGE" "unknown option: $1"
;;
@@ -129,6 +177,14 @@ done
if [ -z "$mkiso_output" ] && [ -z "$mkiso_layout" ]; then
sps_die "$SPS_EX_USAGE" "pass --output FILE.iso or --layout DIR"
fi
+case $mkiso_session in
+ tty|plasma) ;;
+ *) sps_die "$SPS_EX_USAGE" "session must be tty or plasma" ;;
+esac
+case $mkiso_init in
+ systemd|openrc) ;;
+ *) sps_die "$SPS_EX_USAGE" "init must be systemd or openrc" ;;
+esac
mkiso_cleanup()
{
@@ -228,6 +284,148 @@ mkiso_busybox_links()
ln -sf busybox "$mkiso_bbdir/sh"
}
+# Copy a host Plasma/Wayland stack. mkiso does not compile Qt.
+mkiso_copy_plasma()
+{
+ printf '%s: copying host Plasma/Wayland into the live root\n' \
+ "$sps_prog" >&2
+ if [ -f "$mkiso_libdir/plasma-bins" ]; then
+ while IFS= read -r mkiso_bin || [ -n "$mkiso_bin" ]; do
+ case $mkiso_bin in ''|\#*) continue ;; esac
+ [ -e "$mkiso_bin" ] || continue
+ mkiso_copy_path "$mkiso_bin"
+ mkiso_copy_libs "$mkiso_bin"
+ done <"$mkiso_libdir/plasma-bins"
+ fi
+ if [ -f "$mkiso_libdir/plasma-trees" ]; then
+ while IFS= read -r mkiso_tree || [ -n "$mkiso_tree" ]; do
+ case $mkiso_tree in ''|\#*) continue ;; esac
+ [ -e "$mkiso_tree" ] || continue
+ mkiso_copy_path "$mkiso_tree"
+ done <"$mkiso_libdir/plasma-trees"
+ fi
+ # Shared objects the session links by soname, not by a binary's ldd.
+ for mkiso_pat in /usr/lib64/libQt6*.so* /usr/lib64/libKF6*.so* \
+ /usr/lib64/libplasma*.so* /usr/lib64/libkwin*.so* \
+ /usr/lib64/libwayland*.so* /usr/lib64/libGLESv2.so* \
+ /usr/lib64/libEGL.so* /usr/lib64/libGL.so*
+ do
+ for mkiso_so in $mkiso_pat; do
+ [ -e "$mkiso_so" ] || continue
+ mkiso_copy_path "$mkiso_so"
+ done
+ done
+}
+
+# PID 1 must be an ELF. A shell script that execs getty/setsid is what
+# panicked the previous live TTY (init exited 127).
+mkiso_install_pid1()
+{
+ mkdir -p "$mkiso_live/sbin" "$mkiso_live/etc/sps" \
+ "$mkiso_live/etc/systemd/system" \
+ "$mkiso_live/etc/runlevels/default" \
+ "$mkiso_live/etc/runlevels/sysinit" \
+ "$mkiso_live/etc/modprobe.d"
+ install -m 0755 "$mkiso_libdir/live-rc" "$mkiso_live/etc/sps/live-rc"
+ install -m 0755 "$mkiso_libdir/live-plasma" "$mkiso_live/etc/sps/live-plasma"
+ printf '%s\n' "$mkiso_init" >"$mkiso_live/etc/sps/init"
+ printf '%s\n' "$mkiso_session" >"$mkiso_live/etc/sps/live-session"
+
+ mkiso_systemd=
+ for mkiso_p in /usr/lib/systemd/systemd /lib/systemd/systemd \
+ /usr/lib64/systemd/systemd "$mkiso_live/usr/lib/systemd/systemd"
+ do
+ if [ -x "$mkiso_p" ]; then
+ mkiso_systemd=$mkiso_p
+ break
+ fi
+ done
+
+ mkiso_openrc=
+ for mkiso_p in /sbin/openrc-init /usr/sbin/openrc-init \
+ "$mkiso_live/sbin/openrc-init"
+ do
+ if [ -x "$mkiso_p" ]; then
+ mkiso_openrc=$mkiso_p
+ break
+ fi
+ done
+
+ # Always plant busybox init + inittab so the disc boots even when
+ # the preferred init is not on the build host.
+ if [ -n "$mkiso_busybox" ] && [ -x "$mkiso_busybox" ]; then
+ cp -a "$mkiso_busybox" "$mkiso_live/bin/busybox"
+ chmod 755 "$mkiso_live/bin/busybox"
+ mkiso_busybox_links "$mkiso_live/bin"
+ if [ -x "$mkiso_live/bin/bash" ]; then
+ ln -sf bash "$mkiso_live/bin/sh"
+ fi
+ cp -a "$mkiso_live/bin/busybox" "$mkiso_live/sbin/init"
+ chmod 755 "$mkiso_live/sbin/init"
+ fi
+ if [ "$mkiso_session" = plasma ]; then
+ install -m 0644 "$mkiso_libdir/inittab-plasma" \
+ "$mkiso_live/etc/inittab"
+ else
+ install -m 0644 "$mkiso_libdir/inittab" "$mkiso_live/etc/inittab"
+ fi
+
+ if [ "$mkiso_init" = systemd ] && [ -n "$mkiso_systemd" ]; then
+ printf '%s: live PID 1 is systemd (%s)\n' \
+ "$sps_prog" "$mkiso_systemd" >&2
+ mkiso_copy_path "$mkiso_systemd"
+ mkiso_copy_libs "$mkiso_systemd"
+ mkiso_copy_path /usr/bin/systemctl
+ mkiso_copy_libs /usr/bin/systemctl
+ mkiso_copy_path /usr/lib/systemd
+ mkiso_copy_path /lib/systemd
+ mkdir -p "$mkiso_live/etc/systemd/system/multi-user.target.wants" \
+ "$mkiso_live/etc/systemd/system/graphical.target.wants" \
+ "$mkiso_live/etc/systemd/system/getty.target.wants"
+ install -m 0644 "$mkiso_libdir/systemd/sps-live-rc.service" \
+ "$mkiso_live/etc/systemd/system/"
+ install -m 0644 "$mkiso_libdir/systemd/sps-live-serial.service" \
+ "$mkiso_live/etc/systemd/system/"
+ ln -sf /etc/systemd/system/sps-live-rc.service \
+ "$mkiso_live/etc/systemd/system/multi-user.target.wants/"
+ ln -sf /etc/systemd/system/sps-live-serial.service \
+ "$mkiso_live/etc/systemd/system/multi-user.target.wants/"
+ if [ "$mkiso_session" = plasma ]; then
+ install -m 0644 "$mkiso_libdir/systemd/sps-live-plasma.service" \
+ "$mkiso_live/etc/systemd/system/"
+ ln -sf /etc/systemd/system/sps-live-plasma.service \
+ "$mkiso_live/etc/systemd/system/graphical.target.wants/"
+ ln -sf /usr/lib/systemd/system/graphical.target \
+ "$mkiso_live/etc/systemd/system/default.target"
+ else
+ ln -sf /usr/lib/systemd/system/multi-user.target \
+ "$mkiso_live/etc/systemd/system/default.target"
+ ln -sf /usr/lib/systemd/system/getty@.service \
+ "$mkiso_live/etc/systemd/system/getty.target.wants/getty@tty1.service"
+ fi
+ # systemd as PID 1: kernel execs this ELF, not a shell.
+ mkiso_sys_dest=$mkiso_live$mkiso_systemd
+ if [ -x "$mkiso_sys_dest" ]; then
+ ln -sf "$mkiso_systemd" "$mkiso_live/sbin/init"
+ fi
+ elif [ "$mkiso_init" = openrc ] && [ -n "$mkiso_openrc" ]; then
+ printf '%s: live PID 1 is OpenRC (%s)\n' \
+ "$sps_prog" "$mkiso_openrc" >&2
+ mkiso_copy_path "$mkiso_openrc"
+ mkiso_copy_libs "$mkiso_openrc"
+ ln -sf "$mkiso_openrc" "$mkiso_live/sbin/init"
+ else
+ printf '%s: live PID 1 is busybox init (host has no %s binaries)\n' \
+ "$sps_prog" "$mkiso_init" >&2
+ fi
+
+ if [ ! -x "$mkiso_live/sbin/init" ]; then
+ install -m 0755 "$mkiso_libdir/live-init" "$mkiso_live/sbin/init"
+ fi
+ [ -x "$mkiso_live/sbin/init" ] ||
+ sps_die "$SPS_EX_PACKAGE" "failed to install /sbin/init"
+}
+
# --- live root contents -------------------------------------------------
printf '%s\n' "mkiso: installing SPS tools into the live root" >&2
@@ -267,11 +465,10 @@ if [ -n "$mkiso_busybox" ] && [ -x "$mkiso_busybox" ]; then
ln -sf ../bin/busybox "$mkiso_live/sbin/busybox" 2>/dev/null || :
fi
-install -m 0755 "$mkiso_libdir/live-init" "$mkiso_live/sbin/init"
install -m 0644 "$mkiso_libdir/motd" "$mkiso_live/etc/motd"
install -m 0644 "$mkiso_libdir/issue" "$mkiso_live/etc/issue"
printf '%s\n' live >"$mkiso_live/etc/sps/live"
-printf '%s\n' sps-live >"$mkiso_live/etc/hostname"
+printf '%s\n' splux-live >"$mkiso_live/etc/hostname"
printf '%s\n' 'root:x:0:0:root:/root:/bin/sh' >"$mkiso_live/etc/passwd"
printf '%s\n' 'root:x:0:' >"$mkiso_live/etc/group"
# Empty password on the live console only.
@@ -353,6 +550,13 @@ if [ "$mkiso_copy_seed" -eq 1 ] && [ -f "$mkiso_libdir/seed-bins" ]; then
fi
fi
+if [ "$mkiso_session" = plasma ]; then
+ mkiso_copy_plasma
+ if [ -f "$mkiso_libdir/plasma-motd" ]; then
+ install -m 0644 "$mkiso_libdir/plasma-motd" "$mkiso_live/etc/motd"
+ fi
+fi
+
# Initramfs modules are required to mount the ISO even when the live
# squashfs does not ship the full /lib/modules tree.
if [ -z "$mkiso_modules" ]; then
@@ -366,8 +570,8 @@ if [ "$mkiso_copy_modules" -eq 1 ] && [ -d "$mkiso_modules" ]; then
cp -a "$mkiso_modules" "$mkiso_live/lib/modules/"
fi
-# Busybox getty/init must win over a dangling seed copy of /sbin/init.
-install -m 0755 "$mkiso_libdir/live-init" "$mkiso_live/sbin/init"
+# PID 1 last, after seed, so a copied /sbin/init cannot stick around.
+mkiso_install_pid1
if [ -n "$mkiso_layout" ] && [ -z "$mkiso_output" ]; then
printf '%s\n' "$mkiso_live"
@@ -477,20 +681,22 @@ printf '%s: packing initramfs\n' "$sps_prog" >&2
) | gzip -n -9 >"$mkiso_iso/live/initrd.img" ||
sps_die "$SPS_EX_BUILD" "initramfs cpio failed"
-cat >"$mkiso_iso/boot/grub/grub.cfg" <<'EOF'
+mkiso_title="SPS Linux live (Splux)"
+[ "$mkiso_session" = plasma ] && mkiso_title="SPS Linux live Plasma (Splux)"
+cat >"$mkiso_iso/boot/grub/grub.cfg" <<EOF
set timeout=8
set default=0
serial --unit=0 --speed=115200
terminal_input serial console
terminal_output serial console
-menuentry "SPS live TTY" {
- linux /live/vmlinuz boot=live console=tty0 console=ttyS0,115200n8
+menuentry "$mkiso_title" {
+ linux /live/vmlinuz boot=live sps.init=$mkiso_init console=tty0 console=ttyS0,115200n8
initrd /live/initrd.img
}
-menuentry "SPS live TTY (run setup)" {
- linux /live/vmlinuz boot=live sps=setup console=tty0 console=ttyS0,115200n8
+menuentry "$mkiso_title (run setup)" {
+ linux /live/vmlinuz boot=live sps.init=$mkiso_init sps=setup console=tty0 console=ttyS0,115200n8
initrd /live/initrd.img
}
EOF
@@ -524,7 +730,7 @@ if [ -n "$mkiso_efi" ]; then
sps_die "$SPS_EX_BUILD" "grub-mkimage (x86_64-efi) failed"
truncate -s 8M "$mkiso_iso/boot/grub/efi.img"
if command -v mformat >/dev/null 2>&1 && command -v mcopy >/dev/null 2>&1; then
- mformat -v SPS_EFI -i "$mkiso_iso/boot/grub/efi.img" ::
+ mformat -v SPLUX_EFI -i "$mkiso_iso/boot/grub/efi.img" ::
mmd -i "$mkiso_iso/boot/grub/efi.img" ::EFI ::EFI/BOOT
mcopy -i "$mkiso_iso/boot/grub/efi.img" \
"$mkiso_iso/EFI/BOOT/BOOTX64.EFI" ::EFI/BOOT/BOOTX64.EFI
@@ -545,7 +751,7 @@ esac
mkdir -p "$(dirname "$mkiso_output")"
mkiso_xorriso_log=$mkiso_work/xorriso.log
if [ -f "$mkiso_iso/boot/grub/efi.img" ]; then
- xorriso -as mkisofs -r -J -V SPS_LIVE \
+ xorriso -as mkisofs -r -J -V SPLUX_LIVE \
-o "$mkiso_output" \
-b boot/grub/bios.img -no-emul-boot -boot-load-size 4 \
-boot-info-table --grub2-boot-info \
@@ -555,7 +761,7 @@ if [ -f "$mkiso_iso/boot/grub/efi.img" ]; then
"$mkiso_iso" >"$mkiso_xorriso_log" 2>&1 ||
sps_die "$SPS_EX_BUILD" "xorriso failed (see $mkiso_xorriso_log)"
else
- xorriso -as mkisofs -r -J -V SPS_LIVE \
+ xorriso -as mkisofs -r -J -V SPLUX_LIVE \
-o "$mkiso_output" \
-b boot/grub/bios.img -no-emul-boot -boot-load-size 4 \
-boot-info-table --grub2-boot-info \
diff --git a/bin/mkpkg b/bin/mkpkg
index c35bdf3..53aa62e 100755
--- a/bin/mkpkg
+++ b/bin/mkpkg
@@ -274,7 +274,7 @@ function allowed(key) {
return key == "name" || key == "version" || key == "release" ||
key == "arch" || key == "description" || key == "source" ||
key == "hash" || key == "depend" || key == "builddep" ||
- key == "optional" || key == "prepare" || key == "configure" ||
+ key == "optional" || key == "conflict" || key == "prepare" || key == "configure" ||
key == "build" || key == "install"
}
function emit(text, n, key, value, at) {
@@ -797,7 +797,7 @@ $1 == "description" {
next
}
$1 == "source" || $1 == "hash" || $1 == "depend" ||
-$1 == "builddep" || $1 == "optional" {
+$1 == "builddep" || $1 == "optional" || $1 == "conflict" {
repeated[++count] = $0
}
END {
diff --git a/bin/setup b/bin/setup
index e77ba47..ea6f2b8 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,6 +1,8 @@
#!/bin/sh
-# SPS system installer. Interactive screens use dialog(1); every choice is
-# also available as a command-line option for scripts and tests.
+# SPS Linux installer (informally Splux). Interactive screens use dialog(1);
+# every choice is also available as a flag for scripts and tests.
+# This is the distro installer, not a generic LFS helper. SPS the package
+# system still works on other trees without running setup.
sps_prog=setup
sps_bin_dir=$(CDPATH= cd -P "$(dirname "$0")" 2>/dev/null && pwd) || exit 1
@@ -22,8 +24,14 @@ setup_usage()
cat <<'EOF'
usage: setup [options]
-Install an SPS system into a target root using dialog(1) screens, or the
-same choices from the command line.
+Install SPS Linux (Splux) into a target root using dialog(1) screens, or
+the same choices from the command line. This is the distro installer.
+SPS the package system can be used without it (for example on LFS).
+
+Disks: guided layouts erase the chosen disk after two confirmations.
+--plan and --non-interactive never touch a disk unless you pass --disk
+and --format yes together. grub-install still needs --install-bootloader
+and --disk; setup will not guess a target.
options:
--target DIR installation root (SPS_ROOT)
@@ -41,9 +49,23 @@ options:
--ssh yes|no include the ssh set (OpenSSH)
--printing yes|no include the printing set (CUPS)
--flatpak yes|no add Flathub after install when flatpak is present
- --nvidia yes|no include the nvidia set
+ --nvidia yes|no proprietary NVIDIA set (off by default; live uses nouveau)
+ --partition LAYOUT none, existing, efi-root, efi-swap-root,
+ efi-swap-root-home, bios-root, manage
+ --disk DEV whole disk for guided layouts (e.g. /dev/sda)
+ --swap-size SIZE sfdisk size for swap (default 8GiB)
+ --root-part DEV root partition for --partition manage
+ --efi-part DEV EFI partition for --partition manage
+ --swap-part DEV swap partition for --partition manage
+ --home-part DEV /home partition for --partition manage
+ --format yes|no required yes for non-interactive guided layouts
+ --kernel NAME none, linux, linux-desktop, or custom
+ --kernel-config FILE .config for --kernel custom
+ --drivers auto|none map lspci/cpu to packages (microcode, nouveau, iwd)
+ --init systemd|openrc PID 1 on the installed system (default: none in
+ scripts; systemd on the interactive screen)
--bootloader none|grub write bootloader notes; never runs grub-install
- --install-bootloader actually run grub-install (explicit, dangerous)
+ --install-bootloader run grub-install on --disk (explicit, dangerous)
--update|--no-update run or skip src update before installing
--non-interactive do not start dialog; all choices from options
--plan print the package list and exit
@@ -51,11 +73,20 @@ options:
--read-answers FILE load answers (options override file values)
--list-profiles
--list-sets
+ --list-hardware print detected devices and suggested packages
-h, --help
EOF
}
setup_dir=${SPS_SETUP_DIR:-$SPS_LIBDIR/setup}
+if [ -f "$SPS_LIBDIR/setup/partition.sh" ]; then
+ # shellcheck disable=SC1091
+ . "$SPS_LIBDIR/setup/partition.sh"
+fi
+if [ -f "$SPS_LIBDIR/setup/hwdetect.sh" ]; then
+ # shellcheck disable=SC1091
+ . "$SPS_LIBDIR/setup/hwdetect.sh"
+fi
setup_work=
setup_interactive=1
setup_plan=0
@@ -82,6 +113,20 @@ setup_answers_in=
setup_answers_out=
setup_enable=
setup_disable=
+setup_partition=
+setup_disk=
+setup_swap_size=8GiB
+setup_format=
+setup_kernel=
+setup_kernel_config=
+setup_drivers=
+setup_init=
+setup_part_efi=
+setup_part_root=
+setup_part_swap=
+setup_part_home=
+setup_part_bios=
+setup_list_hardware=0
setup_cleanup()
{
@@ -209,6 +254,70 @@ while [ "$#" -gt 0 ]; do
setup_nvidia=$2
shift 2
;;
+ --partition)
+ setup_need_value "$@"
+ setup_partition=$2
+ shift 2
+ ;;
+ --disk)
+ setup_need_value "$@"
+ setup_disk=$2
+ shift 2
+ ;;
+ --swap-size)
+ setup_need_value "$@"
+ setup_swap_size=$2
+ shift 2
+ ;;
+ --root-part)
+ setup_need_value "$@"
+ setup_part_root=$2
+ shift 2
+ ;;
+ --efi-part)
+ setup_need_value "$@"
+ setup_part_efi=$2
+ shift 2
+ ;;
+ --swap-part)
+ setup_need_value "$@"
+ setup_part_swap=$2
+ shift 2
+ ;;
+ --home-part)
+ setup_need_value "$@"
+ setup_part_home=$2
+ shift 2
+ ;;
+ --format)
+ setup_need_value "$@"
+ setup_format=$2
+ shift 2
+ ;;
+ --kernel)
+ setup_need_value "$@"
+ setup_kernel=$2
+ shift 2
+ ;;
+ --kernel-config)
+ setup_need_value "$@"
+ setup_kernel_config=$2
+ shift 2
+ ;;
+ --drivers)
+ setup_need_value "$@"
+ setup_drivers=$2
+ shift 2
+ ;;
+ --init)
+ setup_need_value "$@"
+ setup_init=$2
+ shift 2
+ ;;
+ --list-hardware)
+ setup_list_hardware=1
+ shift
+ ;;
--bootloader)
setup_need_value "$@"
setup_bootloader=$2
@@ -345,8 +454,9 @@ setup_default_sets_for_profile()
setup_ds="$setup_ds $setup_sn"
;;
*)
- # Power defaults on for desktops only.
+ # Power and firmware default on for desktops only.
[ "$setup_sn" = power ] && continue
+ [ "$setup_sn" = firmware ] && continue
[ "$setup_sn" = qol-cli ] &&
setup_ds="$setup_ds $setup_sn"
;;
@@ -377,6 +487,17 @@ if [ "$setup_list_sets" -eq 1 ]; then
exit 0
fi
+if [ "$setup_list_hardware" -eq 1 ]; then
+ if command -v setup_hw_summary >/dev/null 2>&1; then
+ setup_hw_summary
+ printf '\nSuggested packages:\n'
+ setup_hw_packages | sort -u
+ else
+ setup_fail "$SPS_EX_NOTFOUND" "hardware detection is not available"
+ fi
+ exit 0
+fi
+
setup_load_answers()
{
setup_af=$1
@@ -404,6 +525,18 @@ setup_load_answers()
printing) [ -n "$setup_printing" ] || setup_printing=$setup_av ;;
flatpak) [ -n "$setup_flatpak" ] || setup_flatpak=$setup_av ;;
nvidia) [ -n "$setup_nvidia" ] || setup_nvidia=$setup_av ;;
+ partition) [ -n "$setup_partition" ] || setup_partition=$setup_av ;;
+ disk) [ -n "$setup_disk" ] || setup_disk=$setup_av ;;
+ swap-size) [ "$setup_swap_size" = 8GiB ] && [ -n "$setup_av" ] && setup_swap_size=$setup_av ;;
+ root-part) [ -n "$setup_part_root" ] || setup_part_root=$setup_av ;;
+ efi-part) [ -n "$setup_part_efi" ] || setup_part_efi=$setup_av ;;
+ swap-part) [ -n "$setup_part_swap" ] || setup_part_swap=$setup_av ;;
+ home-part) [ -n "$setup_part_home" ] || setup_part_home=$setup_av ;;
+ format) [ -n "$setup_format" ] || setup_format=$setup_av ;;
+ kernel) [ -n "$setup_kernel" ] || setup_kernel=$setup_av ;;
+ kernel-config) [ -n "$setup_kernel_config" ] || setup_kernel_config=$setup_av ;;
+ drivers) [ -n "$setup_drivers" ] || setup_drivers=$setup_av ;;
+ init) [ -n "$setup_init" ] || setup_init=$setup_av ;;
bootloader) [ "$setup_bootloader" = none ] && [ -n "$setup_av" ] && setup_bootloader=$setup_av ;;
enable) setup_append_csv setup_enable "$setup_av" "$setup_enable" ;;
disable) setup_append_csv setup_disable "$setup_av" "$setup_disable" ;;
@@ -431,13 +564,13 @@ setup_dialog()
command -v dialog >/dev/null 2>&1 ||
setup_fail "$SPS_EX_PACKAGE" \
"dialog is not on PATH; install it or use --non-interactive"
- dialog --backtitle "SPS Linux installer" --output-fd 1 "$@"
+ dialog --backtitle "SPS Linux installer (Splux)" --output-fd 1 "$@"
}
setup_msg()
{
if [ "$setup_interactive" -eq 1 ]; then
- dialog --backtitle "SPS Linux installer" --msgbox "$1" 12 64 || :
+ dialog --backtitle "SPS Linux installer (Splux)" --msgbox "$1" 12 64 || :
else
printf '%s\n' "$1"
fi
@@ -446,12 +579,184 @@ setup_msg()
setup_confirm()
{
if [ "$setup_interactive" -eq 1 ]; then
- dialog --backtitle "SPS Linux installer" --yesno "$1" 14 64
+ dialog --backtitle "SPS Linux installer (Splux)" --yesno "$1" 14 64
else
return 0
fi
}
+setup_pick_disk()
+{
+ setup_dargs=
+ setup_dline=
+ setup_dname=
+ setup_drest=
+ if ! command -v setup_disk_menu_lines >/dev/null 2>&1; then
+ setup_fail "$SPS_EX_PACKAGE" "partition helpers are missing"
+ fi
+ while IFS= read -r setup_dline || [ -n "$setup_dline" ]; do
+ [ -n "$setup_dline" ] || continue
+ setup_dname=${setup_dline%% *}
+ setup_drest=$setup_dline
+ # dialog tags cannot contain spaces; NAME is the tag.
+ setup_dargs="$setup_dargs $setup_dname '$setup_drest'"
+ done <<EOF
+$(setup_disk_menu_lines)
+EOF
+ [ -n "$setup_dargs" ] ||
+ setup_fail "$SPS_EX_NOTFOUND" "no disks found (is lsblk on PATH?)"
+ eval "setup_dialog --title 'Select disk' \
+ --menu 'Guided layouts erase this whole disk. Manage existing opens cfdisk first.' \
+ 18 72 10 $setup_dargs" ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+}
+
+setup_pick_partition()
+{
+ # $1 title, $2 allow none
+ setup_title=$1
+ setup_allow_none=$2
+ setup_pargs=
+ [ "$setup_allow_none" = 1 ] && setup_pargs="none 'No partition'"
+ setup_pline=
+ setup_pname=
+ while IFS= read -r setup_pline || [ -n "$setup_pline" ]; do
+ [ -n "$setup_pline" ] || continue
+ setup_pname=${setup_pline%% *}
+ setup_pargs="$setup_pargs $setup_pname '$setup_pline'"
+ done <<EOF
+$(setup_lsblk -n -p -o NAME,SIZE,TYPE,FSTYPE "$setup_disk" 2>/dev/null | awk '$3 == "part" { print }')
+EOF
+ eval "setup_dialog --title '$setup_title' \
+ --menu 'Partition on $setup_disk' 18 72 10 $setup_pargs" ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+}
+
+setup_ask_partition()
+{
+ if [ -n "$setup_partition" ]; then
+ return 0
+ fi
+ setup_partition=$(setup_dialog --title "Disks" \
+ --menu "Partitioning. Guided layouts wait until the final install confirmation before they write anything. Existing mounts are left alone." \
+ 18 72 8 \
+ existing "Use mounts already on the target path" \
+ manage "cfdisk, then pick root / EFI / swap / home" \
+ efi-root "Guided: GPT, EFI + root" \
+ efi-swap-root "Guided: GPT, EFI + swap + root" \
+ efi-swap-root-home "Guided: GPT, EFI + swap + root + home" \
+ bios-root "Guided: GPT, BIOS boot + root" \
+ none "Do not touch disks") ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+
+ case $setup_partition in
+ none|existing)
+ return 0
+ ;;
+ manage|efi-root|efi-swap-root|efi-swap-root-home|bios-root)
+ if [ -z "$setup_disk" ]; then
+ setup_disk=$(setup_pick_disk) ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+ fi
+ ;;
+ esac
+
+ if [ "$setup_partition" = manage ]; then
+ if command -v cfdisk >/dev/null 2>&1 ||
+ [ -x /sbin/cfdisk ]; then
+ setup_cfdisk=$(command -v cfdisk 2>/dev/null || printf /sbin/cfdisk)
+ setup_msg "cfdisk will open on $setup_disk. Write the table, then quit. setup will ask which partitions to use."
+ "$setup_cfdisk" "$setup_disk" || :
+ else
+ setup_msg "cfdisk is not on PATH. Using the partition table that is already on $setup_disk."
+ fi
+ setup_part_root=$(setup_pick_partition "Root partition" 0)
+ [ "$setup_part_root" != none ] ||
+ setup_fail "$SPS_EX_USAGE" "root partition is required"
+ setup_part_efi=$(setup_pick_partition "EFI partition" 1)
+ [ "$setup_part_efi" != none ] || setup_part_efi=
+ setup_part_swap=$(setup_pick_partition "Swap partition" 1)
+ [ "$setup_part_swap" != none ] || setup_part_swap=
+ setup_part_home=$(setup_pick_partition "Home partition" 1)
+ [ "$setup_part_home" != none ] || setup_part_home=
+ if setup_dialog --title "Format" --yesno \
+ "Format the selected partitions (ext4 / vfat / swap) before mounting them on the target?
+
+This erases data on those partitions only, not necessarily the whole disk." 12 64
+ then
+ setup_format=yes
+ else
+ setup_format=no
+ fi
+ fi
+}
+
+setup_ask_kernel()
+{
+ if [ -n "$setup_kernel" ]; then
+ return 0
+ fi
+ setup_kdef=none
+ case $setup_profile in
+ plasma-desktop|plasma-full) setup_kdef=linux-desktop ;;
+ esac
+ setup_kernel=$(setup_dialog --title "SPS Linux kernel" --default-item "$setup_kdef" \
+ --menu "none leaves the host kernel story alone (current live ISO). linux is upstream defconfig. linux-desktop is the SPS Linux (Splux) daily-driver config: DRM (nouveau/i915/amdgpu), NVMe, audio, extra filesystems. custom copies a .config you already have." \
+ 18 72 6 \
+ none "Do not build a kernel package" \
+ linux-desktop "SPS Linux desktop config (Splux; Plasma default)" \
+ linux "Upstream defconfig" \
+ custom "Use a .config from this machine") ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+ if [ "$setup_kernel" = custom ] && [ -z "$setup_kernel_config" ]; then
+ setup_kernel_config=$(setup_dialog --title "Kernel config" \
+ --inputbox "Absolute path to a Linux .config. It is copied to the target as /etc/sps/kernel.config before the kernel package builds." \
+ 10 72 "${setup_kernel_config:-/etc/sps/kernel.config}") ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+ fi
+ # If the user just hit enter on the default-looking first item, keep the
+ # profile default when they picked none on a Plasma install? No: none is
+ # an explicit choice. Profile default only applies when the flag is omitted
+ # in non-interactive mode.
+ :
+}
+
+setup_ask_init()
+{
+ if [ -n "$setup_init" ]; then
+ return 0
+ fi
+ setup_init=$(setup_dialog --title "Init system" --default-item systemd \
+ --menu "PID 1 on the installed system. Live images prefer systemd when the ISO was built with it; busybox init is the live fallback so the disc cannot panic if systemd is missing. These packages conflict; sget will refuse both." \
+ 16 72 4 \
+ systemd "systemd (udev, logind, the default)" \
+ openrc "OpenRC plus eudev and elogind") ||
+ setup_fail "$SPS_EX_USAGE" "cancelled"
+}
+
+setup_ask_drivers()
+{
+ if [ -n "$setup_drivers" ]; then
+ return 0
+ fi
+ setup_hwtxt="Could not probe PCI."
+ if command -v setup_hw_summary >/dev/null 2>&1; then
+ setup_hwtxt=$(setup_hw_summary)
+ fi
+ if setup_dialog --title "Drivers and microcode" --yesno \
+ "Detect hardware and install matching packages from the repos?
+
+$setup_hwtxt
+
+This pulls CPU microcode, Mesa/nouveau, firmware, and wireless helpers. Proprietary NVIDIA modules stay off unless you enable them on the next screen." 18 72
+ then
+ setup_drivers=auto
+ else
+ setup_drivers=none
+ fi
+}
+
+
# Interactive defaults when flags were omitted.
if [ "$setup_interactive" -eq 1 ] && [ ! -t 0 ]; then
setup_fail "$SPS_EX_USAGE" \
@@ -470,21 +775,23 @@ trap setup_cleanup 0 HUP INT TERM
if [ "$setup_interactive" -eq 1 ]; then
if [ -f /etc/sps/live ]; then
setup_msg \
- "This is the SPS live TTY. Mount the destination filesystem on /mnt first.
+ "This is the SPS Linux live image (Splux). You can partition a disk here, or mount something on /mnt first and choose Use existing mounts.
-Then continue. The installer never partitions disks. Bootloader install is a separate, explicit step."
+Guided layouts wait until the last confirmation before they erase a disk. Bootloader install is a separate, explicit step (needs --install-bootloader and --disk)."
else
setup_msg \
- "This installer configures an SPS root the way Slackware and FreeBSD setup do: a sequence of dialog screens, then a visible package install.
+ "This installer configures SPS Linux (Splux) the way Slackware and FreeBSD setup do: disks, profile, kernel, drivers, then a visible package install.
-It never partitions disks. Point it at a mounted filesystem. Installing the bootloader is a separate, explicit step."
+Guided partition layouts erase the chosen disk only after you confirm at the end. grub-install is never implied. An LFS tree using SPS does not have to go through this installer."
fi
+ setup_ask_partition
+
if [ -z "$setup_target" ]; then
setup_target=$(setup_dialog --title "Installation target" \
--inputbox "Absolute path of the target root.
-Use /mnt after mounting the destination filesystem. / is the live system." \
+Use /mnt after mounting the destination filesystem, or after a guided layout (setup mounts it for you at the end). / is the live system." \
10 64 "${setup_target:-/mnt}") ||
setup_fail "$SPS_EX_USAGE" "cancelled"
fi
@@ -502,9 +809,12 @@ Use /mnt after mounting the destination filesystem. / is the live system." \
setup_fail "$SPS_EX_USAGE" "cancelled"
fi
+ setup_ask_kernel
+ setup_ask_init
+
if [ -z "$setup_hostname" ]; then
setup_hostname=$(setup_dialog --title "Hostname" \
- --inputbox "System hostname." 8 50 "${setup_hostname:-sps}") ||
+ --inputbox "System hostname." 8 50 "${setup_hostname:-splux}") ||
setup_fail "$SPS_EX_USAGE" "cancelled"
fi
@@ -556,6 +866,8 @@ Use /mnt after mounting the destination filesystem. / is the live system." \
plasma-desktop|plasma-full) setup_on=on ;;
*)
[ "$setup_sn" = qol-cli ] && setup_on=on
+ [ "$setup_sn" = power ] && setup_on=off
+ [ "$setup_sn" = firmware ] && setup_on=off
;;
esac
fi
@@ -605,11 +917,13 @@ Use /mnt after mounting the destination filesystem. / is the live system." \
fi
fi
+ setup_ask_drivers
+
if [ -z "$setup_nvidia" ]; then
- if setup_dialog --title "NVIDIA" --yesno \
+ if setup_dialog --title "NVIDIA proprietary" --yesno \
"Include NVIDIA open kernel modules and user-space utilities?
-This requires a matching kernel on the target and is off by default." 10 64
+The live image and auto drivers use nouveau. This extra set is the proprietary/open-rm stack and needs a matching kernel on the target. Off by default." 12 64
then
setup_nvidia=yes
else
@@ -660,13 +974,8 @@ This requires a matching kernel on the target and is off by default." 10 64
After the first boot, put an interface file in /etc/network or use the NetworkManager applet on Plasma. Notes are written to /root/sps-network.txt." 12 64 || :
- setup_dialog --title "Disks" --msgbox \
- "This installer does not partition, format, or write /etc/fstab.
-
-Mount the destination filesystem on the target path before running setup. Swap and fstab remain administrator work. Notes are written to /root/sps-fstab.txt." 12 64 || :
-
setup_bl=$(setup_dialog --title "Bootloader" \
- --menu "SPS never runs grub-install unless you pass --install-bootloader. The usual choice is to write notes only." \
+ --menu "SPS never runs grub-install unless you pass --install-bootloader with --disk. The usual choice is to write notes only." \
14 64 3 \
none "Write /root/sps-bootloader.txt only" \
grub "Write GRUB notes (still does not install)") ||
@@ -677,7 +986,7 @@ fi
# Non-interactive defaults
[ -n "$setup_target" ] || setup_target=/mnt
[ -n "$setup_profile" ] || setup_profile=minimal
-[ -n "$setup_hostname" ] || setup_hostname=sps
+[ -n "$setup_hostname" ] || setup_hostname=splux
[ -n "$setup_timezone" ] || setup_timezone=UTC
[ -n "$setup_keymap" ] || setup_keymap=us
[ -n "$setup_locale" ] || setup_locale=C.UTF-8
@@ -686,6 +995,58 @@ setup_flatpak=$(setup_yesno_norm "${setup_flatpak:-no}")
setup_nvidia=$(setup_yesno_norm "${setup_nvidia:-no}")
setup_ssh=$(setup_yesno_norm "${setup_ssh:-no}")
setup_printing=$(setup_yesno_norm "${setup_printing:-no}")
+[ -n "$setup_partition" ] || setup_partition=none
+[ -n "$setup_drivers" ] || setup_drivers=none
+[ -n "$setup_init" ] || setup_init=none
+[ -n "$setup_format" ] || setup_format=no
+setup_format=$(setup_yesno_norm "$setup_format")
+if [ -z "$setup_kernel" ]; then
+ setup_kernel=none
+fi
+case $setup_kernel in
+ none|linux|linux-desktop|custom) ;;
+ *) setup_fail "$SPS_EX_USAGE" "kernel must be none, linux, linux-desktop, or custom" ;;
+esac
+case $setup_drivers in
+ auto|none) ;;
+ *) setup_fail "$SPS_EX_USAGE" "drivers must be auto or none" ;;
+esac
+case $setup_init in
+ none|systemd|openrc) ;;
+ *) setup_fail "$SPS_EX_USAGE" "init must be systemd, openrc, or none" ;;
+esac
+case $setup_partition in
+ none|existing|efi-root|efi-swap-root|efi-swap-root-home|bios-root|manage) ;;
+ *) setup_fail "$SPS_EX_USAGE" "unknown partition layout: $setup_partition" ;;
+esac
+case $setup_partition in
+ efi-root|efi-swap-root|efi-swap-root-home|bios-root)
+ [ -n "$setup_disk" ] ||
+ setup_fail "$SPS_EX_USAGE" "guided --partition needs --disk"
+ case $setup_disk in
+ /dev/*) ;;
+ *) setup_fail "$SPS_EX_USAGE" "disk must be a /dev path" ;;
+ esac
+ if [ "$setup_plan" -eq 0 ] && [ "$setup_format" != yes ]; then
+ setup_fail "$SPS_EX_USAGE" \
+ "guided partitioning requires --format yes (refusing to erase $setup_disk)"
+ fi
+ ;;
+ manage)
+ [ -n "$setup_part_root" ] ||
+ setup_fail "$SPS_EX_USAGE" "--partition manage needs --root-part"
+ ;;
+esac
+if [ "$setup_kernel" = custom ]; then
+ [ -n "$setup_kernel_config" ] ||
+ setup_fail "$SPS_EX_USAGE" "--kernel custom requires --kernel-config FILE"
+ case $setup_kernel_config in
+ /*) ;;
+ *) setup_fail "$SPS_EX_USAGE" "kernel config must be an absolute path" ;;
+ esac
+ [ -f "$setup_kernel_config" ] ||
+ setup_fail "$SPS_EX_NOTFOUND" "kernel config not found: $setup_kernel_config"
+fi
case $setup_bootloader in
none|grub) ;;
*) setup_fail "$SPS_EX_USAGE" "bootloader must be none or grub" ;;
@@ -790,6 +1151,27 @@ for setup_item in $setup_extra; do
setup_add_pkg "$setup_item"
done
+case $setup_kernel in
+ linux) setup_add_pkg linux ;;
+ linux-desktop) setup_add_pkg linux-desktop ;;
+ custom) setup_add_pkg linux-custom ;;
+esac
+
+case $setup_init in
+ systemd) setup_add_pkg init-systemd ;;
+ openrc) setup_add_pkg init-openrc ;;
+esac
+
+if [ "$setup_drivers" = auto ]; then
+ if command -v setup_hw_packages >/dev/null 2>&1; then
+ setup_hw_packages | sort -u |
+ while IFS= read -r setup_pkg; do
+ [ -n "$setup_pkg" ] || continue
+ setup_add_pkg "$setup_pkg"
+ done
+ fi
+fi
+
setup_write_answers_file()
{
setup_out=$1
@@ -808,6 +1190,18 @@ setup_write_answers_file()
printf 'ssh %s\n' "$setup_ssh"
printf 'printing %s\n' "$setup_printing"
printf 'bootloader %s\n' "$setup_bootloader"
+ printf 'partition %s\n' "$setup_partition"
+ [ -n "$setup_disk" ] && printf 'disk %s\n' "$setup_disk"
+ printf 'swap-size %s\n' "$setup_swap_size"
+ [ -n "$setup_part_root" ] && printf 'root-part %s\n' "$setup_part_root"
+ [ -n "$setup_part_efi" ] && printf 'efi-part %s\n' "$setup_part_efi"
+ [ -n "$setup_part_swap" ] && printf 'swap-part %s\n' "$setup_part_swap"
+ [ -n "$setup_part_home" ] && printf 'home-part %s\n' "$setup_part_home"
+ printf 'format %s\n' "$setup_format"
+ printf 'kernel %s\n' "$setup_kernel"
+ [ -n "$setup_kernel_config" ] && printf 'kernel-config %s\n' "$setup_kernel_config"
+ printf 'drivers %s\n' "$setup_drivers"
+ printf 'init %s\n' "$setup_init"
printf 'update %s\n' "$([ "$setup_do_update" -eq 1 ] && printf yes || printf no)"
for setup_item in $setup_enable; do
printf 'enable %s\n' "$setup_item"
@@ -835,6 +1229,11 @@ setup_summary()
printf 'SSH: %s\n' "$setup_ssh"
printf 'Printing: %s\n' "$setup_printing"
printf 'Bootloader: %s\n' "$setup_bootloader"
+ printf 'Partition: %s\n' "$setup_partition"
+ printf 'Disk: %s\n' "${setup_disk:-<none>}"
+ printf 'Kernel: %s\n' "$setup_kernel"
+ printf 'Init: %s\n' "$setup_init"
+ printf 'Drivers: %s\n' "$setup_drivers"
printf '\nPackages:\n'
sort -u "$setup_pkg_file"
}
@@ -849,14 +1248,43 @@ if [ "$setup_plan" -eq 1 ]; then
fi
if [ "$setup_interactive" -eq 1 ]; then
+ setup_warn=
+ case $setup_partition in
+ efi-root|efi-swap-root|efi-swap-root-home|bios-root)
+ setup_warn="
+THIS ERASES $setup_disk. Partition table, filesystems, all of it."
+ ;;
+ manage)
+ if [ "$setup_format" = yes ]; then
+ setup_warn="
+Selected partitions will be formatted: $setup_part_root $setup_part_efi $setup_part_swap $setup_part_home"
+ fi
+ ;;
+ esac
setup_confirm "Install with this configuration?
$(setup_summary)
+$setup_warn
Package builds run on this terminal. Source packages may take a long time." ||
setup_fail "$SPS_EX_USAGE" "cancelled"
fi
+# Guided layouts run here, after the last chance to cancel.
+case $setup_partition in
+ none|existing) ;;
+ *)
+ if [ "$setup_plan" -eq 0 ]; then
+ printf '%s: applying partition layout %s on %s\n' \
+ "$sps_prog" "$setup_partition" "${setup_disk:-partitions}" >&2
+ PATH=/sbin:/usr/sbin:$PATH
+ export PATH
+ setup_apply_partition "$setup_target" ||
+ setup_fail "$SPS_EX_PERMISSION" "partitioning failed"
+ fi
+ ;;
+esac
+
setup_apply_config()
{
setup_etc=$setup_root/etc
@@ -869,6 +1297,7 @@ setup_apply_config()
printf 'KEYMAP=%s\n' "$setup_keymap" >"$setup_etc/vconsole.conf"
printf 'keymap %s\n' "$setup_keymap" >"$setup_etc/sps/keymap"
printf 'LANG=%s\n' "$setup_locale" >"$setup_etc/locale.conf"
+ printf '%s\n' "${setup_init:-none}" >"$setup_etc/sps/init"
if [ -f /usr/share/zoneinfo/"$setup_timezone" ]; then
cp /usr/share/zoneinfo/"$setup_timezone" "$setup_etc/localtime" 2>/dev/null || :
@@ -930,6 +1359,21 @@ Or for BIOS:
Replace DISK with the actual disk. This is an administrator action.
EOF
+ cat >"$setup_root/root/sps-init.txt" <<EOF
+Init choice: ${setup_init:-none}
+
+systemd and OpenRC conflict; sget will not install both. After the
+packages land:
+
+ systemd PID 1 is /usr/lib/systemd/systemd. Enable getty@tty1
+ and, for Plasma, a display manager. /etc/machine-id is
+ created on first boot if missing.
+ openrc PID 1 is OpenRC. Add services with rc-update. eudev and
+ elogind are in the init-openrc set.
+ none no init package was requested. Busybox can still provide
+ an init applet if you copy it to /sbin/init yourself.
+EOF
+
cat >"$setup_root/root/sps-network.txt" <<EOF
SPS setup does not write DHCP or static network configuration.
@@ -941,12 +1385,21 @@ OpenSSH is installed when the server profile or the ssh set is selected.
EOF
cat >"$setup_root/root/sps-fstab.txt" <<EOF
-SPS setup does not partition disks or write /etc/fstab.
-
-Mount the target filesystem on the path you passed to --target (or the
-target screen) before running the installer. Add swap and extra mounts
-yourself after inspecting lsblk and writing /etc/fstab.
+If setup partitioned the disk, /etc/fstab was written from blkid UUIDs.
+Read it before the first boot. Add extra mounts yourself.
+
+Partition layout: $setup_partition
+Disk: ${setup_disk:-<none>}
+Root: ${setup_part_root:-<none>}
+EFI: ${setup_part_efi:-<none>}
+Swap: ${setup_part_swap:-<none>}
+Home: ${setup_part_home:-<none>}
EOF
+
+ if command -v setup_write_fstab >/dev/null 2>&1 &&
+ [ -n "$setup_part_root" ]; then
+ setup_write_fstab "$setup_root" || :
+ fi
}
# Packages first, then hostname/user so filesystem's passwd is already present.
@@ -956,6 +1409,12 @@ mkdir -p "$setup_root" ||
export SPS_ROOT=$setup_root
+if [ "$setup_kernel" = custom ] && [ -n "$setup_kernel_config" ]; then
+ mkdir -p "$setup_root/etc/sps"
+ cp "$setup_kernel_config" "$setup_root/etc/sps/kernel.config" ||
+ setup_fail "$SPS_EX_PERMISSION" "cannot copy kernel config"
+fi
+
if [ "$setup_do_update" -eq 1 ]; then
printf '%s: updating source repositories\n' "$sps_prog" >&2
src update || setup_fail "$SPS_EX_NETWORK" "src update failed"
@@ -998,8 +1457,20 @@ fi
if [ "$setup_install_bootloader" -eq 1 ]; then
command -v grub-install >/dev/null 2>&1 ||
setup_fail "$SPS_EX_PACKAGE" "grub-install is not available"
- setup_fail "$SPS_EX_USAGE" \
- "--install-bootloader requires a disk argument; refusing to guess. See /root/sps-bootloader.txt"
+ [ -n "$setup_disk" ] ||
+ setup_fail "$SPS_EX_USAGE" \
+ "--install-bootloader requires --disk; refusing to guess. See /root/sps-bootloader.txt"
+ printf '%s: grub-install %s (explicit)\n' "$sps_prog" "$setup_disk" >&2
+ if [ -d "$setup_root/boot/efi" ]; then
+ grub-install --target=x86_64-efi --efi-directory="$setup_root/boot/efi" \
+ --bootloader-id=SPS --boot-directory="$setup_root/boot" \
+ "$setup_disk" ||
+ setup_fail "$SPS_EX_BUILD" "grub-install (EFI) failed"
+ else
+ grub-install --target=i386-pc --boot-directory="$setup_root/boot" \
+ "$setup_disk" ||
+ setup_fail "$SPS_EX_BUILD" "grub-install (BIOS) failed"
+ fi
fi
setup_msg "Installation finished.
diff --git a/bin/sget b/bin/sget
index f81e11b..bd80fd4 100755
--- a/bin/sget
+++ b/bin/sget
@@ -191,6 +191,21 @@ sget_compare_selected()
printf '%s\n' "$sget_cmp"
}
+sget_installed_names()
+{
+ sget_list=
+ [ -d "$SPS_DB/installed" ] || {
+ printf ''
+ return 0
+ }
+ for sget_d in "$SPS_DB/installed"/*; do
+ [ -d "$sget_d" ] || continue
+ sget_n=${sget_d##*/}
+ [ -n "$sget_list" ] && sget_list=$sget_list,$sget_n || sget_list=$sget_n
+ done
+ printf '%s' "$sget_list"
+}
+
sget_cleanup_install()
{
if [ -n "${sget_work:-}" ]; then
@@ -300,6 +315,7 @@ sget_install()
else
if ! awk -v action=plan -v roots="$sget_roots" \
-v include_build=1 -v program=sget \
+ -v installed="$(sget_installed_names)" \
-f "$SPS_LIBDIR/version.awk" -f "$SPS_LIBDIR/deps.awk" \
"$sps_index" >"$sget_work/resolution"; then
sps_die "$SPS_EX_DEPENDENCY" "cannot resolve requested packages"
diff --git a/lib/deps.awk b/lib/deps.awk
index c1eefa4..88bd024 100644
--- a/lib/deps.awk
+++ b/lib/deps.awk
@@ -167,9 +167,9 @@ BEGIN {
}
{
- if (NF != 12) {
+ if (NF != 12 && NF != 13) {
deps_error(FILENAME ":" FNR \
- ": malformed index record (expected 12 fields)")
+ ": malformed index record (expected 12 or 13 fields)")
next
}
if ($1 !~ /^[A-Za-z0-9][A-Za-z0-9+_.-]*$/ ||
@@ -191,6 +191,7 @@ BEGIN {
deps_build[$1] = $6
deps_recipe[$1] = $10
deps_recipe_sha256[$1] = $12
+ deps_conflict[$1] = (NF >= 13 ? $13 : "")
} else if (($9 + 0) > deps_selected_priority[$1]) {
deps_selected_priority[$1] = $9 + 0
deps_version[$1] = $2
@@ -200,6 +201,7 @@ BEGIN {
deps_build[$1] = $6
deps_recipe[$1] = $10
deps_recipe_sha256[$1] = $12
+ deps_conflict[$1] = (NF >= 13 ? $13 : "")
}
}
@@ -236,6 +238,42 @@ END {
if (deps_failed)
exit 5
+ # installed=name,name from sget: already-installed packages.
+ if (installed != "") {
+ deps_ninst = split(installed, deps_inst, ",")
+ for (deps_i = 1; deps_i <= deps_ninst; deps_i++) {
+ if (deps_inst[deps_i] != "")
+ deps_installed[deps_inst[deps_i]] = 1
+ }
+ }
+ for (deps_i = 1; deps_i <= deps_order_count; deps_i++)
+ deps_in_order[deps_order[deps_i]] = 1
+ for (deps_i = 1; deps_i <= deps_order_count; deps_i++) {
+ deps_package = deps_order[deps_i]
+ if (deps_conflict[deps_package] == "")
+ continue
+ deps_nc = split(deps_conflict[deps_package], deps_citems, ",")
+ for (deps_j = 1; deps_j <= deps_nc; deps_j++) {
+ deps_other = deps_citems[deps_j]
+ if (deps_other == "" || deps_other == deps_package)
+ continue
+ if (deps_in_order[deps_other]) {
+ deps_error("package '" deps_package "' conflicts with '" \
+ deps_other "' (both in this transaction)")
+ break
+ }
+ if (deps_installed[deps_other]) {
+ deps_error("package '" deps_package "' conflicts with installed '" \
+ deps_other "'")
+ break
+ }
+ }
+ if (deps_failed)
+ break
+ }
+ if (deps_failed)
+ exit 5
+
if (action == "order" || action == "plan") {
for (deps_i = 1; deps_i <= deps_order_count; deps_i++) {
deps_package = deps_order[deps_i]
diff --git a/lib/index.awk b/lib/index.awk
index 34e16cb..5e286de 100644
--- a/lib/index.awk
+++ b/lib/index.awk
@@ -48,6 +48,8 @@ function index_print_show(name) {
? "-" : index_output_fields[6])
print "optional dependencies: " (index_output_fields[7] == "" \
? "-" : index_output_fields[7])
+ print "conflicts: " (index_output_fields[13] == "" \
+ ? "-" : index_output_fields[13])
print "definition sha256: " index_output_fields[12]
}
@@ -60,8 +62,8 @@ BEGIN {
}
{
- if (NF != 12) {
- index_error(FILENAME ":" FNR ": malformed index record (expected 12 fields)")
+ if (NF != 12 && NF != 13) {
+ index_error(FILENAME ":" FNR ": malformed index record (expected 12 or 13 fields)")
next
}
if ($1 !~ /^[A-Za-z0-9][A-Za-z0-9+_.-]*$/ ||
@@ -145,7 +147,7 @@ END {
index_output_fields[12], index_output_fields[2],
index_output_fields[3], index_output_fields[4]
} else {
- if (field !~ /^[[:digit:]]+$/ || field < 1 || field > 12) {
+ if (field !~ /^[[:digit:]]+$/ || field < 1 || field > 13) {
index_stderr((program == "" ? "src" : program) \
": invalid index field '" field "'")
exit 2
diff --git a/lib/mkiso/initrd-init b/lib/mkiso/initrd-init
index f245fd5..7eccc1d 100755
--- a/lib/mkiso/initrd-init
+++ b/lib/mkiso/initrd-init
@@ -1,75 +1,101 @@
-#!/bin/sh
-# Live initramfs. Mount the ISO, squashfs, and overlay, then switch_root.
+#!/bin/busybox sh
+# Initramfs PID 1. Find the ISO, mount squashfs+overlay, switch_root.
+#
+# Do not call `busybox --install -s` here. That writes absolute applet
+# links, and those panic once the host paths are gone. mkiso already
+# planted relative links next to /bin/busybox.
+#
+# Invoke applets as `/bin/busybox NAME` so a missing symlink cannot
+# make PID 1 exit 127.
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
-/bin/busybox --install -s /bin 2>/dev/null || :
-# Applets may already be relative links from mkiso.
+bb=/bin/busybox
+[ -x "$bb" ] || bb=busybox
mkdir -p /proc /sys /dev /tmp /mnt/iso /mnt/ro /mnt/rw /mnt/new
-mount -t proc proc /proc
-mount -t sysfs sysfs /sys
-mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs tmpfs /dev
+"$bb" mount -t proc proc /proc
+"$bb" mount -t sysfs sysfs /sys
+"$bb" mount -t devtmpfs devtmpfs /dev 2>/dev/null ||
+ "$bb" mount -t tmpfs tmpfs /dev
mkdir -p /dev/pts
-mount -t devpts devpts /dev/pts 2>/dev/null || :
+"$bb" mount -t devpts devpts /dev/pts 2>/dev/null || :
+
+rescue()
+{
+ printf '%s\n' "$*"
+ printf '%s\n' "mkiso: dropping to a shell. PID 1 stays this shell."
+ # Do not exec a missing binary; keep this process alive.
+ while :
+ do
+ "$bb" setsid "$bb" sh -c 'exec '"$bb"' sh <>/dev/console >&0 2>&1' ||
+ "$bb" sh
+ "$bb" sleep 1
+ done
+}
-# Load filesystem modules when they are not built in.
for mod in loop squashfs overlay isofs vfat nls_cp437 nls_iso8859-1 \
usb-storage xhci-pci ehci-pci uhci-hcd sr_mod sd_mod ahci nvme \
- virtio_blk virtio_pci virtio_scsi virtio_net
+ virtio_blk virtio_pci virtio_scsi virtio_net virtio_console \
+ sdhci sdhci-pci mmc_block
do
- modprobe "$mod" 2>/dev/null || :
+ "$bb" modprobe "$mod" 2>/dev/null || :
done
sleep 1
found=
-for i in 1 2 3 4 5 6 7 8 9 10; do
- for dev in /dev/sr0 /dev/sr1 /dev/cdrom \
- /dev/nvme0n1p1 /dev/nvme1n1p1 \
- /dev/sda /dev/sda1 /dev/sdb /dev/sdb1 \
- /dev/vda /dev/vda1 /dev/vdb /dev/vdb1
+i=0
+while [ "$i" -lt 15 ]
+do
+ i=$((i + 1))
+ for dev in /dev/sr0 /dev/sr1 /dev/sr2 /dev/cdrom \
+ /dev/nvme0n1 /dev/nvme0n1p1 /dev/nvme1n1p1 \
+ /dev/sda /dev/sda1 /dev/sdb /dev/sdb1 /dev/sdc /dev/sdc1 \
+ /dev/vda /dev/vda1 /dev/vdb /dev/vdb1 \
+ /dev/xvda /dev/xvda1
do
[ -b "$dev" ] || continue
- if mount -t iso9660 -o ro "$dev" /mnt/iso 2>/dev/null ||
- mount -o ro "$dev" /mnt/iso 2>/dev/null; then
+ if "$bb" mount -t iso9660 -o ro "$dev" /mnt/iso 2>/dev/null ||
+ "$bb" mount -o ro "$dev" /mnt/iso 2>/dev/null; then
if [ -f /mnt/iso/live/filesystem.squashfs ]; then
found=$dev
break
fi
- umount /mnt/iso 2>/dev/null || :
+ "$bb" umount /mnt/iso 2>/dev/null || :
fi
done
[ -n "$found" ] && break
sleep 1
done
-if [ -z "$found" ]; then
- printf '%s\n' "mkiso: cannot find the SPS live filesystem on a CD/USB."
- printf '%s\n' "Dropping to a shell."
- exec setsid /bin/sh </dev/console >/dev/console 2>&1
-fi
+[ -n "$found" ] || rescue "mkiso: cannot find the SPS Linux live filesystem on a CD/USB."
-mount -t squashfs -o ro /mnt/iso/live/filesystem.squashfs /mnt/ro || {
- printf '%s\n' "mkiso: squashfs mount failed"
- exec setsid /bin/sh </dev/console >/dev/console 2>&1
-}
+"$bb" mount -t squashfs -o ro /mnt/iso/live/filesystem.squashfs /mnt/ro ||
+ rescue "mkiso: squashfs mount failed"
-mount -t tmpfs tmpfs /mnt/rw
+"$bb" mount -t tmpfs tmpfs /mnt/rw
mkdir -p /mnt/rw/upper /mnt/rw/work
-mount -t overlay overlay \
+"$bb" mount -t overlay overlay \
-o lowerdir=/mnt/ro,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work \
- /mnt/new || {
- printf '%s\n' "mkiso: overlay mount failed"
- exec setsid /bin/sh </dev/console >/dev/console 2>&1
-}
+ /mnt/new ||
+ rescue "mkiso: overlay mount failed"
-mkdir -p /mnt/new/mnt/iso /mnt/new/proc /mnt/new/sys /mnt/new/dev /mnt/new/run /mnt/new/tmp
-mount --move /mnt/iso /mnt/new/mnt/iso 2>/dev/null || :
-mount --move /proc /mnt/new/proc 2>/dev/null || :
-mount --move /sys /mnt/new/sys 2>/dev/null || :
-mount --move /dev /mnt/new/dev 2>/dev/null || :
+mkdir -p /mnt/new/mnt/iso /mnt/new/proc /mnt/new/sys /mnt/new/dev \
+ /mnt/new/run /mnt/new/tmp
+"$bb" mount --move /mnt/iso /mnt/new/mnt/iso 2>/dev/null || :
+"$bb" mount --move /proc /mnt/new/proc 2>/dev/null || :
+"$bb" mount --move /sys /mnt/new/sys 2>/dev/null || :
+"$bb" mount --move /dev /mnt/new/dev 2>/dev/null || :
+
+if [ ! -e /mnt/new/sbin/init ] && [ ! -L /mnt/new/sbin/init ]; then
+ rescue "mkiso: /sbin/init missing on the live filesystem"
+fi
-exec switch_root /mnt/new /sbin/init
+# busybox switch_root replaces PID 1. If the new init cannot exec, we
+# would panic; the live /sbin/init is an ELF (busybox or systemd), not
+# a script that execs a console.
+exec "$bb" switch_root /mnt/new /sbin/init
+rescue "mkiso: switch_root failed"
diff --git a/lib/mkiso/inittab b/lib/mkiso/inittab
new file mode 100644
index 0000000..7755f12
--- /dev/null
+++ b/lib/mkiso/inittab
@@ -0,0 +1,12 @@
+# SPS Linux live busybox inittab.
+# PID 1 is busybox init (an ELF). Consoles are children, not exec targets.
+# The previous live TTY exec'd a shell as PID 1 and died in QEMU when
+# that shell exited 127 (missing setsid/getty/sh, or ttyS0 hangup).
+
+::sysinit:/etc/sps/live-rc
+tty1::respawn:/bin/busybox getty -n -l /bin/sh 38400 tty1
+tty2::respawn:/bin/busybox getty -n -l /bin/sh 38400 tty2
+ttyS0::respawn:/bin/busybox getty -n -l /bin/sh -L 115200 ttyS0
+::ctrlaltdel:/bin/busybox reboot
+::shutdown:/bin/busybox umount -a -r
+::restart:/sbin/init
diff --git a/lib/mkiso/inittab-plasma b/lib/mkiso/inittab-plasma
new file mode 100644
index 0000000..5429000
--- /dev/null
+++ b/lib/mkiso/inittab-plasma
@@ -0,0 +1,8 @@
+# SPS Linux Plasma live: Wayland session on tty1 instead of a getty.
+::sysinit:/etc/sps/live-rc
+tty1::respawn:/etc/sps/live-plasma
+tty2::respawn:/bin/busybox getty -n -l /bin/sh 38400 tty2
+ttyS0::respawn:/bin/busybox getty -n -l /bin/sh -L 115200 ttyS0
+::ctrlaltdel:/bin/busybox reboot
+::shutdown:/bin/busybox umount -a -r
+::restart:/sbin/init
diff --git a/lib/mkiso/issue b/lib/mkiso/issue
index fd669f6..42e1aac 100644
--- a/lib/mkiso/issue
+++ b/lib/mkiso/issue
@@ -1,3 +1,3 @@
-SPS Linux live (console)
+SPS Linux live (Splux)
Mount a destination filesystem on /mnt, then run: setup
diff --git a/lib/mkiso/live-init b/lib/mkiso/live-init
index 491b29b..aedbd16 100755
--- a/lib/mkiso/live-init
+++ b/lib/mkiso/live-init
@@ -1,72 +1,29 @@
#!/bin/sh
-# PID 1 on the live squashfs. Bring up consoles and optionally start setup.
+# Compatibility wrapper. mkiso normally installs busybox or systemd as
+# /sbin/init (an ELF). If this script is still PID 1, hand off to a real
+# init. Do not exec a console here; that killed PID 1 on the old live TTY.
-PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-if [ -d /usr/lib64/go1.27.0/go/bin ]; then
- PATH=/usr/lib64/go1.27.0/go/bin:$PATH
-fi
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
-export HOME=/root
-export TERM=${TERM:-linux}
-export CARGO_HOME=${CARGO_HOME:-/root/.cargo}
-
-mkdir -p /proc /sys /dev /run /tmp /root /mnt /etc/sps
-mount -t proc proc /proc 2>/dev/null || :
-mount -t sysfs sysfs /sys 2>/dev/null || :
-mount -t devtmpfs devtmpfs /dev 2>/dev/null || :
-mkdir -p /dev/pts /dev/shm
-mount -t devpts devpts /dev/pts 2>/dev/null || :
-mount -t tmpfs tmpfs /run 2>/dev/null || :
-mount -t tmpfs tmpfs /tmp 2>/dev/null || :
-if command -v ldconfig >/dev/null 2>&1; then
- ldconfig 2>/dev/null || :
+if [ -x /usr/lib/systemd/systemd ]; then
+ exec /usr/lib/systemd/systemd "$@"
fi
-
-ln -sf /proc/self/fd /dev/fd 2>/dev/null || :
-ln -sf /proc/self/fd/0 /dev/stdin 2>/dev/null || :
-ln -sf /proc/self/fd/1 /dev/stdout 2>/dev/null || :
-ln -sf /proc/self/fd/2 /dev/stderr 2>/dev/null || :
-
-hostname sps-live 2>/dev/null || :
-[ -f /etc/hostname ] || printf '%s\n' sps-live >/etc/hostname
-
-# Load remaining modules from the live image if present.
-if [ -d /lib/modules ] && command -v depmod >/dev/null 2>&1; then
- depmod -a 2>/dev/null || :
+if [ -x /lib/systemd/systemd ]; then
+ exec /lib/systemd/systemd "$@"
fi
-for mod in e1000 e1000e r8169 virtio_net usbnet hid-generic usbhid atkbd
-do
- modprobe "$mod" 2>/dev/null || :
-done
-
-if command -v ip >/dev/null 2>&1; then
- ip link set lo up 2>/dev/null || :
+if [ -x /sbin/openrc-init ]; then
+ exec /sbin/openrc-init "$@"
fi
-if command -v dhcpcd >/dev/null 2>&1; then
- dhcpcd -b 2>/dev/null || :
-elif command -v dhclient >/dev/null 2>&1; then
- dhclient -nw 2>/dev/null || :
+if [ -x /bin/busybox ]; then
+ exec /bin/busybox init
fi
-cat /etc/issue 2>/dev/null || :
-cat /etc/motd 2>/dev/null || :
-
-cmdline=$(cat /proc/cmdline 2>/dev/null || :)
-case " $cmdline " in
- *" sps=setup "*)
- if command -v setup >/dev/null 2>&1; then
- setup || :
- fi
- ;;
-esac
-
-# Consoles: a root shell. There is no login prompt on the live image.
-# ttyS0 is for qemu -nographic; tty1 is a hardware VGA console.
-if [ -c /dev/tty1 ]; then
- setsid /bin/sh -c 'exec /bin/sh <>/dev/tty1 >&0 2>&1' &
-fi
-if [ -c /dev/ttyS0 ]; then
- exec setsid /bin/sh -c 'exec /bin/sh <>/dev/ttyS0 >&0 2>&1'
-fi
-exec /bin/sh </dev/console >/dev/console 2>&1
+# Last resort: stay PID 1 and spawn shells as children.
+while :
+do
+ [ -c /dev/tty1 ] && setsid /bin/sh -c 'exec /bin/sh <>/dev/tty1 >&0 2>&1' &
+ [ -c /dev/ttyS0 ] && setsid /bin/sh -c 'exec /bin/sh <>/dev/ttyS0 >&0 2>&1' &
+ wait
+ sleep 1
+done
diff --git a/lib/mkiso/live-plasma b/lib/mkiso/live-plasma
new file mode 100644
index 0000000..6595087
--- /dev/null
+++ b/lib/mkiso/live-plasma
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Plasma live session. Respawned by busybox init on tty1. Open Konsole
+# and run setup(1). If Plasma is missing, a shell stays on this tty.
+
+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+export PATH
+export HOME=/root
+export XDG_RUNTIME_DIR=/run/user/0
+export XDG_SESSION_TYPE=wayland
+export QT_QPA_PLATFORM=wayland
+export TERM=${TERM:-linux}
+
+mkdir -p "$XDG_RUNTIME_DIR"
+chmod 700 "$XDG_RUNTIME_DIR"
+
+# Live NVIDIA is nouveau. Keep the proprietary stack unloaded.
+modprobe nouveau 2>/dev/null || :
+
+if command -v dbus-daemon >/dev/null 2>&1; then
+ if [ ! -S /run/dbus/system_bus_socket ]; then
+ mkdir -p /run/dbus
+ dbus-daemon --system --fork 2>/dev/null || :
+ fi
+fi
+if command -v seatd >/dev/null 2>&1; then
+ seatd -g video >/dev/null 2>&1 &
+ sleep 1
+fi
+
+if command -v startplasma-wayland >/dev/null 2>&1; then
+ # Give the user a console they can see if Wayland dies.
+ chvt 1 2>/dev/null || :
+ exec startplasma-wayland
+fi
+
+printf '%s\n' "Plasma is not on this image; dropping to a shell on tty1."
+printf '%s\n' "Run setup from here, or use tty2 / ttyS0."
+exec /bin/sh
diff --git a/lib/mkiso/live-rc b/lib/mkiso/live-rc
new file mode 100644
index 0000000..4515cac
--- /dev/null
+++ b/lib/mkiso/live-rc
@@ -0,0 +1,90 @@
+#!/bin/sh
+# Sysinit for the live image. Child of busybox init, or a systemd oneshot.
+# Must not replace PID 1 and must not exec a console. The old live TTY did
+# that and the kernel reported "Attempted to kill init" (exit 127).
+
+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+if [ -d /usr/lib64/go1.27.0/go/bin ]; then
+ PATH=/usr/lib64/go1.27.0/go/bin:$PATH
+fi
+export PATH
+export HOME=/root
+export TERM=${TERM:-linux}
+export CARGO_HOME=${CARGO_HOME:-/root/.cargo}
+
+mkdir -p /proc /sys /dev /run /tmp /root /mnt /etc/sps
+mount -t proc proc /proc 2>/dev/null || :
+mount -t sysfs sysfs /sys 2>/dev/null || :
+mount -t devtmpfs devtmpfs /dev 2>/dev/null || :
+mkdir -p /dev/pts /dev/shm
+mount -t devpts devpts /dev/pts 2>/dev/null || :
+mount -t tmpfs tmpfs /run 2>/dev/null || :
+mount -t tmpfs tmpfs /tmp 2>/dev/null || :
+
+if command -v ldconfig >/dev/null 2>&1; then
+ ldconfig 2>/dev/null || :
+fi
+
+ln -sf /proc/self/fd /dev/fd 2>/dev/null || :
+ln -sf /proc/self/fd/0 /dev/stdin 2>/dev/null || :
+ln -sf /proc/self/fd/1 /dev/stdout 2>/dev/null || :
+ln -sf /proc/self/fd/2 /dev/stderr 2>/dev/null || :
+
+hostname splux-live 2>/dev/null || :
+[ -f /etc/hostname ] || printf '%s\n' splux-live >/etc/hostname
+
+# NVIDIA live path is nouveau. Do not load proprietary nvidia* next to KWin.
+mkdir -p /etc/modprobe.d
+if [ ! -f /etc/modprobe.d/sps-nouveau.conf ]; then
+ printf '%s\n' \
+ 'blacklist nvidia' \
+ 'blacklist nvidia_drm' \
+ 'blacklist nvidia_modeset' \
+ 'blacklist nvidia_uvm' \
+ 'options nouveau modeset=1' \
+ >/etc/modprobe.d/sps-nouveau.conf
+fi
+
+if [ -d /lib/modules ] && command -v depmod >/dev/null 2>&1; then
+ depmod -a 2>/dev/null || :
+fi
+for mod in e1000 e1000e r8169 virtio_net usbnet hid-generic usbhid atkbd \
+ nouveau bochs drm_kms_helper
+do
+ modprobe "$mod" 2>/dev/null || :
+done
+
+if command -v ip >/dev/null 2>&1; then
+ ip link set lo up 2>/dev/null || :
+fi
+if command -v dhcpcd >/dev/null 2>&1; then
+ dhcpcd -b 2>/dev/null || :
+elif command -v dhclient >/dev/null 2>&1; then
+ dhclient -nw 2>/dev/null || :
+fi
+
+# udev if present (eudev or systemd-udevd).
+if command -v udevd >/dev/null 2>&1; then
+ udevd --daemon 2>/dev/null || :
+ udevadm trigger --action=add 2>/dev/null || :
+ udevadm settle --timeout=5 2>/dev/null || :
+elif [ -x /lib/systemd/systemd-udevd ]; then
+ /lib/systemd/systemd-udevd --daemon 2>/dev/null || :
+ udevadm trigger --action=add 2>/dev/null || :
+ udevadm settle --timeout=5 2>/dev/null || :
+fi
+
+cat /etc/issue 2>/dev/null || :
+cat /etc/motd 2>/dev/null || :
+
+cmdline=$(cat /proc/cmdline 2>/dev/null || :)
+case " $cmdline " in
+ *" sps=setup "*)
+ # Child, not PID 1. setup can fail without taking the box down.
+ if command -v setup >/dev/null 2>&1; then
+ setup || :
+ fi
+ ;;
+esac
+
+exit 0
diff --git a/lib/mkiso/motd b/lib/mkiso/motd
index 595f2ba..64f7316 100644
--- a/lib/mkiso/motd
+++ b/lib/mkiso/motd
@@ -1,20 +1,19 @@
-SPS Linux live TTY
+SPS Linux live (Splux)
-This disc boots a console. It does not partition disks.
+This disc boots with a real init as PID 1: systemd if the machine that
+built the ISO had it, otherwise busybox init with gettys on tty1 and
+ttyS0. The shell on those ttys is a child of init, not init itself.
-1. Plug in or identify the destination disk (lsblk).
-2. Partition and format it yourself, then mount it on /mnt.
-3. Run: setup --target /mnt
- Or just: setup
- and point the target screen at /mnt.
+1. Identify the destination disk (lsblk).
+2. Run: setup
+ Guided partitioning is in the installer. You can also mount a
+ filesystem on /mnt and choose "use existing mounts".
-Optional: setup --plan --profile plasma-full --target /mnt
+setup asks which init to install: systemd (default) or OpenRC.
+Those packages conflict; sget will not install both.
-Recipes for sps-core and sps-extra are in /usr/src/sps. Package
-builds run on this live system into the mounted target. A Plasma
-install compiles Qt and KDE from source and takes a long time.
+On a Plasma live image, open Konsole and run setup. On this TTY image,
+run it from the shell.
-Kernel modules match the kernel on this disc. NVIDIA modules still
-need that kernel after the first boot of the installed system.
-
-Root has an open shell on the live image. Set passwords after install.
+SPS Linux is the distro (informally Splux). SPS the package system is
+separate; other projects can use it without this live image or installer.
diff --git a/lib/mkiso/plasma-bins b/lib/mkiso/plasma-bins
new file mode 100644
index 0000000..86aaf59
--- /dev/null
+++ b/lib/mkiso/plasma-bins
@@ -0,0 +1,33 @@
+# Binaries copied into a --session plasma live image from the host.
+# mkiso does not compile Qt or Plasma; it only copies a stack that already
+# works on the machine building the ISO. Konsole is the supported way to
+# run setup(1) from the live session.
+/usr/bin/startplasma-wayland
+/usr/bin/startplasma-x11
+/usr/bin/kwin_wayland
+/usr/bin/kwin_x11
+/usr/bin/plasmashell
+/usr/bin/konsole
+/usr/bin/dolphin
+/usr/bin/kwrite
+/usr/bin/kate
+/usr/bin/kded6
+/usr/bin/kdeinit6
+/usr/bin/kglobalacceld
+/usr/bin/kcminit
+/usr/bin/ksmserver
+/usr/bin/krunner
+/usr/bin/systemsettings
+/usr/bin/dbus-daemon
+/usr/bin/dbus-launch
+/usr/bin/dbus-run-session
+/usr/bin/dbus-uuidgen
+/usr/bin/seatd
+/usr/bin/Xorg
+/usr/bin/xauth
+/usr/bin/xrdb
+/usr/bin/xset
+/usr/bin/setxkbmap
+/usr/bin/pipewire
+/usr/bin/pipewire-pulse
+/usr/bin/wireplumber
diff --git a/lib/mkiso/plasma-motd b/lib/mkiso/plasma-motd
new file mode 100644
index 0000000..1210f06
--- /dev/null
+++ b/lib/mkiso/plasma-motd
@@ -0,0 +1,20 @@
+SPS Linux live Plasma (Splux)
+
+This disc boots a Plasma session on Wayland if the machine that built
+it already had a working Plasma stack. NVIDIA hardware uses nouveau;
+proprietary nvidia modules are blacklisted so they cannot load next
+to KWin.
+
+Open Konsole and run:
+
+ setup
+
+Guided partitioning is in setup. If Plasma does not start, switch to
+tty2 (Ctrl-Alt-F2) for a root shell.
+
+Optional: setup --plan --profile plasma-full --target /mnt
+
+Recipes for sps-core and sps-extra are in /usr/src/sps. Package builds
+run on this live system into the mounted target. A from-source Plasma
+install compiles Qt and KDE and takes a long time. This live session is
+the host stack copied at ISO build time.
diff --git a/lib/mkiso/plasma-trees b/lib/mkiso/plasma-trees
new file mode 100644
index 0000000..e745617
--- /dev/null
+++ b/lib/mkiso/plasma-trees
@@ -0,0 +1,39 @@
+# Directory trees copied into a --session plasma live image.
+# Keep this list to prefixes the live session actually needs. Copying all of
+# /usr is how you accidentally ship a 4G ISO.
+/usr/lib64/qt6
+/usr/lib/qt6
+/usr/share/qt6
+/usr/share/plasma
+/usr/share/kservicetypes6
+/usr/share/kservices6
+/usr/share/qlogging-categories6
+/usr/share/wayland-sessions
+/usr/share/xsessions
+/usr/share/konsole
+/usr/share/applications
+/usr/share/desktop-directories
+/usr/share/icons
+/usr/share/mime
+/usr/share/knotifications6
+/usr/share/kglobalaccel
+/usr/share/kded6
+/usr/share/dbus-1
+/usr/share/polkit-1
+/usr/share/wayland-protocols
+/usr/share/libdrm
+/usr/share/X11
+/usr/lib64/xorg
+/usr/lib/xorg
+/etc/X11
+/etc/xdg
+/usr/lib64/dri
+/usr/lib/dri
+/usr/lib64/gbm
+/usr/lib64/libkwin.so
+/lib/firmware/nvidia
+/lib/firmware/nouveau
+/usr/lib64/pipewire-0.3
+/usr/lib64/spa-0.2
+/usr/share/pipewire
+/usr/share/wireplumber
diff --git a/lib/mkiso/seed-bins b/lib/mkiso/seed-bins
index c6b3be3..8517465 100644
--- a/lib/mkiso/seed-bins
+++ b/lib/mkiso/seed-bins
@@ -125,3 +125,7 @@
/usr/lib64/libstdc++.so.6
/usr/lib64/libgomp.so.1
/usr/lib64/libatomic.so.1
+/usr/bin/setsid
+/sbin/agetty
+/usr/bin/getty
+
diff --git a/lib/mkiso/systemd/sps-live-plasma.service b/lib/mkiso/systemd/sps-live-plasma.service
new file mode 100644
index 0000000..3da6937
--- /dev/null
+++ b/lib/mkiso/systemd/sps-live-plasma.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=SPS Linux live Plasma (Wayland)
+After=sps-live-rc.service
+Conflicts=getty@tty1.service
+
+[Service]
+Type=simple
+TTYPath=/dev/tty1
+TTYReset=yes
+TTYVHangup=yes
+StandardInput=tty
+StandardOutput=tty
+ExecStart=/etc/sps/live-plasma
+Restart=on-failure
+RestartSec=2
+
+[Install]
+WantedBy=graphical.target
diff --git a/lib/mkiso/systemd/sps-live-rc.service b/lib/mkiso/systemd/sps-live-rc.service
new file mode 100644
index 0000000..9892122
--- /dev/null
+++ b/lib/mkiso/systemd/sps-live-rc.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=SPS Linux live sysinit
+DefaultDependencies=no
+After=systemd-udevd.service
+Before=getty.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/etc/sps/live-rc
+StandardOutput=journal+console
+
+[Install]
+WantedBy=multi-user.target
+WantedBy=graphical.target
diff --git a/lib/mkiso/systemd/sps-live-serial.service b/lib/mkiso/systemd/sps-live-serial.service
new file mode 100644
index 0000000..c099776
--- /dev/null
+++ b/lib/mkiso/systemd/sps-live-serial.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=SPS Linux live serial getty
+After=sps-live-rc.service
+ConditionPathExists=/dev/ttyS0
+
+[Service]
+ExecStart=-/sbin/agetty -n -l /bin/sh -L 115200 ttyS0
+Type=idle
+Restart=always
+RestartSec=1
+TTYPath=/dev/ttyS0
+TTYReset=yes
+TTYVHangup=yes
+IgnoreSIGPIPE=no
+SendSIGHUP=yes
+
+[Install]
+WantedBy=multi-user.target
+WantedBy=graphical.target
diff --git a/lib/recipe.awk b/lib/recipe.awk
index e3e1a1d..6ae3952 100644
--- a/lib/recipe.awk
+++ b/lib/recipe.awk
@@ -115,7 +115,8 @@ function recipe_process(line, key, value, separator) {
if (key == "name" || key == "version" || key == "release" ||
key == "arch" || key == "description") {
recipe_set_once(key, value)
- } else if (key == "depend" || key == "builddep" || key == "optional") {
+ } else if (key == "depend" || key == "builddep" || key == "optional" ||
+ key == "conflict") {
recipe_append_dependency(key, value)
} else if (key == "source") {
recipe_source_count++
@@ -181,6 +182,7 @@ END {
recipe_value["depend"] = recipe_expand(recipe_value["depend"])
recipe_value["builddep"] = recipe_expand(recipe_value["builddep"])
recipe_value["optional"] = recipe_expand(recipe_value["optional"])
+ recipe_value["conflict"] = recipe_expand(recipe_value["conflict"])
for (recipe_i = 1; recipe_i <= recipe_hash_count; recipe_i++) {
recipe_hash_value = recipe_expand(recipe_hash[recipe_i])
@@ -213,6 +215,7 @@ END {
recipe_valid_dependency_list("dependency", recipe_value["depend"])
recipe_valid_dependency_list("build dependency", recipe_value["builddep"])
recipe_valid_dependency_list("optional dependency", recipe_value["optional"])
+ recipe_valid_dependency_list("conflict", recipe_value["conflict"])
if (recipe_repo !~ /^[[:alnum:]][[:alnum:].+_-]*$/)
recipe_error("invalid repository name '" recipe_repo "'")
@@ -231,5 +234,6 @@ END {
recipe_value["release"], recipe_value["arch"],
recipe_value["depend"], recipe_value["builddep"],
recipe_value["optional"], recipe_repo, recipe_priority,
- recipe_path, recipe_value["description"], tolower(recipe_sha256)
+ recipe_path, recipe_value["description"], tolower(recipe_sha256),
+ recipe_value["conflict"]
}
diff --git a/lib/relations.awk b/lib/relations.awk
index e2d27dc..12f51fd 100644
--- a/lib/relations.awk
+++ b/lib/relations.awk
@@ -56,7 +56,7 @@ function rel_find(cur, target, i,d) {
BEGIN { FS=OFS="\t" }
/^$/ { next }
{
- if (NF != 12) { rel_err(FILENAME ":" FNR ": malformed index record"); next }
+ if (NF != 12 && NF != 13) { rel_err(FILENAME ":" FNR ": malformed index record"); next }
name=$1; prio=$9+0
if (!(name in selected) || prio > priority[name]) {
selected[name]=1
diff --git a/lib/repository.awk b/lib/repository.awk
index 9f943ad..3df82a5 100644
--- a/lib/repository.awk
+++ b/lib/repository.awk
@@ -152,7 +152,8 @@ END {
repository_index_fields_count = split(repository_index_line,
repository_index_fields,
"\t")
- if (repository_index_fields_count == 12)
+ if (repository_index_fields_count == 12 ||
+ repository_index_fields_count == 13)
repository_package_count[repository_index_fields[8]]++
}
if (repository_getline == 0)
diff --git a/lib/setup/extras b/lib/setup/extras
index b6269f9..1b14166 100644
--- a/lib/setup/extras
+++ b/lib/setup/extras
@@ -69,4 +69,22 @@ vulkan-tools vulkaninfo
ethtool ethtool
nftables nftables
wireguard-tools WireGuard tools
+intel-ucode Intel CPU microcode
+amd-ucode AMD CPU microcode
+nouveau Nouveau (NVIDIA open driver)
+linux-desktop Linux kernel, daily desktop config
+linux Linux kernel, upstream defconfig
+xorg-server X.Org server
+mpv mpv media player
+gimp GNU Image Manipulation Program
+inkscape Inkscape
+libreoffice LibreOffice
+krita Krita
+thunderbird Thunderbird
+qemu QEMU
+htop htop
+btop btop
+tmux tmux
+screen GNU Screen
+
diff --git a/lib/setup/hwdetect.sh b/lib/setup/hwdetect.sh
new file mode 100644
index 0000000..379f3da
--- /dev/null
+++ b/lib/setup/hwdetect.sh
@@ -0,0 +1,103 @@
+# Hardware -> package mapping for the SPS Linux installer.
+#
+# Sourced, not executed. Reads lspci -nn and /proc/cpuinfo. The live ISO and
+# an installed system both have pciutils in the usual place; if lspci is
+# missing we just print nothing and the installer leaves drivers off.
+
+setup_hw_lspci()
+{
+ PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH
+ export PATH
+ if command -v lspci >/dev/null 2>&1; then
+ lspci -nn 2>/dev/null || :
+ fi
+}
+
+setup_hw_cpu_vendor()
+{
+ awk -F: '
+ tolower($1) ~ /vendor_id/ {
+ gsub(/^[ \t]+|[ \t]+$/, "", $2)
+ print $2
+ exit
+ }
+ ' /proc/cpuinfo 2>/dev/null || :
+}
+
+# Print package names, one per line. Idempotent; caller dedupes.
+setup_hw_packages()
+{
+ setup_pci=$(setup_hw_lspci)
+ setup_cpu=$(setup_hw_cpu_vendor)
+
+ # CPU microcode. Intel and AMD both want the matching early-load blob.
+ case $setup_cpu in
+ GenuineIntel) printf '%s\n' intel-ucode ;;
+ AuthenticAMD) printf '%s\n' amd-ucode ;;
+ esac
+
+ # Always useful on real hardware; cheap no-op on a VM without blobs.
+ printf '%s\n' linux-firmware mesa
+
+ # GPUs. Nouveau is the live/install default for NVIDIA. The proprietary
+ # stack is a separate yes/no in setup; we do not pull it in from here.
+ if printf '%s\n' "$setup_pci" | grep -q 'VGA compatible controller.*\[10de:'; then
+ printf '%s\n' nouveau
+ fi
+ if printf '%s\n' "$setup_pci" | grep -q '3D controller.*\[10de:'; then
+ printf '%s\n' nouveau
+ fi
+ if printf '%s\n' "$setup_pci" | grep -q 'VGA compatible controller.*\[8086:'; then
+ printf '%s\n' mesa
+ fi
+ if printf '%s\n' "$setup_pci" | grep -q 'VGA compatible controller.*\[1002:'; then
+ printf '%s\n' mesa amd-ucode
+ fi
+
+ # Intel wireless (CNVi and the usual Wi-Fi class).
+ if printf '%s\n' "$setup_pci" | grep -q 'Network controller.*\[8086:'; then
+ printf '%s\n' iwd linux-firmware
+ fi
+ if printf '%s\n' "$setup_pci" | grep -q '\[0280\].*\[8086:'; then
+ printf '%s\n' iwd linux-firmware
+ fi
+
+ # Broadcom / Realtek wireless: firmware plus iwd is enough to associate.
+ if printf '%s\n' "$setup_pci" | grep -q 'Network controller.*\[14e4:'; then
+ printf '%s\n' iwd linux-firmware
+ fi
+ if printf '%s\n' "$setup_pci" | grep -q 'Network controller.*\[10ec:'; then
+ printf '%s\n' iwd linux-firmware
+ fi
+
+ # Wired Intel often just needs e1000e in the kernel; still pull firmware.
+ if printf '%s\n' "$setup_pci" | grep -q 'Ethernet controller.*\[8086:'; then
+ printf '%s\n' linux-firmware
+ fi
+
+ # Audio: PipeWire userspace is in the Plasma profiles; ALSA utils help
+ # on a console box.
+ if printf '%s\n' "$setup_pci" | grep -q 'Audio device'; then
+ printf '%s\n' alsa-utils
+ fi
+
+ # NVMe / SATA management tools when those controllers are present.
+ if printf '%s\n' "$setup_pci" | grep -q 'Non-Volatile memory controller'; then
+ printf '%s\n' nvme-cli
+ fi
+ if printf '%s\n' "$setup_pci" | grep -q 'SATA controller'; then
+ printf '%s\n' smartmontools
+ fi
+}
+
+setup_hw_summary()
+{
+ setup_pci=$(setup_hw_lspci)
+ setup_cpu=$(setup_hw_cpu_vendor)
+ printf 'CPU vendor: %s\n' "${setup_cpu:-unknown}"
+ printf '%s\n' "$setup_pci" | awk '
+ /VGA compatible controller|3D controller|Network controller|Ethernet controller|Audio device|SATA controller|Non-Volatile memory/ {
+ print
+ }
+ '
+}
diff --git a/lib/setup/partition.sh b/lib/setup/partition.sh
new file mode 100644
index 0000000..23e2f5f
--- /dev/null
+++ b/lib/setup/partition.sh
@@ -0,0 +1,282 @@
+# Disk helpers for the SPS Linux (Splux) installer.
+#
+# This is sourced, not executed. Guided layouts run sfdisk(8) and mkfs only
+# after the administrator has confirmed twice. setup never calls grub-install
+# from here; bootloader install is a separate, explicit step.
+#
+# PATH on a live image often omits /sbin. Look there first.
+
+setup_part_path()
+{
+ setup_old_path=$PATH
+ PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH
+ export PATH
+ command -v "$1" 2>/dev/null || {
+ PATH=$setup_old_path
+ return 1
+ }
+}
+
+setup_partname()
+{
+ # nvme0n1 -> nvme0n1p1; sda -> sda1
+ setup_disk=$1
+ setup_n=$2
+ case $setup_disk in
+ *[0-9]) printf '%sp%s\n' "$setup_disk" "$setup_n" ;;
+ *) printf '%s%s\n' "$setup_disk" "$setup_n" ;;
+ esac
+}
+
+setup_lsblk()
+{
+ setup_part_path lsblk >/dev/null || return 1
+ lsblk "$@"
+}
+
+setup_disk_menu_lines()
+{
+ # NAME SIZE MODEL TRAN, one disk per line. Skip loop/rom.
+ setup_lsblk -dn -p -o NAME,SIZE,TYPE,TRAN,MODEL 2>/dev/null |
+ awk '$3 == "disk" { print }'
+}
+
+setup_partition_tool_ok()
+{
+ setup_part_path sfdisk >/dev/null || return 1
+ setup_part_path mkfs.ext4 >/dev/null || return 1
+ setup_part_path mkfs.vfat >/dev/null || return 1
+ setup_part_path mkswap >/dev/null || return 1
+ return 0
+}
+
+setup_wipe_disk()
+{
+ setup_d=$1
+ if setup_part_path wipefs >/dev/null; then
+ wipefs -a "$setup_d" >/dev/null 2>&1 || :
+ fi
+}
+
+setup_sfdisk_apply()
+{
+ setup_d=$1
+ setup_script=$2
+ setup_part_path sfdisk >/dev/null || return 1
+ sfdisk --wipe always --wipe-partitions always "$setup_d" <"$setup_script"
+}
+
+setup_write_guided_script()
+{
+ # $1 = dest file, $2 = layout, $3 = swap size (sfdisk size token, e.g. 8GiB)
+ setup_out=$1
+ setup_layout=$2
+ setup_swap=${3:-8GiB}
+ case $setup_layout in
+ efi-root)
+ cat >"$setup_out" <<'EOF'
+label: gpt
+size=512MiB, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name=EFI
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
+EOF
+ ;;
+ efi-swap-root)
+ cat >"$setup_out" <<EOF
+label: gpt
+size=512MiB, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name=EFI
+size=$setup_swap, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=swap
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
+EOF
+ ;;
+ efi-swap-root-home)
+ cat >"$setup_out" <<EOF
+label: gpt
+size=512MiB, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name=EFI
+size=$setup_swap, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=swap
+size=48GiB, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=home
+EOF
+ ;;
+ bios-root)
+ cat >"$setup_out" <<'EOF'
+label: gpt
+size=1MiB, type=21686148-6449-6E6F-744E-656564454649, name=BIOS
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
+EOF
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+setup_assign_guided_parts()
+{
+ setup_d=$1
+ setup_layout=$2
+ setup_part_efi=
+ setup_part_swap=
+ setup_part_root=
+ setup_part_home=
+ setup_part_bios=
+ case $setup_layout in
+ efi-root)
+ setup_part_efi=$(setup_partname "$setup_d" 1)
+ setup_part_root=$(setup_partname "$setup_d" 2)
+ ;;
+ efi-swap-root)
+ setup_part_efi=$(setup_partname "$setup_d" 1)
+ setup_part_swap=$(setup_partname "$setup_d" 2)
+ setup_part_root=$(setup_partname "$setup_d" 3)
+ ;;
+ efi-swap-root-home)
+ setup_part_efi=$(setup_partname "$setup_d" 1)
+ setup_part_swap=$(setup_partname "$setup_d" 2)
+ setup_part_root=$(setup_partname "$setup_d" 3)
+ setup_part_home=$(setup_partname "$setup_d" 4)
+ ;;
+ bios-root)
+ setup_part_bios=$(setup_partname "$setup_d" 1)
+ setup_part_root=$(setup_partname "$setup_d" 2)
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+setup_mount_prepared()
+{
+ setup_tgt=${1%/}
+ [ -n "$setup_part_root" ] || return 1
+ mkdir -p "$setup_tgt" || return 1
+ mount "$setup_part_root" "$setup_tgt" || return 1
+ if [ -n "$setup_part_efi" ]; then
+ mkdir -p "$setup_tgt/boot/efi"
+ mount "$setup_part_efi" "$setup_tgt/boot/efi" || return 1
+ fi
+ if [ -n "$setup_part_home" ]; then
+ mkdir -p "$setup_tgt/home"
+ mount "$setup_part_home" "$setup_tgt/home" || return 1
+ fi
+ if [ -n "$setup_part_swap" ]; then
+ swapon "$setup_part_swap" 2>/dev/null || :
+ fi
+ return 0
+}
+
+setup_format_and_mount()
+{
+ setup_tgt=${1%/}
+ [ -n "$setup_part_root" ] || return 1
+ mkdir -p "$setup_tgt" || return 1
+
+ if [ -n "$setup_part_efi" ]; then
+ mkfs.vfat -F 32 -n SPS_EFI "$setup_part_efi" || return 1
+ fi
+ if [ -n "$setup_part_swap" ]; then
+ mkswap -L sps-swap "$setup_part_swap" || return 1
+ fi
+ mkfs.ext4 -F -L sps-root "$setup_part_root" || return 1
+ if [ -n "$setup_part_home" ]; then
+ mkfs.ext4 -F -L sps-home "$setup_part_home" || return 1
+ fi
+
+ mount "$setup_part_root" "$setup_tgt" || return 1
+ if [ -n "$setup_part_efi" ]; then
+ mkdir -p "$setup_tgt/boot/efi"
+ mount "$setup_part_efi" "$setup_tgt/boot/efi" || return 1
+ fi
+ if [ -n "$setup_part_home" ]; then
+ mkdir -p "$setup_tgt/home"
+ mount "$setup_part_home" "$setup_tgt/home" || return 1
+ fi
+ if [ -n "$setup_part_swap" ]; then
+ swapon "$setup_part_swap" 2>/dev/null || :
+ fi
+ return 0
+}
+
+setup_uuid_of()
+{
+ setup_p=$1
+ [ -n "$setup_p" ] || return 0
+ if setup_part_path blkid >/dev/null; then
+ blkid -s UUID -o value "$setup_p" 2>/dev/null || :
+ fi
+}
+
+setup_write_fstab()
+{
+ setup_tgt=${1%/}
+ setup_fstab=$setup_tgt/etc/fstab
+ mkdir -p "$setup_tgt/etc" || return 1
+ {
+ printf '%s\n' '# /etc/fstab written by SPS setup. Review before the first boot.'
+ setup_u=$(setup_uuid_of "$setup_part_root")
+ if [ -n "$setup_u" ]; then
+ printf 'UUID=%s / ext4 defaults 1 1\n' "$setup_u"
+ elif [ -n "$setup_part_root" ]; then
+ printf '%s / ext4 defaults 1 1\n' "$setup_part_root"
+ fi
+ setup_u=$(setup_uuid_of "$setup_part_efi")
+ if [ -n "$setup_u" ]; then
+ printf 'UUID=%s /boot/efi vfat umask=0077 0 2\n' "$setup_u"
+ elif [ -n "$setup_part_efi" ]; then
+ printf '%s /boot/efi vfat umask=0077 0 2\n' "$setup_part_efi"
+ fi
+ setup_u=$(setup_uuid_of "$setup_part_home")
+ if [ -n "$setup_u" ]; then
+ printf 'UUID=%s /home ext4 defaults 1 2\n' "$setup_u"
+ elif [ -n "$setup_part_home" ]; then
+ printf '%s /home ext4 defaults 1 2\n' "$setup_part_home"
+ fi
+ setup_u=$(setup_uuid_of "$setup_part_swap")
+ if [ -n "$setup_u" ]; then
+ printf 'UUID=%s none swap defaults 0 0\n' "$setup_u"
+ elif [ -n "$setup_part_swap" ]; then
+ printf '%s none swap defaults 0 0\n' "$setup_part_swap"
+ fi
+ } >"$setup_fstab"
+}
+
+setup_apply_partition()
+{
+ # Destructive. Caller already confirmed.
+ setup_tgt=$1
+ case $setup_partition in
+ none|existing|'')
+ return 0
+ ;;
+ esac
+ [ -n "$setup_disk" ] || return 1
+ setup_partition_tool_ok || return 1
+ case $setup_partition in
+ efi-root|efi-swap-root|efi-swap-root-home|bios-root)
+ setup_script=$setup_work/sfdisk.script
+ setup_write_guided_script "$setup_script" "$setup_partition" \
+ "${setup_swap_size:-8GiB}" || return 1
+ setup_wipe_disk "$setup_disk"
+ setup_sfdisk_apply "$setup_disk" "$setup_script" || return 1
+ # Let the kernel reread the partition table.
+ if setup_part_path partprobe >/dev/null; then
+ partprobe "$setup_disk" 2>/dev/null || :
+ fi
+ sleep 1
+ setup_assign_guided_parts "$setup_disk" "$setup_partition" || return 1
+ setup_format_and_mount "$setup_tgt" || return 1
+ ;;
+ manage)
+ [ -n "$setup_part_root" ] || return 1
+ if [ "${setup_format:-no}" = yes ]; then
+ setup_format_and_mount "$setup_tgt" || return 1
+ else
+ setup_mount_prepared "$setup_tgt" || return 1
+ fi
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ return 0
+}
diff --git a/lib/setup/sets/drivers.set b/lib/setup/sets/drivers.set
new file mode 100644
index 0000000..06aa9df
--- /dev/null
+++ b/lib/setup/sets/drivers.set
@@ -0,0 +1,9 @@
+name drivers
+title Detected drivers
+description Mesa, nouveau, firmware, and related GPU/NIC helpers. Auto-detection also adds these.
+default 0
+package mesa
+package nouveau
+package linux-firmware
+package intel-ucode
+package amd-ucode
diff --git a/lib/setup/sets/firmware.set b/lib/setup/sets/firmware.set
index 7ac51fd..93b9607 100644
--- a/lib/setup/sets/firmware.set
+++ b/lib/setup/sets/firmware.set
@@ -1,5 +1,5 @@
name firmware
title Firmware
description linux-firmware blob collection. Needed for many NICs and GPUs.
-default 0
+default 1
package linux-firmware
diff --git a/man/mkiso.1 b/man/mkiso.1
index daedc93..92b0f99 100644
--- a/man/mkiso.1
+++ b/man/mkiso.1
@@ -1,6 +1,6 @@
.TH MKISO 1 "2026-08-31" "SPS 1.0" "SPS Commands"
.SH NAME
-mkiso \- build an SPS live TTY ISO
+mkiso \- build an SPS Linux live ISO (Splux)
.SH SYNOPSIS
.nf
mkiso --output FILE.iso [options]
@@ -8,16 +8,20 @@ mkiso --layout DIR [options]
.fi
.SH DESCRIPTION
.B mkiso
-builds a hybrid ISO that boots to a text console with
+builds a hybrid ISO for
+.B SPS Linux
+(informally Splux). The image boots with a real init as PID 1 and
.BR setup (1)
-on PATH. Flash it to USB, boot, mount a destination filesystem on
-.IR /mnt ,
-and run
-.B setup .
+on PATH. Default
+.B --init
+is systemd. If the build host has no systemd binaries, busybox init
+respawns gettys on tty1 and ttyS0 instead of exec-ing a shell as PID 1.
+SPS the package system does not require these images.
.PP
-The image does not partition disks and never runs
-.B grub-install
-on a hard disk.
+Flash it to USB, boot, and run
+.B setup .
+.B --session plasma
+copies a host Plasma/Wayland stack; NVIDIA live graphics use nouveau.
.B --layout
writes the live root only, for tests and inspection.
.PP
@@ -34,6 +38,17 @@ Write a bootable ISO.
.BI --layout " dir"
Populate a live root and exit.
.TP
+.BI --session " name"
+.B tty
+or
+.B plasma .
+.TP
+.BI --init " name"
+.B systemd
+or
+.B openrc .
+Default systemd.
+.TP
.BI --busybox " file"
Static busybox used for the initramfs and live
.BR /bin/busybox .
diff --git a/man/setup.1 b/man/setup.1
index 48a76a5..d86c7e4 100644
--- a/man/setup.1
+++ b/man/setup.1
@@ -1,6 +1,6 @@
.TH SETUP 1 "2026-08-31" "SPS 1.0" "SPS Commands"
.SH NAME
-setup \- dialog-based SPS system installer
+setup \- dialog-based SPS Linux installer (Splux)
.SH SYNOPSIS
.nf
setup [options]
@@ -10,7 +10,14 @@ setup --plan [options]
.fi
.SH DESCRIPTION
.B setup
-installs an SPS system into a target root. Interactive use is a sequence of
+installs
+.B SPS Linux
+(informally Splux) into a target root. It is the distro installer, not a
+generic Linux From Scratch helper. SPS the package system can be used on
+other trees without running this program; partitioning, users, and boot
+layout on those trees are yours to decide.
+.PP
+Interactive use is a sequence of
.BR dialog (1)
screens in the same spirit as the Slackware and FreeBSD installers: profile,
optional package sets, extra packages, locale, hostname, timezone, keymap,
@@ -24,14 +31,21 @@ or
does not start
.BR dialog (1).
.PP
-The installer does not partition disks, format filesystems, or run
+The installer does not run
.B grub-install
unless
.B --install-bootloader
-is given, and even then it refuses to guess a disk. Bootloader instructions
-are written to
-.I /root/sps-bootloader.txt
-in the target.
+is given with
+.B --disk .
+Guided partitioning waits until the last confirmation.
+.B --init systemd
+or
+.B --init openrc
+selects PID 1;
+.B sget
+will not install both.
+Live images prefer systemd. If the build host has no systemd binaries,
+busybox init runs instead and respawns gettys on tty1 and ttyS0.
.SH PROFILES
.TP
.B minimal
@@ -120,6 +134,11 @@ First unprivileged account (uid 1000). The password is left locked; see
.B --flatpak yes|no
Install the Flatpak set and attempt to add Flathub after packages land.
.TP
+.B --init systemd|openrc
+PID 1 on the installed system. Scripts default to none so a fixture
+install does not pull systemd. The interactive screen defaults to
+systemd.
+.TP
.B --nvidia yes|no
Install NVIDIA open kernel modules and utilities. A matching kernel is
required; this installer does not build one.
diff --git a/tests/run b/tests/run
index 0c47769..a09670a 100755
--- a/tests/run
+++ b/tests/run
@@ -7,6 +7,12 @@ case $0 in
esac
test_dir=$(CDPATH= cd -- "$test_dir" 2>/dev/null && pwd)
+# A developer shell that has run src/sget exports SPS_* into the environment.
+# Drop those so `make check` does not read the host config by accident.
+unset SPS_ROOT SPS_CONFIG SPS_REPOS_CONFIG SPS_DB SPS_CACHE SPS_BUILD \
+ SPS_LIBDIR SPS_REPO_ROOT SPS_COMPRESSION SPS_ARCH SPS_PRESERVE \
+ SPS_MAKEJOBS 2>/dev/null || :
+
status=0
count=0
for test_file in "$test_dir"/test_*.sh; do
diff --git a/tests/test_conflicts.sh b/tests/test_conflicts.sh
new file mode 100644
index 0000000..7f41c5f
--- /dev/null
+++ b/tests/test_conflicts.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+# sget refuses systemd/OpenRC-style recipe conflicts.
+
+set -eu
+
+case $0 in */*) test_dir=${0%/*} ;; *) test_dir=. ;; esac
+project_dir=$(CDPATH= cd "$test_dir/.." 2>/dev/null && pwd) || exit 1
+fail() { printf 'test_conflicts: %s\n' "$*" >&2; exit 1; }
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/sps-conflicts.XXXXXX") || fail mktemp
+trap 'rm -rf "$tmp"' 0 HUP INT TERM
+
+mkdir -p "$tmp/root" "$tmp/cache" "$tmp/db" "$tmp/build" \
+ "$tmp/repo/alpha" "$tmp/repo/beta" "$tmp/repo/ok"
+: >"$tmp/sps.conf"
+printf 'dir test %s 10\n' "$tmp/repo" >"$tmp/repos.conf"
+
+write_recipe()
+{
+ dir=$1
+ name=$2
+ shift 2
+ {
+ printf 'name %s\n' "$name"
+ printf '%s\n' 'version 1.0' 'release 1' "description $name"
+ for rec
+ do
+ printf '%s\n' "$rec"
+ done
+ printf '%s\n' 'install mkdir -p "$PKG/usr/share/sps-test"'
+ printf 'install printf %%s\\n %s >"$PKG/usr/share/sps-test/%s"\n' \
+ "$name" "$name"
+ } >"$dir/recipe"
+}
+
+write_recipe "$tmp/repo/alpha" alpha 'conflict beta'
+write_recipe "$tmp/repo/beta" beta 'conflict alpha'
+write_recipe "$tmp/repo/ok" ok
+
+SPS_ROOT=$tmp/root
+SPS_DB=$tmp/db
+SPS_CACHE=$tmp/cache
+SPS_BUILD=$tmp/build
+SPS_CONFIG=$tmp/sps.conf
+SPS_REPOS_CONFIG=$tmp/repos.conf
+export SPS_ROOT SPS_DB SPS_CACHE SPS_BUILD SPS_CONFIG SPS_REPOS_CONFIG
+export SPS_LIBDIR=$project_dir/lib
+export PATH=$project_dir/bin:$PATH
+
+src update >/dev/null
+
+show=$(src show alpha)
+case $show in
+ *'conflicts: beta'*) ;;
+ *) fail "src show did not list conflict: $show" ;;
+esac
+
+set +e
+sget install --plan alpha beta >/dev/null 2>"$tmp/err"
+st=$?
+set -e
+[ "$st" -ne 0 ] || fail 'sget install --plan alpha beta should fail'
+grep -q conflict "$tmp/err" || fail "error did not mention conflict: $(cat "$tmp/err")"
+
+sget install --plan alpha ok >/dev/null || fail 'alpha+ok should resolve'
+sget install --plan ok >/dev/null || fail 'ok should resolve'
diff --git a/tests/test_install.sh b/tests/test_install.sh
index 046cd4a..d3cf153 100755
--- a/tests/test_install.sh
+++ b/tests/test_install.sh
@@ -44,9 +44,12 @@ cp "$project_dir/examples/sps.conf" "$root/etc/sps/sps.conf"
printf 'repo examples %s 100\n' "$project_dir/examples" >"$root/etc/sps/repos.conf"
SPS_ROOT=$root
+SPS_CONFIG=$root/etc/sps/sps.conf
+SPS_REPOS_CONFIG=$root/etc/sps/repos.conf
SPS_COMPRESSION=none
PATH=$image/usr/bin:$PATH
-export SPS_ROOT SPS_COMPRESSION PATH
+export SPS_ROOT SPS_CONFIG SPS_REPOS_CONFIG SPS_COMPRESSION PATH
+unset SPS_LIBDIR SPS_DB SPS_CACHE SPS_BUILD SPS_REPO_ROOT
"$image/usr/bin/src" update >/dev/null || fail 'installed src update failed'
plan=$("$image/usr/bin/sget" install --plan hello-sps) ||
diff --git a/tests/test_mkiso.sh b/tests/test_mkiso.sh
index c36c714..ac20ce3 100755
--- a/tests/test_mkiso.sh
+++ b/tests/test_mkiso.sh
@@ -20,6 +20,14 @@ case $help in
*'--with-firmware'*) ;;
*) fail 'help missing --with-firmware' ;;
esac
+case $help in
+ *'--init'*) ;;
+ *) fail 'help missing --init' ;;
+esac
+case $help in
+ *'--session'*) ;;
+ *) fail 'help missing --session' ;;
+esac
tmp=$(mktemp -d "${TMPDIR:-/tmp}/sps-test-mkiso.XXXXXX") || fail mktemp
trap 'rm -rf "$tmp"' 0 HUP INT TERM
@@ -40,8 +48,19 @@ fi
[ -f "$layout/etc/sps/live" ] || fail 'live marker missing'
[ -f "$layout/etc/motd" ] || fail 'motd missing'
[ -x "$layout/sbin/init" ] || fail 'live init missing'
+[ -x "$layout/etc/sps/live-rc" ] || fail 'live-rc missing'
+[ -f "$layout/etc/inittab" ] || fail 'inittab missing'
+[ "$(cat "$layout/etc/sps/init")" = systemd ] || fail 'live default init is not systemd'
[ -x "$layout/usr/bin/setup" ] || fail 'setup not installed into layout'
grep -q 'setup' "$layout/etc/motd" || fail 'motd does not mention setup'
+# PID 1 must not be the old script that execs a console (exit 127 panic).
+if [ -f "$layout/sbin/init" ]; then
+ init_head=$(head -c 2 "$layout/sbin/init" 2>/dev/null || :)
+ if [ "$init_head" = '#!' ] && grep -q 'exec setsid' "$layout/sbin/init"
+ then
+ fail 'PID 1 still execs a console as init'
+ fi
+fi
[ -f "$layout/etc/sps/repos.conf" ] || fail 'repos.conf missing'
if [ -x "$layout/bin/busybox" ] && [ -L "$layout/bin/mkdir" ]; then
target=$(readlink "$layout/bin/mkdir")
diff --git a/tests/test_recipe_contract.sh b/tests/test_recipe_contract.sh
index 4fc62b0..bdf0b2f 100755
--- a/tests/test_recipe_contract.sh
+++ b/tests/test_recipe_contract.sh
@@ -90,7 +90,7 @@ EOF_VALID
valid_record=$(parse_recipe "$tmp/valid/recipe") ||
fail 'complete recipe was rejected by the index parser'
printf '%s\n' "$valid_record" | awk -F '\t' '
- NF == 12 && $1 == "contract-valid" && $2 == "1.0" &&
+ NF == 13 && $1 == "contract-valid" && $2 == "1.0" &&
$3 == "1" && $4 == "any" { valid=1 }
END { exit !valid }
' || fail 'complete recipe produced the wrong index identity'
diff --git a/tests/test_repository.sh b/tests/test_repository.sh
index 1b9b730..8f106e0 100755
--- a/tests/test_repository.sh
+++ b/tests/test_repository.sh
@@ -90,7 +90,7 @@ contains "$update_output" 'updated 3 repositories, 4 package records'
index=$tmp/cache/indexes/packages.index
[ -r "$index" ] || fail 'aggregate index was not created'
-awk -F '\t' 'NF != 12 { bad = 1 }
+awk -F '\t' 'NF != 12 && NF != 13 { bad = 1 }
END { exit bad || NR != 4 }' "$index" ||
fail 'aggregate index does not contain four valid records'
diff --git a/tests/test_setup.sh b/tests/test_setup.sh
index c9db617..24a7ac8 100755
--- a/tests/test_setup.sh
+++ b/tests/test_setup.sh
@@ -30,12 +30,20 @@ contains "$sets" 'storage'
contains "$sets" 'wifi'
contains "$sets" 'firmware'
contains "$sets" 'bluetooth'
+contains "$sets" 'drivers'
plan=$("$setup" --plan --target /mnt --profile minimal --disable qol-cli)
contains "$plan" 'Profile: minimal'
contains "$plan" 'base-system'
-contains "$plan" 'dialog'
-contains "$plan" 'Locale: C.UTF-8'
+contains "$plan" 'Init: none'
+
+plan=$("$setup" --plan --target /mnt --profile minimal --init systemd --disable qol-cli)
+contains "$plan" 'Init: systemd'
+contains "$plan" 'init-systemd'
+
+plan=$("$setup" --plan --target /mnt --profile minimal --init openrc --disable qol-cli)
+contains "$plan" 'Init: openrc'
+contains "$plan" 'init-openrc'
plan=$("$setup" --plan --target /mnt --profile plasma-desktop --user alex \
--hostname darkstar --timezone UTC --keymap us --nvidia yes \