Hermes NixOS Secret Management

Summary

This law defines the mandatory method for injecting secrets into the Hermes Agent on NixOS. The nix-hermes module’s activation script overwrites ~/.hermes/.env on every nixos-rebuild switch, clobbering manually placed secrets or symlinks. The solution is an ExecStartPre append from a persistent secrets file.

Details

The Problem

Hermes is hardcoded to read credentials from a physical .env file at $HERMES_HOME/.env. The NixOS module’s activation script recreates this file from non-secret Nix config on every rebuild, destroying any secrets placed there.

Failed Approaches

  1. Symlinking via tmpfiles (L+): Module activation runs after systemd-tmpfiles, replacing the symlink with a regular file.
  2. systemd EnvironmentFile: Injects into os.environ, but Hermes prioritizes the .env file on disk. Missing keys in .env cause auth failures even if the variable exists in the environment.
  3. Manual placement: Overwritten on next nixos-rebuild switch.

The Fix: ExecStartPre Append

Append secrets to the generated .env immediately before service start:

systemd.services.hermes = {
  serviceConfig = {
    # '+' prefix = root privileges to read protected secrets
    ExecStartPre = [
      "+/bin/sh -c 'cat /path/to/secrets/hermes.env >> /var/lib/hermes/.hermes/.env'"
    ];
  };
};

Constraints

  • + prefix required: The command needs root to read from the secrets directory and write to the Hermes user’s home.
  • Symlink cleanup: If previously using tmpfiles symlink approach, delete the symlink at ~/.hermes/.env first — otherwise cat appends a file to itself (“file is output file” error).
  • Idempotency: The Nix activation script truncates .env on every switch, so >> appends don’t accumulate duplicates across restarts.
  • Variable split: Non-secret vars go in the Nix environment option; sensitive credentials (API keys, tokens) go in the secrets file only.

Secret File Location

On Sokrates boxes: /var/lib/sokrates/secrets/hermes/hermes.env containing OPENROUTER_API_KEY, DISCORD_BOT_TOKEN, DISCORD_ALLOWED_USERS, ANTHROPIC_API_KEY.