Fix/issue 4 hardening sweep - #6
Merged
Merged
Conversation
Closes the install half of #4: the new dbus.service reload after copying io.otectus.Archer1.conf removes the "had to reboot to connect" symptom. - modules/gui.sh: reload dbus.service (with kill -HUP fallback) on install and uninstall so the policy file takes effect immediately. - modules/gui.sh: pre-flight existence checks for every source file so a missing .conf or .svg fails the module instead of silently leaving the system half-installed. - install.sh, uninstall.sh: source new lib/modules.sh which holds the canonical MODULE_IDS / MODULE_LABELS arrays and an is_known_module allowlist. The uninstall manifest source loop now refuses to source untrusted module names (e.g. "../../tmp/evil") that a tampered install-manifest.json could otherwise inject. - lib/manifest.sh: move manifest to /var/lib/archer/install-manifest.json (root-owned, 0644). Resolves the install-as-user / uninstall-as-sudo HOME mismatch and stops local users tampering with manifest entries that uninstall later sources. migrate_legacy_manifest() handles existing installs from $HOME or /root, including the legacy DAMX path. - install.sh: per-module rollback. INSTALLED_FILES / DKMS / PACKAGES are snapshotted before each module_install and restored on failure, so the saved manifest only lists modules that actually installed. - uninstall.sh: guard rm -rf "$HOME/.local/share/..." against unset or "/" HOME (sudo can leave HOME unset). - tests/install.bats: cover is_known_module against path-traversal, shell metacharacters, well-formed-but-unknown IDs, and every canonical module ID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Unix-socket fallback in the daemon was unreachable from the user-mode
GUI (the socket was created with mode 0o660 root:root). Removing it means
D-Bus is now mandatory, and a real D-Bus startup failure produces an
actionable error message instead of a misleading "deprecated" warning.
- gui/archer-daemon.service: switch PID/socket scratch to
RuntimeDirectory=archer (systemd creates /run/archer/ at every start
with 0755 root:root and removes it on stop). Add ReadWritePaths=
/etc/archer because ProtectSystem=full would otherwise block the
settings.json writes. Add Requires=dbus.service so the daemon won't
start before the bus.
- gui/archer_daemon.py: drop the DaemonServer class and its 30+
_cmd_* dispatch methods. Drop SOCKET_PATH and the socket import.
Move PID_FILE to /run/archer/daemon.pid. Rewrite main() so a missing
or failed D-Bus registration logs a clear error pointing at the
policy file + dbus reload, then exits.
- gui/archer/pages/internals.py: replace the stale
"Daemon Socket: /var/run/archer.sock" UI string with the actual
D-Bus name "io.otectus.Archer1 (system bus)".
- gui/install-gui.sh: removed (legacy wrapper, no in-tree references;
use ./install.sh --modules gui).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This is the first half of the issue #4 user-visible fix. The GUI no longer hangs when the daemon is slow, and the user sees an actionable message when D-Bus is genuinely down. - gui/archer/client.py: every D-Bus method invocation now passes timeout=TIMEOUT (5s default) including the startup Ping(). Setters that legitimately take longer (envycontrol, fwupd, restart) get LONG_TIMEOUTS overrides. Without this, dbus-python defaulted to no timeout and a single hung sysfs read in the daemon would freeze the polling thread forever. - gui/archer/client.py: drop the dead Unix-socket fallback and its 234 lines of retry/backoff. The daemon socket has been root:root 0o660 since the move to D-Bus, so the user-mode GUI was never actually able to fall back. _init_dbus failures are now stored on self._init_error (and logged at WARNING) so the window can show them in a toast instead of silently flipping to "Daemon Offline". Add reconnect() so the window's backoff loop can re-handshake after a daemon restart (the old proxy is invalidated). - gui/archer/window.py: replace the fixed-5s reconnect with an exponential backoff sequence (5, 10, 20, 60s) so a long-down daemon doesn't get hammered. Reset to step 0 on the first successful settings load. Show the client's init_error in a toast at the moment the GUI first detects the offline daemon. - gui/archer/application.py: tray import / startup failures are logged at WARNING with the exception (was bare 'except: pass'). First time the window is shown after a tray-init failure, a one- shot toast tells the user that close-to-tray won't work this session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second half of the issue #4 user-visible fix. Telemetry now arrives via a D-Bus signal instead of being polled from the GUI thread, so a hanging daemon can never freeze the dashboard. - gui/archer_dbus.py: ArcherDBusService now arms a 2s GLib timer that serializes get_monitoring_data() into the existing TelemetryUpdated signal (declared but never emitted in the previous version). Returns True so GLib re-arms; transient sysfs errors are logged but don't kill the timer. - gui/archer/window.py: rip out _poll_monitoring / _fetch_monitoring (and the associated thread spawning) and replace with a signal subscription on TelemetryUpdated. _on_telemetry_signal updates the last-seen monotonic timestamp and pushes data into the dashboard; _check_staleness flips the status label to "Stale" if no signal arrives within 6s and kicks the existing reconnect/backoff path. - gui/archer/window.py: re-subscribe on every _start_monitoring call by removing the prior SignalMatch first, so a daemon restart (which invalidates the old proxy) doesn't leave the GUI permanently listening on a dead handle. - gui/archer/window.py: _on_settings_loaded clears _is_stale on success so the state machine can't get stuck "Stale" if the next signal is delayed. GetMonitoringData stays on the wire for the initial settings fetch and backward compat with older clients; the GUI subscribes after the first successful settings load. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- run_cmd now refuses cmds containing shell metacharacters (;, &&,
||, $(, backtick) unless the caller passes shell_meta_ok=True.
No present callsite is exploitable, but the guard catches future
regressions where a user-supplied value flows into a shell string.
All legitimate static-pipeline callsites (awk, lspci|grep, the
systemd-run wrappers) are tagged explicitly.
- _TtlCache (5s) wraps every nvidia-smi, lspci, and `which envycontrol`
invocation. The GUI's monitoring loop hits get_gpu_temp /
get_gpu_usage / get_display_mode every 2s; without the cache that
spawns nvidia-smi 30+ times per minute, which is both slow and a
common hang source on systems with troubled NVIDIA drivers. All
cached probes also pick up timeout=3 so a hung subprocess can't
starve the telemetry timer.
- get_fan_rpm now matches hwmon devices against an Acer-relevant
chipset allowlist (linuwu_sense, acer_wmi, nct67xx, it87,
dell_smm_hwmon) before falling back to "first device with
fan1_input". Previously the first-found rule could return RPMs from
e.g. a discrete GPU sensor instead of the platform fan controller.
- restart_daemon and restart_drivers_and_daemon use systemd-run
--on-active=2s --no-block so the D-Bus reply for the Restart*
method has time to flush before SIGTERM lands. The previous
synchronous systemctl restart killed the daemon in-flight and the
GUI saw a "connection lost" toast on every legitimate restart.
- gui/archer_dbus.py: hoist `import os` and `import threading` out of
method bodies. Cosmetic; keeps imports in one place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… failure
- gui/archer_dbus.py: declare AudioEnhancementChanged(b) and emit it
after the noise-suppress filter chain rename. Removes the broken
`systemctl --user restart pipewire.service` from inside the root
daemon (it targeted root's systemd-user instance and never restarted
the actual user's pipewire — the noise toggle was effectively a
no-op until the next pipewire restart).
- gui/archer/window.py: subscribe to AudioEnhancementChanged on the
same path that subscribes to TelemetryUpdated; on receipt, run
`systemctl --user restart pipewire.service` from the GUI process
so the restart hits the actual user instance, then toast the user.
Resubscribe after each daemon restart by tracking _audio_signal_match
alongside _telemetry_signal_match.
- gui/archer/widgets/async_set.py: new helper. Standard
async_set(setter_fn, args, on_success, on_failure) wraps a daemon
call in a thread, marshals the result onto the GLib main thread,
and runs on_failure with the daemon's error string when the call
times out, gets denied by polkit, or returns success=False.
- gui/archer/pages/battery.py, system.py, keyboard.py: convert the
fire-and-forget `threading.Thread(target=lambda: client.set_X(...))`
pattern to async_set with revert-on-failure. Switches and combos
flip back to the previous value with an explanatory toast when the
daemon refuses; the keyboard color/effect setters get a toast (no
revert because the UI doesn't track committed colour state).
The display, performance, gamemode, audio_enhance, and firmware pages
already had local _apply / _revert paths and were left alone — converting
them to async_set is mechanical churn for no behavioural gain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- tests/dbus_smoke.py + tests/dbus_smoke.sh: headless smoke harness
that runs ArcherDBusService against an ephemeral session bus with a
mocked HardwareManager. Asserts Ping, the required methods (incl.
GetMonitoringData, SetAudioEnhancement) and signals (TelemetryUpdated,
AudioEnhancementChanged, ProfileChanged) appear in the introspection
XML, and waits up to 5s for the first TelemetryUpdated emit. Exits
nonzero on any miss so CI fails loudly.
- .github/workflows/ci.yml: new python-syntax job that py_compiles
every gui/**/*.py. New dbus-introspect-smoke job that boots a
private session bus via dbus-run-session and runs tests/dbus_smoke.sh.
Runs in parallel with the existing ShellCheck/bats/flake8 jobs.
- .github/ISSUE_TEMPLATE/gui-not-updating.md: structured template for
the exact failure mode in #4. Asks for distro+kernel, daemon status,
journal, busctl introspect, GUI logs, and the 'did you try' boxes.
Would have closed #4 in one round trip.
- README.md: add a Troubleshooting section covering "Daemon Offline /
Stale" and "GUI hangs forever". Update the Install Manifest note to
point at the new /var/lib/archer location and mention the legacy-
path migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hardening-sweep release (closes #4 + audit findings). - lib/utils.sh: INSTALLER_VERSION 2.0.0 -> 2.0.1 - gui/archer_daemon.py: VERSION 2.0.0 -> 2.0.1 - gui/archer_dbus.py: Ping fallback 2.0.0 -> 2.0.1 - gui/archer/__init__.py: __version__ 1.0.0 -> 1.0.1 - gui/archer/pages/system.py: GUI Version row 1.0.0 -> 1.0.1 - CHANGELOG.md: new file. Categorized Fixed / Changed / Security / Added / Removed entries with file references; migration notes for upgraders from 2.0.0 covering the manifest path move, RuntimeDirectory rename, and the brief dbus.service reload. - README.md: link the changelog from the new "Changelog" section above Contributing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.