Skip to content

feat(gateway): support custom systemd unit names - #76461

Closed
kortylokai-web wants to merge 1 commit into
NousResearch:mainfrom
kortylokai-web:feat/upstream-systemd-unit-override
Closed

feat(gateway): support custom systemd unit names#76461
kortylokai-web wants to merge 1 commit into
NousResearch:mainfrom
kortylokai-web:feat/upstream-systemd-unit-override

Conversation

@kortylokai-web

@kortylokai-web kortylokai-web commented Aug 1, 2026

Copy link
Copy Markdown

What does this PR do?

Adds a generic gateway.systemd_unit_name configuration option for Linux deployments that need a stable gateway systemd unit name instead of the default profile-derived name.

The configured name accepts an optional .service suffix, is normalized to a base name, and is validated before it can be used in a unit path or lifecycle command. The resolved name is persisted in generated user and system units so install, status, restart, update, uninstall, profile cleanup, PID discovery, and child-triggered lifecycle operations all address the same unit. Existing systemd hardening and non-Linux behavior remain unchanged.

Related Issue

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Added the validated gateway.systemd_unit_name config key in hermes_cli/config_defaults.py and unit-name resolution in hermes_cli/gateway.py.
  • Applied the resolved name consistently across gateway lifecycle, update, uninstall, profile cleanup, and lifecycle-guard paths.
  • Added ownership and collision checks before custom-name units are mutated.
  • Added focused coverage for normalization, invalid values, user/system paths, persistence, lifecycle callers, collisions, and child behavior.
  • Documented the Linux systemd configuration in website/docs/user-guide/messaging/index.md and cli-config.yaml.example.

How to Test

  1. Run the affected gateway and systemd tests:

    python -m pytest tests/hermes_cli/test_gateway_systemd_unit_name.py tests/hermes_cli/test_gateway_restart_loop.py tests/hermes_cli/test_update_fleet_restart_timeout.py tests/hermes_cli/test_gateway_service.py tests/hermes_cli/test_systemd_optional_directives.py tests/hermes_cli/test_gateway.py -q

    Expected: all selected tests pass.

  2. Run lint and the cross-platform guard:

    ruff check cron/lifecycle_guard.py hermes_cli/config_defaults.py hermes_cli/gateway.py hermes_cli/profiles.py hermes_cli/uninstall.py hermes_cli/update_cmd.py tests/hermes_cli/test_gateway_restart_loop.py tests/hermes_cli/test_gateway_systemd_unit_name.py tests/hermes_cli/test_update_fleet_restart_timeout.py
    python scripts/check-windows-footguns.py --diff main
  3. Run the full test suite:

    ./scripts/run_tests.sh

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
    • The earlier branch run completed with 23,827 passed, 17 failed. A clean run at that target commit completed with 23,763 passed, 17 failed, with the same failures in the same 9 unrelated test files.
    • After rebasing onto 7f4d15515, the branch run completed with 23,993 passed, 17 failed. Clean current main reproduced 16 failures in the same 8 unrelated files. The one additional failure was in untouched tests/test_tui_gateway_server.py; its focused rerun passed all 501 tests.
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Arch Linux, with automated user- and system-scope systemd coverage

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A; no architecture or workflow changes
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — macOS launchd and Windows behavior are unchanged
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A; no tool behavior changed

Screenshots / Logs

N/A — no UI changes.

@kortylokai-web
kortylokai-web marked this pull request as ready for review August 1, 2026 23:18
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 1, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for covering the lifecycle, update, and system-service paths for a real current-main gap: hermes_cli/gateway.py:1798-1815 still supports only profile-derived systemd names.

Problems

  • hermes_cli/gateway.py:1039-1043 accepts an existing custom unit using only its unit name and Hermes ExecStart marker. It does not compare persisted HERMES_HOME. Therefore two profiles configured with the same custom name are treated as the same owner: systemd_install() proceeds through that check at hermes_cli/gateway.py:3639-3646, while profile cleanup can stop and unlink the same unit at hermes_cli/profiles.py:1719-1734.

Suggested changes

  • Include the expected persisted HERMES_HOME in custom-unit ownership validation, and reject cross-profile collisions across install, refresh, uninstall, and cleanup.
  • Add a two-profile same-custom-name regression test proving the original unit remains intact.

Automated hermes-sweeper review.

Comment thread hermes_cli/gateway.py
return (
unit_path.name == f"{persisted}.service"
and persisted == expected_name
and _systemd_unit_invokes_hermes_gateway(unit_text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ownership predicate validates only the unit name and Hermes-looking ExecStart. It must also require the persisted HERMES_HOME to match the profile being operated on; otherwise two profiles configured with the same custom name each pass this check, and the later install/refresh can overwrite the first profile’s service.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Aug 1, 2026
@kortylokai-web

Copy link
Copy Markdown
Author

Closing this PR because the arbitrary gateway.systemd_unit_name configuration is broader than the underlying problem.

The actual requirement is to prevent collisions when isolated Hermes roots use the same profile name while keeping their systemd units inside the Hermes-owned namespace. The review also exposed an ownership gap: different HERMES_HOME values could claim the same custom unit. Correctly supporting arbitrary names across installation, refresh, uninstall, and cleanup would require substantially more discovery and ownership machinery.

I’m going back to a smaller design where Hermes composes the unit name from a validated instance scope and profile, preserves the hermes-gateway-* namespace, persists that scope for child lifecycle operations, and refuses destructive actions when the existing unit belongs to another HERMES_HOME.

This PR will remain closed rather than being expanded in place.

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

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants