# shroud Single-binary Go replacement for the [Outline VPN](https://getoutline.org/) server stack. Replaces the original 3-process deployment (Node.js shadowbox + outline-ss-server + Prometheus) with one Go binary that uses [outline-ss-server](https://golang.getoutline.org/tunnel-server) as a library. Supports three protocols: **Shadowsocks**, **[AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)** (obfuscated WireGuard with HTTP/3 cover traffic), and **[VLESS+REALITY](https://github.com/XTLS/REALITY)** (TLS 1.3 camouflage proxy). ## Features - **Single binary** — no Node.js, no child processes, no sidecar Prometheus - **Shadowbox-compatible REST API** — drop-in replacement for existing Outline clients - **Hot-reload** — adding/removing keys swaps cipher lists and listeners without downtime - **AmneziaWG** — DPI-resistant WireGuard VPN with automatic TLS certificates and HTTP/3 cover server - **VLESS+REALITY** — TLS 1.3 camouflage proxy with auto-detection of decoy servers - **Multi-protocol keys** — each access key automatically gets credentials for all enabled protocols - **Built-in metrics** — Prometheus endpoint with optional node_exporter collectors (Linux) - **GeoIP metrics** — per-country and per-ASN traffic stats with auto-updating MMDB databases - **CLI management** — manage keys, server config, and VLESS targets without a running server - **Atomic state persistence** — single YAML file with safe write-via-rename ## Quick Start ### Install on Ubuntu/Debian ```bash # Shadowsocks only curl -fsSL https://bigbes.sourcecraft.dev/shroud/install.sh | sudo bash # Shadowsocks + AmneziaWG curl -fsSL https://bigbes.sourcecraft.dev/shroud/install.sh | sudo bash -s -- --awg --domain vpn.example.com # Shadowsocks + VLESS+REALITY (auto-detects decoy server) curl -fsSL https://bigbes.sourcecraft.dev/shroud/install.sh | sudo bash -s -- --vless ``` The installer builds from source, creates a systemd service, configures firewall rules, and prints the management API URL on completion. ### Build from source ```bash go build ./cmd/shroud/ go build -ldflags='-X main.version=1.0.0' ./cmd/shroud/ # with version ``` ### Run ```bash ./shroud -c config.example.yaml # start the server ./shroud -c config.yaml -v # with verbose logging ``` ## CLI Usage ```bash # Server management (no running server required) shroud server info -c config.yaml shroud server set-port 50000 -c config.yaml shroud server set-hostname vpn.example.com -c config.yaml shroud server set-name "My VPN" -c config.yaml # Access key management (no running server required) shroud key list -c config.yaml shroud key add -n "user1" -c config.yaml shroud key add -n "user2" -p 51234 --cipher chacha20-ietf-poly1305 -c config.yaml shroud key remove 1 -c config.yaml shroud key rename 1 "new-name" -c config.yaml # VLESS+REALITY management shroud vless info -c config.yaml shroud vless keygen # generate x25519 keypair shroud vless share 0 -c config.yaml # generate share link shroud vless scan --addr 203.0.113.0/24 # scan for decoy targets shroud vless autodetect --write -c config.yaml # auto-detect best decoy # Shell completions shroud completion bash shroud completion zsh ``` ## Configuration See [`config.example.yaml`](config.example.yaml) for the full configuration reference. ```yaml server: name: "My Outline Server" hostname: "example.com" api: listen_addr: ":8081" metrics: listen_addr: "127.0.0.1:8082" node_exporter_collectors: - cpu - meminfo - loadavg - filesystem - diskstats - netdev shadowsocks: default_port: 0 # 0 = pick random available port default_cipher: chacha20-ietf-poly1305 replay_history: 10000 amneziawg: enabled: false listen_port: 443 address: "10.14.0.0/24" domain: "vpn.example.com" # for automatic Let's Encrypt certs vless: enabled: false listen_addr: ":443" server_names: # SNIs accepted by REALITY handshake - www.microsoft.com dest: "www.microsoft.com:443" # decoy forward target state_file: state.yaml ``` ## REST API All management endpoints live under `//` prefix. The secret is auto-generated on first run and printed to the log. | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `//server` | Server info | | `PUT` | `//name` | Rename server | | `GET` | `//access-keys` | List all keys | | `POST` | `//access-keys` | Create key | | `GET` | `//access-keys/{id}` | Get key | | `PUT` | `//access-keys/{id}` | Upsert key | | `DELETE` | `//access-keys/{id}` | Delete key | | `PUT` | `//access-keys/{id}/name` | Rename key | | `PUT` | `//access-keys/{id}/data-limit` | Set data limit | | `DELETE` | `//access-keys/{id}/data-limit` | Remove data limit | | `PUT` | `//server/port-for-new-access-keys` | Set default port | | `PUT` | `//server/hostname-for-access-keys` | Set hostname | | `PUT` | `//server/access-key-data-limit` | Set default data limit | | `DELETE` | `//server/access-key-data-limit` | Remove default data limit | | `GET` | `//metrics/enabled` | Check metrics status | | `PUT` | `//metrics/enabled` | Toggle metrics | | `GET` | `//metrics/transfer` | Per-key byte transfer | | `GET` | `//access-keys/{id}/awg-config` | Download AmneziaWG config | | `GET` | `//access-keys/{id}/outline-config` | Download Outline Smart Dialer config | Public endpoints (no auth): | Method | Endpoint | Server | Description | |--------|----------|--------|-------------| | `GET` | `/metrics` | Metrics | Prometheus scrape target | | `GET` | `/healthz` | Metrics | Health check | ## Architecture ``` cmd/shroud/main.go CLI entry point, cobra commands, signal handling | +-> internal/config/ YAML config loading and validation +-> internal/store/ YAML file persistence (atomic write via rename) +-> internal/ssserver/ Wraps outline-ss-server as a library +-> internal/metrics/ Prometheus registry + TransferTracker +-> internal/api/ REST API handlers (shadowbox-compatible) +-> internal/awgserver/ AmneziaWG server + HTTP/3 QUIC cover +-> internal/vless/ VLESS+REALITY proxy server +-> internal/reality/ TLS scanner + auto-detection for REALITY decoys +-> internal/mmdb/ GeoIP database downloader with auto-update ``` The Shadowsocks proxy is imported as a Go library — not run as a subprocess. When keys change, `SyncKeys()` rebuilds cipher lists and hot-swaps listeners. The integration surface is ~100 lines in `internal/ssserver/server.go`. ## Main Dependencies | Dependency | Version | Purpose | |------------|---------|---------| | [outline-ss-server](https://golang.getoutline.org/tunnel-server) | v1.9.3-rc2 | Shadowsocks proxy (used as library) | | [outline-sdk](https://golang.getoutline.org/sdk) | v0.0.21 | Outline transport primitives | | [amneziawg-go](https://github.com/amnezia-vpn/amneziawg-go) | v1.0.4 | DPI-resistant WireGuard implementation | | [xtls/reality](https://github.com/XTLS/REALITY) | latest | REALITY protocol for TLS camouflage | | [quic-go](https://github.com/quic-go/quic-go) | v0.59.0 | QUIC/HTTP3 for AWG cover traffic | | [prometheus/client_golang](https://github.com/prometheus/client_golang) | v1.23.2 | Go Prometheus client and registry | | [prometheus/node_exporter](https://github.com/prometheus/node_exporter) | v1.10.2 | OS-level metrics collectors (Linux) | ## Testing ```bash go test ./... ```