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
8788on 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_SECRETmust match the secret used when installing the Talk bot.TALK_BRIDGE_BIND=0.0.0.0allows the Nextcloud AIO server to reach the bridge through NetBird.HERMES_BIN=/home/tony/.local/bin/hermesis the absolute path to the Hermes CLI.HERMES_YOLO=0keeps 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.1confirms that the bridge is running locally on the notebook.100.82.45.173from the notebook confirms that the bridge is listening on the NetBird IP.100.82.45.173from 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:
- update
TALK_BOT_SECRETin:
~/nextcloud-talk-hermes-bridge/.env
- restart the bridge:
systemctl --user restart nextcloud-talk-hermes-bridge.service
- 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.
