# Optional: run nexthopd as a systemd --user unit so monitoring continues # while omarchy-shell is down (a shell restart, a different session). # # mkdir -p ~/.config/systemd/user # sed "s|@PLUGIN_DIR@|$HOME/.config/omarchy/plugins/io.github.x3me.nexthop|" \ # nexthopd.service > ~/.config/systemd/user/nexthopd.service # systemctl --user enable --now nexthopd # # The shell's Service.qml sees the flock is held and simply attaches. # # The other direction needs a line too: if this unit starts while the # shell's own daemon already holds the lock, ours exits 3 ("lock held"). # That is a clean outcome, not a failure, and without # RestartPreventExitStatus systemd would restart it every 5 s until the # next shell restart handed the lock over. # # Restart=always rather than on-failure, because the version handover # (Service.qml, after `omarchy plugin update`) retires the running daemon # with SIGTERM and the daemon exits 0 on it. on-failure would leave this # unit inactive after every update, and the shell would quietly take the # daemon over — ending the "survives a shell restart" promise this unit # exists for. RestartSec must beat the shell's own respawn, which fires # 2.5 s after the retire: whoever takes the flock first keeps it, and it # should be us. systemd's default start limit still stops a crash loop. [Unit] Description=Nexthop internet quality monitor daemon After=network.target [Service] ExecStart=/usr/bin/python3 -m nexthopd WorkingDirectory=@PLUGIN_DIR@ Restart=always RestartPreventExitStatus=3 RestartSec=1 Nice=10 MemoryMax=256M # Containment, limited to what the daemon can live inside. Each line below # was exercised: a second daemon ran under this exact set against its own # state and runtime dirs and measured everything — both probe kinds, iw, # nmcli, ss with socket owners, the reachability curl. NoNewPrivileges=yes RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes LockPersonality=yes MemoryDenyWriteExecute=yes RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK SystemCallArchitectures=native SystemCallFilter=@system-service # Deliberately NOT set: ProtectSystem=, ProtectHome=, PrivateTmp=, # ProtectKernelTunables=, ProtectKernelModules=, ProtectControlGroups=. # In a user unit each of those is built on a user namespace, and from # inside one /proc//fd of every other process is unreadable, so # `ss -p` sees every socket and can name the owner of none of them. # Measured: 45 sockets with owners outside, 0 under any one of those six. # That would blank the Apps tab and the kernel socket timing, which is # most of what this daemon knows about the user's own traffic. [Install] WantedBy=default.target