Skip to content

feat(linux): install artifacts and docs#173

Open
cserby wants to merge 14 commits into
AprilNEA:masterfrom
cserby:story/linux-packaging/install-script-and-docs
Open

feat(linux): install artifacts and docs#173
cserby wants to merge 14 commits into
AprilNEA:masterfrom
cserby:story/linux-packaging/install-script-and-docs

Conversation

@cserby

@cserby cserby commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • packaging/linux/udev/70-openlogi.rulesTAG+="uaccess" for Logitech hidraw nodes and uinput; no group membership required on systemd-logind systems. Includes non-systemd GROUP="input" fallback instructions in comments.
  • packaging/linux/systemd/openlogi-agent.service — packaged user-unit template for /usr/lib/systemd/user/; complements the per-user unit written by the launch_at_login setting.
  • packaging/linux/desktop/openlogi.desktop — XDG application launcher entry.
  • packaging/linux/install.sh / uninstall.sh — POSIX sh, set -eu; copies binaries and all artifacts to system paths, reloads udevadm, and performs best-effort icon cache and desktop database updates.
  • docs/INSTALL-linux.md — full Linux install guide: prerequisites, build from source, udev rules (uaccess + non-systemd fallback), install.sh usage, autostart via systemctl, verification steps, known limitations.
  • README.md — Linux subsection under ## Install with the quick-start snippet and a link to the full guide.

Test plan

  • desktop-file-validate packaging/linux/desktop/openlogi.desktop — clean
  • shellcheck packaging/linux/install.sh packaging/linux/uninstall.sh — clean (requires shellcheck)
  • udevadm verify packaging/linux/udev/70-openlogi.rules — clean (requires udevadm ≥ 252)
  • bash -n packaging/linux/install.sh && bash -n packaging/linux/uninstall.sh — no syntax errors
  • Full install.sh run after cargo build --release — binaries, rules, unit, desktop, icon all land in expected paths
  • udevadm trigger after install — /dev/uinput and /dev/hidraw* become accessible without sudo
  • uninstall.sh — all installed files removed, udevadm reloaded

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds the full Linux packaging layer for OpenLogi: udev rules, a systemd user-unit template, a .desktop launcher, install.sh/uninstall.sh scripts, and a comprehensive install guide. It also wires up Linux-specific autostart (launch_at_login via a per-user systemd unit) and a device-access permission indicator in the Settings UI.

  • Packaging artifacts (packaging/linux/): POSIX sh install/uninstall scripts correctly handle SUDO_USER, escape @BINDIR@ for sed, set XDG_RUNTIME_DIR for systemctl --user, and trigger udev on both install and uninstall. The new udev rules use SUBSYSTEM=="hidraw" (more reliable than the old KERNEL=="hidraw*") and broaden the Bluetooth KERNELS pattern.
  • Rust changes: reconcile_linux in launch_agent.rs writes/removes ~/.config/systemd/user/openlogi-agent.service idempotently, with correct disable-before-remove ordering and a well-tested escape_systemd_exec function. The Linux permission model in permissions.rs probes /dev/uinput and /dev/hidraw* via sysfs uevent vendor matching, with a pure classify function tested independently.
  • Docs/README: docs/INSTALL-linux.md covers prerequisites through verification steps; the --check-uinput diagnostic snippet references a flag not yet implemented in the binary, and uninstall.sh does not remove the per-user unit file written by the launch_at_login feature.

Confidence Score: 5/5

Safe to merge; all install/uninstall logic is correct and the Rust permission and autostart code is well-tested.

The install/uninstall scripts handle the tricky sudo -u + XDG_RUNTIME_DIR pattern correctly, udev trigger runs on both install and uninstall, and the Rust reconciliation logic has solid unit tests. The two findings are documentation and cleanup gaps that do not affect correctness of the installed system.

packaging/linux/uninstall.sh and docs/INSTALL-linux.md

Important Files Changed

Filename Overview
packaging/linux/install.sh POSIX sh install script with set -eu; correctly uses SUDO_USER to detect the real user, sets XDG_RUNTIME_DIR for the systemctl --user daemon-reload call, and properly escapes the sed replacement for @bindir@.
packaging/linux/uninstall.sh Correctly sets XDG_RUNTIME_DIR for systemctl --user disable --now, and triggers udev after rule removal. The per-user unit at ~/.config/systemd/user/openlogi-agent.service written by launch_at_login is not removed.
crates/openlogi-agent/src/launch_agent.rs Adds Linux systemd user-unit reconciliation: idempotent write/remove of ~/.config/systemd/user/openlogi-agent.service, with correct disable-before-remove ordering and well-tested escape_systemd_exec covering %, $, spaces, and quoted paths.
crates/openlogi-core/src/paths.rs Extracts xdg_config_home() as a public helper; existing config_dir() is correctly refactored to delegate through it, preserving behavior.
crates/openlogi-gui/src/platform/permissions.rs Adds Linux permission model: probe_uinput, probe_logitech_hidraw, and a well-tested pure classify function. HID_ID vendor parsing via sysfs uevent is correct. macOS-only variants are properly gated.
crates/openlogi-gui/src/windows/settings.rs Permissions page split into macOS and Linux cfg blocks; permission_item and permission_field correctly gated to macOS-only. Linux gets a single Input device access item with inline status and guidance text.
packaging/linux/udev/70-openlogi.rules Replaces KERNEL=="hidraw*" with SUBSYSTEM=="hidraw" (more reliable), broadens Bluetooth KERNELS match from 0005:046D:* to :046D:, and drops redundant MODE="0660" alongside TAG+="uaccess".
docs/INSTALL-linux.md Comprehensive install guide covering prerequisites, build, udev rules, install script usage, and autostart. References openlogi-agent --check-uinput which is not implemented in the binary.
packaging/linux/systemd/openlogi-agent.service Systemd user unit template with @bindir@ placeholder substituted by install.sh at install time; matches the unit rendered by launch_agent.rs::render_unit exactly.

Fix All in Codex Fix All in Claude Code

Reviews (9): Last reviewed commit: "refactor(linux): use @BINDIR@ placeholde..." | Re-trigger Greptile

Comment thread packaging/linux/systemd/openlogi-agent.service Outdated
Comment thread packaging/linux/uninstall.sh
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 8, 2026
- install.sh: rewrite ExecStart in the systemd unit via sed so the
  installed service always points to $BINDIR/openlogi-agent, not the
  hardcoded /usr/bin path (which mismatches the default /usr/local prefix)
- uninstall.sh: add udevadm trigger after reload so hidraw and uinput
  nodes lose the uaccess tag immediately, not only after re-plug/reboot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 8, 2026
- install.sh: escape sed replacement string for BINDIR metacharacters
  (& \ |) so paths like /opt/my&pkg don't corrupt the unit file
- install.sh: best-effort daemon-reload via sudo -u $SUDO_USER after
  writing the unit so reinstalls pick up the new ExecStart immediately
- uninstall.sh: use SUDO_USER to target the real user's systemd session
  when the script is run under sudo, preventing the disable from silently
  targeting root's session while the user's agent keeps running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packaging/linux/uninstall.sh Outdated
@recchia

recchia commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Nice to see the packaging come together — and thanks for carrying the Bluetooth/uhid matcher forward into the comment block; generalizing it to :046D: is a good call.
One thing I'd flag: this adds packaging/linux/udev/70-openlogi.rules as a new file, but udev/70-openlogi.rules already exists in master and neither this PR nor #179 removes it. If both land as-is, the tree ends up with two copies of the rules that will drift apart on the next fix. Suggest making this a git mv of the existing file into packaging/linux/udev/ so there's a single source of truth.

cserby and others added 6 commits June 10, 2026 08:42
Reconcile the agent's autostart state on Linux by writing/removing a
systemd user unit at $XDG_CONFIG_HOME/systemd/user/openlogi-agent.service.
Mirrors the macOS LaunchAgent semantics exactly:

- Restart=on-failure (crash respawns; clean exit(0) stays stopped)
- WantedBy=graphical-session.target (takes effect at next login)
- ExecStart escaped for systemd (% doubled, spaces quoted)
- Idempotent write/remove — only touches disk when content changes
- systemctl --user daemon-reload + enable/disable best-effort

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a Linux-specific permission probe and settings page row:

- probe_uinput(): checks write access to /dev/uinput
- probe_logitech_hidraw(): iterates /dev/hidraw*, confirms Logitech
  vendor (HID_ID sysfs field parsed numerically — 0000046D matches 046d)
- classify(uinput_ok, hidraw_ok): pure function → Granted/Denied/Unknown
- Settings → Permissions shows one "Input device access" row on Linux
  with description only when access is not yet granted (no noise when
  everything works)
- macOS permission rows and helpers gated #[cfg(target_os = "macos")]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Accessibility footer in the main window hidden on Linux
- "Open" button in permission rows gated to macOS only
- Launch-at-login description no longer says "log in to macOS"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove misplaced sysfs comment above the open() call in
  probe_logitech_hidraw (the sysfs check is in is_logitech_hidraw)
- Remove dead #[cfg(not(target_os = "macos"))] suppressor inside the
  already-macOS-gated permission_field function
- Split Denied/Unknown description text: Unknown means uinput is
  accessible but no Logitech device is connected, so point the user
  at the device rather than the udev install guide

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- escape_systemd_exec: double $ → $$ to prevent systemd variable
  substitution in ExecStart paths containing a literal dollar sign
- paths: add pub xdg_config_home() that returns the raw XDG config base
  without the openlogi sub-directory; refactor config_dir() to call it
- unit_path: use xdg_config_home() directly instead of relying on the
  fragile .parent() traversal from config_dir()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added missing translations for the Linux permission row label introduced
in this PR. Follows the same pattern as 'Input Monitoring'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 10, 2026
- install.sh: rewrite ExecStart in the systemd unit via sed so the
  installed service always points to $BINDIR/openlogi-agent, not the
  hardcoded /usr/bin path (which mismatches the default /usr/local prefix)
- uninstall.sh: add udevadm trigger after reload so hidraw and uinput
  nodes lose the uaccess tag immediately, not only after re-plug/reboot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 10, 2026
- install.sh: escape sed replacement string for BINDIR metacharacters
  (& \ |) so paths like /opt/my&pkg don't corrupt the unit file
- install.sh: best-effort daemon-reload via sudo -u $SUDO_USER after
  writing the unit so reinstalls pick up the new ExecStart immediately
- uninstall.sh: use SUDO_USER to target the real user's systemd session
  when the script is run under sudo, preventing the disable from silently
  targeting root's session while the user's agent keeps running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cserby cserby force-pushed the story/linux-packaging/install-script-and-docs branch from b57e9e0 to e76e794 Compare June 10, 2026 06:51
@cserby

cserby commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Nice to see the packaging come together — and thanks for carrying the Bluetooth/uhid matcher forward into the comment block; generalizing it to :046D: is a good call. One thing I'd flag: this adds packaging/linux/udev/70-openlogi.rules as a new file, but udev/70-openlogi.rules already exists in master and neither this PR nor #179 removes it. If both land as-is, the tree ends up with two copies of the rules that will drift apart on the next fix. Suggest making this a git mv of the existing file into packaging/linux/udev/ so there's a single source of truth.

Thank you @recchia for pointing this out, I was not aware of your work in the area being merged upstream.

cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 10, 2026
- install.sh: rewrite ExecStart in the systemd unit via sed so the
  installed service always points to $BINDIR/openlogi-agent, not the
  hardcoded /usr/bin path (which mismatches the default /usr/local prefix)
- uninstall.sh: add udevadm trigger after reload so hidraw and uinput
  nodes lose the uaccess tag immediately, not only after re-plug/reboot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 10, 2026
- install.sh: escape sed replacement string for BINDIR metacharacters
  (& \ |) so paths like /opt/my&pkg don't corrupt the unit file
- install.sh: best-effort daemon-reload via sudo -u $SUDO_USER after
  writing the unit so reinstalls pick up the new ExecStart immediately
- uninstall.sh: use SUDO_USER to target the real user's systemd session
  when the script is run under sudo, preventing the disable from silently
  targeting root's session while the user's agent keeps running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cserby cserby force-pushed the story/linux-packaging/install-script-and-docs branch from e76e794 to bafdb4a Compare June 10, 2026 07:10
cserby and others added 8 commits June 10, 2026 09:14
… scope

- Add 'Unifying receiver' and 'Input device access' keys to en.yml
  (all locale files must match en.yml for the i18n test)
- Add 'Ricevitore Unifying' to it.yml for key parity
- Move InteractiveElement import outside #[cfg(target_os = "macos")]
  so on_action() calls for CloseWindow/Minimize/Zoom work on Linux
- Fix rustfmt line-wrap in launch_agent::unit_path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- udev/70-openlogi.rules: TAG+="uaccess" for hidraw (Logitech VID 046d)
  and uinput; includes non-systemd GROUP="input" fallback instructions
- systemd/openlogi-agent.service: packaged user-unit template for
  /usr/lib/systemd/user/ (complements the per-user unit written by
  launch_at_login)
- desktop/openlogi.desktop: XDG application launcher
- install.sh / uninstall.sh: POSIX sh, set -eu; copies binaries + all
  artifacts, reloads udevadm, best-effort systemd and icon cache updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs/INSTALL-linux.md covers: prerequisites, build from source, udev
rules (uaccess + non-systemd fallback), install.sh usage, autostart via
systemctl, verification steps, and known limitations table.

README.md gets a Linux subsection under ## Install with the minimal
quick-start (build + udev) and a link to the full guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- install.sh: rewrite ExecStart in the systemd unit via sed so the
  installed service always points to $BINDIR/openlogi-agent, not the
  hardcoded /usr/bin path (which mismatches the default /usr/local prefix)
- uninstall.sh: add udevadm trigger after reload so hidraw and uinput
  nodes lose the uaccess tag immediately, not only after re-plug/reboot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- install.sh: escape sed replacement string for BINDIR metacharacters
  (& \ |) so paths like /opt/my&pkg don't corrupt the unit file
- install.sh: best-effort daemon-reload via sudo -u $SUDO_USER after
  writing the unit so reinstalls pick up the new ExecStart immediately
- uninstall.sh: use SUDO_USER to target the real user's systemd session
  when the script is run under sudo, preventing the disable from silently
  targeting root's session while the user's agent keeps running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bluetooth HID devices go through the uhid virtual bus, which has no
idVendor sysfs attribute — ATTRS{idVendor}=="046d" doesn't match them.
Add a second rule matching on the HID kernel name format
"bus:VID:PID.iface", whose VID field (046D) covers both BT and USB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sudo -u strips the environment, so systemctl --user cannot locate the
user's D-Bus socket without XDG_RUNTIME_DIR. Without it, disable --now
in uninstall.sh silently fails (exit code 1, swallowed by || true),
leaving the agent enabled even after the binary is removed.
daemon-reload in install.sh has the same problem — it would silently
skip the reload, requiring a manual reload before the updated unit takes
effect.

Fix both by computing REAL_UID / INSTALL_USER and passing
XDG_RUNTIME_DIR=/run/user/<uid> explicitly to the sudo -u invocation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both install.sh (PREFIX/bin) and nfpm postinstall (/usr/bin) now
expand the placeholder explicitly, removing the implicit assumption
that the template's hardcoded path matches any particular installer.
@cserby cserby force-pushed the story/linux-packaging/install-script-and-docs branch from bafdb4a to 0c40851 Compare June 10, 2026 07:16
@recchia

recchia commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Confirmed on the new push — single source of truth restored, and the rewritten rules look good (generalizing the Bluetooth matcher to :046D: covers more transports than my original). One small thing from my hardware testing that got lost in the rewrite, in case it's worth a line in INSTALL-linux.md: on my setup (Bolt receiver and Bluetooth-direct), already-connected devices needed a replug/power-cycle after installing the rules before the uaccess ACL applied — udevadm trigger alone didn't re-grant it on the existing hidraw nodes. Since install.sh will mostly run with the device already connected, a one-line note could save some "rules don't work" reports. Resolving this thread — thanks for the quick turnaround on the relocation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants