5.8 KiB
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
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 ciafter editing config files or the changes will be lost on reboot - The server runs in
sysmode 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 -tfirst, thenrc-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