Connecting Hermes Agent to Nextcloud Talk using nextcloud-talk-hermes-bridge

Connecting Hermes Agent to Nextcloud Talk using nextcloud-talk-hermes-bridge

1. Introduction

I am sharing this guide as one possible way to connect a local Hermes Agent to Nextcloud Talk.

This is not meant to be the only correct solution, nor an official recommended architecture. It simply documents the way I have it running in my own setup. In my case, the Hermes CLI runs locally on a Linux notebook, while Nextcloud AIO runs on a separate server. Communication between them is handled through a private NetBird network.

The goal is to make a Nextcloud Talk bot forward messages from a Talk room to a small bridge service, which then calls the local Hermes CLI and sends the answer back to the Talk room.

This guide does not cover AppAPI / ExApp integration and does not cover any advanced Hermes skill automation. It only documents the basic working integration:

Nextcloud Talk ↔ Talk bot webhook ↔ bridge ↔ local Hermes CLI

2. Resulting architecture

Nextcloud Talk room
    ↓
Talk bot webhook
    ↓
Private NetBird IP of the notebook
    ↓
nextcloud-talk-hermes-bridge running on Linux
    ↓
local Hermes CLI
    ↓
reply back to the Talk room

In this setup:

  • the bridge runs standalone on a Linux notebook,
  • it does not run inside the Nextcloud AIO container,
  • Nextcloud AIO reaches the bridge through a private NetBird IP,
  • the bridge calls the local Hermes CLI,
  • Hermes replies back into the Talk room.

Example architecture with anonymized values:

Nextcloud AIO server
    ↓
private NetBird network
    ↓
Linux notebook 100.82.45.173:8788
    ↓
nextcloud-talk-hermes-bridge
    ↓
Hermes CLI

3. Why NetBird is used

The bridge runs on my notebook, not directly on the Nextcloud AIO server.

The Nextcloud AIO server must be able to call the bridge webhook:

http://100.82.45.173:8788/hook

In my setup, this is done through the notebook’s private NetBird IP:

100.82.45.173

The reason for using NetBird is simple:

  • the bridge does not need to be exposed publicly to the internet,
  • there is no need to open port 8788 on the router,
  • there is no need to create a public DNS record for the notebook,
  • communication stays inside the private NetBird network,
  • the Nextcloud AIO server can still reach the bridge.

Because of this, the bridge must listen on a network interface, not only on localhost.

For local-only testing this is enough:

TALK_BRIDGE_BIND=127.0.0.1

For the Nextcloud AIO server to reach the bridge through NetBird, I changed it to:

TALK_BRIDGE_BIND=0.0.0.0

4. Tested environment

Nextcloud

Nextcloud AIO
Nextcloud Hub 26 Winter
Nextcloud: 33.0.6
Nextcloud Talk: 23.0.7
app_api: 33.0.0
Domain: https://cloud.example.net

Hermes

OS: CachyOS Linux notebook
User: tony
Hermes CLI: working locally
Hermes binary: /home/tony/.local/bin/hermes
Notebook NetBird IP: 100.82.45.173

Bridge

Repository: https://github.com/robertlmann02/nextcloud-talk-hermes-bridge
Local path: ~/nextcloud-talk-hermes-bridge
systemd user service: nextcloud-talk-hermes-bridge.service
Port: 8788
Webhook: http://100.82.45.173:8788/hook
Healthcheck: http://100.82.45.173:8788/health

Talk bot

Bot name: Hermes
Bot ID: 47
Talk room URL: https://cloud.example.net/call/abcd1234
Room token: abcd1234

5. Clone the bridge repository

On the Linux notebook:

cd ~
git clone https://github.com/robertlmann02/nextcloud-talk-hermes-bridge.git
cd nextcloud-talk-hermes-bridge

The repository contains an .env.example, but I did not use it directly as my final configuration.

The reason was that I wanted a smaller and more controlled configuration for my use case.

The most important environment variables are:

TALK_BOT_SECRET
NEXTCLOUD_URL
TALK_BRIDGE_PORT
TALK_BRIDGE_BIND
HERMES_BIN
HERMES_PROFILE
HERMES_HOME_DIR
HERMES_TOOLSETS
HERMES_YOLO

Important endpoints:

/hook    = Talk webhook endpoint
/health  = bridge healthcheck endpoint

6. Generate the shared secret for the Talk bot

The Talk bot needs a shared secret between Nextcloud Talk and the bridge.

Generate it with:

openssl rand -hex 32

Do not publish the real secret.

In this guide I use the placeholder:

CHANGE_ME_SECRET

The same secret must be used in both places:

1. TALK_BOT_SECRET in ~/nextcloud-talk-hermes-bridge/.env
2. the secret argument in occ talk:bot:install

If these values do not match, the Talk bot will not communicate with the bridge correctly.


7. Back up the existing .env

Before replacing the .env, I created a timestamped backup:

cd ~/nextcloud-talk-hermes-bridge

cp .env .env.bak-pre-minimal-$(date +%Y%m%d-%H%M%S)

Later, when fixing the Hermes binary path after reboot, I also created a second backup:

cd ~/nextcloud-talk-hermes-bridge

cp .env .env.bak-hermes-bin-$(date +%Y%m%d-%H%M%S)

8. Create a new .env

The new .env was created using a heredoc:

cd ~/nextcloud-talk-hermes-bridge

cat > .env <<'EOF'
TALK_BOT_SECRET=CHANGE_ME_SECRET
NEXTCLOUD_URL=https://cloud.example.net

TALK_BRIDGE_PORT=8788
TALK_BRIDGE_BIND=0.0.0.0

HERMES_BIN=/home/tony/.local/bin/hermes
HERMES_PROFILE=default
HERMES_HOME_DIR=/home/tony
HERMES_SOURCE=nextcloud-talk-hermes-bridge

HERMES_TOOLSETS=web,memory,session_search,clarify
HERMES_SKILLS=
HERMES_MAX_TURNS=30
HERMES_ACCEPT_HOOKS=0
HERMES_YOLO=0

ASSISTANT_NAME=Hermes
ASSISTANT_ROLE=You are my private Hermes assistant in a Nextcloud Talk room. Reply clearly, practically and concisely.

TALK_BRIDGE_APP_NAME=nextcloud-talk-hermes-bridge
TALK_BRIDGE_LOG=/home/tony/.local/state/nextcloud-talk-hermes-bridge/bridge.log
TALK_CONTEXT_DIR=/home/tony/.local/share/nextcloud-talk-context/nextcloud-talk-hermes-bridge
TALK_BRIDGE_SOFT_TIMEOUT=180
TALK_BRIDGE_HARD_TIMEOUT=900
TALK_BRIDGE_BACKGROUND_HEARTBEAT=120

TALK_LOCAL_MEMORY_CONTEXT=1
TALK_MEMORY_NAMESPACE=talk

NEXTCLOUD_AI_CONTEXT=0

NEXTCLOUD_DATA_ROOT=/var/www/html/data
TALK_NEXTCLOUD_CONTAINER=nextcloud
TALK_FFMPEG_BIN=ffmpeg
TALK_WHISPER_BIN=whisper-cli
TALK_TRANSCRIBE_MAX_BYTES=52428800
TALK_TRANSCRIBE_TIMEOUT=180

TALK_MEDIA_CACHE_DIR=/home/tony/.cache/talk-media-vision
TALK_IMAGE_MAX_BYTES=26214400
EOF

Important values:

TALK_BOT_SECRET=CHANGE_ME_SECRET
TALK_BRIDGE_BIND=0.0.0.0
HERMES_BIN=/home/tony/.local/bin/hermes
HERMES_YOLO=0

Meaning:

  • TALK_BOT_SECRET must match the secret used when installing the Talk bot.
  • TALK_BRIDGE_BIND=0.0.0.0 allows the Nextcloud AIO server to reach the bridge through NetBird.
  • HERMES_BIN=/home/tony/.local/bin/hermes is the absolute path to the Hermes CLI.
  • HERMES_YOLO=0 keeps a safer mode and does not automatically approve risky actions.

9. Prepare and run the bridge locally

Create the required directories:

cd ~/nextcloud-talk-hermes-bridge

mkdir -p /home/tony/.local/state/nextcloud-talk-hermes-bridge
mkdir -p /home/tony/.local/share/nextcloud-talk-context/nextcloud-talk-hermes-bridge
mkdir -p /home/tony/.cache/talk-media-vision

Create the Python virtual environment:

python3 -m venv .venv
. .venv/bin/activate
pip install -e .

Load the .env file:

set -a
. ./.env
set +a

Start the bridge manually:

python -m nextcloud_talk_hermes_bridge.bridge

10. Check the bridge health endpoint

On the Linux notebook:

curl http://127.0.0.1:8788/health
curl http://100.82.45.173:8788/health

From the Nextcloud AIO server:

curl http://100.82.45.173:8788/health

Expected output:

{"status": "ok", "app_id": "hermes_talk_bridge", "version": "1.0.1"}

Meaning:

  • 127.0.0.1 confirms that the bridge is running locally on the notebook.
  • 100.82.45.173 from the notebook confirms that the bridge is listening on the NetBird IP.
  • 100.82.45.173 from the Nextcloud AIO server confirms that the server can actually reach the bridge.

11. Install the Talk bot in Nextcloud

On the Nextcloud AIO server:

sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ talk:bot:install \
  -f webhook \
  -f response \
  "Hermes" \
  "CHANGE_ME_SECRET" \
  "http://100.82.45.173:8788/hook" \
  "Hermes bridge via Linux notebook"

Expected result:

Bot installed
ID: 47

In my case the bot ID was:

47

12. Add the bot to a Talk room

Example Talk room URL:

https://cloud.example.net/call/abcd1234

Room token:

abcd1234

Command:

sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ talk:bot:setup 47 abcd1234

This adds the Hermes bot to the selected Talk room.


13. Test in the Talk room

I sent this message in the Talk room:

Hermes, reply only with OK

Expected result:

OK

This confirms that the full flow works:

Talk room β†’ webhook β†’ bridge β†’ Hermes CLI β†’ reply back to Talk

14. Run the bridge as a systemd user service

On the Linux notebook:

cd ~/nextcloud-talk-hermes-bridge

mkdir -p ~/.config/systemd/user
cp systemd/nextcloud-talk-hermes-bridge.service ~/.config/systemd/user/

systemctl --user daemon-reload
systemctl --user enable --now nextcloud-talk-hermes-bridge.service

Check the service:

systemctl --user status nextcloud-talk-hermes-bridge.service --no-pager -l

Check the health endpoint:

curl http://100.82.45.173:8788/health

Expected service state:

Active: active (running)

15. Enable linger for the user

To allow the systemd user service to keep running even when the user is not actively logged in, enable linger:

loginctl enable-linger tony

Check it:

loginctl show-user tony | grep Linger

Expected result:

Linger=yes

16. Check after reboot

After rebooting the notebook:

systemctl --user status nextcloud-talk-hermes-bridge.service --no-pager -l
curl http://100.82.45.173:8788/health

Expected service state:

Active: active (running)

Expected healthcheck:

{"status": "ok", "app_id": "hermes_talk_bridge", "version": "1.0.1"}

This confirms that the bridge starts automatically after reboot.


17. Issue after reboot: Hermes returned an internal error

After reboot, the bridge was available and /health worked, but Hermes replied in Talk with:

I hit an internal error while answering.

The bridge log showed:

ask exception: FileNotFoundError(2, 'No such file or directory')

Cause:

The .env originally contained:

HERMES_BIN=hermes

This worked in an interactive terminal because the shell had the correct PATH.

However, the systemd user service after reboot did not have the same PATH as the interactive shell, so it could not find the hermes executable.

Check the Hermes binary path:

command -v hermes

Example result:

/home/tony/.local/bin/hermes

Fix:

cd ~/nextcloud-talk-hermes-bridge

cp .env .env.bak-hermes-bin-$(date +%Y%m%d-%H%M%S)

sed -i 's|^HERMES_BIN=.*|HERMES_BIN=/home/tony/.local/bin/hermes|' .env

grep '^HERMES_BIN=' .env

Expected result:

HERMES_BIN=/home/tony/.local/bin/hermes

Restart the service:

systemctl --user restart nextcloud-talk-hermes-bridge.service
systemctl --user status nextcloud-talk-hermes-bridge.service --no-pager -l
curl http://100.82.45.173:8788/health

After this, the Talk test worked again.


18. Logs and diagnostics

Bridge application log:

tail -n 150 /home/tony/.local/state/nextcloud-talk-hermes-bridge/bridge.log

systemd service log:

journalctl --user -u nextcloud-talk-hermes-bridge.service -n 150 --no-pager -l

Check the running service environment:

pid=$(systemctl --user show -p MainPID --value nextcloud-talk-hermes-bridge.service)

tr '\0' '\n' < /proc/$pid/environ | grep -E '^HERMES_|^TALK_|^NEXTCLOUD_' | sort

For fish shell:

set pid (systemctl --user show -p MainPID --value nextcloud-talk-hermes-bridge.service)

tr '\0' '\n' < /proc/$pid/environ | grep -E '^HERMES_|^TALK_|^NEXTCLOUD_' | sort

19. Quick troubleshooting logic

/health does not respond

Check:

systemctl --user status nextcloud-talk-hermes-bridge.service --no-pager -l
journalctl --user -u nextcloud-talk-hermes-bridge.service -n 150 --no-pager -l

If /health does not work from the Nextcloud AIO server, the issue is probably before Hermes:

network
NetBird connectivity
bind address
port
systemd service

Talk bot does not reply at all

Check:

webhook URL
shared secret
request signature
NetBird connectivity
bridge port
Talk bot room setup

Talk bot replies with an internal error

If Talk replies with:

I hit an internal error while answering.

and /health works, then the webhook and bridge are probably working.

Check mainly:

HERMES_BIN
PATH in the systemd user service
bridge.log

The most common fix in my case was:

HERMES_BIN=/home/tony/.local/bin/hermes

20. Security notes

Never publish the real shared secret.

Use a placeholder in documentation:

TALK_BOT_SECRET=CHANGE_ME_SECRET

If the secret was accidentally pasted into a chat, log, or forum post, rotate it.

Generate a new one:

openssl rand -hex 32

Then:

  1. update TALK_BOT_SECRET in:
~/nextcloud-talk-hermes-bridge/.env
  1. restart the bridge:
systemctl --user restart nextcloud-talk-hermes-bridge.service
  1. update or reinstall the Talk bot with the new secret.

The secret must match on both sides:

Nextcloud Talk bot
nextcloud-talk-hermes-bridge .env

21. Verified result

Final state:

The bridge runs standalone on the Linux notebook.
Nextcloud AIO communicates with the notebook through NetBird.
The Hermes bot replies in the Talk room.
The systemd user service survives reboot.
linger is enabled for the user.

Key final values:

TALK_BRIDGE_BIND=0.0.0.0
HERMES_BIN=/home/tony/.local/bin/hermes
HERMES_YOLO=0

Final healthcheck:

curl http://100.82.45.173:8788/health

Expected output:

{"status": "ok", "app_id": "hermes_talk_bridge", "version": "1.0.1"}

Final Talk test:

Hermes, reply only with OK

Expected result:

OK

At this point, the basic Nextcloud Talk ↔ Hermes integration through nextcloud-talk-hermes-bridge is working.

2 Likes