@simplified-org/tunnel
v1.1.0
Published
CLI to expose a local port via simplified-org.com with one command
Readme
simplified-tunnel
Portable tunneling stack built on FRP, consisting of:
- FRP server (
frps): terminates tunnels and routes subdomains to clients. - Tunnel Manager API + admin UI (
src/): hands out subdomains, tracks activity, exposes WebSocket updates, and serves a localhost-only dashboard. - CLI (
simplified): one-command client that requests a tunnel and runsfrpcfor you.
Use this README to self-host the full stack (Docker or bare metal), understand the architecture, and see operational quirks.
How the system behaves
- A client (via CLI or HTTP) asks the Tunnel Manager for a subdomain. If none is provided, the manager generates a memorable adjective-noun (optionally with numeric suffix).
- The manager returns FRP connection info plus the public URL (
https://<subdomain>.<FRP_DOMAIN>). - The client starts
frpcto connect to the FRP server using the shared token. - FRP server (
frps) accepts the tunnel and vhosts HTTP traffic for that subdomain to the client’s local port. - All requests are logged; admin dashboard/WebSocket streams live stats and logs (localhost-only by default).
Repo layout
Dockerfile,start.sh,frps.ini.template,docker-compose.yml– FRP server container + compose with API.bin/simplified.js,lib/*– CLI entrypoint, tunnel API client, frpc downloader/runner.src/– Tunnel Manager API + admin UI.server/index.jsHTTP + WebSocket server, middleware, admin gating.server/api.jsREST routes.server/tunnelManager.jsin-memory + file-backed tunnel state, history, WebSocket broadcasting.server/logger.jsrequest/tunnel logging to file + buffer + WS.server/randomWords.jssubdomain generation/validation.admin/static dashboard (served locally).data/runtime state/logs (gitignored).
test/webapp– simple sample webapp + FRP binaries for local testing.
Prerequisites for self-hosting
- A domain; add a wildcard DNS record
*.your-domainpointing to the host runningfrps(DNS-only/proxied off). - Open ports:
80(HTTP vhost) and7000(FRP control). - A shared FRP token (
FRP_TOKEN). - Node.js 18+ if running the API/CLI directly; Docker if containerizing.
- TLS termination handled externally (e.g., Cloudflare “Full” mode) — this stack serves HTTP only.
Option A: Docker Compose (FRP server + Tunnel Manager)
cp docker-compose.yml docker-compose.local.yml
# edit env values in the file or export FRP_TOKEN/FRP_SERVER_ADDR etc.
docker compose -f docker-compose.local.yml up --buildServices:
frps(ports 80, 7000) built from the Dockerfile; requiresFRP_DOMAIN,FRP_BIND_PORT,FRP_HTTP_PORT,FRP_TOKEN, optionalFRP_MAX_POOL.api(port 4000) runs/srcwithnpm install && npm start; setADMIN_HOST=0.0.0.0only if you intend to expose the dashboard and secure it yourself (defaults to localhost when run bare).
Recommended extras:
- Mount
./src/dataas a volume to keep tunnel/log/history data across restarts. - Set
FRP_SERVER_ADDRto the public IP/DNS of thefrpshost (defaults to34.227.103.55in compose; change this).
Option B: Docker only (FRP server)
Build and run the FRP server container:
docker build -t simplified-tunnel-frps .
docker run --rm -p 80:80 -p 7000:7000 \
-e FRP_DOMAIN=your-domain.com \
-e FRP_BIND_PORT=7000 \
-e FRP_HTTP_PORT=80 \
-e FRP_TOKEN=your-token \
-e FRP_MAX_POOL=200 \
simplified-tunnel-frpsThen run the Tunnel Manager separately (Node or another container) pointed at this FRP endpoint.
Option C: Bare metal
FRP server
export FRP_DOMAIN=your-domain.com
export FRP_BIND_PORT=7000
export FRP_HTTP_PORT=80
export FRP_TOKEN=your-token
# place a compatible frps binary in PATH or alongside start.sh
./start.shTunnel Manager API + admin
cd src
npm install
TUNNEL_MANAGER_PORT=4000 \
ADMIN_HOST=127.0.0.1 \
FRP_DOMAIN=your-domain.com \
FRP_SERVER_ADDR=<frps-host> \
FRP_BIND_PORT=7000 \
FRP_TOKEN=your-token \
npm startAdmin dashboard lives at http://127.0.0.1:4000/admin (localhost-only). APIs under /api.
CLI (client) usage against your deployment
npm install -g simplified
SIMPLIFIED_API_BASE=http://<api-host>:4000 simplified --port 3000
# optional: --subdomain my-site- Downloads
frpcfrom GitHub releases (cached at~/.simplified/frpc). - Supported platforms: macOS (arm64/amd64), Linux (arm64/amd64), Windows (amd64/arm64).
Configuration reference
FRP server (start.sh / Dockerfile / compose)
- Required:
FRP_DOMAIN,FRP_BIND_PORT,FRP_HTTP_PORT,FRP_TOKEN - Optional:
FRP_MAX_POOL(default 200),FRPS_BIN(custom binary path)
Tunnel Manager (src/config.js)
TUNNEL_MANAGER_PORT(default 4000)ADMIN_HOST(default127.0.0.1— set to0.0.0.0only when you secure access)FRP_DOMAIN(defaultsimplified-org.com)FRP_SERVER_ADDR(defaultlocalhost)FRP_BIND_PORT(default7000)FRP_TOKEN(defaultsupersecret)MAX_TUNNELS_PER_USER(default 5)TUNNEL_TIMEOUT(default 24h, ms)- Data files live in
src/data:tunnels.json,history.json,requests.log(gitignored).
CLI
SIMPLIFIED_API_BASEoverrides the API URL (defaults tohttps://api.simplified-org.com).
DNS/TLS:
- Create wildcard A record
*.<FRP_DOMAIN>to thefrpshost. - TLS terminates at your DNS/CDN;
frpsserves HTTP only.
Code architecture and flow
- CLI
bin/simplified.js: parses flags, calls tunnel API, ensuresfrpc, spawns it.lib/api.js: POST/api/tunnels, handles errors.lib/downloader.js: downloads/extractsfrpc(v0.61.0) per OS/arch into~/.simplified.lib/tunnel.js: spawnsfrpc http -s <addr> -P <port> -l <local> --sd <subdomain> -t <token>.
- Tunnel Manager (src/server)
index.js: Express + WS server; logs every request; gates admin routes to localhost; serves/admin; health/root endpoints.api.js: REST routes for tunnel CRUD, availability checks, suggestions, stats, admin logs/history/config.tunnelManager.js: in-memory map + JSON persistence; random subdomains; conflict resolution; TTL cleanup; WebSocket broadcasts of tunnels/stats; modes (productionvslocal).logger.js: request/tunnel log levels, file append, in-memory buffer, WS fanout, log filtering/cleanup APIs.randomWords.js: adjective/noun lists, suffixes, validation and alternative generation.config.js: env parsing and defaults.admin/: static dashboard (dark theme) consuming/wsfor live updates.
- FRP server
start.sh: validates env, rendersfrps.inifrom template viaenvsubst, execsfrps.Dockerfile: alpine base, downloads FRPfrps(default v0.52.3) for detected arch, runs as non-root.
Quirks and operational notes
- Admin access is localhost-only in code. In
docker-compose.yml,ADMIN_HOSTis set to0.0.0.0; you must secure that port yourself (firewall, auth, VPN). - Data persistence:
src/datais not volume-mounted by default in compose—mount it to avoid losing tunnels/logs/history. - FRP versions differ: container pulls
frpsv0.52.3 while the CLI fetchesfrpcv0.61.0; align versions if you see protocol warnings. - Stats persistence is throttled (writes every 10 requests) to reduce disk I/O.
- Tunnel expiry defaults to 24h; expired tunnels are removed and added to history.
- Subdomain suggestions reserve names like
admin,api,localhost; conflicts return alternatives. - HTTPS termination is out of scope; keep DNS/CDN in “DNS only” (no orange cloud) unless you handle TLS separately.
- CLI cache location:
~/.simplified/frpc; removal is manual. - WebSocket and admin endpoints reject non-local IPs; log entries include IP/User-Agent for auditing.
- Compose uses a hardcoded example IP
34.227.103.55forFRP_SERVER_ADDR; change it to your host.
How to propose improvements
Ideas based on current code paths:
- Align
frps/frpcversions and make the version configurable in both Dockerfile and CLI. - Add auth to the admin dashboard and optionally allow remote access with proper protection.
- Persist data to a real store (SQLite/Postgres) and run migrations instead of JSON files.
- Add rate limits / per-IP quotas in
requestTunnelto prevent abuse. - Expose metrics (Prometheus) and health for
frpsalongside the API health check. - Support HTTPS termination (LetsEncrypt/ACME) or behind-proxy documentation.
- Provide official images for the API (not just compose-on-the-fly).
- Add integration tests that spin up
frps+ API + samplefrpcto validate end-to-end.
Testing the sample webapp locally
cd test/webapp/backend
npm install
npm start # serves http://localhost:3000Use the CLI with --port 3000 against your running manager + frps to publish it.
Uninstalling the CLI
npm uninstall -g simplified
rm -rf ~/.simplified/frpc