Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/iso-check/inspect.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,35 @@ for f in usr/bin/seg etc/init.d/seg-zram etc/init.d/seg-tune etc/runlevels/defau
if [[ $EDITION == full ]]; then
for f in usr/bin/rg usr/bin/fd usr/bin/eza usr/bin/zoxide etc/bash/bashrc.d/seglinux-rust-tools.bashrc usr/bin/xwayland usr/bin/Xwayland; do has "$f"; done
fi
echo "== gaming files (both editions)"
for f in etc/sudoers.d/seg-game etc/init.d/seg-game etc/runlevels/default/seg-game etc/runit/sv/seg-game/run \
etc/dinit.d/seg-game etc/s6/sv/seg-game/type etc/s6/sv/seg-game/up usr/share/seg/mangohud-gaming.conf \
usr/share/seg/pipewire/pipewire.conf.d/99-seg-lowlatency.conf usr/share/seg/pipewire/pipewire-pulse.conf.d/99-seg-lowlatency.conf \
etc/sysctl.d/80-seglinux-gaming.conf usr/lib/modprobe.d/seglinux-nvidia.conf etc/profile.d/seglinux-gaming.sh \
etc/security/limits.d/90-seglinux-esync.conf etc/modules-load.d/10-ntsync.conf; do has "$f"; done
echo "-- sudoers file as installed (mode must not be writable, must be root-owned):"
grep -E 'squashfs-root/etc/sudoers.d/seg-game$' "$list" || true
if [[ $EDITION == full ]]; then
echo "== gaming packages (full)"
for p in wine-staging lib32-mangohud mangohud steam gamescope winetricks lutris vulkan-tools lib32-vulkan-icd-loader lib32-mesa linux-zen gamemode; do
grep -qE "squashfs-root/var/lib/pacman/local/$p-[0-9][^/]*/desc\$" "$list" && echo "present $p" || echo "ABSENT $p"
done
echo "-- plain wine must be replaced by wine-staging:"
grep -cE 'squashfs-root/var/lib/pacman/local/wine-[0-9][^/]*/desc$' "$list" | sed 's/^/packages named exactly wine: /'
echo "-- NVIDIA driver must NOT be on the image (installed by Setup for NVIDIA hardware only):"
grep -cE 'squashfs-root/var/lib/pacman/local/nvidia-(open-)?dkms-' "$list" | sed 's/^/nvidia dkms packages: /'
fi
echo "== should be gone"
for f in usr/bin/Xorg usr/bin/spectacle usr/bin/inxi; do has "$f"; done
echo "kernel header folders: $(grep -cE 'squashfs-root/usr/src/[^/]+$' "$list") (0 expected)"
echo "== enabled in the image"
grep -E 'squashfs-root/etc/runlevels/[a-z]+/[^/]+ ->' "$list" | sed -E 's#.*squashfs-root/etc/runlevels/##; s# ->.*##' | sort | tr '\n' ' '
echo
echo "== packages that need the most room (installed size, MiB)"
sudo unsquashfs -f -d /tmp/rt /mnt/iso/LiveOS/rootfs.img var/lib/pacman/local >/dev/null 2>&1
sudo unsquashfs -f -d /tmp/rt /mnt/iso/LiveOS/rootfs.img var/lib/pacman/local etc/group etc/sudoers.d >/dev/null 2>&1
for d in /tmp/rt/var/lib/pacman/local/*/; do
n=$(basename "$d"); size=$(awk '/^%SIZE%/{getline; print $1}' "$d/desc"); echo "$((size / 1048576)) $n"
done | sort -rn | head -12
echo "== /etc/group gamemode line:"; grep -E '^gamemode:' /tmp/rt/etc/group || echo "NO gamemode group"
echo "== /etc/sudoers.d/seg-game:"; sudo cat /tmp/rt/etc/sudoers.d/seg-game 2>&1 | grep -v '^#' || true
echo "total installed MiB: $(for d in /tmp/rt/var/lib/pacman/local/*/; do awk '/^%SIZE%/{getline; s+=$1} END{print s+0}' "$d/desc"; done | awk '{t+=$1} END{printf "%d", t / 1048576}')"
21 changes: 17 additions & 4 deletions .github/iso-check/qemu_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
WORK = "/tmp/qemu-check"
os.makedirs(WORK, exist_ok=True)

KEYS = {" ": "spc", "-": "minus", ".": "dot", ";": "semicolon", "/": "slash", "\n": "ret", "_": "shift-minus", "=": "equal"}
KEYS = {" ": "spc", "-": "minus", ".": "dot", ";": "semicolon", "/": "slash", "\n": "ret", "_": "shift-minus", "=": "equal",
"|": "shift-backslash", "'": "apostrophe", ",": "comma", "$": "shift-4", "^": "shift-6", ":": "shift-semicolon",
"[": "bracket_left", "]": "bracket_right", "*": "shift-8", "(": "shift-9", ")": "shift-0", "&": "shift-7",
"?": "shift-slash", ">": "shift-dot", "<": "shift-comma", '"': "shift-apostrophe"}


def log(msg):
Expand Down Expand Up @@ -67,7 +70,7 @@ def key(self, name):

def type(self, text):
for ch in text:
self.key(KEYS.get(ch, ch))
self.key("shift-" + ch.lower() if ch.isupper() else KEYS.get(ch, ch))

def screen_text(self, tag):
ppm = f"{WORK}/{tag}.ppm"
Expand Down Expand Up @@ -113,11 +116,21 @@ def main():
time.sleep(4)
mon.type("seglinux\n")
time.sleep(8)
for i, command in enumerate(["seg info", "seg zram status", "seg tune", "seg svc list --enabled", "seg doctor"]):
commands = [
"seg info",
"seg game check | cut -c1-92 | grep -v ' fix'",
"seg game run --dry-run -- echo hi",
"seg game run -- sleep 2; echo exit=$?",
"sysctl vm.max_map_count vm.compaction_proactiveness kernel.split_lock_mitigate; ls -l /dev/ntsync; getent group gamemode",
"seg game setup; seg tune gaming --dry-run | head -12",
"seg svc list --enabled | grep -E 'seg|sysctl|udev'",
"seg doctor",
]
for i, command in enumerate(commands):
mon.type("clear\n")
time.sleep(1)
mon.type(command + "\n")
time.sleep(12)
time.sleep(14)
log(f"--- $ {command}:\n" + mon.screen_text(f"cmd{i}"))
except Exception as e:
log(f"qemu check stopped: {type(e).__name__}: {e}")
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,17 @@ jobs:
# Without the test cfg, where unwrap and expect are compile errors.
- run: cargo clippy --locked -- -D warnings
- run: cargo test --locked
- name: Dependencies stay minimal (seg and lexopt only)
- name: Dependencies stay minimal (seg, lexopt and libc only)
run: |
cargo tree --locked -e normal --prefix none | sort -u
test "$(cargo tree --locked -e normal --prefix none | sort -u | wc -l)" -le 2
test "$(cargo tree --locked -e normal --prefix none | sort -u | wc -l)" -le 3
# The only unsafe code is the few libc calls of src/game/proc.rs.
- name: unsafe stays in one file
run: |
test "$(grep -rEl '\bunsafe[[:space:]]*(\{|fn|impl|extern)' segctl/src)" = "segctl/src/game/proc.rs"
- name: sudoers rule is valid
run: |
sudo visudo -cf dist/sudoers-seg-game
- name: Release binary size
run: |
cargo build --locked --release
Expand All @@ -106,7 +113,7 @@ jobs:
data/desktop/labwc/autostart data/system/seglinux-terminal data/system/restore-trimmed-files \
data/system/profile-gaming.sh data/greeter/seglinux-greeter-session data/system/zram-setup \
tools/seg/dist/fstrim-weekly tools/seg/dist/runit/*/run tools/seg/dist/runit/seg-zram/finish \
tools/seg/dist/openrc/seg-tune tools/seg/dist/openrc/seg-zram
tools/seg/dist/openrc/seg-tune tools/seg/dist/openrc/seg-zram tools/seg/dist/openrc/seg-game
shellcheck -s bash pkg/segctl/segctl.install
shellcheck -s bash iso/iso-profiles/seglinux-lite/live-overlay/etc/skel/.bash_profile
- name: YAML, JSON and XML syntax
Expand Down
10 changes: 4 additions & 6 deletions .github/workflows/iso-check.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
name: ISO check

# Builds an image on a branch WITHOUT publishing anything, looks inside it
# and boots the Lite one in QEMU. Starts on a push to the work branch whose
# commit message contains "[iso check]". Temporary: removed once the
# seg/rust-lean-overhaul branch is checked.
# and boots the Lite one in QEMU. Runs on pushes to a throwaway branch that
# is deleted after the check; it is never part of a pull request.
on:
push:
branches: [seg/rust-lean-overhaul]
branches: [seg/iso-check-gaming]

permissions:
contents: read

jobs:
iso:
name: Build and inspect (${{ matrix.edition }}, openrc, linux-zen)
if: contains(github.event.head_commit.message, '[iso check]')
runs-on: ubuntu-latest
timeout-minutes: 150
strategy:
Expand All @@ -41,7 +39,7 @@ jobs:
SEGLINUX_PKGVER="0.1.0.$(git rev-list --count origin/main)" \
./iso/build.sh "${edition[@]}" -e docker -i openrc -k linux-zen
- name: Inside the image
run: .github/iso-check/inspect.sh "$(ls iso/out/seglinux-*.iso)"
run: bash .github/iso-check/inspect.sh "$(ls iso/out/seglinux-*.iso)"
- name: Boot it in QEMU (Lite)
if: matrix.edition == 'lite'
continue-on-error: true
Expand Down
88 changes: 88 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Changelog

Notable changes to SegLinux. The project has no version numbers yet, so this
lists what changed since the last ISO build of 19 October 2026 (`4ca2f45`).
Measurements and the reasons behind each removal are in
[docs/lean-audit.md](docs/lean-audit.md).

## Unreleased

### Added

- **`seg game`**, a small gaming layer in the `seg` tool, the same on OpenRC,
runit, dinit and s6 ([docs/gaming.md](docs/gaming.md#seg-game)):
- `seg game run -- CMD` (Steam: `seg game run -- %command%`) raises the CPU
governor and energy preference and an AMD GPU's power level while a game
runs, gives the game a little more CPU and the best I/O priority, holds
off the screen saver, can pin it to the V-Cache chiplet of a two-chiplet
X3D CPU, and restores everything when the game ends, on Ctrl+C, SIGTERM,
SIGHUP or a crash of `seg`.
- `seg game check`: PASS/WARN/FAIL/SKIP for Vulkan, the GPU driver, 32-bit
drivers, NTSYNC, `vm.max_map_count`, open files, kernel, fallback kernel,
CPU governor and driver, boost permission, hugepages, split lock,
compaction and mitigations, each with the exact fix.
- `seg game setup [--yes]`: the driver packages for the GPUs found in the
computer, and only those.
- `seg game profiles`, per-game profiles in `~/.config/seg/games.toml`
(governor, CPU pinning, MangoHud, gamescope arguments, nice).
- `seg game apply` and the **`seg-game` service** (OpenRC, runit, dinit,
s6): the gaming sysctls and `/dev/ntsync` permissions at boot.
- `seg game audio on|off`: low-latency PipeWire, off by default.
- `seg game unsafe-boost on|off`: `mitigations=off`, the only way to get
it, behind a warning and `--i-understand`.
- `seg tune gaming`: low swappiness, warm caches, short write-back, CPU
left dynamic.
- A sudoers rule for the `gamemode` group (two commands, nothing else), a
MangoHud preset and PipeWire drop-ins under `/usr/share/seg` (all off
until asked for).
- Setup installs the **other kernel** (`linux` beside `linux-zen`, or the
reverse) as a boot menu entry to fall back on when online
(`[performance] fallback_kernel`); the installed kernel is never removed.
- Kernel command line: `transparent_hugepage=madvise` everywhere and
`amd_pstate=active` on AMD processors (an answer file's parameter of the
same name replaces the default).
- `vm.compaction_proactiveness = 0` with the other gaming sysctls.
- NVIDIA: `nvidia_drm modeset=1 fbdev=1` (Wayland compositors, VT switch).
- `seg` (`tools/seg`, package `segctl`): `info`, `svc`, `clean`, `tune`,
`zram`, `doctor` for OpenRC, runit, dinit and s6, with the `seg-zram` and
`seg-tune` services and a weekly `fstrim` cron job.
- `ripgrep`, `fd`, `eza` and `zoxide` in the full edition.
- `docs/lean-audit.md`: what the images weigh before and after, the bloat
table, the bugs found, what was not verified.
- CI: a job for `seg` (fmt, clippy, tests, release size, dependency and
`unsafe` guards, `visudo -cf` of the sudoers rule), `udevadm verify` for
the udev rules, a check for repeated packages, more `shellcheck` targets.

### Changed

- `wine` -> `wine-staging` (the Staging patches: esync/fsync/NTSYNC, game
fixes) in the full edition. `lib32-mangohud` was tried and is not in the
Artix repositories, so 32-bit games get no MangoHud overlay.
- Open-file hard limit 524288 -> 1048576 (the kernel's maximum); the
`gamemode` group may use `nice -10`.
- Shader caches: Mesa 4 GB -> 10 GB; NVIDIA's shader cache set to 10 GB and
no longer deleted when full (`/etc/profile.d`, so on every init).
- `makepkg` builds with every thread; the I/O scheduler rule skips partitions
and covers virtual disks.

### Removed

- From the full image: `linux-headers` (325 MiB; Setup downloads the running
kernel's headers together with the NVIDIA DKMS driver), `spectacle` and
`opencv`, the X server and its drivers (`xorg-xwayland` stays), `inxi`,
and five packages that were listed twice. The full ISO is 4.7 % smaller and
475 MiB less is installed.
- `cupsd` is no longer started by default (the packages stay; `seg svc enable
cupsd`).

### Fixed

- The NVIDIA DKMS driver could only build because the headers were on every
image: Setup now installs them in that step.
- The artools patch that reads the kernel version without headers applies to
every kernel, not only the non-stock ones.
- The I/O scheduler udev rule matched partitions (errors in the log) and
missed virtual disks.
- `data/system/zram-setup` and the udev rules were not checked by CI.
- `seg doctor` called OpenRC services of the `shutdown` runlevel (`killprocs`,
`mount-ro`, `savecache`) failed; found by booting the Lite image.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ published.
kitty as the terminal (File Explorer → Dolphin, Store → Discover, ...).
The terminal opens with system information under the SegLinux logo
(fastfetch).
* **Built for gaming.** linux-zen kernel, Steam with Proton, Lutris,
GameMode, MangoHud and gamescope; a Steam "Gaming Mode" session; variable
refresh rate and tearing for fullscreen games; zram swap, NTSYNC, I/O
scheduler and sysctl tuning; NVIDIA's driver installed automatically on
GeForce GTX 16/RTX cards. See [docs/gaming.md](docs/gaming.md).
* **Built for gaming.** linux-zen kernel (with the stock kernel beside it to
fall back on), Steam with Proton, Lutris, Wine Staging, GameMode, MangoHud
and gamescope; a Steam "Gaming Mode" session; variable refresh rate and
tearing for fullscreen games; zram swap, NTSYNC, I/O scheduler and sysctl
tuning; NVIDIA's driver installed automatically on GeForce GTX 16/RTX
cards, and only for hardware that is there. `seg game run -- %command%` in
Steam's launch options boosts the CPU and GPU while a game runs and puts
everything back after; `seg game check` says what is missing, with the
fix. See [docs/gaming.md](docs/gaming.md).
* **Runs Windows programs.** Double-click an `.exe`, `.msi`, `.lnk` or
`.bat`, or type `./setup.exe`: `segrun` inspects the program, prepares a
Wine prefix (with DXVK/VKD3D-Proton when the program uses Direct3D) and
Expand All @@ -103,10 +107,12 @@ published.
or dinit; rolling release.
* **`seg`, one small tool for the system.** `seg info`, `seg svc` (the same
commands on OpenRC, runit, dinit and s6), `seg clean`, `seg tune`
(light, balanced or performance), `seg zram` and `seg doctor`, which finds
problems and prints their fixes. A single Rust binary of about 550 KiB; see
(light, balanced, gaming or performance), `seg zram`, `seg game` (see
above) and `seg doctor`, which finds problems and prints their fixes. A
single Rust binary of about 700 KiB; see
[tools/seg](tools/seg/README.md). What the images weigh and what was taken
off them: [docs/lean-audit.md](docs/lean-audit.md).
off them: [docs/lean-audit.md](docs/lean-audit.md). What changed:
[CHANGELOG.md](CHANGELOG.md).

## Website

Expand Down
1 change: 1 addition & 0 deletions crates/seglinux-installer/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ pub fn demo_facts() -> Facts {
memory: 8 * GIB,
gpus: vec![],
kernel: Some("linux-zen".into()),
cpu_vendor: None,
timezones: Some(set(&[
"UTC",
"Africa/Tunis",
Expand Down
3 changes: 2 additions & 1 deletion crates/seglinux-setup/autounattend.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ os_prober = true # list Windows in the boot menu
# Gaming and boot tuning.
[performance]
parallel_boot = true # OpenRC: start services in parallel
kernel_parameters = [] # added to: loglevel=3 quiet nowatchdog nmi_watchdog=0
kernel_parameters = [] # added to: loglevel=3 quiet nowatchdog nmi_watchdog=0 transparent_hugepage=madvise
fallback_kernel = true # also install the other kernel (linux beside linux-zen), a boot menu entry to fall back on

# GPU drivers. "auto" installs NVIDIA's driver for GeForce GTX 16/RTX cards
# when the network is up; "never" keeps the open-source driver.
Expand Down
11 changes: 10 additions & 1 deletion crates/seglinux-setup/src/answer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,22 @@ pub struct PerformanceSection {
/// OpenRC: start services in parallel (`rc_parallel`) for a faster boot.
pub parallel_boot: bool,
/// Kernel parameters added to SegLinux's defaults
/// (`loglevel=3 quiet nowatchdog nmi_watchdog=0`).
/// (`loglevel=3 quiet nowatchdog nmi_watchdog=0 transparent_hugepage=madvise`,
/// `zswap.enabled=0` with zram, `amd_pstate=active` on AMD processors).
/// A parameter given here replaces the default of the same name.
pub kernel_parameters: Vec<String>,
/// Install the other kernel (`linux` next to `linux-zen`, the reverse for
/// the stock kernel) as a boot menu entry to fall back on, when the
/// network is reachable. The kernel that is installed is never removed.
pub fallback_kernel: bool,
}

impl Default for PerformanceSection {
fn default() -> Self {
Self {
parallel_boot: true,
kernel_parameters: Vec::new(),
fallback_kernel: true,
}
}
}
Expand Down Expand Up @@ -342,6 +349,8 @@ pub const DEFAULT_SERVICES: &[&str] = &[
// From the segctl package: zram swap and the saved `seg tune` profile.
"seg-zram",
"seg-tune",
// The gaming sysctls and /dev/ntsync permissions, on every init.
"seg-game",
];

/// Groups every user joins (missing ones are skipped).
Expand Down
3 changes: 1 addition & 2 deletions crates/seglinux-setup/src/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,7 @@ impl Layout {
));
}
if self.zram.is_some() {
// Created and formatted at boot: the udev rule 60-seglinux-zram.rules runs
// seg zram on (the seg-zram service does too).
// Created and formatted by /etc/udev/rules.d/99-seglinux-zram.rules.
out.push_str("/dev/zram0 none swap defaults,discard,pri=100 0 0\n");
}
out
Expand Down
22 changes: 22 additions & 0 deletions crates/seglinux-setup/src/facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub struct Facts {
/// Package name of the running kernel (`linux-zen`): the new system gets
/// the same one, and its headers when a driver has to be built.
pub kernel: Option<String>,
/// `vendor_id` of the processor in /proc/cpuinfo (`AuthenticAMD`).
pub cpu_vendor: Option<String>,
/// Known values for validation and pick lists; `None` = not checked.
pub timezones: Option<BTreeSet<String>>,
pub locales: Option<BTreeSet<String>>,
Expand Down Expand Up @@ -144,6 +146,9 @@ impl Facts {
memory: memory_bytes().unwrap_or(0),
gpus: list_gpus(Path::new("/sys/bus/pci/devices")),
kernel: running_kernel(),
cpu_vendor: fs::read_to_string("/proc/cpuinfo")
.ok()
.and_then(|t| parse_cpu_vendor(&t)),
timezones: Some(list_timezones(Path::new("/usr/share/zoneinfo")))
.filter(|s| !s.is_empty()),
locales: fs::read_to_string("/usr/share/i18n/SUPPORTED")
Expand Down Expand Up @@ -304,6 +309,14 @@ pub fn running_kernel() -> Option<String> {
parse_pkgbase(&pkgbase)
}

/// `vendor_id : AuthenticAMD` of the first processor.
pub fn parse_cpu_vendor(text: &str) -> Option<String> {
text.lines().find_map(|l| {
let (key, value) = l.split_once(':')?;
(key.trim() == "vendor_id").then(|| value.trim().to_string())
})
}

pub fn parse_pkgbase(text: &str) -> Option<String> {
let name = text.lines().next()?.trim();
let valid = !name.is_empty()
Expand Down Expand Up @@ -509,6 +522,15 @@ mod tests {
assert_eq!(parse_meminfo_total("nope"), None);
}

#[test]
fn cpu_vendor_from_cpuinfo() {
assert_eq!(
parse_cpu_vendor("processor\t: 0\nvendor_id\t: AuthenticAMD\nmodel name\t: x\n"),
Some("AuthenticAMD".into())
);
assert_eq!(parse_cpu_vendor("model name : x\n"), None);
}

#[test]
fn kernel_package_from_pkgbase() {
assert_eq!(parse_pkgbase("linux-zen\n"), Some("linux-zen".into()));
Expand Down
Loading
Loading