sawa-control-panel/docs/CLAUDE.md

169 lines
5.8 KiB
Markdown

# CLAUDE.md — Sawa Control Panel
**Project:** sawa-control-panel
**Stack:** React + Node.js + Alpine Linux
**Purpose:** Web-based server control panel for Sawa home servers
---
## Project Overview
A lightweight, self-hosted web control panel for managing Alpine Linux server instances running the Sawa stack. Served by nginx on the local server. Accessible from LAN and remotely via client certificate authentication — no login page, no password prompt. Unauthorized devices cannot reach the interface at all.
---
## Tech Stack
- **Frontend:** React (Vite), Tailwind CSS
- **Backend:** Node.js with Express — thin API layer
- **Auth:** nginx mutual TLS (mTLS) — client certificates only
- **Transport:** HTTPS only, self-signed cert on local network
- **Deployment:** Built React app served as static files by nginx
- **Process manager:** PM2 with OpenRC on Alpine
---
## Architecture
The control panel has two parts:
- **Static frontend** — React app built and served from `/var/www/panel/`
- **API backend** — Node.js Express app running on `127.0.0.1:3001`, proxied by nginx
nginx handles:
- TLS termination with client certificate verification
- Serving the React static build
- Reverse proxying `/api/*` to the Node.js backend
The Node.js backend executes OpenRC commands via `child_process` to control services. It never exposes raw shell access — only a whitelist of allowed actions.
---
## Security Model
- Client certificates issued manually — one per device (laptop, phone, etc.)
- nginx `ssl_verify_client on` — connection dropped before HTTP if no valid cert
- API backend binds only to `127.0.0.1` — never exposed directly
- All service control actions are whitelisted — no arbitrary command execution
- nftables firewall — port 443 open, everything else locked
---
## Directory Structure
```
sawa-control-panel/
├── frontend/ # React Vite app
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ └── App.jsx
│ └── package.json
├── backend/ # Node.js Express API
│ ├── routes/
│ ├── services/
│ └── index.js
├── nginx/ # nginx config snippets
├── certs/ # cert generation scripts
└── CLAUDE.md
```
---
## Environment Variables
```
PORT=3001
ALLOWED_SERVICES=nginx,postgresql,mariadb,redis,memcached,sshd,nftables
LOG_PATH=/var/log
```
---
## Key Commands
```bash
npm run dev # start frontend dev server
npm run build # build frontend for production
node backend/index.js # start API server
pm2 start backend/index.js --name panel-api # production start
```
---
## API Structure
All endpoints prefixed with `/api/v1/`
```
GET /api/v1/services # list all services with status
POST /api/v1/services/:name/start # start a service
POST /api/v1/services/:name/stop # stop a service
POST /api/v1/services/:name/restart # restart a service
GET /api/v1/system/cpu # CPU usage percent
GET /api/v1/system/memory # used/total/free RAM
GET /api/v1/system/disk # disk usage per partition
GET /api/v1/system/uptime # system uptime
GET /api/v1/system/load # 1/5/15 min load averages
GET /api/v1/logs/:service # last 100 lines of service log
GET /api/v1/vhosts # list virtual hosts
POST /api/v1/vhosts # create virtual host
DELETE /api/v1/vhosts/:name # remove virtual host
POST /api/v1/vhosts/:name/enable # enable site
POST /api/v1/vhosts/:name/disable # disable site
POST /api/v1/nginx/reload # reload nginx config
```
---
## Coding Conventions
- React functional components only — no class components
- Tailwind for all styling — no separate CSS files
- API routes prefixed with `/api/v1/`
- All service actions POST only — never GET for mutations
- Error responses always return JSON with `{ error: string }`
- Never execute arbitrary shell commands — use whitelist pattern only
---
## Alpine Linux / Server Notes
**Critical for AI agents working on this project:**
- This is Alpine Linux — use **OpenRC**, not systemd
- Service control: `rc-service <name> start|stop|restart|status`
- Service autostart: `rc-update add <name> default`
- Config persistence: always run `lbu ci` after editing config files or the changes will be lost on reboot
- The server runs in `sys` mode on a USB stick — write operations should be minimized
- All data lives under `/data/` (postgresql, mysql, redis subdirs)
- The backend must run as a non-root user with a strict sudoers whitelist for rc-service
- Deployments go to `/var/www/` on the server via SFTP (WinSCP or scp)
- Test all API endpoints with curl before wiring to frontend
- nginx virtual host configs live in `/etc/nginx/conf.d/` — one file per site
- After any nginx config change: `nginx -t` first, then `rc-service nginx reload`
---
## Managed Services
The following services are installed and managed on the server:
| Service | Port/Socket | Purpose |
|---------|-------------|---------|
| nginx | 80, 443 | Web server / reverse proxy |
| postgresql | 127.0.0.1:5432 | PostgreSQL 18 (data at /data/postgresql) |
| mariadb | 127.0.0.1:3306 | MariaDB (data at /data/mysql) |
| redis | 127.0.0.1:6379 | Redis with persistence (data at /data/redis) |
| memcached | 127.0.0.1:11211 | Memcached cache |
| sshd | 22 | SSH (key auth only) |
| nftables | — | Firewall |
---
## Future Scope
- Multi-node support — manage multiple Sawa servers from one panel
- Traffic analytics — nginx access log parsing (AWStats-style)
- Distributed LLM inference management via exo/llama.cpp
- Node cluster view — aggregate resource monitoring across all nodes