Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ USER root
# Install ttyd for web terminal (static binary from GitHub releases)
ADD --chmod=755 https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64 /usr/local/bin/ttyd

# Pre-install WhatsApp bridge dependencies (upstream ships the script but not node_modules)
RUN cd /opt/hermes/scripts/whatsapp-bridge && npm install --omit=dev --no-audit --no-fund

# Copy setup wizard into the image
COPY setup-wizard/ /opt/setup-wizard/

# Copy DAppNode context files (seeded into HERMES_HOME on first boot)
COPY dappnode/ /opt/dappnode/

# Copy entrypoint script
COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh

Expand All @@ -24,4 +30,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["hermes", "gateway", "run"]
CMD ["gateway", "run"]
83 changes: 83 additions & 0 deletions dappnode/dappnode/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: dappnode
description: >
DAppNode package operations — HTTPS exposure, port mapping, inter-package
connectivity, Nexus provider setup, and troubleshooting.
Use when the user asks about networking, exposing services, connecting
to other packages, or configuring AI providers on DAppNode.
version: 1.0.0
author: DAppNode Association
license: MIT
metadata:
hermes:
tags: [dappnode, networking, infrastructure, https, nexus, provider]
category: devops
related_skills: [webhook-subscriptions]
---

# DAppNode Package Operations

This Hermes Agent runs as a DAppNode package. This skill covers DAppNode-specific procedures.

## Exposing a Service via HTTPS

By default, services are only reachable from inside the DAppNode network. To make a service publicly accessible:

1. Direct the user to open the DAppNode UI (`http://my.dappnode`), find the Hermes Agent package, and go to its **Network** tab.
2. They configure:
- **Subdomain**: a name they choose (e.g., `hermes-api`)
- **Port**: which container port to expose (8081 for dashboard, 3000 for API, etc.)
- **Basic auth** (optional but recommended): username and password
3. The resulting public URL will be: `https://<subdomain>.<dyndns-domain>`

This is powered by the `https.dnp.dappnode.eth` package — an Nginx reverse proxy with automatic TLS via DAppNode's dyndns wildcard certificates.

### Security Notes
- Always recommend basic auth when exposing the dashboard (port 8081)
- The API server (port 3000) has its own key (`API_SERVER_KEY` env var), but basic auth adds a second layer
- Some webhook providers (Telegram, Discord) don't support basic auth in callback URLs — for those, rely on the API key alone and skip basic auth

### For Webhooks (Telegram, Discord, Slack, etc.)
If the user needs a publicly reachable webhook URL:
1. They must expose port 3000 via HTTPS first (see above)
2. The webhook callback URL will be `https://<subdomain>.<dyndns-domain>/...`
3. Configure this URL in the messaging platform's bot settings

## Port Mapping to Host

Users can map container ports directly to the host machine's network via the **Network** tab of the Hermes Agent package in the DAppNode UI (`http://my.dappnode`).

This allows access from the local network without VPN — useful for LAN-only setups.

## Inter-Package Connectivity

All DAppNode packages share the `dncore_network` Docker bridge network. Packages are reachable via DNS aliases.

### DNS Pattern
- Mono-service packages: `<shortname>.dappnode`
- Multi-service packages: `<service>.<shortname>.dappnode`

### Testing Connectivity
```bash
# Test if another package is reachable (not all packages expose /health)
curl -sf http://<package-alias>.dappnode:<port>/ -o /dev/null && echo "reachable" || echo "unreachable"
```

### Using DAppNode Nexus (Recommended)
DAppNode Nexus (`https://nexus.dappnode.com`) is DAppNode's own privacy-focused LLM gateway. It is OpenAI-compatible and prompts are never logged, stored, or used for training. Check the Nexus website for available models and pricing.

To configure, use the Setup Wizard at `http://hermes-agent.dappnode:8080` and select Nexus as the provider. Or manually:
1. Sign up at `https://nexus.dappnode.com` and create an API key
2. In the Setup Wizard or `config.yaml`, set the provider to Nexus with base URL `https://nexus-api.dappnode.com/v1`

## Troubleshooting

### Package Not Reachable
- The target package may not be installed or may be stopped — direct the user to the DAppNode UI (`http://my.dappnode`) to check
- Hermes cannot install or manage other DAppNode packages

### Configuration
- Environment variables: editable in the **Config** tab of the Hermes Agent package in the DAppNode UI
- Config file: `/opt/data/config.yaml`
- API keys: `/opt/data/.env`
- Logs: viewable in the **Logs** tab of the Hermes Agent package in the DAppNode UI
26 changes: 26 additions & 0 deletions dappnode/hermes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# DAppNode Environment

This Hermes Agent instance runs as a DAppNode package inside an isolated Docker container.

## Network Access

Users connected to the DAppNode (via VPN or WiFi) access services at:

| Service | URL |
|-----------------|------------------------------------------|
| Web Dashboard | http://hermes-agent.dappnode:8081 |
| Setup Wizard | http://hermes-agent.dappnode:8080 |
| Gateway API | http://hermes-agent.dappnode:3000 |
| Web Terminal | http://hermes-agent.dappnode:7681 |

These services are important, dont kill them.

**IMPORTANT**: Since this instance runs inside a DAppNode package, `localhost` does not work for users. Always give URLs using the DAppNode Hermes namespace: `http://hermes-agent.dappnode:<port>` (e.g., `http://hermes-agent.dappnode:3000` for the API). The user accesses these from their browser while connected to the DAppNode network.

## DAppNode Admin

The user manages this package (environment variables, port mappings, logs, HTTPS exposure) via the DAppNode UI at `http://my.dappnode` — find the Hermes Agent package and use the Config, Network, and Logs tabs.

## External Access

Services are only reachable from inside the DAppNode network by default. The user can expose any port to the public internet with an HTTPS subdomain (and optional basic auth) via the DAppNode network tab. Load the `dappnode` skill for details on how to guide the user through this.
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ services:
GATEWAY_ALLOW_ALL_USERS: "true"
volumes:
- hermes_data:/opt/data
security_opt:
- no-new-privileges:true
logging:
driver: json-file
options:
Expand Down
64 changes: 42 additions & 22 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
#!/bin/bash
# DAppNode Hermes Agent entrypoint
# Based on upstream docker/entrypoint.sh (v2026.4.23) with DAppNode additions.
set -e

HERMES_HOME="${HERMES_HOME:-/opt/data}"
INSTALL_DIR="/opt/hermes"

# Activate virtualenv if present (upstream images after v2026.4.8 use one)
if [ -f "${INSTALL_DIR}/.venv/bin/activate" ]; then
source "${INSTALL_DIR}/.venv/bin/activate"
fi
# --- Activate virtualenv ---
source "${INSTALL_DIR}/.venv/bin/activate"

# Make `hermes` discoverable inside the ttyd web terminal.
# ttyd spawns `bash -l`, which resets PATH from /etc/profile and loses the
# venv we activated above. Drop a profile.d snippet so login shells re-add it.
# ttyd spawns `bash -l`, which resets PATH. Drop a profile.d snippet so
# login shells re-add the venv and start in HERMES_HOME.
if [ -d /etc/profile.d ] && [ ! -f /etc/profile.d/hermes-venv.sh ]; then
cat > /etc/profile.d/hermes-venv.sh <<EOF
# Added by DAppNode Hermes Agent package: expose the upstream venv to login shells
if [ -d "${INSTALL_DIR}/.venv/bin" ]; then
export PATH="${INSTALL_DIR}/.venv/bin:\$PATH"
export VIRTUAL_ENV="${INSTALL_DIR}/.venv"
cat > /etc/profile.d/hermes-venv.sh <<'PROFILE'
# DAppNode Hermes Agent: expose venv + HERMES_HOME to login shells (ttyd)
if [ -d "/opt/hermes/.venv/bin" ]; then
export PATH="/opt/hermes/.venv/bin:$PATH"
export VIRTUAL_ENV="/opt/hermes/.venv"
fi
export HERMES_HOME="${HERMES_HOME}"
EOF
export HERMES_HOME="${HERMES_HOME:-/opt/data}"
cd "$HERMES_HOME"
PROFILE
chmod 0644 /etc/profile.d/hermes-venv.sh
fi

# Clean stale runtime files from previous container runs
rm -f "$HERMES_HOME"/gateway.lock "$HERMES_HOME"/gateway.pid

# --- Bootstrap config files (mirrors upstream entrypoint) ---
# Layout matches upstream docker/entrypoint.sh in v2026.4.23 (Hermes v0.11.0).
mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}

if [ ! -f "$HERMES_HOME/.env" ]; then
Expand All @@ -34,8 +37,20 @@ fi
if [ ! -f "$HERMES_HOME/config.yaml" ]; then
cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
fi

if [ ! -f "$HERMES_HOME/SOUL.md" ]; then
cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md" 2>/dev/null || touch "$HERMES_HOME/SOUL.md"
cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md"
fi

# --- DAppNode: seed context files (first boot only) ---
if [ ! -f "$HERMES_HOME/.hermes.md" ] && [ -f /opt/dappnode/hermes.md ]; then
cp /opt/dappnode/hermes.md "$HERMES_HOME/.hermes.md"
echo "Seeded .hermes.md with DAppNode context"
fi
if [ ! -d "$HERMES_HOME/skills/devops/dappnode" ] && [ -d /opt/dappnode/dappnode ]; then
mkdir -p "$HERMES_HOME/skills/devops/dappnode"
cp -r /opt/dappnode/dappnode/* "$HERMES_HOME/skills/devops/dappnode/"
echo "Seeded DAppNode skill"
fi

# --- DAppNode: patch config.yaml for network access ---
Expand All @@ -54,26 +69,31 @@ cui = gw.setdefault('controlUi', {})
cui.setdefault('dangerouslyAllowHostHeaderOriginFallback', True)
cui.setdefault('allowInsecureAuth', True)
cui.setdefault('dangerouslyDisableDeviceAuth', True)
term = config.setdefault('terminal', {})
term['cwd'] = '$HERMES_HOME'
with open(config_path, 'w') as f:
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
print('Patched config.yaml for DAppNode (port=3000, bind=lan)')
" || echo "Warning: Could not patch config.yaml, continuing with defaults"

# --- Sync bundled skills ---
if [ -d "$INSTALL_DIR/skills" ] && [ -f "$INSTALL_DIR/tools/skills_sync.py" ]; then
python3 "$INSTALL_DIR/tools/skills_sync.py" || true
# --- Sync bundled skills (matches upstream — no || true) ---
if [ -d "$INSTALL_DIR/skills" ]; then
python3 "$INSTALL_DIR/tools/skills_sync.py"
fi

# --- Start background services ---
# --- Start DAppNode background services ---
node /opt/setup-wizard/server.cjs &
echo "Setup wizard started on port 8080"

# Start the web dashboard (serves pre-built UI from HERMES_WEB_DIST)
hermes dashboard --port 8081 --host 0.0.0.0 --no-open --insecure &
echo "Web dashboard started on port 8081"

ttyd -p 7681 -W bash -l &
echo "Web terminal started on port 7681"

# --- Run hermes ---
exec "$@"
# --- Run hermes (matches upstream exec logic) ---
# If $1 is an executable on PATH, run it directly; otherwise wrap with `hermes`.
if [ $# -gt 0 ] && command -v "$1" >/dev/null 2>&1; then
exec "$@"
fi
exec hermes "$@"
13 changes: 5 additions & 8 deletions getting-started.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# Hermes Agent

Self-hosted AI agent for DappNode — by [Nous Research](https://nousresearch.com).
A **self-hosted AI agent** by [Nous Research](https://nousresearch.com) that runs on your DAppNode. Hermes learns from experience, remembers you across sessions, and connects to Telegram, Discord, Slack, and more — all from a single process.

## Quick Start
## Getting started

1. Open the **[Web UI](http://hermes-agent.dappnode:8080)** to configure your provider, API key, and model.
2. Connect any OpenAI-compatible client to `http://hermes-agent.dappnode:3000`.

The Web UI includes a **Dashboard**, **Setup** wizard, and an embedded **Terminal** for running `hermes` CLI commands.

Full docs: [hermes-agent.nousresearch.com/docs](https://hermes-agent.nousresearch.com/docs/)
1. **Set up a provider** — Open the [Setup Wizard](http://hermes-agent.dappnode:8080) and pick an AI provider.
2. **Talk to Hermes** — Message your bot on Telegram/Discord, run `hermes chat` in the [Web Terminal](http://hermes-agent.dappnode:7681), or connect any OpenAI-compatible client to `http://hermes-agent.dappnode:3000`.
3. **Manage your agent** — The [Dashboard](http://hermes-agent.dappnode:8081) lets you view sessions, manage API keys, configure skills, set up scheduled tasks, and check logs.
Loading
Loading