Autoconnect belongs to the service, not the gui #191
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
autoconnectis a global setting inROOTDIR/config.json, toggleable from both clients (aqomui -e autoconnect), but the only thing that acts on it is the gui:net_state_changed→connect_last_server(aqomui_gui.py:1312-1327), plus gui startup. So:The service is the only component that exists at boot (
WantedBy=multi-user.target) and the only one alive continuously. It should own autoconnect.How the field does it
The daemon is the app; the gui is a thin client.
tailscaledowns state in/var/lib/tailscale; cli and gui are clients over a local socket/etc/NetworkManager/system-connections(root, 0700),connection.autoconnectis a profile property, brought up at boot with no session in existencesystemctl enable wg-quick@wg0; the unit is the feature, there is no policy layermullvad-daemonholds the account, settings and relay constraints;mullvad auto-connect set on; relay selection runs in the daemonEven the app-centric clients run daemon-owned execution: ProtonVPN disconnects when its app quits, but that is the app requesting a disconnect from its system service as a UX policy — the permanent kill switch keeps enforcing after the app exits, which proves where the tunnel actually lives. Quit semantics are a policy knob, not an architecture.
The common invariant: the state a daemon acts on unattended is daemon-owned. None of them have root parse a user-writable file and act on it with no session present. NM's secret-agent protocol is the sanctioned exception and it is narrow — secrets, on demand, with a session.
Why that invariant matters here
Per-user data (
server.json,profile.json,last_server.json) lives in~/.aqomui, which for the root service is/root/.aqomui. Having the service read the calling user's home to resolve "random favourite" at boot would turn a user-writable file into an unattended root action.Today a dict reaching
connect_to_serverhas passedrequire_authorization(polkit, active local session), and the dict'spathis a config file the service hands to openvpn as root. The import path comments outup/downscript directives (custom.py:145-146), but a file written directly at that path never went through import. The polkit gate is doing real work; removing it for an unattended boot-time path turns a same-privilege capability into a persistence primitive.The intent model
"Autoconnect" and "reconnect" are different questions the current single flag conflates: should the machine come up tunneled? (default state) vs I connected and the network blipped — should it come back? (continuity of something already asked for). The field's answer is not two toggles, it is intent tracking à la Mullvad's target state:
{intent, server dict}, written on every connect/disconnect it performs.autoconnectsetting shrinks to one honest question: intent at boot. On = the service starts with intent up, replaying the record. Off = starts neutral.Shape
{intent, last server dict}in its own root-owned storage next toconfig.json— content vetted by an authorized session at the moment it was used.require_server_dictvalidation on a replayed dict as on one arriving over D-Bus.connect_last_servershrinks to re-rolling a policy pick at startup — the only part that needs the catalog.Gui lifecycle
Without these the model is incoherent — autoconnect brings the tunnel up at boot, the user opens the gui to check on it, and aqomui_gui.py:148-149 disconnects it:
disconnect main/disconnect bypasson init becomes "readget_tunnel_state, render it" (the read already exists three lines up).disconnect on quit): Mullvad semantics leave the tunnel up, ProtonVPN semantics tear it down. Both are one line once the service owns the tunnel; switching the default silently is the only wrong option.Consequences to accept deliberately
homedirs.jsonand the bypass owner. Document, don't arbitrate.Longer arc, not this issue
The field's answer to wanting random/fastest at boot is daemon-owned preferences, not root reading user files: move the preference, and the catalog it resolves against, into service-owned storage set through the authenticated API. The service already downloads and builds the server list in
import_threadand then delivers it into the user's home (homedirs.jsonremembers where), so the data already originates root-side — inverting that is closer to undoing a detour than to a new imposition.Worth knowing before chasing it: "fastest" at boot is partly illusory today. Latencies are only measured by the gui (
get_latencies), so a boot-time resolution uses whatever was last measured — possibly days old, possibly on a different network.Depends on #123 for the decision module.