4.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Sub Router is a Surge proxy subscription management system. It aggregates nodes from upstream subscriptions, allows adding custom static nodes and rules, and generates a combined Surge configuration file served at /surge/:token.
Commands
# Development (starts both server and web dev server concurrently)
pnpm dev
# Server only (port 3456, hot-reload via tsx watch)
pnpm dev:server
# Frontend only (port 5173, proxies /api and /surge to :3456)
pnpm dev:web
# Build frontend for production
pnpm build:web
# Production start (serves built frontend + API on port 3456)
pnpm start
# Type check frontend
cd web && pnpm exec tsc --noEmit
# Type check server
cd server && pnpm exec tsc --noEmit
# Docker
docker compose up -d
Architecture
Monorepo with NPM Workspaces: server/ (Express + better-sqlite3) and web/ (React + Vite). In production, Express serves the built frontend static files and API from a single port (3456).
Key data flow
- User adds upstream subscription URLs → backend fetches and parses nodes into
fetched_nodestable - User adds static nodes via URI (ss://, vmess://, trojan://) → parsed and stored in
static_nodestable GET /surge/:token→generator.tsrebuilds the Surge config:- Takes first enabled subscription's
raw_configas base template - Replaces
[Proxy]section entirely with only enabled nodes (static first, then fetched) - Rebuilds
[Proxy Group]select groups with only enabled node names - Injects user rules at the top of
[Rule]section - Rewrites
#!MANAGED-CONFIGURL
- Takes first enabled subscription's
Auth model
Two separate credentials exist:
- Admin password: Protects all
/api/*routes (except/api/auth/loginand/api/auth/status). Sent asAuthorization: Bearer <password>. On first login with no password set, the submitted password becomes the password. - Surge token: UUID stored in
configtable assurge_token. Embedded in the Surge subscription URL itself (/surge/:token). Returns 404 for wrong tokens. Regeneratable viaPOST /api/config/surge-token.
API routes
POST /api/auth/login,GET /api/auth/status— auth (no auth required)GET /api/stats— node/rule counts summary/api/subscriptions→subscriptions.ts(CRUD +/:id/fetch,/:id/nodes)/api/nodes→nodes.ts(fetched node toggle, static node CRUD)/api/rules→rules.ts(CRUD +/reorder)/api/config→surge.ts(/surge-tokenGET/POST,/previewGET)
Database
SQLite with WAL mode at server/data/sub-router.db. Five tables: subscriptions, fetched_nodes (FK to subscriptions, CASCADE delete), static_nodes, rules, config (KV store for password and surge_token).
Parsers (server/src/parsers/)
Convert protocol URIs to Surge proxy lines. Each parser returns { name, type, server, port, surgeLine }. The subscription content parser handles both base64-encoded URI lists and raw Surge config format (extracting from [Proxy] section).
- SS: No TLS wrapping (Surge doesn't support SS over TLS; SS 2022 has built-in encryption)
- VMess/Trojan:
skip-cert-verify=false(strict TLS verification)
On re-fetch, enabled state is preserved by node name: existing name→enabled mapping is saved before delete, then reapplied to new rows.
When renaming a static node (PUT /api/nodes/static/:id), the surge_line prefix is also updated to match the new name.
Frontend
Five panels: Subscriptions (CRUD + fetch trigger), Static Nodes (paste URI, custom naming, double-click to rename), Node Selector (per-subscription toggle with regex batch operations), Rules (CRUD + drag reorder), Output (subscription URL + config preview). Dark cyberpunk theme with CSS variables, JetBrains Mono font.
Generator limitation
rebuildProxyGroup in generator.ts only updates the first = select, group (due to handled = true flag). If a Surge template has multiple select groups, only the first gets its node list replaced.
Route ordering matters
In server/src/routes/: specific paths like /reorder and /fetched/batch must be registered before parameterized /:id routes to avoid being captured by Express route matching.
Legacy file
index.js at the repo root is a pre-rewrite single-file prototype. It is not used by the current system and can be ignored.
Deployment
Multi-stage Docker build: frontend built on node:22-slim, output copied into production image alongside the server. The ./data volume maps to server/data/ inside the container for SQLite persistence.
The Content-Disposition header on /surge/:token controls the config profile name in Surge client (currently IPLC.MAX.conf). Certificate renewal uses acme.sh with dns_cf (Cloudflare DNS API validation).