76f6ba3706
Home Manager separates an installation from a daemon. This module put both under `services.hermes-agent`, and `installPackage` added a program to the PATH from a service module. `programs.hermes-agent` now installs the command line application and the desktop application. `services.hermes-agent` keeps the state, the configuration and the daemons, and stays the authority: the new module reads `hermesHome` and the backend address from it. A person can enable one without the other, which is a machine with an application and no gateway, or a headless gateway with no display. The desktop application needs this split to work correctly. A launcher that starts from the desktop menu reads no shell profile, thus the HERMES_HOME that `home.sessionVariables` exports reaches an interactive shell only. Home Manager writes `systemd.user.sessionVariables` to environment.d, and this module puts no HERMES_HOME there, because that file applies to each user unit. The application then opens ~/.hermes while the services use `hermesHome`, and the person sees no sessions and no keys. Thus the launcher carries the value itself, through a new `extraEnv` argument on the desktop package. The application also gets the Nix agent package, with HERMES_DESKTOP_HERMES. The usual distribution of the Electron application carries its own Hermes runtime and downloads more at the first start. `hermesDesktop` is a passthru of the agent and pins `finalAttrs.finalPackage`, so an override of `extraPythonPackages` or `extraDependencyGroups` reaches both. One machine thus has one runtime. `backend.sessionTokenFile` connects the application to the backend of the service. Without it the module runs `hermes serve` and the application starts a backend of its own, which gives two backends on one HERMES_HOME. The backend reads the file into HERMES_DASHBOARD_SESSION_TOKEN. The launcher reads the same file into HERMES_DESKTOP_REMOTE_TOKEN, beside a HERMES_DESKTOP_REMOTE_URL that names the address of the service. Measurements against a live `hermes serve` on loopback show why that shape is the correct one: - `_resolve_session_token()` reads HERMES_DASHBOARD_SESSION_TOKEN, and `_has_valid_session_token` accepts that value as a Bearer credential. A request without it gets 401, and a request with the wrong value gets 401. - The /api/ws socket accepts a query parameter only. A header gets 403, and `?token=` connects. Hermes Desktop builds exactly that URL, in `apps/desktop/electron/connection-config.ts`. Thus a test of the HTTP leg alone is a false positive. - `resolveDesktopRemoteRoute` throws when the URL is set and the token is not. Thus the two variables travel together or not at all. The token enters no Nix store path. `makeWrapper --set` and a systemd `Environment=` value both write a literal into the store, which all users can read. Thus each side reads the file at start time. The launcher does it through a new `extraRun` argument on the desktop package, and the backend through the launcher script that `backend.waitFor` already uses. launchd has no EnvironmentFile, so a script is the one shape that works on Linux and on Darwin. `backendArgv` gives the plain argv only when nothing must run before the backend. `services.hermes-agent.installPackage` is removed. It defaulted to true, so a person who never named it still got the command line. A silent removal thus gives them a machine with no `hermes` and no message. The module refuses a configuration that sets it, and the text names the exact replacement for the value they gave. Checks: - the launcher carries HERMES_HOME - the launcher reports HERMES_MANAGED only when the services own the configuration, because no activation writes a marker without them - the launcher pins the agent package that `programs.enable` installs - the launcher names the backend of the service, and gives a token beside the URL - the backend reads the session token - each side reads the file at start time, and the token is no `--set` value - `programs.enable` alone starts no service - `installPackage` is refused, with a message that names the replacement, and its absence evaluates Each check reads the wrapper of the real package, and not an option value. Each one was tested with a mutation that breaks the behavior it asserts.
Website
This website is built using Docusaurus, a modern static website generator.
Installation
yarn
Local Development
yarn start
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
Build
yarn build
This command generates static content into the build directory and can be served using any static contents hosting service.
Deployment
Using SSH:
USE_SSH=true yarn deploy
Not using SSH:
GIT_USER=<Your GitHub username> yarn deploy
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the gh-pages branch.
Diagram Linting
CI runs ascii-guard to lint docs for ASCII box diagrams. Use Mermaid (````mermaid`) or plain lists/tables instead of ASCII boxes to avoid CI failures.