aboutsummaryrefslogtreecommitdiffstatshomepage
Commit message (Collapse)AuthorAgeFilesLines
* fix(nix,meta): keep imv/wl-mirror/sparrow on pacman (OpenGL context)Libravatar sommerfeld2 days2-19/+14
| | | | | | | | Same root cause as ghostty: imv (OpenGL), wl-mirror (EGL) and sparrow (JavaFX/OpenGL) are GL/EGL apps that can't find the system Mesa/DRI driver when built by nix on a non-NixOS host. Remove them from nix/host.nix; add imv + wl-mirror to meta/base.txt (sparrow already lives in meta/btc.txt as sparrow-wallet). Refresh the stale base.txt media comment accordingly.
* fix(nix,meta): keep ghostty on pacman to fix missing OpenGL contextLibravatar sommerfeld2 days2-3/+11
| | | | | | | | ghostty is a GPU/OpenGL terminal. Nix-built GL apps on a non-NixOS host can't locate the system Mesa/DRI driver (FHS /usr/lib drivers don't match nix's search paths), so the nix-migrated ghostty failed to start with "missing OpenGL context". Move it back to meta/base.txt (pacman) so it links against system Mesa. Same caveat flagged for imv/wl-mirror/sparrow.
* fix(systemd): add environment.d PATH so user units find nix binariesLibravatar sommerfeld2 days1-0/+22
| | | | | | | | | | | | | | | The user-leaf tools (waybar, swayidle, swayr, mako, cliphist, inhibridge, wob, …) were migrated to the Nix home profile and their .service units reference them by bare name. The systemd user manager does not source ~/.zprofile, so its PATH lacked ~/.nix-profile/bin and every bare-name ExecStart failed with status=203/EXEC. The sway config's `systemctl --user import-environment PATH` raced with `systemctl --user start sway-session.target`; when the start won, units launched with the default PATH. environment.d is read at manager startup before any unit, closing the race deterministically. Pick up via fresh login/boot or `systemctl --user daemon-reexec`.
* fix(nix): replace nonexistent podman-docker attr with writeShellScriptBin shimLibravatar sommerfeld2 days1-1/+5
| | | | | | | nixpkgs has no top-level `podman-docker` attribute — that's an Arch convenience pkg. NixOS exposes it via the `virtualisation.podman. dockerCompat` option but that's not reachable from home-manager. Ship a one-line writeShellScriptBin instead; same result, no module rewire.
* chore(bootstrap): drop manual paru-bin AUR build; provision subuid/subgidLibravatar sommerfeld2 days1-51/+55
| | | | | | | | | | | | | | | | | | | | | | | | | | Two changes: 1. Eliminate the paru-bin chicken-and-egg. Old flow had to manually git-clone aur/paru-bin and run makepkg -si before just-init could call paru. Now paru ships from nixpkgs via nix/host.nix, so the bootstrap reorders to: pacman -S … nix enable nix-daemon git clone <dotfiles> just nix-switch # installs paru + chezmoi + … into ~/.nix-profile PATH=$HOME/.nix-profile/bin:$PATH just init # _chezmoi-init/apply/pkg-apply/unit-apply pkg-apply now finds paru on PATH from the nix profile, so the remaining AUR entries in meta/base.txt (arkenfox-user.js, protonmail-bridge-core, pass-secret-service-bin, zsa-udev, kernel- modules-hook, …) install correctly. chezmoi drops out of PREREQS — it comes from the nix profile too. just stays on pacman so the pre-nix-switch `just nix-switch` invocation works. 2. Add idempotent subuid/subgid provisioning. Required for rootless nix-installed podman; pacman's podman package handled this via its post-install hook, but nix-installed podman doesn't touch /etc/subuid. Range 100000-165535 is the conventional default.
* chore(meta): drop migrated packages from base.txtLibravatar sommerfeld2 days1-73/+23
| | | | | | | | | | | | | | | | | | | | | | | | | | Remove every entry now provisioned via nix/host.nix or nix/common.nix: - Core CLIs: chezmoi, paru, qrencode, torsocks, lshw - Podman stack: podman-compose, podman-docker (rest already nix in common.nix via the previous unify commit) - Wayland session: waybar, fuzzel, wofi, mako, swayidle, swayr, inhibridge, bemoji, wob, poweralertd, ghostty - Wayland capture/clipboard: grim, slurp, wf-recorder, wtype, wl-clipboard, cliphist, imv, wl-mirror - Media control: playerctl, pulsemixer - Streaming: yt-dlp, streamlink - OCR: tesseract, tesseract-data-eng, tesseract-data-por - STT: whisper.cpp-vulkan, whisper.cpp-model-base Update the section comments to point at the nix profile for the new home of each group. Sway itself stays on pacman (login-manager session entry), as does libnotify (system shared lib), zbar (linked by other pacman pkgs), swaylock (setuid + PAM), pass-secret-service-bin (D-Bus activation), zsa-udev (udev rule), and the smartcard/font/Qt-plugin stacks. Drop whisper.cpp-vulkan from IgnorePkg in etc/pacman.conf — the package no longer exists on the system.
* fix(systemd,scripts): unhardcode /usr/bin paths for nix-migrated toolsLibravatar sommerfeld2 days11-17/+18
| | | | | | | | | | | | | | | | | | | | | The chezmoi-owned user units and ~/.local/bin wrapper scripts called the migrated tools by absolute /usr/bin/ path. After the move to nix, those binaries live under ~/.nix-profile/bin (no /usr/bin alias). systemd user units: drop the /usr/bin/ prefix on cliphist-{text,image} (wl-paste), inhibridge, swayidle, swayrd, waybar, and the inner wob in wob.service (outer /usr/bin/sh stays, sh is system). systemd resolves bare names through the unit's inherited PATH, which includes ~/.nix-profile/bin via hm-session-vars. dictate: default_model now points at ~/.nix-profile/share/whisper-cpp-models/ggml-base.bin (overridable via $WHISPER_MODEL). Header rewritten to mention nix instead of AUR. yt-dlp / streamlink wrappers: pass $HOME/.nix-profile/bin/<tool> to _sandbox-net-parser so the bwrap-sandboxed binary is resolved explicitly (the wrappers shadow PATH lookup inside their own ~/.local/bin so re-entry would loop).
* feat(nix): migrate user-leaf tools to host profileLibravatar sommerfeld2 days1-10/+114
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Pull every pacman/AUR entry that is (1) packaged in nixpkgs and (2) free of tight system coupling out of meta/base.txt and into nix/host.nix. System coupling = setuid, /usr/lib/systemd/system unit, udev rule, /usr/share/dbus-1/services file, /usr/share/wayland-sessions entry, shared lib other pacman pkgs link, /etc/makepkg.conf reference, system fontconfig path, PAM, Qt plugin search path, or kernel/ firmware/bootloader touchpoint. User-scope systemd units are NOT coupling — nix drops them in ~/.nix-profile/share/systemd/user/ and systemd picks them up; the chezmoi-owned unit files that referenced /usr/bin/<tool> paths are fixed in a follow-up commit. Wayland session: waybar, mako, fuzzel, wofi, swayidle, swayr, inhibridge, bemoji, wob, poweralertd, grim, slurp, wf-recorder, wtype, wl-clipboard, cliphist, imv, wl-mirror, playerctl, pulsemixer, ghostty. General CLIs: qrencode, torsocks, lshw, yt-dlp, streamlink, chezmoi, paru. GUI: sparrow. OCR: tesseract collapsed with .override { enableLanguages = [eng por] } — replaces tesseract + tesseract-data-eng + tesseract-data-por. STT: whisper-cpp.override { vulkanSupport = true; } plus an inline whisper-cpp-model-base derivation that fetches ggml-base.bin from the upstream huggingface mirror into ~/.nix-profile/share/whisper-cpp-models/.
* feat(nix): unify rootless podman across host and VMLibravatar sommerfeld2 days4-40/+61
| | | | | | | | | | | | | | | Move the podman stack (podman, crun, conmon, netavark, aardvark-dns, slirp4netns, passt, podman-compose, podman-docker) from a vm-only block into common.nix so the Arch host and the Ubuntu remote-dev VM run the same nix-pinned versions. This drops podman-compose + podman-docker from pacman as well — they were the only podman-stack pieces still sourced from there on the host. Relocate registries.conf + policy.json into the chezmoi tree at dot_config/containers/ so both flavors share them; vm.nix now picks them up via the existing link helper. storage.conf stays inline in vm.nix because the VM needs the overlay driver while the Arch host uses the btrfs driver (root fs is btrfs there).
* chore(nix): flake.lock update (home-manager)Libravatar sommerfeld2 days1-7/+7
| | | | | | home-manager: 7d8127d3 (master, 26.11) → b179bde2 (release-26.05) Follow-up to the release-branch pin in the previous commit.
* chore(nix): pin home-manager to release-26.05 (match nixpkgs)Libravatar sommerfeld2 days1-1/+6
| | | | | | | | | HM master had rolled to the 26.11 development cycle while the nixos-unstable nixpkgs snapshot we follow is still on 26.05. Activation emitted the 'mismatched versions' warning at every nix-switch. Pin HM to its release-26.05 branch so the two stay in lockstep; bump the branch name when nixpkgs lib.version rolls over.
* chore(nix): flake.lock update (home-manager, tuicr)Libravatar sommerfeld9 days1-6/+6
|
* feat(nix/common): expose run-clang-tidy missing from nixpkgs clang-toolsLibravatar sommerfeld9 days1-1/+21
| | | | | | | | | nixpkgs' clang-tools derivation symlinks scripts from clang-unwrapped only when they're executable; run-clang-tidy loses the +x bit during the multi-output split and gets skipped. Re-expose it ourselves by probing clang-unwrapped's main and python outputs (bin/ first, then the legacy share/clang/ layout) and installing the first hit at $out/bin/run-clang-tidy.
* refactor(meta/nvidia): drop linux-headers (covered by base kernels)Libravatar sommerfeld9 days1-1/+0
| | | | | | | linux-hardened-headers and linux-lts-headers in meta/base.txt already cover every installed kernel, so 'linux-headers' here would only pull the stock 'linux' kernel back in via dependency — which we just removed.
* feat(kernel): swap stock linux for linux-lts as fallback kernelLibravatar sommerfeld9 days5-33/+46
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Promotes linux-hardened to the sole primary kernel and replaces linux with linux-lts as the safety-net fallback. Rationale: - linux and linux-hardened track the same upstream major version and ship within days of each other, so 'linux' was a poor fallback for the regression class that historically takes out the hardened kernel on this hardware (e.g. checkpoint 026 wake-from-suspend panic). linux-lts lags by weeks/months and is almost always known-good when hardened breaks. - Drop etc/mkinitcpio.d/linux.preset, add linux-lts.preset. Hardened preset header + bootstrap.sh efibootmgr instructions updated accordingly (hardened registered first so it's the default; lts registered as the on-demand fallback). - Also add mkinitcpio-firmware (AUR) to silence the spurious 'missing firmware' warnings during initramfs builds. Manual host-side steps after deploy: paru -S linux-lts linux-lts-headers mkinitcpio-firmware sudo pacman -Rsn linux # or via 'just pkg-apply' undeclared flow sudo rm -f /etc/mkinitcpio.d/linux.preset # chezmoi-deployed, not pkg-owned sudo mkinitcpio -P sudo efibootmgr # add the Arch LTS entries, drop the stock linux ones Note: meta/nvidia.txt still lists 'linux-headers' for nvidia-dkms. That's a per-host concern; flagged for follow-up if any nvidia host moves to the linux-lts world.
* feat(etc/resolved): forward single-label queries upstreamLibravatar sommerfeld9 days1-0/+8
| | | | | | | | Enables ResolveUnicastSingleLabel=yes so non-FQDN names like 'sw-jenkins01' get sent to the configured DNS server instead of being dropped to LLMNR/mDNS. Needed for corp shortname resolution via Pi-hole CNAME records that point at *.xsight.ent (resolved by unbound's forward-zone over the new WireGuard bridge).
* refactor(eer): install external-editor-revived via nix on the hostLibravatar sommerfeld9 days4-19/+38
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The AUR `external-editor-revived` PKGBUILD declares a hard `thunderbird` dependency, which blocks removing the unused system Thunderbird binary alongside the org.mozilla.thunderbird flatpak (and pacman's `AssumeInstalled` is a CLI flag, not a pacman.conf directive, so the previous workaround was nonfunctional). Nixpkgs' `external-editor-revived` is just `rustPlatform.buildRustPackage` plus a relocatable native-messaging manifest — zero mailer dep — so the host gets it from nix instead. * nix/host.nix: add `external-editor-revived` to `home.packages`. Kept out of `common.nix` so the remote-dev VM (which has no Thunderbird) doesn't carry the build closure. * run_onchange_after_deploy-tb-eer.sh.tmpl: search `~/.nix-profile/{bin,lib/mozilla/native-messaging-hosts}` first and fall through to the legacy pacman paths. The chezmoi manifest-hash probe now checks the nix path too, so the hook re-runs cleanly when nix bumps the EER version. * meta/base.txt: drop the `external-editor-revived` AUR entry and rewrite the comment to point at the nix declaration. * etc/pacman.conf: revert the bogus `AssumeInstalled` directive (CLI-only, not pacman.conf). On-host migration: home-manager switch --flake ~/dotfiles/nix#host # picks up EER sudo pacman -Rns external-editor-revived thunderbird mpv chezmoi apply -v # re-runs tb-eer hook
* refactor(flatpak): route mpv and thunderbird via flatpak; drop system pkgsLibravatar sommerfeld9 days10-23/+29
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Both org.mozilla.thunderbird and io.mpv.Mpv are already installed via flatpak, but several places still launched the system binaries (because they were in PATH). Worse, `mpv` was kept on the host *only* for the streamlink-launches-mpv path, and `thunderbird` was being pulled in as a hard dep of external-editor-revived even though it was never the mailer actually used. Untangle both. Thunderbird ----------- * dot_config/sway/executable_tb-toggle.sh, dot_config/sway/executable_tb-autostart.sh: swap `thunderbird` → `flatpak run org.mozilla.thunderbird`. The `app_id` matcher in sway config already targets the flatpak id, so the scratchpad-stash and Super+t toggle keep working unchanged. * etc/pacman.conf: add `AssumeInstalled = thunderbird=999.0-1`. external-editor-revived (AUR) hard-depends on `thunderbird`; this satisfies the dep without installing the package. Run `sudo pacman -Rns thunderbird` after deploy to remove the now-unneeded system binary. * meta/base.txt: document the AssumeInstalled trick next to the external-editor-revived entry. mpv --- * dot_config/streamlink/config: `player=mpv` → `player=flatpak run io.mpv.Mpv`. The flatpak already pulls in our ~/.config/mpv via the read-only filesystem override (see run_onchange_after_deploy-flatpak-overrides.sh.tmpl), so behavior is unchanged. * dot_local/bin/executable_linkhandler: same swap for inline video URLs. * dot_local/bin/executable_mpv: deleted. The wrapper only existed to bwrap /usr/bin/mpv into _sandbox-net-parser; flatpak's own sandbox supersedes that. * dot_local/bin/executable__sandbox-net-parser, dot_local/bin/executable_streamlink: comment refresh — mpv is no longer one of the tools this wraps, and the streamlink wrapper now forwards to the flatpak player rather than nested-bwrap caveats. * meta/base.txt: drop `mpv` from the host package list and update the surrounding comment. README.md: refresh the media row of the stack table to match. On-host steps: chezmoi apply -v sudo pacman -Syu # picks up AssumeInstalled sudo pacman -Rns thunderbird mpv # safe now flatpak install -y flathub org.mozilla.thunderbird io.mpv.Mpv swaymsg reload # pick up new tb scripts
* chore(nix): flake.lock update (tuicr)Libravatar sommerfeld9 days1-3/+3
|
* chore(thunderbird): switch flatpak app id to org.mozilla.thunderbirdLibravatar sommerfeld9 days8-21/+21
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Upstream marked org.mozilla.Thunderbird end-of-life. Flathub split it into two replacement IDs: org.mozilla.thunderbird monthly release channel (new default) org.mozilla.thunderbird_esr ESR / long-term-support channel Move to the lowercase monthly-release flatpak, which is what Mozilla now recommends for regular desktop users and gets features at the same cadence as Firefox. Renamed references in: * meta/flatpak.txt - the package list the user installs from * meta/base.txt - comment in the mail-bits section * dot_config/sway/config - window-match app_id rule for marking * dot_config/mimeapps.list - mailto/ics/webcal handler .desktop names * run_onchange_after_deploy-thunderbird.sh.tmpl - profile path under ~/.var/app/<id>/.thunderbird/ * run_onchange_after_deploy-tb-eer.sh.tmpl - flatpak override target and sandbox path for External Editor Revived bridge * run_onchange_after_deploy-pteid-pkcs11.sh.tmpl - Mozilla-family flatpak NSS DB registration list * README.md - doc snippets and xdg-mime example On-host migration: flatpak install -y flathub org.mozilla.thunderbird # Preserve accounts, OpenPGP keys, calendars, EER bridge wrapper: mv ~/.var/app/org.mozilla.Thunderbird ~/.var/app/org.mozilla.thunderbird flatpak uninstall -y org.mozilla.Thunderbird chezmoi apply -v update-desktop-database ~/.local/share/applications 2>/dev/null || true Verify mail handler: xdg-mime query default x-scheme-handler/mailto # -> org.mozilla.thunderbird.desktop
* refactor(suspend): drop SSH session inhibit; AC rule handles itLibravatar sommerfeld9 days1-20/+0
| | | | | | | | | | | Same rationale as the previous commit: the new policy is "if you don't want the machine to sleep, plug it in." An SSH-only inhibit in zprofile is redundant on AC (logind already ignores the lid) and inconsistent on battery (it would hold the lock for an SSH-attached idle session, defeating the schedule). Also removes a stale doc reference to zellij-inhibit-suspend.path which no longer exists.
* refactor(suspend): gate suspend on AC, drop bespoke zellij inhibitLibravatar sommerfeld9 days8-126/+33
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | New, simpler suspend policy: AC plugged in -> never auto-suspends (lid close ignored, idle no-op) On battery only -> lid close suspends, swayidle suspends at 30 min idle This replaces the SSH/zellij-aware inhibit machinery with a rule that matches the user's mental model: if you don't want the machine to sleep, plug it in. Long-running tasks (builds, downloads, SSH sessions, headless services) just need AC. Changes: * etc/systemd/logind.conf.d/20-lid-ac.conf: set HandleLidSwitchExternalPower=ignore so logind itself handles the AC case at the source. No userspace daemon, no race, no rate-limit risk. * dot_local/bin/on-battery-suspend: tiny POSIX wrapper that exits 0 when any /sys/class/power_supply/{AC,ADP}*/online == 1, else execs `systemctl suspend`. * dot_config/systemd/user/swayidle.service: add `timeout 1800 on-battery-suspend`. Idle suspend now exists, but only when on battery. * Delete zellij-inhibit-suspend.{path,service} + watcher script and remove the entry from systemd-units/user.txt. The .path re-trigger storm bug is moot because the whole mechanism is gone. Manual suspends (sway XF86Sleep keybind, sway power submode `s`, `systemctl suspend` over SSH) still always work regardless of AC -- explicit user intent wins. Also drop /migrate-podman-to-btrfs.sh from .gitignore; the one-off migration script has been deleted now that the user has switched their podman storage to the btrfs driver. On-host steps to apply: chezmoi apply -v systemctl --user daemon-reload systemctl --user reset-failed zellij-inhibit-suspend.service zellij-inhibit-suspend.path || true systemctl --user stop zellij-inhibit-suspend.path zellij-inhibit-suspend.service || true systemctl --user disable zellij-inhibit-suspend.path || true systemctl --user restart swayidle.service # logind drop-in is reloaded automatically by the etc deploy script. Verify: systemctl status systemd-logind | grep -i lid loginctl show-session $XDG_SESSION_ID | grep -i lid # Unplug AC -> close lid -> should suspend. # Plug AC -> close lid -> nothing happens.
* fix(suspend): make zellij inhibit watcher resilient to local-only sessionsLibravatar sommerfeld9 days2-15/+59
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | The previous watcher exited immediately whenever no SSH-spawned zellij was present. That caused a start-rate-limit storm: .path triggers service (zellij dir non-empty) -> watcher exits because no SSH zellij -> service stops -> .path retriggers (zellij dir still non-empty) -> ... 5 starts in 10s, systemd stops the path unit -> no inhibitor ever again, even after you SSH in Restructure so the watcher stays alive for the entire zellij socket directory lifetime and acquires/releases its own systemd-inhibit lock dynamically based on SSH-zellij presence: * Watcher now polls and exits only when the zellij socket dir is empty, matching the .path's trigger condition so it never re-fires while zellij is alive. * systemd-inhibit removed from ExecStart - watcher self-inhibits via a child 'systemd-inhibit ... sleep infinity' it can terminate on demand. * StartLimitIntervalSec=0 on the service as belt-and-braces against any future regression of the cycle. Recovery from the rate-limit hit: systemctl --user reset-failed zellij-inhibit-suspend.service zellij-inhibit-suspend.path systemctl --user daemon-reload systemctl --user restart zellij-inhibit-suspend.path
* feat(suspend): re-enable suspend on s2idle, drop diagnostic scaffoldingLibravatar sommerfeld9 days11-51/+36
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Confirmed root cause: this hardware's S3 (deep) firmware path triggers a fatal wake-from-suspend hang only on linux-hardened. INIT_ON_FREE + slab hardening + tighter locking turn a latent driver race that stock linux gets away with into an unrecoverable panic so early the journal isn't even flushed. mem_sleep_default=s2idle bypasses the BIOS S3 path entirely (s0ix is a pure-kernel low-power state) and suspends/resumes reliably under hardened. This is a widespread Lenovo S3 firmware issue across post-2018 ThinkPads (see Ubuntu T560, X1C9/10/11 reports). Lenovo themselves moved newer firmwares to s2idle-only. Not a linux-hardened bug per se; just hardened being a strict enough kernel to make the bug fatal. Keep: * mem_sleep_default=s2idle in etc/kernel/cmdline-linux-hardened.tmpl (only the hardened UKI; stock linux keeps unchanged shared cmdline) Revert (all the diagnostic / speculative scaffolding from the last few commits): * MODULES=(intel_lpss_pci) → MODULES=() — Arch wiki touchpad fix was not the cause here * nmi_watchdog=panic softlockup_panic=1 panic=10 — only needed to auto-reboot during diagnosis * no_console_suspend — diagnostic-only * etc/systemd/logind.conf.d/20-no-suspend.conf — masking workaround * sleep-target masking block in run_onchange_after_deploy-etc.sh.tmpl, replaced with a one-shot cleanup that removes any leftover /dev/null symlinks from systems that ran the previous version * systemd-pstore.service from systemd-units/system.txt — added only to catch the diagnostic panic * diagnose-suspend.sh helper (and its .gitignore/.chezmoiignore entries) * sway suspend → lock-session keybind workaround * power-menu.sh Suspend entry restoration * KEYBINDS.md docs
* fix(suspend): switch hardened to s2idle, keep console alive, archive pstoreLibravatar sommerfeld9 days2-1/+2
| | | | | | | | | | | | | | | | | | | | Previous attempt (early-loading intel_lpss_pci) did not fix the wake-from-suspend panic on linux-hardened. The journal of the failed boot ends cleanly at the last sync with no panic, oops, or even 'PM: suspend entry' message — the kernel dies so fast nothing is flushed, even with panic=10 + watchdog knobs. Three changes to make progress: * mem_sleep_default=s2idle: switch S3 'deep' (broken firmware path on Coffee Lake ThinkPads) to s2idle / s0ix. Many Lenovo machines only suspend reliably via s2idle; the stock linux kernel may be masking the issue elsewhere. * no_console_suspend: keep console alive across the suspend/resume cycle so the panic actually prints somewhere visible, instead of being eaten when the framebuffer goes dark. * systemd-pstore.service: archive /sys/fs/pstore/* to /var/lib/systemd/pstore/ on every boot, so the next panic (if EFI variables capture it) survives. Drop 'quiet' from hardened cmdline so console messages are visible.
* fix(suspend): load intel_lpss_pci from initramfs (Arch wiki touchpad fix)Libravatar sommerfeld9 days3-6/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Symptoms (Intel CPU + linux-hardened + blinking caps lock + hard hang on resume from S3) are a direct match for the Arch wiki entry: https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate#Touchpad_causes_a_kernel_panic_on_resume https://bbs.archlinux.org/viewtopic.php?id=231881 When intel_lpss_pci is loaded late (via udev after userspace is up), the touchpad/I2C controller it parents can be torn down by suspend before the module's resume callback is registered, leading to a NULL-deref panic during resume. The kernel never makes it far enough to flush logs — which matches our 'PM: suspend entry (deep)' being the last journal line. Fix: load intel_lpss_pci from the initramfs so it's available before the suspend/resume code path runs. Why this only bites linux-hardened: the hardening config enables INIT_ON_FREE, slab freelist hardening, page poisoning, and stricter pointer validation, which turn what's a silent UAF on stock linux into an immediate panic on hardened. Stock 'just works' by accident. Also drop the speculative init_on_free=0 from the hardened cmdline now that we have a targeted hypothesis. Keep nmi_watchdog=panic + softlockup_panic=1 + panic=10 as belt-and-braces: if this fix is wrong, the next hang will auto-reboot with a usable panic log in 'journalctl -b -1 -k' instead of needing the power button again.
* feat(suspend): hardened-only init_on_free=0 + hang-detection cmdlineLibravatar sommerfeld9 days4-2/+9
| | | | | | | | | | | | | | | | | | | | | | | | Split the hardened UKI cmdline off the shared etc/kernel/cmdline.tmpl so we can carry workarounds without poking the stock linux build. Daily-driving linux-hardened on this hardware has reliably hung on resume from S3: black screen, blinking caps-lock + power LED, only the power button helps. The kernel journal stops at 'PM: suspend entry (deep)' with nothing after, so the freeze is below the level where logs can flush — characteristic of a hard hang inside a device driver's suspend/resume callback rather than a userspace bug. linux-hardened defaults init_on_free=1, which zeroes pages on free. On Intel + iwlwifi/i915/nvme stacks this routinely surfaces latent UAFs as suspend hangs that are invisible on stock linux. Drop that knob to 0 for the hardened cmdline as the working hypothesis. Add nmi_watchdog=panic, softlockup_panic=1, panic=10 so if the next attempt still wedges, a stuck CPU self-panics and auto-reboots within ~10s, giving us a 'journalctl -b -1 -k' trace to look at instead of having to force-power-off blindly. Stock linux is untouched.
* feat(suspend): disable system suspend until hardened kernel resume issue is ↵Libravatar sommerfeld9 days5-21/+47
| | | | | | | | | | | | | | | | | | | | | | | fixed linux-hardened wedges on resume from S3 (NVMe/i915/iwlwifi driver UAF exposed by INIT_ON_FREE + slab hardening). Until root-caused, take suspend off the table while keeping lock + DPMS intact. - etc/systemd/logind.conf.d/20-no-suspend.conf: lid close, suspend key, hibernate key all map to 'lock'; IdleAction=ignore (swayidle drives DPMS+swaylock independently). - run_onchange_after_deploy-etc.sh.tmpl: mask sleep.target, suspend.target, hibernate.target, hybrid-sleep.target, suspend-then-hibernate.target via /etc/systemd/system -> /dev/null symlinks. Catches 'systemctl suspend' from any source. - dot_config/sway/config: XF86Sleep and system-mode 's' now run loginctl lock-session instead of systemctl suspend. - dot_config/sway/executable_power-menu.sh: drop Suspend entry. - KEYBINDS.md: reflect new behaviour. To re-enable later: remove the logind drop-in + symlink loop, then sudo systemctl daemon-reload.
* fix(suspend): only inhibit for SSH-spawned zellij sessionsLibravatar sommerfeld9 days3-13/+37
| | | | | | | | | | | | | | | | | | | A local zellij session (sway terminal, attended) shouldn't keep the laptop awake — that's the user actively in front of the machine, and normal suspend behaviour should apply. Only zellij sessions that were spawned from an SSH context need the persistent inhibit, so detach + disconnect leaves the host awake until the session ends. Use /proc/<pid>/environ to detect SSH-spawned zellij: the daemonised zellij server is exec'd by the client and Linux preserves the exec-time environment for the life of the process, so SSH_CONNECTION= survives the SSH session closing. Walk every running `zellij` pid; hold the lock as long as at least one of them has SSH_CONNECTION in its environ. The .path unit still fires on every zellij socket creation, but if no SSH-spawned zellij exists the watcher exits immediately and the service stops with no harm done — a couple of cheap process spawns per local session start, no inhibitor side-effects.
* fix(iwd): revert MAC randomization — broke DHCPLibravatar sommerfeld9 days1-23/+0
| | | | | | | | | | | | | | | | | | `AddressRandomization=network` made iwd present a per-SSID random MAC to every Wi-Fi network. On networks that pin DHCP leases or 802.1X access to a specific hardware MAC (corporate Wi-Fi, routers with DHCP reservations, MAC-filtered networks) this means iwd associates fine but DHCP never completes — the new MAC is unknown to the upstream. The privacy gain is marginal when the user only connects to a small set of known APs anyway, and the cost (no IP on a familiar network) is much worse than the threat model justified. Drop the override entirely; iwd's defaults (permanent MAC, no IP config — systemd-networkd remains the IP-layer authority via etc/systemd/network/30-wifi-bond0.network) match what we actually want. If we want privacy MAC again later, the right place is a systemd .link file with MACAddressPolicy=random, applied per-interface, not iwd-wide.
* feat(suspend): hold inhibit lock while any zellij session existsLibravatar sommerfeld9 days5-5/+65
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The SSH-shell inhibitor in dot_zprofile is bound to the lifetime of the login shell, so it disappears the moment the user detaches a zellij session and disconnects — defeating the whole point of using zellij for persistent remote work. Add a user-scope path+service+watcher trio that ties the inhibit lock to the existence of zellij sessions instead: - dot_local/bin/executable_zellij-inhibit-watcher Polls `zellij list-sessions --short` every 15s, exits when none remain. Override poll interval via $ZELLIJ_INHIBIT_POLL. - dot_config/systemd/user/zellij-inhibit-suspend.service Wraps the watcher in `systemd-inhibit --what=sleep:idle:handle-lid-switch --mode=block`. When the watcher exits, the service stops and the lock is released. - dot_config/systemd/user/zellij-inhibit-suspend.path Activates the service whenever $XDG_RUNTIME_DIR/zellij becomes non-empty (i.e. zellij creates its first session socket). Re-fires on every empty→non-empty transition. Enable via systemd-units/user.txt (the .path unit; the service is on-demand). The existing SSH-shell inhibitor is kept as a backstop for non-zellij remote sessions and is now documented as such. VM (nix/vm.nix) deliberately not updated: the Ubuntu remote-dev VM never suspends, so the inhibit machinery would be inert there.
* feat(zsh): inhibit suspend while an SSH session is activeLibravatar sommerfeld9 days1-0/+17
| | | | | | | | | | | | | | | A remote session is useless if the laptop suspends mid-command, but logind doesn't suppress lid-close or idle-suspend for SSH sessions on its own — you have to hold an explicit inhibitor lock. When $SSH_CONNECTION is set, re-exec the login shell under `systemd-inhibit --what=sleep:idle:handle-lid-switch --mode=block` so the lock is bound to the shell's lifetime: it covers swayidle, logind's HandleLidSwitch, and any other consumer that respects inhibit locks, and it's released the moment the SSH session ends. A guard env var prevents recursion if the user nests a login shell inside the wrapped one (e.g. `exec zsh -l`).
* feat(suspend): bounce snx-rs around system sleepLibravatar sommerfeld9 days2-0/+48
| | | | | | | | | | | | | | | | | | | | | | snx-rs (Check Point VPN) doesn't notice that its tunnel died during suspend: the IKE keepalive is interrupted and the SAML cookie may expire, but the daemon happily sits on dead sockets after resume. `snxctl status` keeps reporting "Connected" while no traffic actually flows, so the user has to manually disconnect+reconnect. Install an /etc/systemd/system-sleep/ hook that stops the user-scope snx-rs.service before suspend and starts it on resume. The tunnel is left disconnected after resume; the waybar toggle (or any `snxctl connect`) re-establishes it, going through SAML only if the cached cookie has actually expired. The hook enumerates logged-in users via loginctl and skips any that don't have snx-rs.service enabled, so it's a no-op on machines that don't use the VPN. Also teach run_onchange_after_deploy-etc.sh.tmpl to install files under etc/systemd/system-sleep/ with mode 0755 (systemd ignores sleep hooks that aren't executable).
* feat(podman): switch rootless storage driver to btrfsLibravatar sommerfeld9 days4-4/+19
| | | | | | | | | | | | | | | | | | | | | | | | | fuse-overlayfs is dog-slow on `podman commit` (and noticeably slower than native overlay/btrfs for layer extraction in general) because every read/write round-trips through a FUSE daemon. The kernel overlay driver does not support btrfs as a lowerdir, so on a btrfs root fs the choices were: - fuse-overlayfs (slow, but works) - btrfs (native subvolume + CoW snapshot per layer; fast) Switching graph drivers is destructive — the on-disk layout is incompatible, so a one-time `podman system reset --force` is required. A migration helper script lives at the repo root (gitignored, chezmoiignored) that snapshots stateful containers, exports images and volumes, runs the reset, and restores everything on the new driver. Drops fuse-overlayfs from meta/base.txt — no longer needed and pulls in libfuse3 transitively for nothing. (Flatpak still depends on it for its own sandbox; pacman won't actually uninstall the binary while flatpak is around — that's fine.) VM (nix/vm.nix) is unaffected: it sets its own storage.conf inline with driver=overlay since its rootfs is ext4.
* fix(hardened): restore podman compatibility on linux-hardenedLibravatar sommerfeld9 days2-0/+8
| | | | | | | | | | | | | | | | | Two breakages observed on first linux-hardened boot: 1. `podman run` failed because linux-hardened sets kernel.unprivileged_userns_clone=0 by default (stock linux: 1). Rootless podman requires unprivileged user namespaces. Restoring the stock-kernel default via sysctl — this is a documented hardened knob meant to be flipped back if you actually use rootless containers. No-op on stock kernel. 2. "kernel does not support overlay fs: 'overlay' is not supported over btrfs". Kernel overlayfs cannot use a btrfs subvolume as lowerdir; podman needs fuse-overlayfs as the user-mode shim. ~10-30% slower I/O than native overlay but works correctly and is the upstream recommendation for btrfs-backed rootless storage.
* docs(bootstrap): also suggest fallback UKI EFI entriesLibravatar sommerfeld9 days1-1/+7
| | | | | | | | Pair each default UKI entry with its fallback so the boot order list mirrors the four UKIs mkinitcpio produces. Fallback entries are optional — UEFI firmware menus can usually pick UKIs from /EFI/Linux/ directly — but having named entries lets you reorder / --bootnext them without dropping into the firmware menu.
* Revert "refactor(boot): drop linux-hardened-fallback UKI"Libravatar sommerfeld9 days1-1/+4
| | | | | | | | | Keeping the fallback after all — leaves the door open to dropping the stock 'linux' package entirely once linux-hardened is proven as a daily driver. Without hardened-fallback, that future single-kernel config would have zero autodetect recovery path. This reverts commit c0c9183.
* refactor(boot): drop linux-hardened-fallback UKILibravatar sommerfeld9 days1-4/+1
| | | | | | | | | Stock linux-fallback already covers the 'autodetect missed a module' recovery scenario, regardless of which kernel you tried to boot. hardened being opt-in means a hardened-default failure naturally falls back to stock — no need for hardened-fallback as a second safety net. Saves ESP space and mkinitcpio regen time on each linux-hardened update.
* docs(bootstrap): mention optional linux-hardened EFI entryLibravatar sommerfeld9 days1-0/+4
| | | | | | The hardened kernel ships as a parallel UKI; document its efibootmgr registration alongside the stock one. Stock stays default-boot; hardened is selected on demand (efibootmgr --bootnext or firmware menu).
* feat(sandbox): bwrap wrappers for mpv, yt-dlp, streamlinkLibravatar sommerfeld9 days5-0/+75
| | | | | | | | | | | | | | | | | | | | | | | | | These three tools are the native (non-flatpak) network parsers in the install set — every other internet-facing app is already flatpak'd. The threat model is a RCE in a subtitle/extractor/muxer that walks $HOME looking for SSH keys, GPG keyring, pass store, cloud tokens, etc. Approach (defence in depth, not full sandboxing): - bwrap --bind / / keeps Wayland, PipeWire, DBus, GPU, hwaccel and all config files working transparently. - --tmpfs over known-sensitive dirs (.ssh, .gnupg, .password-store, .config/gh, .config/op, .aws, .local/share/keyrings) blanks them from the sandbox view; a compromised parser literally cannot see them. - inner PATH stripped of ~/.local/bin so streamlink's spawn of `mpv` resolves to /usr/bin/mpv and does not re-enter the sandbox. - --die-with-parent + --new-session for tidy lifecycle. - Escape hatch: SANDBOX=0 mpv ... bypasses for one invocation. - Graceful degradation if bwrap is missing (warns and execs anyway). bubblewrap added explicitly to meta/base.txt (was implicit via flatpak). Wrappers in ~/.local/bin shadow /usr/bin via dot_zprofile:15 PATH order. Not symlinked into the Ubuntu VM (nix/vm.nix does not touch ~/.local/bin), which is fine: those tools on the headless VM don't need sandboxing.
* feat(boot): add linux-hardened as parallel UKILibravatar sommerfeld9 days2-0/+21
| | | | | | | | | | | | | | | | | | | | Installs linux-hardened + linux-hardened-headers alongside the stock linux kernel. Stock kernel remains the default; linux-hardened is opt-in via efibootmgr --bootnext after the EFI entry is registered (one-time host-side step, documented in the preset). After first 'just pkg-apply', mkinitcpio auto-builds /boot/EFI/Linux/arch-linux-hardened.efi from the new preset (sharing etc/kernel/cmdline.tmpl with the stock UKI — same LUKS root, no kernel-specific cmdline knobs). Host-side EFI entry registration: sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \ --label 'Arch Hardened' --loader '\\EFI\\Linux\\arch-linux-hardened.efi' Roll back any time by removing both packages and the preset file; the stock kernel and its UKI are untouched.
* feat(iwd): per-SSID MAC randomisationLibravatar sommerfeld9 days1-0/+23
| | | | | | | | | | | AddressRandomization=network: iwd generates a deterministic per-SSID random MAC. Hardware MAC is never exposed on Wi-Fi; reconnects to the same network reuse the same MAC, so DHCP leases, WPA-EAP creds and captive portals stay stable. EnableNetworkConfiguration=false keeps systemd-networkd authoritative for IP — the existing 30-wifi-bond0.network setup is unaffected and the wlan interface still gets enslaved into bond0.
* feat(polkit): restrict systemd + udisks system actions to active local sessionsLibravatar sommerfeld9 days2-0/+26
| | | | | | | | | | | | | | | | Two narrow defence-in-depth rules: - 52-systemd-local-only: org.freedesktop.systemd1.* requires both subject.local and subject.active. Wheel-via-sudo-rs is on a different path (sudoers) and is not affected. Stops a non-active or remote polkit caller from start/stop/restart of system units. - 53-udisks-system-mount: filesystem-mount-system and modify-system require subject.active. The everyday USB auto-mount path uses filesystem-mount (no -system suffix) and is unaffected. Audited against current workflow (virt-manager, networkctl, USB mount, bluetoothctl, fwupdmgr) — none of these break.
* feat(sysctl): kernel info-disclosure + ICMP/IPv6 RA hardeningLibravatar sommerfeld9 days1-1/+39
| | | | | | | | | | | | | | | | | | | | | Adds standard KSPP-style sysctl hardening that does not interfere with the existing dev workflow: - kptr_restrict=2, unprivileged_bpf_disabled=1, bpf_jit_harden=2 - kexec_load_disabled=1 (no kexec in use) - fs.suid_dumpable=0 - ICMP broadcast/bogus-error ignores - tcp_timestamps=0 (BBR+cake do not need RFC1323 timestamps) - IPv6 RA disabled at kernel layer (systemd-networkd is authoritative) - explicit tcp_syncookies=1 Drops 'kernel.yama.ptrace_scope = 0' so the kernel default 1 (parent only) applies. Debugging own builds via 'gdb ./a.out', 'lldb -- ./bin', 'rust-gdb' still works; only attach-by-PID now needs sudo, accepted trade-off. Intentionally kept dev-permissive: kernel.sysrq=1, kernel.dmesg_restrict=0, kernel.perf_event_paranoid=-1
* chore(nix): flake.lock update (home-manager, nixpkgs, tuicr)Libravatar sommerfeld9 days1-9/+9
|
* fix(nix): tuicr switched to packages.${system}.default schemaLibravatar sommerfeld9 days1-3/+1
| | | | | | | | Upstream tuicr commit 5b19712 migrated from the legacy `defaultPackage.<system>` flake output to the standard `packages.<system>.default`, which broke `nix-update` with: error: attribute 'defaultPackage' missing
* fix(nftables): waydroid DHCP/DNS ingress, drop manual NAT tableLibravatar sommerfeld2026-05-221-19/+9
| | | | | | | | | | Mirror the libvirt pattern by accepting DHCP+DNS on waydroid0 so the Android container's DhcpClient can lease an IP from dnsmasq. Remove the manual ip nat MASQUERADE table: waydroid-container installs its own MASQUERADE rule via iptables-nft compat, so the explicit table is redundant (and was clobbering anything else in ip nat via the destroy table).
* fix(sway): disable shortcut inhibitor for waydroid windowsLibravatar sommerfeld2026-05-221-0/+1
|
* fix(nftables): add MASQUERADE for waydroid0Libravatar sommerfeld2026-05-221-3/+19
| | | | | | | waydroid-container ships only the iptables-legacy code path for adding its POSTROUTING MASQUERADE; on a host with pure nftables the rule never lands and the Android container has no outbound NAT. Declare it explicitly in our nftables.conf for determinism.
* Revert "fix(sysctl): enable net.ipv4.ip_forward for NAT bridges"Libravatar sommerfeld2026-05-221-5/+0
| | | | This reverts commit eca1a71fc486690489f7aef671d7beccc2ec3f25.