cf60ebbdfd264c3fc067b9e3230df8752c50d2ec
23 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fd3a783a3e |
feat(nix): wait for the backend bind target before it starts
The backend binds to `backend.host` immediately. The bind fails when the target is not ready, because uvicorn cannot bind a name that does not resolve, or an address that no interface holds. A unit that starts at boot loses this race against the daemon that supplies the target, such as tailscaled. A bind to a Tailscale MagicDNS name shows the problem. The name is the correct bind target, because the dashboard refuses each request with a Host header that is different from the address that the server bound to, and a shared machine has a different address in each tailnet. But the name does not resolve until tailscaled is up, so the unit fails at each boot until `Restart=on-failure` finds the moment when the name works. A systemd user unit cannot order itself after a system unit. `After=` and `Requires=` are silent no-ops across that boundary. Thus the wait is a poll, and not a dependency. This change adds three options to `services.hermes-agent.backend` on both the NixOS module and the Home Manager module: - `waitFor` — `null` (the default, unchanged behavior), `"hostname"`, or `"interface"` - `interfaceName` — the interface to take the address from - `waitTimeout` — the time in seconds before the unit stops With `waitFor`, ExecStart becomes a launcher that polls for the target and then execs hermes. `exec` keeps hermes as the MainPID, so the restart logic of systemd sees the real process. A timeout stops the unit with an error. It does not bind a fallback address, because a fallback can expose the backend more widely than the user intends. The default is not changed. Without `waitFor`, ExecStart is the same command line as before. |
||
|
|
d5a9c2ba6c |
feat(nix): home-manager module, shared with the NixOS module
Hermes is an agent for one person. The credentials, the memory, the sessions and the cron jobs all belong to that person. But the only declarative path was a NixOS system service. Issue #9056 asks for the user-level equivalent. 25 public Nix configurations already write one by hand, and several of them copy nix/nixosModules.nix and edit the systemd part. This module is not a second copy of that file. The code that both modules share moves into nix/moduleCommon.nix: - the options - the renderers for config.yaml, .env and the documents - the activation body - the command lines of the processes nixosModules.nix keeps only the parts that need root. Those parts are the service user, stateDir, addToSystemPackages, container mode and tmpfiles. The file goes from 1008 lines to 666. `services.hermes-agent` is now the same option set on both modules. A NixOS example works on Home Manager without a change, and an option added one time appears on both. The Home Manager module is different only where it must be. It uses systemd.user.services on Linux and launchd.agents on Darwin. It uses home.activation and not system.activationScripts. It sets HERMES_HOME directly, with the default ~/.hermes, so an existing directory continues to work. It uses the modes 0600 and 0700, because the state has one user and does not need the group-shared umask of the NixOS module. It does not support container mode, which needs root and the Docker socket. The change also makes four corrections that apply to both modules: - backend.mode runs `hermes serve` or `hermes dashboard`. Both modules had only the gateway. But Hermes Desktop and the web dashboard connect to a different process, so six of the configurations in public repos add a second unit by hand. serve and dashboard are one entry point with one flag of difference, and you can run only one of them. Thus the option is an enum. The NixOS module asserts against container mode with a backend, and does not make a unit that cannot start. - hermesHomeFiles installs files into HERMES_HOME. The `documents` option installs into the working directory, which is correct for AGENTS.md but wrong for SOUL.md and memories/. Hermes reads those files from HERMES_HOME, in agent/prompt_builder.py:2095. A SOUL.md in `documents` made a workspace file that Hermes never loaded as the identity. The documentation said this in prose, but two directory diagrams showed the opposite. This change corrects both. A key in either option can now contain subdirectories. - `documents` needs an explicit `workingDirectory`. The default of that option is bad on both modules. It is the home directory of the user on Home Manager, and ${stateDir}/workspace on NixOS. A user who declares workspace files without a directory therefore gets a place that the user did not select. The place is also different on each module. The modules now refuse that combination. The test is on the priority of the option and not on its value. An option that nothing sets keeps the priority of its own default, and each definition from a user is stronger. Thus a directory with the same text as the default still counts as a selection, and so does a mkDefault. A comparison of values detects neither case. - Each activation writes .env again from a base in the Nix store, and does not add to the file that exists. Thus a second activation cannot put the same secret in the file two times, and a removed environmentFile goes away. environmentFiles keeps the type `listOf str` and not `path`, so Nix cannot copy a sops-nix or agenix path into the Nix store, which all users can read. - HERMES_MANAGED and the .managed marker now hold the name of the system that manages the install. Thus a refusal says "managed by home-manager" and not "managed by NixOS", and `hermes update` gives the Nix guidance for both shapes. The CLI does not print a rebuild command for each system. It names the owner, and the user knows their own tool. A bare `true` and an empty marker still mean NixOS, so this does not change an existing install. Verification. Six new checks, all built: nixos-module evaluates the module with evalModules and the NixOS module list. It asserts both units, one HERMES_HOME, and that the module refuses container mode with a backend. home-manager-module evaluates the module with the homeManagerConfiguration function of home-manager. The process assertions run against systemd units on Linux and launchd agents on Darwin. module-option-parity asserts that each shared option is on both modules, and that the two exclusion lists name only options that exist. env-file-assembly runs the real .env script and checks the contents, the mode, that a second run gives the same bytes, and that a removed file goes away. workspace-files-need-a-directory checks that the module refuses `documents` without a directory, and accepts a directory that has the same text as the default. service-argv runs each command line that the modules build through the real parser of the CLI, with one sentinel flag added, and requires that argparse refuses only the sentinel. `nix flake check` passes, with 21 checks in total. The CLI branches that treat an install as a Nix install move to one helper, is_nix_install_method. Four call sites in main.py, web_server.py, update_cmd.py and doctor.py tested the literal set {"nix", "nixos"}, and each one missed home-manager. recommended_update_command asks the managed state before the code-scoped stamp again, because a managed install can carry a stale stamp that names an update path the managed guard refuses. The metrics contract gets a home-manager bucket, so a Home Manager install does not report as unknown. Each check was mutation-probed. 22 faults were injected, and the checks caught all 22: - a lost --no-open - a backend that runs the gateway - an overwritten config.yaml - documents in the wrong directory - a different HERMES_HOME on the two processes - a lost HERMES_HOME export - a missing backend unit - a removed assertion - an .env file that grows at each activation - an install that reports NixOS - an empty .managed marker - an option on the NixOS module only - a stale entry in an exclusion list - a renamed subcommand - an unknown flag - the workspace-files assertion always passes - the assertion compares values instead of priorities - an off-by-one that lets an untouched default through - the assertion also fires for hermesHomeFiles - a mkDefault no longer counts as a selection - the Home Manager module stops wiring the assertion - the NixOS module stops wiring the assertion The 16 Python tests in tests/hermes_cli/test_managed_install_shapes.py were probed the same way. 8 faults were injected and 8 were caught. These tests fail on this tree. They fail in the same way on the stashed HEAD, and they have no relation to Nix: - test_git_probe_tree_kill.py (2 tests) - test_update_import_guard.py (1 test) - test_telegram_media_read_timeout.py (2 tests) - test_teams.py (a collection error) Closes #9056 # Conflicts: # hermes_cli/main.py # hermes_cli/update_cmd.py # hermes_cli/web_server.py |
||
|
|
f8b6d381e2 |
fix(nix): dirty-tree wrapper bug + filtered rebuild scope + overlay alias (#65237)
* fix(nix): fold makeWrapper line continuations into optionalStrings
When rev == null (any dirty-tree build), the empty optionalString
expansion left the previous line's trailing backslash dangling onto a
blank line, ending the makeWrapper command early and running
`--suffix PYTHONPATH ...` as its own shell command (`--suffix: command
not found`, exit 127). Clean trees passed CI; dirty trees with
extraPythonPackages failed — exactly the path the NixOS module
exercises.
The continuation now lives inside each optionalString (" \\\n --set
..."), so the makeWrapper chain stays intact whether or not the
optional flags expand.
Verified by building with rev = null + extraPythonPackages =
[ pyfiglet ]: wrapper builds, PYTHONPATH suffix lands inside the
makeWrapper call, wrapped `hermes --version` runs, and the collision
check still executes (certifi correctly rejected).
* perf(nix): filter derivation sources to shrink rebuild scope
Every derivation previously saw the whole repo, so any file change
rebuilt everything. Each derivation now gets a filtered src with only
the files it consumes:
- lib.nix: derive npm workspace topology from the root package.json
`workspaces` globs (single source of truth — a new workspace member
is picked up with zero nix edits). pythonSrc (cleanSourceWith)
excludes the JS workspace trees, docs/website, docker/.github,
tests, nix/, flake.nix/flake.lock, root docs, and skills/ +
optional-skills/. importNpmLock reads from a fileset-filtered
npmRoot (root manifests + member package.jsons only).
- mkNpmPassthru takes `dirs` — the workspace dirs the package
contains — and builds a per-package fileset src from them. web and
desktop include apps/shared (file: dep). One shared
`nix run .#update-npm-lockfile` replaces the per-package
update_*_lockfile bins that only existed inside build sandboxes.
- python.nix: release venv loads the uv2nix workspace from pythonSrc.
The editable venv keeps an unfiltered ./.. root —
mkEditablePyprojectOverlay calls lib.path.splitRoot, which rejects
a cleanSourceWith set, and the editable install reads the live
checkout anyway.
- hermes-agent.nix: skills ship exclusively via HERMES_BUNDLED_SKILLS
/ HERMES_OPTIONAL_SKILLS (same mechanism as Homebrew packaging;
setup.py's _data_file_tree returns [] for missing dirs), so
SKILL.md edits no longer rebuild the venv. optional-mcps stays in
the wheel — pyproject.toml lists its manifests as explicit
data-files. Bundled assets are symlinked instead of copied, making
the wrapper drv near-instant when only an input changed.
__pycache__ filtered from bundled skills.
- checks.nix: find -L through the new symlinks; assert
optional-skills presence + HERMES_OPTIONAL_SKILLS in the wrapper.
- run_tests.sh: fall back to $HERMES_PYTHON when no local venv
exists, guarded by an `import pytest` probe (HERMES_PYTHON from a
wrapped hermes binary points at the release venv, which has no
pytest — without the guard every test file dies with "No module
named pytest" while the runner exits 0).
Verified: nix flake check exit 0; built .#default .#tui .#web
.#desktop; SKILL.md and flake.nix edits leave the venv drvPath
unchanged; .py edits leave the tui drvPath unchanged; .tsx edits
leave the venv drvPath unchanged (and do change the tui drv);
scripts/run_tests.sh runs 299 tests green through both the venv and
HERMES_PYTHON paths, and rejects a pytest-less HERMES_PYTHON.
* refactor(nix): overlay aliases the flake's own package instead of re-instantiating
The overlay previously re-called callPackage against the consumer's
nixpkgs (final), so pkgs.hermes-agent could be a different derivation
than nix build .#default and the NixOS module's default — an untested
build matrix against arbitrary consumer nixpkgs versions, for a
package whose Python side is uv2nix-locked anyway.
Now the overlay is a pure alias for the flake's own locked package:
one callPackage site (packages.nix), everything else references it.
.override { ... } still works — callPackage's makeOverridable travels
with the derivation.
Verified: direct drvPath == overlaid drvPath; .override produces a
distinct drv.
* fix(nix): dedupe extraPlugins assertions, replace MESSAGING_CWD with terminal.cwd
- Delete the duplicated extraPlugins duplicate-name assertions block
(same assertion declared twice back to back).
- Stop setting the deprecated MESSAGING_CWD env var, which made the
module trip hermes' own startup deprecation warning. The working
directory is now injected as terminal.cwd into the generated
config.yaml; cfg.settings wins via recursiveUpdate, and container
mode maps to the in-container mount path.
|
||
|
|
4219a91df5 |
fix(nix): make config.yaml group-writable under addToSystemPackages (#41940)
addToSystemPackages exports HERMES_HOME system-wide and puts the hermes CLI on interactive users' PATH, so those users (in the hermes group) share the gateway's state — that's the option's whole purpose. But the activation script wrote config.yaml as 0640 (group read-only), so an interactive user saving a setting via the CLI/TUI hit: error: [Errno 13] Permission denied: '/var/lib/hermes/.hermes/config.yaml' Make the mode conditional: 0660 when addToSystemPackages is set (group hermes can write), else the previous 0640. .env stays 0640 either way — it holds secrets, not user-facing settings. The config merge already preserves user-added keys across rebuilds, so this simply lets interactive hermes-group users actually make those edits. Verified by evaluating the module's activation script for both option values: addToSystemPackages=true -> chmod 0660, false -> chmod 0640. |
||
|
|
119390a2a1 | docs(config): deprecate MESSAGING_CWD guidance | ||
|
|
64145a1996 |
fix(nix): replace chown -R with targeted find in container entrypoint (#23633)
The container entrypoint ran `chown -R` on $HERMES_HOME every start. `chown` strips the setgid bit (kernel security behavior), destroying the 2770 permissions the NixOS activation script sets for group access by hostUsers. This caused PermissionError for interactive CLI users even though they were in the hermes group. Replace with `find ... ! -user $UID -exec chown` which only touches files with wrong ownership, leaving correctly-owned directories and their permission bits intact. Affects: container.enable + container.hostUsers + addToSystemPackages Related: #19795, #19788, #9383 |
||
|
|
5606258855 |
feat(nix): add extraDependencyGroups for sealed venv extras (#21817)
Expose the dependency-groups parameter from python.nix through
hermes-agent.nix and the NixOS module, allowing users to opt into
pyproject.toml optional extras (e.g. hindsight, voice, matrix) that
are resolved by uv inside the sealed venv.
Unlike extraPythonPackages (which appends to PYTHONPATH and requires
collision checking), extraDependencyGroups resolves the full dependency
graph in a single uv pass — no PYTHONPATH patching, no version
conflicts, no collision risk.
When to use which:
- extraDependencyGroups: enable a pyproject.toml optional extra
- extraPythonPackages: add an external Python plugin not in pyproject.toml
Usage:
services.hermes-agent.extraDependencyGroups = [ "hindsight" ];
Or via overlay:
pkgs.hermes-agent.override { extraDependencyGroups = [ "hindsight" ]; }
Refs: #8873, #9194
|
||
|
|
868bc1c242 |
feat(irc): add interactive setup
feat(gateway): refine Platform._missing_ and platform-connected dispatch Restricts plugin-name acceptance to bundled plugin scan + registry (no arbitrary string -> enum-pollution), pulls per-platform connectivity checks into a _PLATFORM_CONNECTED_CHECKERS lambda map with a clean _is_platform_connected method, and adds tests covering the checker map, plugin platform interface, and IRC setup wizard. |
||
|
|
4bf0e75ae9 |
fix(nix): make extraPackages actually work via per-user profile (#17047)
* fix(nix): make extraPackages actually work — wire into per-user profile #17030 deprecated extraPackages because it only set the systemd service PATH, which the terminal backend's login-shell snapshot discards. Instead of deprecating, fix it: set users.users.${cfg.user}.packages so NixOS builds a per-user profile at /etc/profiles/per-user/hermes/bin. This path is included in PATH by /etc/set-environment, which the login shell sources, so the terminal backend's snapshot picks it up. One line of actual logic: users.users.${cfg.user}.packages = cfg.extraPackages; Verified in a NixOS VM test: su - hermes -c 'which hello' resolves to /etc/profiles/per-user/hermes/bin/hello. Reverts the deprecation warning and docs changes from #17030, restores extraPackages as the recommended way to give the agent extra tools. Container mode is unaffected — extraPackages was always native-only (the systemd path line is inside !cfg.container.enable). * nix: clarify additive merge semantics for extraPackages user profile --------- Co-authored-by: Siddharth Balyan <daimon@noreply.github.com> |
||
|
|
be41ccd0af |
fix(nix): deprecate extraPackages — does not reach terminal/skills (#17030)
extraPackages adds packages to the systemd service PATH, but the terminal backend's login-shell snapshot rebuilds PATH from NixOS system profiles, so tools added via extraPackages are invisible to terminal commands, skills, and cron jobs — the entire use case. Changes: - Mark the option description as deprecated with explanation - Emit a NixOS warning when extraPackages is non-empty, including a ready-to-paste environment.systemPackages replacement - Update docs: quick-reference table, plugin example, and options reference all point to environment.systemPackages The option still functions (non-breaking) so existing configs keep working while users migrate. |
||
|
|
ef41d3bd45 |
feat(nix): declarative plugin installation for NixOS module (#15953)
* feat(nix): parameterize dependency-groups in python.nix
* refactor(nix): extract package to callPackage-able hermes-agent.nix
Makes the package overridable via .override{} and adds
extraPythonPackages parameter for PYTHONPATH injection.
Includes build-time collision check using PEP 503 name
canonicalization.
* feat(nix): add overlay for external NixOS consumption
External flakes can now add overlays = [ inputs.hermes-agent.overlays.default ]
to get pkgs.hermes-agent with full .override support.
* test(nix): add check for extraPythonPackages PYTHONPATH injection
Verifies wrapper has PYTHONPATH when extras provided, and
base package has no PYTHONPATH without extras.
* feat(nix): add extraPlugins option for directory-based plugins
Symlinks plugin packages into HERMES_HOME/plugins/ at activation time.
Validates plugin.yaml presence. Asserts unique plugin names at eval time.
Hermes discovers them automatically via its directory scan.
* feat(nix): add extraPythonPackages option for entry-point plugins
Overrides the hermes package with PYTHONPATH injection when
extraPythonPackages is non-empty. Plugin .dist-info directories
become visible to importlib.metadata for entry-point discovery.
Works in both native systemd and container modes.
* docs: add NixOS declarative plugin installation to nix-setup, plugins, and build-a-plugin guides
- nix-setup.md: new Plugins section with extraPlugins/extraPythonPackages
examples, overlay usage, collision checking note, options reference rows
- plugins.md: Nix row in discovery table, NixOS declarative plugins section
- build-a-hermes-plugin.md: Distribute for NixOS section after pip section
* fix: address review feedback — remove unrelated umask, fix fetchFromGitHub naming, simplify checks
- Remove accidentally introduced umask/migration changes (unrelated to plugins)
- Add pluginName helper, fix fetchFromGitHub producing name='source'
- Show name= in extraPlugins example docs
- Simplify checks.nix: use hermes-agent.override instead of re-callPackage
- Fix fragile grep shell logic in checks
* refactor: address simplify feedback — lib.getName, drop unused inputs', Python list for extras
- Use lib.getName instead of custom pluginName helper
- Drop unused inputs' from checks.nix perSystem args
- Pass extraPythonPackages as Python list literal instead of colon-split string
* fix: walk propagatedBuildInputs for plugin PYTHONPATH and collision check
Uses python312.pkgs.requiredPythonModules to resolve the full transitive
closure of extraPythonPackages. Without this, a plugin with third-party
deps (e.g. requests) would fail at runtime if those deps weren't already
in the sealed uv2nix venv. The collision check now also scans the full
closure, catching transitive conflicts.
* cleanup: fold plugins into subdir loop, use find for symlink cleanup, inline lib.getName
- Add 'plugins' to the existing cron/sessions/logs/memories subdir loop
instead of a separate mkdir/chown/chmod block
- Replace fragile for-glob with find -delete for stale symlink cleanup
- Inline lib.getName at both call sites, remove pluginName wrapper
|
||
|
|
15efb410d0 | fix(nix): make working directory writable | ||
|
|
e826cc42ef |
fix(nix): use stdenv.hostPlatform.system instead of system
system has been deprecated for a while and emits a deprecation warning when evaluated |
||
|
|
b0efdf37d7 | fix(nix): upgrade Python 3.11 → 3.12, add cross-platform eval check (#12208) | ||
|
|
6fb69229ca |
fix(nix): fix build failures, TUI Node.js crash, and upgrade container to Node 22 (#12159)
* Add setuptools build dep for legacy alibabacloud packages and updated stale npm-deps hash * Add HERMES_NODE env var to pin Node.js version The TUI requires Node.js 20+ for regex `/v` flag support (used by string-width). Instead of relying on PATH lookup, explicitly set HERMES_NODE to the bundled Node 22 in the Nix wrapper, and add a fallback check in the Python code to use HERMES_NODE if available. Also upgrade container provisioning to Node 22 via NodeSource (Ubuntu 24.04 ships Node 18 which is EOL) and add a Nix check to verify the wrapper and Node version at build time. |
||
|
|
cab814af15 |
feat(nix): container-aware CLI — auto-route into managed container (#7543)
* feat(nix): container-aware CLI — auto-route all subcommands into managed container
When container.enable = true, the host `hermes` CLI transparently execs
every subcommand into the managed Docker/Podman container. A symlink
bridge (~/.hermes -> /var/lib/hermes/.hermes) unifies state between host
and container so sessions, config, and memories are shared.
CLI changes:
- Global routing before subcommand dispatch (all commands forwarded)
- docker exec with -u exec_user, env passthrough (TERM, COLORTERM,
LANG, LC_ALL), TTY-aware flags
- Retry with spinner on failure (TTY: 5s, non-TTY: 10s silent)
- Hard fail instead of silent fallback
- HERMES_DEV=1 env var bypasses routing for development
- No routing messages (invisible to user)
NixOS module changes:
- container.hostUsers option: lists users who get ~/.hermes symlink
and automatic hermes group membership
- Activation script creates symlink bridge (with backup of existing
~/.hermes dirs), writes exec_user to .container-mode
- Cleanup on disable: removes symlinks + .container-mode + stops service
- Warning when hostUsers set without addToSystemPackages
* fix: address review — reuse sudo var, add chown -h on symlink update
- hermes_cli/main.py: reuse the existing `sudo` variable instead of
redundant `shutil.which("sudo")` call that could return None
- nix/nixosModules.nix: add missing `chown -h` when updating an
existing symlink target so ownership stays consistent with the
fresh-create and backup-replace branches
* fix: address remaining review items from cursor bugbot
- hermes_cli/main.py: move container routing BEFORE parse_args() so
--help, unrecognised flags, and all subcommands are forwarded
transparently into the container instead of being intercepted by
argparse on the host (high severity)
- nix/nixosModules.nix: resolve home dirs via
config.users.users.${user}.home instead of hardcoding /home/${user},
supporting users with custom home directories (medium severity)
- nix/nixosModules.nix: gate hostUsers group membership on
container.enable so setting hostUsers without container mode doesn't
silently add users to the hermes group (low severity)
* fix: simplify container routing — execvp, no retries, let it crash
- Replace subprocess.run retry loop with os.execvp (no idle parent process)
- Extract _probe_container helper for sudo detection with 15s timeout
- Narrow exception handling: FileNotFoundError only in get_container_exec_info,
catch TimeoutExpired specifically, remove silent except Exception: pass
- Collapse needs_sudo + sudo into single sudo_path variable
- Simplify NixOS symlink creation from 4 branches to 2
- Gate NixOS sudoers hint with "On NixOS:" prefix
- Full test rewrite: 18 tests covering execvp, sudo probe, timeout, permissions
---------
Co-authored-by: Hermes Agent <hermes@nousresearch.com>
|
||
|
|
1789c2699a |
feat(nix): shared-state permission model for interactive CLI users (#6796)
* feat(nix): shared-state permission model for interactive CLI users Enable interactive CLI users in the hermes group to share full read-write state (sessions, memories, logs, cron) with the gateway service via a setgid + group-writable permission model. Changes: nix/nixosModules.nix: - Directories use setgid 2770 (was 0750) so new files inherit the hermes group. home/ stays 0750 (no interactive write needed). - Activation script creates HERMES_HOME subdirs (cron, sessions, logs, memories) — previously Python created them but managed mode now skips mkdir. - Activation migrates existing runtime files to group-writable (chmod g+rw). Nix-managed files (config.yaml, .env, .managed) stay 0640/0644. - Gateway systemd unit gets UMask=0007 so files it creates are 0660. hermes_cli/config.py: - ensure_hermes_home() splits into managed/unmanaged paths. Managed mode verifies dirs exist (raises RuntimeError if not) instead of creating them. Scoped umask(0o007) ensures SOUL.md is created as 0660. hermes_logging.py: - _ManagedRotatingFileHandler subclass applies chmod 0660 after log rotation in managed mode. RotatingFileHandler.doRollover() creates new files via open() which uses the process umask (0022 → 0644), not the scoped umask from ensure_hermes_home(). Verified with a 13-subtest NixOS VM integration test covering setgid, interactive writes, file ownership, migration, and gateway coexistence. Refs: #6044 * Fix managed log file mode on initial open Co-authored-by: Siddharth Balyan <alt-glitch@users.noreply.github.com> * refactor: simplify managed file handler and merge activation loops - Cache is_managed() result in handler __init__ instead of lazy-importing on every _open()/_chmod_if_managed() call. Avoids repeated stat+env checks on log rotation. - Merge two for-loops over the same subdir list in activation script into a single loop (mkdir + chown + chmod + find in one pass). --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Siddharth Balyan <alt-glitch@users.noreply.github.com> |
||
|
|
a8b85bb887 | fix(nix): make setupSecrets activation script optional (#6227) (#6261) | ||
|
|
8de91ce9d2 |
fix(nix): make addToSystemPackages fully functional for interactive CLI (#6317)
* fix(nix): export HERMES_HOME system-wide when addToSystemPackages is true
The `addToSystemPackages` option's documentation (and the `:::tip` block in
`website/docs/getting-started/nix-setup.md`) promises that enabling it both
puts the `hermes` CLI on PATH and sets `HERMES_HOME` system-wide so interactive
shells share state with the gateway service. The module only did the former,
so running `hermes` in a user shell silently created a separate `~/.hermes/`
directory instead of the managed `${stateDir}/.hermes`.
Implement the documented behavior by also setting
`environment.variables.HERMES_HOME = "${cfg.stateDir}/.hermes"` in the same
mkIf block, and update the option description to match.
Fixes #6044
* fix(nix): preserve group-readable permissions in managed mode
The NixOS module sets HERMES_HOME directories to 0750 and files to 0640
so interactive users in the hermes group can share state with the gateway
service. Two issues prevented this from working:
1. hermes_cli/config.py: _secure_dir() unconditionally chmod'd HERMES_HOME
to 0700 on every startup, overwriting the NixOS module's 0750. Similarly,
_secure_file() forced 0600 on config files. Both now skip in managed mode
(detected via .managed marker or HERMES_MANAGED env var).
2. nix/nixosModules.nix: the .env file was created with 0600 (owner-only),
while config.yaml was already 0640 (group-readable). Changed to 0640 for
consistency — users granted hermes group membership should be able to read
the managed .env.
Verified with a NixOS VM integration test: a normal user in the hermes group
can now run `hermes version` and `hermes config` against the managed
HERMES_HOME without PermissionError.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zerone0x <zerone0x@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
e651e04100 |
fix(nix): read version, regen uv.lock, fix packages.nix to add hermes_logging (#5651)
* - read version from pyproject for nix - regen uv.lock - add hermes_logging to packages.nix * fix secret regen w/ sops |
||
|
|
ffdfeb91d8 |
fix(nix): unify directory and file permissions across all three layers (#3619)
Activation script, tmpfiles, and container entrypoint now agree on 0750 for all directories. Tighten config.yaml and workspace documents from 0644 to 0640 (group-readable, no world access). Add explicit chmod for .managed marker and container $TARGET_HOME to eliminate umask dependence. Secrets (auth.json, .env) remain 0600. |
||
|
|
cc4514076b |
feat(nix): add suffix PATHs during nix build for more agent-friendliness (#3274)
* refactor: suffix runtimeDeps PATH so apt-installed tools take priority Changes makeWrapper from --prefix to --suffix. In container mode, tools installed via apt in /usr/bin now win over read-only nix store copies. Nix store versions become dead-letter fallbacks. Native NixOS mode unaffected — tools in /run/current-system/sw/bin already precede the suffix. * feat(container): first-boot apt provisioning for agent tools Installs nodejs, npm, curl via apt and uv via curl on first container boot. Uses sentinel file so subsequent boots skip. Container recreation triggers fresh install. Combined with --suffix PATH change, agents get mutable tools that support npm i -g and uv without hitting read-only nix store paths. * docs: update nixosModules header for tool provisioning * feat(container): consolidate first-boot provisioning + Python 3.11 venv Merge sudo and tool apt installs into a single apt-get update call. Move uv install outside the sentinel so transient failures retry on next boot. Bootstrap a Python 3.11 venv via uv (--seed for pip) and prepend ~/.venv/bin to PATH so agents get writable python/pip/node out of the box. --------- Co-authored-by: Hermes Agent <hermes@nousresearch.com> |
||
|
|
b6461903ff |
feat: nix flake — uv2nix build, NixOS module, persistent container mode (#20)
* feat: nix flake, uv2nix build, dev shell and home manager * fixed nix run, updated docs for setup * feat(nix): NixOS module with persistent container mode, managed guards, checks - Replace homeModules.nix with nixosModules.nix (two deployment modes) - Mode A (native): hardened systemd service with ProtectSystem=strict - Mode B (container): persistent Ubuntu container with /nix/store bind-mount, identity-hash-based recreation, GC root protection, symlink-based updates - Add HERMES_MANAGED guards blocking CLI config mutation (config set, setup, gateway install/uninstall) when running under NixOS module - Add nix/checks.nix with build-time verification (binary, CLI, managed guard) - Remove container.nix (no Nix-built OCI image; pulls ubuntu:24.04 at runtime) - Simplify packages.nix (drop fetchFromGitHub submodules, PYTHONPATH wrappers) - Rewrite docs/nixos-setup.md with full options reference, container architecture, secrets management, and troubleshooting guide Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update config.py * feat(nix): add CI workflow and enhanced build checks - GitHub Actions workflow for nix flake check + build on linux/macOS - Entry point sync check to catch pyproject.toml drift - Expanded managed-guard check to cover config edit - Wrap hermes-acp binary in Nix package - Fix Path type mismatch in is_managed() * Update MCP server package name; bundled skills support * fix reading .env. instead have container user a common mounted .env file * feat(nix): container entrypoint with privilege drop and sudo provisioning Container was running as non-root via --user, which broke apt/pip installs and caused crashes when $HOME didn't exist. Replace --user with a Nix-built entrypoint script that provisions the hermes user, sudo (NOPASSWD), and /home/hermes inside the container on first boot, then drops privileges via setpriv. Writable layer persists so setup only runs once. Also expands MCP server options to support HTTP transport and sampling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix group and user creation in container mode * feat(nix): persistent /home/hermes and MESSAGING_CWD in container mode Container mode now bind-mounts ${stateDir}/home to /home/hermes so the agent's home directory survives container recreation. Previously it lived in the writable layer and was lost on image/volume/options changes. Also passes MESSAGING_CWD to the container so the agent finds its workspace and documents, matching native mode behavior. Other changes: - Extract containerDataDir/containerHomeDir bindings (no more magic strings) - Fix entrypoint chown to run unconditionally (volume mounts always exist) - Add schema field to container identity hash for auto-recreation - Add idempotency test (Scenario G) to config-roundtrip check * docs: add Nix & NixOS setup guide to docs site Add comprehensive Nix documentation to the Docusaurus site at website/docs/getting-started/nix-setup.md, covering nix run/profile install, NixOS module (native + container modes), declarative settings, secrets management, MCP servers, managed mode, container architecture, dev shell, flake checks, and full options reference. - Register nix-setup in sidebar after installation page - Add Nix callout tip to installation.md linking to new guide - Add canonical version pointer in docs/nixos-setup.md * docs: remove docs/nixos-setup.md, consolidate into website docs Backfill missing details (restart/restartSec in full example, gateway.pid, 0750 permissions, docker inspect commands) into the canonical website/docs/getting-started/nix-setup.md and delete the old standalone file. * fix(nix): add compression.protect_last_n and target_ratio to config-keys.json New keys were added to DEFAULT_CONFIG on main, causing the config-drift check to fail in CI. * fix(nix): skip checks on aarch64-darwin (onnxruntime wheel missing) The full Python venv includes onnxruntime (via faster-whisper/STT) which lacks a compatible uv2nix wheel on aarch64-darwin. Gate all checks behind stdenv.hostPlatform.isLinux. The package and devShell still evaluate on macOS. * fix(nix): skip flake check and build on macOS CI onnxruntime (transitive dep via faster-whisper) lacks a compatible uv2nix wheel on aarch64-darwin. Run full checks and build on Linux only; macOS CI verifies the flake evaluates without building. * fix(nix): preserve container writable layer across nixos-rebuild The container identity hash included the entrypoint's Nix store path, which changes on every nixpkgs update (due to runtimeShell/stdenv input-addressing). This caused false-positive identity mismatches, triggering container recreation and losing the persistent writable layer. - Use stable symlink (current-entrypoint) like current-package already does - Remove entrypoint from identity hash (only image/volumes/options matter) - Add GC root for entrypoint so nix-collect-garbage doesn't break it - Remove global HERMES_HOME env var from addToSystemPackages (conflicted with interactive CLI use, service already sets its own) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |