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
2 changes: 1 addition & 1 deletion DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ curl http://localhost:4820/api/health
docker compose logs -f
```

The included `docker-compose.yml` at the project root runs the dashboard on port `4820` with a persistent `./data` volume for SQLite.
The included `docker-compose.yml` at the project root runs the dashboard on port `4820` with a persistent named volume (`dashboard-data` → `/app/data`) for SQLite. The image binds `0.0.0.0` inside the container and writes data to `/app/data` (both baked into the `Dockerfile`); Compose publishes the port on `127.0.0.1` only, so the dashboard is local-only out of the box. To expose it on a LAN, change the `ports` mapping to `"${DASHBOARD_PORT:-4820}:4820"` and set `DASHBOARD_TOKEN`.

---

Expand Down
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,16 @@ EXPOSE 4820

ENV NODE_ENV=production

# In a container the app MUST bind all interfaces: the server binds loopback by
# default (GHSA-gr74-4xfh-6jw9), but a container's loopback is a separate
# namespace the published port cannot reach, so a loopback bind makes the port
# unreachable. Inside a container the trust boundary is the *host* port publish
# (keep it on 127.0.0.1 — see docker-compose.yml / INSTALL.md), not this bind.
ENV DASHBOARD_HOST=0.0.0.0

# The recommended ~/.claude bind mount is read-only, so the server cannot write
# its default data dir (~/.claude/agent-dashboard). Persist to the mounted
# volume at /app/data instead.
ENV DASHBOARD_DATA_DIR=/app/data

CMD ["node", "server/index.js"]
7 changes: 5 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,15 +399,15 @@ Open **http://localhost:4820** in your browser.
# Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor

# Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
Expand All @@ -420,6 +420,9 @@ podman run -d --name agent-monitor \
| `~/.claude:/root/.claude:ro` | Lets the server import legacy Claude session history |
| `agent-monitor-data:/app/data` | Persists the SQLite database across container restarts |

> [!NOTE]
> The image sets `DASHBOARD_HOST=0.0.0.0` and `DASHBOARD_DATA_DIR=/app/data` internally (baked into the `Dockerfile`). The `0.0.0.0` bind is required because a container's loopback is a separate namespace the published port cannot reach; the SQLite data dir is redirected off the read-only `~/.claude` mount onto the writable volume. The trust boundary is the **host** port publish — the examples use `-p 127.0.0.1:4820:4820`, so the dashboard is local-only. To expose it on a LAN, publish on `0.0.0.0` (`-p 4820:4820`) **and** set `DASHBOARD_TOKEN`.

> [!IMPORTANT]
> Claude Code hooks run on the host, not inside the container. After the container is healthy on `http://localhost:4820`, run `npm run install-hooks` on the host so Claude Code posts hook events back to the containerized server. The installer refuses to run inside a container (issue #193) to avoid writing a container-internal handler path into a bind-mounted `~/.claude`; use `CCAM_ALLOW_CONTAINER_HOOKS=1` only if you run Claude Code inside the same container.

Expand Down
6 changes: 3 additions & 3 deletions README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,21 +434,21 @@ CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor

# Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
```

Dashboard 可通过 `http://localhost:4820` 访问。
随后即可通过 `http://localhost:4820` 访问 Dashboard。镜像在**容器内部**绑定 `0.0.0.0`(`DASHBOARD_HOST`),并将 SQLite 写入 `/app/data` 卷(`DASHBOARD_DATA_DIR`)—— 两者都已固化在 `Dockerfile` 中,因此 Compose 和纯 `docker run` 都能开箱即用。信任边界在于**宿主机**的端口发布:示例仅发布在 `127.0.0.1` 上,因此 Dashboard 开箱即用时无法从局域网访问。若要在局域网中暴露,请在 `0.0.0.0` 上发布(去掉 `127.0.0.1:` 前缀,例如 `-p 4820:4820`)**并**设置 `DASHBOARD_TOKEN`

**卷挂载:**

Expand Down
6 changes: 3 additions & 3 deletions README-KO.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,21 +426,21 @@ CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor

# Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
```

이후 대시보드는 `http://localhost:4820`에서 사용할 수 있습니다. 서버는 기본적으로 `127.0.0.1`에 바인딩되므로, 컨테이너 자체의 루프백 너머에서 접근할 수 있게 하려면 `DASHBOARD_HOST=0.0.0.0` **및** `DASHBOARD_TOKEN`을 설정하세요([설정](#설정)과 [`.github/SECURITY.md`](./.github/SECURITY.md) 참조).
이후 대시보드는 `http://localhost:4820`에서 사용할 수 있습니다. 이미지는 **컨테이너 내부**에서 `0.0.0.0`에 바인딩하며(`DASHBOARD_HOST`) SQLite를 `/app/data` 볼륨에 기록합니다(`DASHBOARD_DATA_DIR`). 두 설정 모두 `Dockerfile`에 내장되어 있어 Compose와 일반 `docker run`이 그대로 동작합니다. 신뢰 경계는 **호스트** 포트 게시입니다. 예시는 `127.0.0.1`에만 게시하므로 대시보드는 기본적으로 LAN에서 접근할 수 없습니다. LAN에 노출하려면 `0.0.0.0`에 게시하고(`127.0.0.1:` 접두사를 제거, 예: `-p 4820:4820`) **및** `DASHBOARD_TOKEN`을 설정하세요([설정](#설정)과 [`.github/SECURITY.md`](./.github/SECURITY.md) 참조).

**볼륨 마운트:**

Expand Down
9 changes: 3 additions & 6 deletions README-VN.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,24 +407,21 @@ CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor

# Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
```

Bảng điều khiển sau đó có sẵn tại `http://localhost:4820`.

> [!QUAN TRỌNG]
> **Phơi bày mạng:** Theo mặc định, máy chủ chỉ bind `127.0.0.1` (loopback) nên không truy cập được từ mạng (GHSA-gr74-4xfh-6jw9). Để mở ra LAN, đặt cả `DASHBOARD_HOST` (ví dụ `0.0.0.0`) **và** `DASHBOARD_TOKEN` — khi token được đặt, mọi yêu cầu `/api/*` cùng kết nối WebSocket đều phải xuất trình nó. Liệt kê các hostname LAN trong `DASHBOARD_ALLOWED_HOSTS` (phân tách bằng dấu phẩy). Xem `.env.example` và [`.github/SECURITY.md`](./.github/SECURITY.md) để biết chi tiết.
Bảng điều khiển sau đó có sẵn tại `http://localhost:4820`. Image bind `0.0.0.0` **bên trong container** (`DASHBOARD_HOST`) và ghi SQLite vào volume `/app/data` (`DASHBOARD_DATA_DIR`) — cả hai đều được nướng sẵn vào `Dockerfile` nên Compose và `docker run` đơn giản đều hoạt động ngay lập tức. Ranh giới tin cậy là việc publish cổng trên **host**: các ví dụ chỉ publish trên `127.0.0.1`, nên bảng điều khiển không truy cập được từ mạng LAN theo mặc định. Để mở ra LAN, hãy publish trên `0.0.0.0` (bỏ tiền tố `127.0.0.1:`, ví dụ `-p 4820:4820`) **và** đặt `DASHBOARD_TOKEN` (xem [Cấu hình](#cấu-hình) và [`.github/SECURITY.md`](./.github/SECURITY.md)).

**Gắn kết âm lượng:**

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,21 +426,21 @@ CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor

# Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
```

The dashboard is then available at `http://localhost:4820`. The server binds `127.0.0.1` by default, so to make it reachable beyond the container's own loopback set `DASHBOARD_HOST=0.0.0.0` **and** `DASHBOARD_TOKEN` (see [Configuration](#configuration) and [`.github/SECURITY.md`](./.github/SECURITY.md)).
The dashboard is then available at `http://localhost:4820`. The image binds `0.0.0.0` **inside the container** (`DASHBOARD_HOST`) and writes SQLite to the `/app/data` volume (`DASHBOARD_DATA_DIR`) — both baked into the `Dockerfile` so Compose and plain `docker run` work as-is. The trust boundary is the **host** port publish: the examples publish on `127.0.0.1` only, so the dashboard is not LAN-reachable out of the box. To expose it on a LAN, publish on `0.0.0.0` (drop the `127.0.0.1:` prefix, e.g. `-p 4820:4820`) **and** set `DASHBOARD_TOKEN` (see [Configuration](#configuration) and [`.github/SECURITY.md`](./.github/SECURITY.md)).

**Volume mounts:**

Expand Down
6 changes: 4 additions & 2 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Plain Docker
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor

# Plain Podman
podman build -t agent-monitor .
podman run -d --name agent-monitor \
-p 4820:4820 \
-p 127.0.0.1:4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
Expand All @@ -84,6 +84,8 @@ podman run -d --name agent-monitor \
Container-specific behavior:

- The dashboard is available at `http://localhost:4820`
- The image sets `DASHBOARD_HOST=0.0.0.0` (bind inside the container — its loopback is a separate namespace the published port cannot reach) and `DASHBOARD_DATA_DIR=/app/data` internally; both are baked into the `Dockerfile`
- The examples publish on `127.0.0.1` only, so the dashboard is local-only. To expose it on a LAN, publish on `0.0.0.0` (`-p 4820:4820`) **and** set `DASHBOARD_TOKEN`
- `~/.claude:/root/.claude:ro` is used for history import only
- `agent-monitor-data:/app/data` persists the SQLite database
- Claude Code hooks still execute on the host, so install them from the host with `npm run install-hooks`
Expand Down
8 changes: 7 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ services:
build: .
container_name: agent-monitor
ports:
- "${DASHBOARD_PORT:-4820}:4820"
# Publish on the host loopback only, so the dashboard is not reachable
# from the LAN out of the box (matches the server's default trust
# boundary — GHSA-gr74-4xfh-6jw9). To expose it on a LAN, change this to
# "${DASHBOARD_PORT:-4820}:4820" and set DASHBOARD_TOKEN below.
- "127.0.0.1:${DASHBOARD_PORT:-4820}:4820"
volumes:
- dashboard-data:/app/data
- ${CLAUDE_HOME:-~/.claude}:/root/.claude:ro
environment:
- NODE_ENV=production
- DASHBOARD_PORT=4820
# DASHBOARD_HOST=0.0.0.0 and DASHBOARD_DATA_DIR=/app/data are baked into
# the image (see Dockerfile) so both `docker run` and Compose work as-is.
restart: unless-stopped

volumes:
Expand Down
6 changes: 6 additions & 0 deletions server/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
let Database;
try {
Database = require("better-sqlite3");
// `require` only loads better-sqlite3's JS; its native addon is resolved
// lazily on the first `new Database(...)`. Probe it here (a throwaway
// in-memory handle) so a missing or ABI-mismatched binary — e.g. installed
// for a different Node version — falls back to node:sqlite now, instead of
// crashing later at the real `new Database(DB_PATH)` outside any try/catch.
new Database(":memory:").close();
} catch {
try {
Database = require("./compat-sqlite");
Expand Down
6 changes: 6 additions & 0 deletions wiki/i18n-content.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading