| Commit message (Collapse) | Author | Age | Files | Lines |
| | |
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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`).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| | |
|
| |
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
| |
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).
|
| | |
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
| |
This reverts commit eca1a71fc486690489f7aef671d7beccc2ec3f25.
|
| |
|
|
|
|
|
| |
waydroid (and libvirt with finicky guests) need the host to route
between their NAT bridge and the upstream NIC. libvirtd usually
enables this on demand but it doesn't persist, so the container has
no internet on a fresh boot until something else flips the bit.
|
| |
|
|
|
|
|
|
|
|
| |
Name= negation list was failing in practice — veth/waydroid interfaces
were still being enslaved into bond0, taking down host networking.
Switch to positive matching: Path=pci-*|platform-* AND Name=en* AND
Type=ether. Virtual interfaces (veth, virbr, waydroid0, docker0, ...)
have no udev ID_PATH and never start with 'en', so they're cleanly
excluded by the AND of all three keys.
|
| |
|
|
|
|
|
|
|
| |
systemd-networkd's Type=ether matcher was enslaving waydroid0 into
bond0 the moment 'waydroid session start' ran, taking down the host's
default route. Mirror the libvirt/docker negation pattern.
Also mirror the existing virbr0 forward accepts for waydroid0 so the
Android container can actually reach the internet through MASQUERADE.
|
| | |
|
| |
|
|
|
|
| |
Add dot_config/tuicr/config.toml with theme = "gruvbox-dark".
Symlinked from nix/vm.nix per the symlink invariant so the same
config applies on both host (via chezmoi) and VM (via home-manager).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Previously every new login retargeted ~/.ssh/agent.sock to its own
per-connection forwarded socket. That broke a multi-connection setup
when the most-recent connection (which 'won' the symlink) dropped:
all surviving connections' panes would point at a dead socket until a
fresh login from a surviving connection re-ran zprofile.
zprofile: only retarget when the existing symlink target is dead
(sshd unlinks the per-connection socket on disconnect, so [[ -S ]] on
the resolved path is a reliable liveness probe). First connection
seeds the symlink, subsequent logins keep using it.
ssh-agent-refresh: scan /tmp/ssh-*/agent.* for any live forwarded
socket and retarget to the first that responds to ssh-add. Lets the
surviving connection recover without waiting for a new login shell.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Switching to nix's zsh on the Arch host left two functional gaps the
Arch zsh package used to fill:
1. /usr/share/zsh/site-functions in fpath: pacman, paru, systemctl,
journalctl, flatpak, docker, kubectl, makepkg etc. drop their
completions there. nix zsh's compiled-in fpath doesn't include
/usr/share so we lose all of them silently. Added that path (and
vendor-completions for the VM's apt-installed completions) to the
existing fpath loop, guarded by [[ -d ]].
2. HELPDIR for the run-help / help-alias machinery: needed so 'help cd'
etc. find the per-builtin help docs. Pick the first existing version
dir, preferring nix-profile so it matches the running zsh version.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
tuicr's upstream flake uses the legacy 'defaultPackage.<system>' output
schema, not 'packages.<system>.default' — fixes the home-manager switch
error 'attribute packages missing' at nix/flake.nix:28.
zsh: removing the system zsh package took /etc/zsh/zprofile with it,
which used to 'source /etc/profile' and pull in /etc/profile.d/*.sh
(flatpak.sh, nix.sh, etc.). Reconstruct XDG_DATA_DIRS in dot_zprofile
defensively, including per-user + system flatpak exports + nix-profile
share, so 'flatpak update' stops warning and desktop entries from
flatpak/nix-installed apps work in launchers (fuzzel).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Waybar (and other user services) was inheriting the bare pre-login
PATH from systemd --user, missing ~/.nix-profile/bin and ~/.local/bin.
Modules that call nix-provisioned binaries (pass, python3, ncat from
common.nix) silently picked up system copies instead — symptom was
waybar showing different output from the same script when invoked
manually (thunderbird tb-unread.sh, wifi-status.sh).
Also propagate GNUPGHOME and GPG_TTY so pinentry / pass-otp inside
user services behave the same as in the interactive shell.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Forwarded SSH_AUTH_SOCK lives at /tmp/ssh-XXX/agent.NNN — a
per-connection path that disappears on disconnect, leaving every
long-running zellij pane (and its children: claude-code, nvim, …)
pointing at a dead socket. Reattaching after reconnect doesn't help:
the env was captured when zellij first started.
Fix: maintain ~/.ssh/agent.sock as a symlink, re-aimed at the live
forwarded socket on every login (zprofile). Export the stable path so
processes inherit a value that survives reconnects — git fetch /
commit signing keep working in re-attached zellij panes with zero
per-pane re-export.
Adds 'ssh-agent-refresh' helper for transitional panes still holding
the dead per-connection path: re-exports SSH_AUTH_SOCK to the stable
symlink and validates with ssh-add -l. Already-running children
(claude-code) must still be restarted since env is inherited, not
observed.
|
| |
|
|
|
|
|
|
| |
Make it explicit that adding, removing, or renaming any chezmoi-deployed
config requires a matching update to nix/vm.nix's xdg.configFile /
home.file blocks when the corresponding binary is in common.nix.
Drives the audit performed in the previous commit.
|