<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotfiles/systemd-units, branch master</title>
<subtitle>My linux config and rc files</subtitle>
<id>https://git.sommerfeld.dev/dotfiles/atom/systemd-units?h=master</id>
<link rel='self' href='https://git.sommerfeld.dev/dotfiles/atom/systemd-units?h=master'/>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/'/>
<updated>2026-06-05T10:06:03Z</updated>
<entry>
<title>Update nix tools and system units</title>
<updated>2026-06-05T10:06:03Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-06-05T10:06:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=ddb31f2e965fb71ab949a54043c82f958b818442'/>
<id>urn:sha1:ddb31f2e965fb71ab949a54043c82f958b818442</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Move more host tooling to Nix</title>
<updated>2026-06-05T10:06:02Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-06-05T10:06:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=b0e83e2ee3fc328e55119ee7c1f09ad7ed20a635'/>
<id>urn:sha1:b0e83e2ee3fc328e55119ee7c1f09ad7ed20a635</id>
<content type='text'>
</content>
</entry>
<entry>
<title>refactor(mail): migrate protonmail-bridge from pacman to nix</title>
<updated>2026-06-05T10:06:02Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-06-05T10:06:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=4d8cf1bc30a076e2976787051d28d8072f8f5321'/>
<id>urn:sha1:4d8cf1bc30a076e2976787051d28d8072f8f5321</id>
<content type='text'>
Move the ProtonMail Bridge off the AUR protonmail-bridge-core package
and onto nix/host.nix, consistent with the other migrated user-leaf
tools. Since the AUR package previously supplied the systemd user unit
(customized via a drop-in), ship a repo-owned
dot_config/systemd/user/protonmail-bridge.service instead: it runs the
nix binary by absolute %h/.nix-profile/bin path with --noninteractive
and folds the former drop-in's PASSWORD_STORE_DIR into the unit, so the
now-redundant protonmail-bridge.service.d/override.conf is removed.

Drop protonmail-bridge-core from meta/base.txt (the git send-email Perl
prereqs stay). No vm.nix change: the bridge is host-only and user units
are not symlinked on the headless VM.
</content>
</entry>
<entry>
<title>fix(systemd): ship poweralertd.service user unit</title>
<updated>2026-06-05T10:06:02Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-06-05T10:06:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=7414a25165e1469cacee3fa9d8e9dd25df640aa0'/>
<id>urn:sha1:7414a25165e1469cacee3fa9d8e9dd25df640aa0</id>
<content type='text'>
poweralertd was migrated to nix (host.nix) but, like mako, the nix
package does not ship a systemd user unit on the manager's search
path. sway-session.target's Wants=poweralertd.service referenced a
non-existent unit (previously the pacman package supplied
/usr/lib/systemd/user/poweralertd.service), so battery/AC
notifications never started at login.

Add a repo-owned poweralertd.service (absolute nix-profile path) and
register it in systemd-units/user.txt.
</content>
</entry>
<entry>
<title>fix(systemd): ship mako.service user unit</title>
<updated>2026-06-05T10:06:01Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-06-05T10:06:01Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=c613f5b23418c5b68c6116ec22ce46d1a1fb42a2'/>
<id>urn:sha1:c613f5b23418c5b68c6116ec22ce46d1a1fb42a2</id>
<content type='text'>
The nix mako package does not ship a systemd user unit on the user
manager's search path, so sway-session.target's Wants=mako.service
referenced a non-existent unit after the pacman-&gt;nix migration
(previously the Arch mako package provided /usr/lib/systemd/user/
mako.service). mako only started on first D-Bus notification, never
eagerly at session login.

Add a repo-owned mako.service (Type=dbus, org.freedesktop.Notifications)
using the absolute nix-profile path, matching the other sway-session
units, and register it in systemd-units/user.txt.
</content>
</entry>
<entry>
<title>refactor(suspend): gate suspend on AC, drop bespoke zellij inhibit</title>
<updated>2026-05-29T10:18:15Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-05-29T10:18:15Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=ec3734c5ef9fcfe97c21cd19f198ec779ab5f052'/>
<id>urn:sha1:ec3734c5ef9fcfe97c21cd19f198ec779ab5f052</id>
<content type='text'>
New, simpler suspend policy:

  AC plugged in   -&gt; never auto-suspends (lid close ignored, idle no-op)
  On battery only -&gt; 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 -&gt; close lid -&gt; should suspend.
  # Plug AC   -&gt; close lid -&gt; nothing happens.
</content>
</entry>
<entry>
<title>feat(suspend): re-enable suspend on s2idle, drop diagnostic scaffolding</title>
<updated>2026-05-29T10:18:15Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-05-29T10:18:15Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=6e0c5c33438e5e898bd075c33a45b3abf9d1b26b'/>
<id>urn:sha1:6e0c5c33438e5e898bd075c33a45b3abf9d1b26b</id>
<content type='text'>
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
</content>
</entry>
<entry>
<title>fix(suspend): switch hardened to s2idle, keep console alive, archive pstore</title>
<updated>2026-05-29T10:18:14Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-05-29T10:18:14Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=ad8e14860fa0ca978f5ef6e02860d24f5e39c361'/>
<id>urn:sha1:ad8e14860fa0ca978f5ef6e02860d24f5e39c361</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat(suspend): hold inhibit lock while any zellij session exists</title>
<updated>2026-05-29T10:18:13Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-05-29T10:18:13Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=01df321e907b6c8568bb8622eb44a5c1486a0631'/>
<id>urn:sha1:01df321e907b6c8568bb8622eb44a5c1486a0631</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat: teams autostart, llama-cpp-vulkan ignore, snxctl-chromium wrapper</title>
<updated>2026-05-14T09:58:37Z</updated>
<author>
<name>sommerfeld</name>
<email>sommerfeld@sommerfeld.dev</email>
</author>
<published>2026-05-14T09:58:37Z</published>
<link rel='alternate' type='text/html' href='https://git.sommerfeld.dev/dotfiles/commit/?id=3fc4705b6c711871a920290feef138694e72d29a'/>
<id>urn:sha1:3fc4705b6c711871a920290feef138694e72d29a</id>
<content type='text'>
systemd/user/teams-{sii,xsight}.service: autostart both Teams flatpak
profiles on sway-session.target login. KillMode=mixed so SIGTERM hits
only the wrapper process — both instances share the same flatpak app
id, so killing by app id would take down the sibling instance. A 15s
SIGKILL fallback covers the case where Electron tray-hides instead of
quitting. Both units listed in systemd-units/user.txt.

etc/pacman.conf: IgnorePkg = llama-cpp-vulkan. The AUR package rebuilds
on every llama.cpp commit (multi-hour build). Update manually with
`paru -S llama-cpp-vulkan` when intended.

snxctl-chromium wrapper:
- dot_local/share/snx-rs/bin/xdg-open: shim that flatpak-runs
  ungoogled-chromium, used only by snx-rs.
- dot_config/systemd/user/snx-rs.service.d/10-chromium-saml.conf:
  drop-in prepending that dir to the daemon's PATH so snx-rs's
  opener-crate call to xdg-open lands in chromium, without affecting
  xdg-open for any other process.
- dot_local/bin/snxctl-chromium: convenience wrapper that
  daemon-reloads and restarts snx-rs.service if the drop-in isn't yet
  applied, then execs `snxctl connect`.

firefox/user-overrides.js: revert the dom.security.https_only_mode.
upgrade_local and network.lna.local-network-to-localhost.skip-checks
prefs — they didn't actually fix the SAML flow. Replaced with a
comment pointing to the wrapper instead.
</content>
</entry>
</feed>
