← Về thư mục
📄 / / root / .hermes / skills / deploy-internal-dashboard / SKILL.md

name: deploy-internal-dashboard description: Deploy internal SPAs (HTML/JS/CSS) with Python API backends behind Caddy as systemd services. Covers stdlib http.server/ThreadingHTTPServer, env-based basicauth, static file serving, JSON API routing, and lifecycle.


Skill: Deploy Internal Dashboard

Deploy an internal SPA dashboard (static HTML/JS/CSS) with a Python stdlib API backend, running as a systemd service behind Caddy reverse proxy.

When to use

Architecture Pattern

User → Browser → Caddy (HTTPS, public, reverse proxy)
                  → 127.0.0.1:<PORT> (Python HTTP server, internal only)
                         → static/     (SPA: index.html, app.js, style.css)
                         → /api/*      (JSON endpoints → calls lib/ CLI helpers)
                         → /files/*    (optional: read-only file browser)

Server implementation

Use http.server.ThreadingHTTPServer (Python 3.7+, stdlib) — single dependency, no framework.

Key design decisions: - Bind 127.0.0.1 only: security by network boundary. Caddy is the public face. - Env-based basicauth: credentials via env vars, not code. Format: DASH_USER, DASH_PASS_HASH. - Hash generation: printf '%s' "$PASSWORD" | sha256sum | awk '{print $1}'. Store hex hash, never plaintext. - Read-only by default: projection-only endpoints (not full file contents). - ThreadingHTTPServer: prevents blocking on concurrent requests (e.g. browser favicon + page load).

Systemd service setup

Create a .service file at /etc/systemd/system/<name>.service:

[Unit]
Description=<app> dashboard
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/path/to/project
EnvironmentFile=/path/to/instance/<app>.env
ExecStart=/usr/bin/python3 /path/to/server.py
Restart=always

[Install]
WantedBy=multi-user.target

Activation:

systemctl daemon-reload
systemctl enable --now <name>.service
systemctl is-active <name>.service

Caddy reverse proxy

Append to /root/.caddy/Caddyfile:

<subdomain>.<domain>.sslip.io {
    reverse_proxy 127.0.0.1:<PORT>
}

Reload Caddy:

docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile

BUILD.txt two-tier (CEO AI OS specific)

BUILD.txt exists at TWO levels — both must be kept consistent: - /opt/ai-os/BUILD.txt — instance-level, checked by setup scripts and installer - /opt/ai-os/products/ceo/BUILD.txt — product-level, checked by instance_info.py

When enabling a dashboard feature, update BOTH files. The setup script silently skips if only the product-level BUILD.txt has dashboard: true but the instance-level still says dashboard: false.

Verification

File Browser & Advanced Kanban OS Implementation

When adding a /files/ browser or Custom Kanban OS viewer (e.g. at http://100.71.157.103:9120/): - Set FILES_ROOT = "/": For a full-system view, serve from the filesystem root. - Path Traversal Protection: Use os.path.realpath(os.path.join(FILES_ROOT, rel)) then verify the result still starts with FILES_ROOT before opening. - Clickable Breadcrumbs on ALL pages: Not just Markdown views — directory listings need them too. Split the path into clickable segments everywhere. - Multi-Board Selector: Parse the query parameter (e.g., ?board=nv-office) and list all directories under /root/.hermes/kanban/boards as buttons to switch between boards. Read the SQLite database (kanban.db) dynamically for the chosen board. - Sorting Options: Implement a dropdown sorting selector (e.g. priority, created date, title, ascending/descending) that adds a sort query parameter and changes the SQL ORDER BY statement. - Pagination / Collapse Pattern: To keep column sizes manageable, display only the first 5 tasks per column by default. Provide a "▼ Xem thêm (N)" button that uses client-side JavaScript to toggle visibility of a .hidden-tasks container containing the remaining items. Avoid complex modal overlays. - Clickable Task Details: Make task titles clickable and point to a /task?id=<task_id>&board=<board> route. Display full fields (assignee, priority, created_at, status), optionally the project workspace path, description (body), and worker execution logs (result). - Remove redundant navigation: Once breadcrumbs are clickable and show the full parent path, do NOT add a separate "Thư mục cha" (parent directory) link in the listing. It duplicates the last breadcrumb segment and clutters the UI. - Define link colors explicitly on dark themes: Browser-default colors on #1a1b26 backgrounds are nearly invisible. Always specify: .breadcrumb a { color: #7aa2f7; font-weight: bold; } and li a { color: #ffd166; } with hover states. Separate icon from text via so the icon stays outside the tag. - Address-in-Use fix: Set socketserver.ThreadingTCPServer.allow_reuse_address = True before starting the server.

Pitfalls