A native install on Raspberry Pi OS, over SSH. Do the steps yourself, or hand this page to an AI coding agent and let it do most of the work.
aarch64), Raspberry Pi OS, reachable over SSHsudo on the Pi — passwordless keeps the install unattendedSteps 3–6 run in a shell on the Pi. Not reachable yet? Find it and name it.
Stock Raspberry Pi OS answers to raspberrypi.local over mDNS. If that doesn’t resolve, check your router’s device list, and ~/.ssh/config for an alias you already saved.
$ grep -iE 'host|hostname' ~/.ssh/config # is there already an alias for the Pi? $ ping -c1 raspberrypi.local # does the default name resolve? $ dns-sd -B _ssh._tcp # browse SSH hosts on the LAN (Ctrl-C to stop)
Use the username set when the Pi was imaged; there’s no universal default.
piOne config entry and one key copy make the Pi answer to ssh pi, passwordless:
$ cat >> ~/.ssh/config <<'EOF' Host pi HostName raspberrypi.local User <user> EOF $ ssh-copy-id pi # passwordless login from here on $ ssh pi # connect — then run the steps below
Confirm the architecture, sudo, and a route to the installer:
$ uname -m # expect: aarch64 $ sudo -v # confirm you can elevate (installer uses apt) $ curl -fsSI https://hermes-agent.nousresearch.com/install.sh >/dev/null && echo net-ok
sudo asks for a password, the installer’s apt step stalls. Cache it first (sudo -v, keep the session open), or pre-install the packages so the installer never needs sudo:sudo apt-get install -y git curl xz-utils gcc python3-dev libffi-dev ripgrep ffmpegOne pipe installs everything. --skip-setup skips the interactive sign-in wizard; the sign-in is Step 5.
$ curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-setup
It lands the CLI at ~/.local/bin/hermes and all state under ~/.hermes/. Confirm:
$ ~/.local/bin/hermes version # prints a version number
~/.local/bin/hermes. A non-interactive ssh <pi> '…' shell has no ~/.local/bin on PATH, and source ~/.bashrc won’t fix that (bashrc returns early when non-interactive). bash -lc also works.The sign-in uses your account and a browser, so it’s yours. The default path is OpenAI Codex (ChatGPT); any provider Hermes supports works:
$ ~/.local/bin/hermes auth add openai-codex # suggested: ChatGPT sign-in — prints a URL + code open https://auth.openai.com/codex/device # approve in the browser
For another provider: ~/.local/bin/hermes auth add <provider>. Leave the model to Step 6.
~/.local/bin/hermes auth status <provider> (reads logged in) and continue with Step 6.See what you signed in with:
$ ~/.local/bin/hermes auth list openai-codex (1 credentials): #1 openai-codex-oauth-1 oauth device_code ←
Set the default model to match (here: provider openai-codex, model gpt-5.5):
$ ~/.local/bin/hermes config set model.provider openai-codex ✓ Set model.provider = openai-codex in /home/<user>/.hermes/config.yaml $ ~/.local/bin/hermes config set model.default gpt-5.5 ✓ Set model.default = gpt-5.5 in /home/<user>/.hermes/config.yaml
Verify:
$ ~/.local/bin/hermes chat -q 'reply with the word ok' ok
Hermes is running; choose how you’ll reach it.
$ hermes # interactive chat (in your own ssh session) $ ~/.local/bin/hermes chat -q 'your prompt' # one-shot (full path over ssh)
Hermes has a web dashboard. On a headless Pi, keep it on loopback and skip the browser auto-open:
$ ~/.local/bin/hermes dashboard --host 127.0.0.1 --port 9119 --no-open
With the dashboard running, forward the port from the Mac:
$ ssh -f -N -L 9119:localhost:9119 pi # -f backgrounds it; -N = tunnel only, no shell $ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:9119/ # expect: 200 # then open in your Mac browser: http://localhost:9119
The dashboard is reachable only while that ssh process runs. -f backgrounds the tunnel until you kill it (pkill -f '9119:localhost:9119') or reboot. Without -f it prints nothing; that’s normal for -N, so leave the window open.
active right away), and until then the curl probe prints 000 or nothing. Re-run it until it prints 200.--insecure bypasses it and belongs only behind a tunnel or VPN.The dashboard dies at logout. Run it as a user service:
$ mkdir -p ~/.config/systemd/user $ cat > ~/.config/systemd/user/hermes-dashboard.service <<'EOF' [Unit] Description=Hermes dashboard After=network-online.target [Service] ExecStart=%h/.local/bin/hermes dashboard --host 127.0.0.1 --port 9119 --no-open Restart=on-failure [Install] WantedBy=default.target EOF $ loginctl enable-linger "$USER" # survive logout / reboot (easy to forget) $ systemctl --user daemon-reload $ systemctl --user enable --now hermes-dashboard.service $ systemctl --user is-active hermes-dashboard.service # active
This keeps the dashboard up across Pi reboots; the LaunchAgent below does the same for the tunnel on the Mac.
The ssh -f -N tunnel dies at logout. To keep http://localhost:9119 up across reboots, run the tunnel as a LaunchAgent:
ssh pi true should return without prompting; Step 2’s ssh-copy-id pi set this up.launchctl bootstrap, run the curl probe, then tell the human the dashboard is live at http://localhost:9119.$ mkdir -p ~/Library/LaunchAgents $ cat > ~/Library/LaunchAgents/co.plow.hermes-tunnel.plist <<'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key><string>co.plow.hermes-tunnel</string> <key>ProgramArguments</key> <array> <string>/usr/bin/ssh</string> <string>-N</string> <string>-o</string><string>ServerAliveInterval=30</string> <string>-o</string><string>ServerAliveCountMax=3</string> <string>-o</string><string>ExitOnForwardFailure=yes</string> <string>-L</string><string>9119:localhost:9119</string> <string>pi</string> </array> <key>RunAtLoad</key><true/> <key>KeepAlive</key><true/> </dict> </plist> EOF $ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/co.plow.hermes-tunnel.plist # load it now
RunAtLoad starts the tunnel at login; KeepAlive restarts ssh if it dies; ServerAliveInterval=30 / ServerAliveCountMax=3 kill a hung connection within about 90 seconds; ExitOnForwardFailure=yes fails loudly if port 9119 is taken. No autossh needed.
Turn it off:
$ launchctl bootout gui/$(id -u)/co.plow.hermes-tunnel $ rm ~/Library/LaunchAgents/co.plow.hermes-tunnel.plist
Either way the dashboard is exposed only on the Mac’s loopback (127.0.0.1:9119). On Linux, use a systemctl --user unit instead of a LaunchAgent.
Optional next step: Hermes is running. Want to text it from your phone? Give your Hermes agent a phone number.
Read once before you start.
| Edge | What to do |
|---|---|
| Model / provider mismatch | If hermes chat errors about the model, the pairing is off. Step 6 fixes it: read the provider from hermes auth list, then set hermes config set model.provider and hermes config set model.default together. |
hermes not found over SSH | A non-interactive ssh <pi> 'hermes …' shell has no ~/.local/bin on PATH, and source ~/.bashrc won’t help (bashrc returns early). Use ~/.local/bin/hermes, or wrap in bash -lc. |
| The installer runs a setup wizard | Bare install.sh ends in an interactive wizard. --skip-setup installs binaries only; sign in in Step 5. |
| Sudo is only for apt packages | Only the installer’s apt step needs sudo. Cache it (sudo -v) or pre-install the packages; the rest runs password-free. |
| Service dies when you log out | Without loginctl enable-linger "$USER", a systemctl --user service stops at logout. tmux and nohup die on reboot regardless. |
| Dashboard exposure | Default bind is loopback (127.0.0.1:9119). Don’t rebind to 0.0.0.0; tunnel instead. Non-loopback trips an auth gate by design. |
~/.hermes/ is everything | Config, auth, sessions, skills, memories: one directory. It’s the backup unit, and removing it is most of an uninstall. |
Two halves: the tunnel on the Mac, then Hermes on the Pi.
Unload the LaunchAgent:
$ launchctl bootout gui/$(id -u)/co.plow.hermes-tunnel 2>/dev/null; rm -f ~/Library/LaunchAgents/co.plow.hermes-tunnel.plist
A hand-started tunnel (ssh -f -N) dies with pkill -f '9119:localhost:9119' instead.
Stop the service, then remove the install and the PATH line the installer added:
$ systemctl --user disable --now hermes-dashboard.service $ rm -f ~/.config/systemd/user/hermes-dashboard.service $ systemctl --user daemon-reload $ loginctl disable-linger "$USER" $ pkill -f "hermes-agent/venv/bin/hermes" || true $ rm -rf ~/.hermes ~/.local/bin/hermes ~/.local/bin/node ~/.local/bin/npm ~/.local/bin/npx $ sed -i '/# Hermes Agent/,+1d' ~/.bashrc
~/.hermes removes config, auth, and all sessions. No undo.Left in place, harmless: the apt packages (git curl xz-utils gcc python3-dev libffi-dev ripgrep ffmpeg) and the uv caches (~/.local/share/uv, ~/.cache/uv).