Skip to content

Commit c7079d0

Browse files
Prithvi-Webclaude
andcommitted
feat(deploy): Dockerfile, compose, systemd unit + implementation notes
Multi-stage image: build with dev deps, run node dist/index.js on node:20-slim with production deps only, as the unprivileged node user, data in a /data volume. Inside the container the server binds 0.0.0.0 (a loopback bind is unreachable through a published port); docker-compose.yml restores the default localhost-only posture by publishing on 127.0.0.1:4280 — widening it plus TREEMAP_TOKEN is a deliberate act. Containers have no OS Trash, so the image is documented as scan-and-analyze. deploy/treemap.service is a hardened systemd sample with StateDirectory-managed app data. Docker is not installed on this machine, so `docker build` itself could not run here; the runtime recipe was proven directly instead — dist + public + `npm ci --omit=dev` staged clean served /api/system, /api/capabilities and the UI. IMPLEMENTATION_NOTES.md records that, the R2 cookie decision, the two new dependencies, and every design choice of the upgrade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 88f6807 commit c7079d0

5 files changed

Lines changed: 199 additions & 0 deletions

File tree

‎.dockerignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules
2+
dist
3+
release
4+
build/gdu
5+
gdu
6+
.git
7+
*.log
8+
docs
9+
tests
10+
electron

‎Dockerfile‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# TreeMap server image — multi-stage: compile with dev deps, run on a slim
2+
# Node 20 base with production deps only.
3+
#
4+
# Inside the container the server binds 0.0.0.0 (a 127.0.0.1 bind would be
5+
# unreachable through the published port); docker-compose.yml maps it to
6+
# 127.0.0.1 on the HOST by default, so out of the box the app stays
7+
# localhost-only — the same posture as running it directly. Widen the port
8+
# mapping and set TREEMAP_TOKEN when you actually want remote access.
9+
#
10+
# A container has no OS Trash and no display: treat this profile as
11+
# scan-and-analyze (mount data read-only); destructive actions belong to the
12+
# desktop app or a host-run server.
13+
14+
# ---- build: dev deps + tsc ----
15+
FROM node:20-slim AS build
16+
WORKDIR /app
17+
COPY package.json package-lock.json ./
18+
RUN npm ci
19+
COPY tsconfig.json ./
20+
COPY src ./src
21+
RUN npm run build
22+
23+
# ---- runtime: production deps only ----
24+
FROM node:20-slim
25+
ENV NODE_ENV=production \
26+
HOST=0.0.0.0 \
27+
PORT=4280 \
28+
TREEMAP_DATA_DIR=/data \
29+
TREEMAP_NO_GDU=1
30+
WORKDIR /app
31+
COPY package.json package-lock.json ./
32+
RUN npm ci --omit=dev && npm cache clean --force
33+
COPY --from=build /app/dist ./dist
34+
COPY public ./public
35+
RUN mkdir -p /data && chown node:node /data
36+
USER node
37+
VOLUME /data
38+
EXPOSE 4280
39+
CMD ["node", "dist/index.js"]

‎IMPLEMENTATION_NOTES.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Implementation notes — agent-native upgrade
2+
3+
Decisions, dependencies and deviations from the agent-upgrade master prompt.
4+
Everything else is documented user-facing in `AGENTS.md`, `GET
5+
/api/capabilities` and `GET /api/openapi.json`.
6+
7+
## The R2 decision: how the browser UI stays authorized under TREEMAP_TOKEN
8+
9+
**Approach: server-side session cookie, set when the UI page is served.**
10+
When `TREEMAP_TOKEN` is set, `GET /` (or `/index.html`) responds with an
11+
`HttpOnly; SameSite=Strict` cookie carrying the token, and the `/api` auth
12+
middleware accepts either `Authorization: Bearer <token>` or that cookie.
13+
14+
Why this and not the alternatives:
15+
16+
- The frontend is frozen (R1): its literal `fetch('/api/…')` calls and
17+
`new EventSource('/api/…')` cannot be taught to send headers — and
18+
`EventSource` *cannot* send custom headers at all, so header-only auth
19+
would silently kill every SSE stream the UI depends on. Cookies ride along
20+
on both automatically.
21+
- A "same-origin requests are exempt" rule would trust `Origin`/`Referer`
22+
headers, which any non-browser client can forge — that's an auth bypass,
23+
not an exemption.
24+
25+
Threat model (stated plainly, also in AGENTS.md): the token gates API access
26+
for non-browser clients, and `SameSite=Strict` + CORS-off blocks cross-site
27+
browser attacks; anyone who can load the UI page itself gets a session. If
28+
the port is exposed beyond localhost, front it with a reverse proxy that
29+
authenticates page loads and terminates TLS.
30+
31+
## New dependencies (and why so few)
32+
33+
- `@modelcontextprotocol/sdk` — the official MCP implementation; hand-rolling
34+
the protocol would be invented risk. Runtime dep (the MCP server runs from
35+
`dist/` with production deps only).
36+
- `zod` — peer dependency of the SDK and the input-validation layer for the
37+
MCP tools' schemas.
38+
- **CORS is implemented in-tree** (`src/middleware/cors.ts`): the policy is a
39+
handful of headers; the MIT `cors` package would add a dependency for no
40+
behavior we need.
41+
- **Zero frontend dependencies added**; `public/index.html` is untouched.
42+
43+
## Design choices worth knowing
44+
45+
- `prepareOffload` was extracted from `startOffload` (same file, byte-same
46+
checks) so dry-run and real-run share one source of truth; `startOffload`
47+
accepts the prepared plan back to avoid planning twice.
48+
- The agent policy (`agent-policy.json`) is **deliberately not writable via
49+
the API** — a policy an agent could rewrite for itself would be theatre.
50+
`GET /api/policy` shows the resolved policy and the file path; the human
51+
edits the file.
52+
- MCP `scan_path` accepts either `path` (start) or `scanId` (keep waiting) —
53+
the polling pattern for scans that outlive one tool-call timeout. The same
54+
pattern applies to `find_duplicates` and `offload` (via `jobId`).
55+
- MCP tool responses skip MCP `outputSchema`: the structured payloads are
56+
typed against `src/models/types.ts` at compile time, which keeps one source
57+
of truth instead of a second, silently-driftable schema layer.
58+
- Audit entries record refusals too (`outcome: "refused"`, with the policy
59+
code) — an agent probing the rails leaves a trace.
60+
- In the container the server binds `0.0.0.0` (a 127.0.0.1 bind is
61+
unreachable through a published port); `docker-compose.yml` restores the
62+
localhost-only posture by publishing on `127.0.0.1:4280` — widen the
63+
mapping and set `TREEMAP_TOKEN` deliberately for remote use. Containers
64+
have no OS Trash, so the container profile is scan-and-analyze; the compose
65+
file suggests read-only data mounts.
66+
67+
## Deviations from the prompt
68+
69+
- **Phase 6 gate (`docker build`) could not be executed on this machine:
70+
Docker is not installed.** Everything Docker would validate short of the
71+
image layer itself was proven directly: the exact runtime recipe
72+
(`package.json` + `npm ci --omit=dev` + `dist/` + `public/` +
73+
`node dist/index.js`) was staged in a clean directory and served
74+
`/api/system`, `/api/capabilities` and the UI correctly with production
75+
dependencies only. Run `docker build .` on a Docker-equipped machine (or
76+
CI) to close the loop.
77+
- No other deviations: every existing endpoint's response shape is unchanged
78+
(golden + contract suites), and with all new env vars unset the server's
79+
behavior is byte-identical to before (explicit tests assert the historical
80+
shapes and header-free responses).

‎deploy/treemap.service‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# TreeMap as a systemd service (sample).
2+
#
3+
# Install:
4+
# sudo useradd --system --home /var/lib/treemap --shell /usr/sbin/nologin treemap
5+
# sudo mkdir -p /opt/treemap && sudo cp -r dist public package.json package-lock.json /opt/treemap/
6+
# cd /opt/treemap && sudo npm ci --omit=dev
7+
# sudo cp deploy/treemap.service /etc/systemd/system/treemap.service
8+
# sudo systemctl daemon-reload && sudo systemctl enable --now treemap
9+
#
10+
# Default posture: binds 127.0.0.1 — reachable only from the machine itself.
11+
# For remote access set HOST=0.0.0.0 AND TREEMAP_TOKEN in
12+
# /etc/treemap/env (see AGENTS.md for the threat model), ideally behind a
13+
# TLS-terminating reverse proxy.
14+
15+
[Unit]
16+
Description=TreeMap disk-space visualizer
17+
Documentation=https://github.com/Prithvi-Web/TreeMap-Disk-Visualizer
18+
After=network.target
19+
20+
[Service]
21+
Type=simple
22+
User=treemap
23+
Group=treemap
24+
WorkingDirectory=/opt/treemap
25+
ExecStart=/usr/bin/node /opt/treemap/dist/index.js
26+
Restart=on-failure
27+
RestartSec=5
28+
29+
Environment=NODE_ENV=production
30+
Environment=HOST=127.0.0.1
31+
Environment=PORT=4280
32+
# Snapshots, settings, audit log and policy live here (created by systemd):
33+
Environment=TREEMAP_DATA_DIR=/var/lib/treemap
34+
StateDirectory=treemap
35+
# Optional secrets (TREEMAP_TOKEN=…, TREEMAP_ALLOWED_ORIGINS=…):
36+
EnvironmentFile=-/etc/treemap/env
37+
38+
# Hardening
39+
NoNewPrivileges=true
40+
PrivateTmp=true
41+
ProtectSystem=full
42+
ProtectHome=read-only
43+
44+
[Install]
45+
WantedBy=multi-user.target

‎docker-compose.yml‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# TreeMap server — `docker compose up -d`, then open http://127.0.0.1:4280
2+
#
3+
# Default posture matches running the app directly: the port is published on
4+
# 127.0.0.1 only, so nothing outside this machine can reach it. To expose it
5+
# beyond localhost, change the mapping to "4280:4280" AND set TREEMAP_TOKEN
6+
# (see AGENTS.md for the full server profile and its threat model).
7+
services:
8+
treemap:
9+
build: .
10+
container_name: treemap
11+
ports:
12+
- "127.0.0.1:4280:4280"
13+
volumes:
14+
- treemap-data:/data
15+
# Mount whatever you want to analyze. Read-only is the honest mode for
16+
# a container: there is no OS Trash in here, so treat it as
17+
# scan-and-analyze and do destructive cleanups from the desktop app.
18+
# - /home/you:/scan/home:ro
19+
# environment:
20+
# TREEMAP_TOKEN: change-me-long-random # bearer auth for /api
21+
# TREEMAP_ALLOWED_ORIGINS: https://tools.example.com
22+
restart: unless-stopped
23+
24+
volumes:
25+
treemap-data:

0 commit comments

Comments
 (0)