Skip to content
Merged

Main #12

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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,19 @@ jobs:
SUDO_USER=tester SEGLINUX_SOURCE="$PWD" SEGLINUX_VERSION=0.1.0.999999 \
bash data/system/seglinux-upgrade --noconfirm
pacman -Q seglinux | grep -q ' 0\.1\.0\.999999-'
# seg (package segctl) comes with it, with its group, sudoers rule
# and services (pacman -U ran the package's install script).
pacman -Q segctl
seg --version
getent group gamemode
test "$(stat -c %a /etc/sudoers.d/seg-game)" = 440
visudo -cf /etc/sudoers.d/seg-game
test -x /etc/init.d/seg-game && test -f /etc/runit/sv/seg-game/run && test -f /etc/dinit.d/seg-game
ls /etc/dinit.d/boot.d /etc/runlevels/default 2>/dev/null | grep -E '^seg-' || true
# seg game works on a machine with no GPU: it reports, it does not crash.
seg game check > /tmp/game-check.txt || test $? -eq 1
grep -q 'max_map_count' /tmp/game-check.txt
seg game run --dry-run -- true
test -x /usr/bin/seglinux-upgrade && test -L /usr/bin/seglinux-update
test ! -e /usr/local/bin/seglinux-upgrade
seglinux-shell --version
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ Measurements and the reasons behind each removal are in
- 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.
- Transparent hugepages go from `always` to `madvise` at boot (`seg-game`
service), so the live image and upgraded systems get it too, not only
fresh installs.
- 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).
Expand All @@ -62,6 +65,9 @@ Measurements and the reasons behind each removal are in
`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).
- `sudo seglinux-upgrade` (the from-source path) now also builds and
installs `segctl`, so an older install gets `seg`, `seg game`, the
services and the sudoers rule; CI checks this on an Artix container.
- `makepkg` builds with every thread; the I/O scheduler rule skips partitions
and covers virtual disks.

Expand Down
36 changes: 33 additions & 3 deletions data/system/seglinux-upgrade
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ latest() {
# makepkg does not build as root, so it builds as the account that ran sudo.
build_seglinux() {
local user home version sha installed work top deps packages
local segctl_dir segctl_version segctl_deps segctl_packages=()
user=${SUDO_USER:-}
if [[ -z $user || $user == root ]]; then
warn "SegLinux itself is built from your account: run sudo seglinux-upgrade as yourself, not as root."
Expand Down Expand Up @@ -218,19 +219,40 @@ build_seglinux() {
tar -C "$work/x" -czf "$work/seglinux-$version.tar.gz" "seglinux-$version"
fi
cp "$work/x/seglinux-$version/packaging/seglinux/"{PKGBUILD,seglinux.install} "$work/"
# seg, the system tool (package segctl: seg game, the zram and tuning
# services), is built from the same source: its recipe, and its folder as
# the source tarball the recipe names.
segctl_dir=
if [[ -f $work/x/seglinux-$version/pkg/segctl/PKGBUILD ]]; then
segctl_dir=$work/segctl
mkdir "$segctl_dir"
cp "$work/x/seglinux-$version/pkg/segctl/"{PKGBUILD,segctl.install} "$segctl_dir/"
segctl_version=$(sed -n 's/^pkgver=//p' "$segctl_dir/PKGBUILD")
tar -C "$work/x/seglinux-$version" --transform "s,^\./tools/seg,segctl-$segctl_version/tools/seg," \
-czf "$segctl_dir/segctl-$segctl_version.tar.gz" ./tools/seg
fi
rm -rf "$work/x" "$work/source.tar.gz"
sed -i "s/^pkgver=.*/pkgver=$version/" "$work/PKGBUILD"
chown -R "$user:" "$work"
as_user() { (cd "$work" && runuser -u "$user" -- env HOME="$home" CARGO_HOME="$home/.cache/seglinux/cargo" "$@"); }
as_user_in() {
local dir=$1
shift
(cd "$dir" && runuser -u "$user" -- env HOME="$home" CARGO_HOME="$home/.cache/seglinux/cargo" "$@")
}
as_user() { as_user_in "$work" "$@"; }
# What it needs to build and run, as dependencies (pacman -Qdt lists
# them once nothing needs them).
deps=$(as_user makepkg --printsrcinfo | sed -n 's/^\t\(make\)\{0,1\}depends = //p') || {
rm -rf "$work"
warn "SegLinux's build recipe could not be read; SegLinux itself is not upgraded this time."
return 0
}
segctl_deps=
if [[ -n $segctl_dir ]]; then
segctl_deps=$(as_user_in "$segctl_dir" makepkg --printsrcinfo | sed -n 's/^\t\(make\)\{0,1\}depends = //p') || segctl_dir=
fi
# shellcheck disable=SC2086 # one package name per word
pacman -S --needed --noconfirm --asdeps base-devel $deps || {
pacman -S --needed --noconfirm --asdeps base-devel $deps $segctl_deps || {
rm -rf "$work"
warn "The tools to build SegLinux could not be installed; SegLinux itself is not upgraded this time."
return 0
Expand All @@ -240,8 +262,16 @@ build_seglinux() {
warn "SegLinux $version did not build; your SegLinux stays as it is. Please report it: $source_url/issues"
return 0
fi
# seg is a smaller extra: if it does not build, SegLinux is still upgraded.
if [[ -n $segctl_dir ]]; then
if as_user_in "$segctl_dir" makepkg -f --nocheck --noconfirm; then
segctl_packages=("$segctl_dir"/segctl-*.pkg.tar.zst)
else
warn "seg (the segctl package) did not build; it is not upgraded this time."
fi
fi
packages=("$work"/seglinux-"$version"-*.pkg.tar.zst)
pacman -U "$@" "${packages[0]}" || warn "SegLinux $version was not installed."
pacman -U "$@" "${packages[0]}" "${segctl_packages[@]}" || warn "SegLinux $version was not installed."
rm -rf "$work"
}

Expand Down
5 changes: 3 additions & 2 deletions docs/gaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ a build container with no GPU and the stock kernel, to show the format:
`--games` adds Steam, gamescope, MangoHud (64-bit only), Wine Staging and
winetricks; `--lutris` adds Lutris. It also tells you when `[lib32]` is not
enabled in `/etc/pacman.conf`.
* `sudo seg game apply` applies the gaming sysctls and makes `/dev/ntsync`
* `sudo seg game apply` applies the gaming sysctls, sets transparent
hugepages to `madvise` (from `always` only) and makes `/dev/ntsync`
usable now; the `seg-game` service runs it at boot on every init.
* `seg game audio on|off|status` lowers PipeWire's buffer to 256 samples at
48 kHz (about 5 ms), for you only (`~/.config/pipewire/...conf.d`). **Off by
Expand Down Expand Up @@ -345,7 +346,7 @@ leave it off.
| `nmi_watchdog=0`, `nowatchdog`, watchdog modules blacklisted | kernel command line (Setup), `/usr/lib/modprobe.d/seglinux-nowatchdog.conf` | Fewer periodic interrupts |
| `vm.compaction_proactiveness = 0` | same | No background memory defragmentation while a game allocates (the default is 20); a program that needs a contiguous block still gets it by compacting on demand. What SteamOS sets. |
| `fs.inotify.max_user_watches` | sysctl | Launchers watching many files |
| `transparent_hugepage=madvise` | kernel command line (Setup) | Huge pages only for programs that ask (games, databases); `always` can stall on compaction |
| `transparent_hugepage=madvise` | kernel command line (Setup), and `seg game apply` at boot | Huge pages only for programs that ask (games, databases); `always` can stall on compaction. linux-zen starts with `always`, so the live image and a system upgraded with `seglinux-upgrade` get `madvise` from the `seg-game` service (it changes `always` only; `never` is left alone). Found by booting the Lite image: the check said `[always]` before this. |
| `amd_pstate=active` | kernel command line (Setup, AMD processors only) | The CPU picks its frequency itself, guided by the energy hint `seg game run` sets. Kernels or CPUs without it ignore it. |
| I/O schedulers: NVMe none, SSD mq-deadline, HDD BFQ | `/usr/lib/udev/rules.d/60-seglinux-ioschedulers.rules` | Low latency while games stream assets |
| NTSYNC | `/etc/modules-load.d/10-ntsync.conf`, udev rule | Windows synchronisation in the kernel, used by Wine 11 and Proton |
Expand Down
6 changes: 4 additions & 2 deletions docs/lean-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,10 @@ Found, not changed:
the udev rule starts it whatever the answer says.
* The two greetd `config.toml` and `pacman.conf` copies in the root overlays
are identical to each other (and the first to `data/greeter/greetd.toml`).
* `seglinux-upgrade` updates only the `seglinux` package, so a system
installed before this work does not get `segctl` that way yet.
* `seglinux-upgrade` updated only the `seglinux` package, so a system
installed before this work did not get `segctl`. Fixed in the gaming work:
the from-source upgrade also builds and installs `segctl` (tested in CI on
an Artix container); the signed repository still carries `seglinux` only.

## Decisions for the maintainers

Expand Down
8 changes: 7 additions & 1 deletion docs/updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,13 @@ each marked `# Added by seglinux-upgrade.`. It keeps the previous file as

When SegLinux is built from source, the source comes over HTTPS from
SegLinux's GitHub repository, as the ISO and this command do; pacman installs
the package it builds as a local file, like any AUR package.
the package it builds as a local file, like any AUR package. The same build
also makes `segctl` from the `tools/seg` folder (the `seg` tool: `seg game`,
the zram, tuning and game services, the sudoers rule for the `gamemode`
group) and installs the two together; if only `segctl` fails to build,
SegLinux is still upgraded. SegLinux's signed repository does not carry
`segctl` yet (`.github/workflows/update.yml` builds `seglinux` only), so once
packages are published `seg` still needs `--from-source` or the ISO.

## When an update fails

Expand Down
2 changes: 1 addition & 1 deletion tools/seg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ safety notes are in [docs/gaming.md](../../docs/gaming.md#seg-game).
| `seg game check` | PASS/WARN/FAIL/SKIP for Vulkan, GPU driver, lib32 drivers, ntsync, `vm.max_map_count`, open files, kernel, fallback kernel, governor, boost permission, hugepages, split lock, compaction, mitigations; the fix under each; exit 1 if any FAIL |
| `seg game setup [--yes] [--games] [--lutris]` | the driver packages for the GPUs in this computer (from `/sys/class/drm` and `lspci`) and the command that installs them; `--yes` runs it |
| `seg game profiles [FILE]` | check `~/.config/seg/games.toml` and list what it holds |
| `seg game apply` | the gaming sysctls and `/dev/ntsync` permissions now (the `seg-game` service) |
| `seg game apply` | the gaming sysctls, hugepages `always` -> `madvise`, and `/dev/ntsync` permissions now (the `seg-game` service) |
| `seg game audio on\|off\|status` | low-latency PipeWire for the current user |
| `seg game unsafe-boost on\|off\|status` | `mitigations=off`; needs `--i-understand` after a warning |

Expand Down
66 changes: 60 additions & 6 deletions tools/seg/segctl/src/game/apply.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! `seg game apply`, run at boot by the `seg-game` service of every init:
//! the gaming sysctls and the permissions of /dev/ntsync. OpenRC and udev do
//! both too; runit, dinit and s6 images may not, and applying twice is
//! harmless.
//! the gaming sysctls, transparent hugepages on `madvise` and the permissions
//! of /dev/ntsync. OpenRC and udev do the sysctls and the device too; runit,
//! dinit and s6 images may not, and applying twice is harmless.

use std::fs;
use std::os::unix::fs::PermissionsExt;
Expand All @@ -15,12 +15,23 @@ pub const HELP: &str = "\
Usage: seg game apply

Applies /etc/sysctl.d/80-seglinux-gaming.conf now (vm.max_map_count,
kernel.split_lock_mitigate, vm.compaction_proactiveness, ...) and makes
/dev/ntsync usable by every user, so Wine and Proton can use NTSYNC. Needs
root. The seg-game service runs it at boot.
kernel.split_lock_mitigate, vm.compaction_proactiveness, ...), changes
transparent hugepages from `always` to `madvise` (linux-zen starts with
`always`, which can stall a game on memory compaction; `never` is left as it
is) and makes /dev/ntsync usable by every user, so Wine and Proton can use
NTSYNC. Needs root. The seg-game service runs it at boot.
";

pub const SYSCTL_FILE: &str = "etc/sysctl.d/80-seglinux-gaming.conf";
const THP_FILE: &str = "sys/kernel/mm/transparent_hugepage/enabled";

/// The word in brackets of `always [madvise] never`.
fn thp_selected(text: &str) -> Option<&str> {
text.split_once('[')?
.1
.split_once(']')
.map(|(word, _)| word)
}

/// Apply the sysctls of the gaming file below `root`; then fix /dev/ntsync.
pub fn apply(root: &Path) -> Result<Vec<String>> {
Expand All @@ -45,6 +56,13 @@ pub fn apply(root: &Path) -> Result<Vec<String>> {
));
}
}
let thp = root.join(THP_FILE);
if let Ok(text) = fs::read_to_string(&thp)
&& thp_selected(&text) == Some("always")
{
fs::write(&thp, "madvise").context(format!("writing {}", thp.display()))?;
lines.push("transparent hugepages: always -> madvise".into());
}
let ntsync = root.join("dev/ntsync");
if let Ok(meta) = fs::metadata(&ntsync) {
let mode = meta.permissions().mode() & 0o777;
Expand Down Expand Up @@ -117,6 +135,42 @@ mod tests {
assert!(again.iter().all(|l| l.starts_with("note:")), "{again:?}");
}

#[test]
fn hugepages_go_from_always_to_madvise_and_nothing_else_changes() {
let t = TempDir::new();
t.put(THP_FILE, "[always] madvise never\n");
let lines = apply(t.path()).unwrap();
assert!(
lines
.iter()
.any(|l| l == "transparent hugepages: always -> madvise"),
"{lines:?}"
);
assert_eq!(
fs::read_to_string(t.path().join(THP_FILE)).unwrap(),
"madvise"
);
// madvise is left alone, and so is never (someone chose it).
for already in ["always [madvise] never\n", "always madvise [never]\n"] {
let t = TempDir::new();
t.put(THP_FILE, already);
let lines = apply(t.path()).unwrap();
assert!(lines.iter().all(|l| !l.contains("hugepages")), "{lines:?}");
assert_eq!(
fs::read_to_string(t.path().join(THP_FILE)).unwrap(),
already
);
}
}

#[test]
fn the_hugepage_setting_is_read_from_the_brackets() {
assert_eq!(thp_selected("[always] madvise never"), Some("always"));
assert_eq!(thp_selected("always [madvise] never"), Some("madvise"));
assert_eq!(thp_selected("always madvise never"), None);
assert_eq!(thp_selected(""), None);
}

#[test]
fn no_file_and_no_device_is_not_an_error() {
let t = TempDir::new();
Expand Down
Loading