From f351e0e2219a80b39e0a2f018ccdeb4c059ad133 Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Thu, 19 Mar 2026 07:52:30 +0300 Subject: [PATCH] refactor: rename project to shroud Rename project from outline-distro to shroud for better branding. - Update module path in go.mod from outline-distro to shroud. - Rename binary, service file, and all installation paths. - Update all import paths across internal packages. - Add comprehensive README.md with usage and architecture docs. - Update install script and systemd unit file for new naming. - Update config example with new cert cache path. --- .gitignore | 4 +- README.md | 157 ++++++++++++++++++ cmd/{outline-distro => shroud}/main.go | 34 ++-- config.example.yaml | 2 +- ...{outline-distro.service => shroud.service} | 18 +- go.mod | 2 +- install.sh | 38 ++--- internal/api/handlers.go | 4 +- internal/api/router.go | 10 +- internal/awgserver/server.go | 2 +- internal/config/config.go | 2 +- internal/metrics/collector.go | 2 +- internal/ssserver/server.go | 4 +- 13 files changed, 218 insertions(+), 61 deletions(-) create mode 100644 README.md rename cmd/{outline-distro => shroud}/main.go (94%) rename dist/{outline-distro.service => shroud.service} (73%) diff --git a/.gitignore b/.gitignore index 6e6012e4a117b0130a27d67975da8a60bdd58bf9..08132c2732dfe01be800cb799c0cfe2ea73e6412 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Binary (root only, not cmd/outline-distro/) -/outline-distro +# Binary (root only, not cmd/shroud/) +/shroud # State/runtime state.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8e56b3078ecb37edfdf3378ec38e96a0198b0171 --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# 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://github.com/Jigsaw-Code/outline-ss-server) as a library. + +Optionally includes [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) support with HTTP/3 QUIC cover traffic on port 443. + +## 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** — obfuscated WireGuard VPN with automatic TLS certificates and HTTP/3 cover server +- **Built-in metrics** — Prometheus endpoint with optional node_exporter collectors (Linux) +- **CLI management** — manage keys and server config without a running server +- **Atomic state persistence** — single YAML file with safe write-via-rename + +## Quick Start + +### Build from source + +```bash +go build ./cmd/shroud/ +``` + +### Run + +```bash +# Start the server +./shroud -c config.example.yaml + +# With verbose logging +./shroud -c config.yaml -v +``` + +### Install on Ubuntu/Debian + +```bash +curl -fsSL https://sourcecraft.dev/bigbes/shroud/raw/branch/main/install.sh | sudo bash +``` + +The installer builds from source, creates a systemd service, configures firewall rules, and prints the management API URL on completion. + +## 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 + +# 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:8081" + 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 + +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 | + +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 +``` + +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`. + +## Building with version info + +```bash +go build -ldflags='-X main.version=1.0.0' ./cmd/shroud/ +``` + +## Testing + +```bash +go test ./... +``` diff --git a/cmd/outline-distro/main.go b/cmd/shroud/main.go similarity index 94% rename from cmd/outline-distro/main.go rename to cmd/shroud/main.go index 2b9b14ff3e85bfc34368f369502cd92c657db01c..33d35ff8d750653aca2b325009f73e1e1824062d 100644 --- a/cmd/outline-distro/main.go +++ b/cmd/shroud/main.go @@ -25,12 +25,12 @@ import ( "golang.org/x/term" "gopkg.in/yaml.v3" - "sourcecraft.dev/bigbes/outline-distro/internal/api" - "sourcecraft.dev/bigbes/outline-distro/internal/awgserver" - "sourcecraft.dev/bigbes/outline-distro/internal/config" - "sourcecraft.dev/bigbes/outline-distro/internal/metrics" - "sourcecraft.dev/bigbes/outline-distro/internal/ssserver" - "sourcecraft.dev/bigbes/outline-distro/internal/store" + "sourcecraft.dev/bigbes/shroud/internal/api" + "sourcecraft.dev/bigbes/shroud/internal/awgserver" + "sourcecraft.dev/bigbes/shroud/internal/config" + "sourcecraft.dev/bigbes/shroud/internal/metrics" + "sourcecraft.dev/bigbes/shroud/internal/ssserver" + "sourcecraft.dev/bigbes/shroud/internal/store" ) var version = "dev" @@ -48,8 +48,8 @@ func newRootCmd() *cobra.Command { ) root := &cobra.Command{ - Use: "outline-distro", - Short: "Single-binary Outline Shadowsocks server with Prometheus metrics", + Use: "shroud", + Short: "Shadowsocks + AmneziaWG VPN server", Version: version, RunE: func(cmd *cobra.Command, args []string) error { return runServe(configFile, verbose) @@ -77,27 +77,27 @@ func newCompletionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "completion [bash|zsh|fish|powershell]", Short: "Generate shell completion scripts", - Long: `Generate shell completion scripts for outline-distro. + Long: `Generate shell completion scripts for shroud. To load completions: Bash: - $ source <(outline-distro completion bash) + $ source <(shroud completion bash) # To install permanently: - $ outline-distro completion bash > /etc/bash_completion.d/outline-distro + $ shroud completion bash > /etc/bash_completion.d/shroud Zsh: - $ source <(outline-distro completion zsh) + $ source <(shroud completion zsh) # To install permanently: - $ outline-distro completion zsh > "${fpath[1]}/_outline-distro" + $ shroud completion zsh > "${fpath[1]}/_shroud" Fish: - $ outline-distro completion fish | source + $ shroud completion fish | source # To install permanently: - $ outline-distro completion fish > ~/.config/fish/completions/outline-distro.fish + $ shroud completion fish > ~/.config/fish/completions/shroud.fish PowerShell: - PS> outline-distro completion powershell | Out-String | Invoke-Expression + PS> shroud completion powershell | Out-String | Invoke-Expression `, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, @@ -689,7 +689,7 @@ func runServe(configFile string, verbose bool) error { logger.Info("Access info written.", "file", infoFile) } - logger.Info("outline-distro started.", + logger.Info("shroud started.", "version", version, "api", cfg.API.ListenAddr, "metrics", cfg.Metrics.ListenAddr, diff --git a/config.example.yaml b/config.example.yaml index 52a67cc1132925ba77187769a64fa5ac86bdad2d..1ba58b1d733ba499e50a126e5a49decfa37587a0 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -36,7 +36,7 @@ amneziawg: dns: "1.1.1.1, 8.8.8.8" # HTTP/3 cover for DPI resistance (requires domain) domain: "" # e.g., vpn.example.com - cert_cache: /var/lib/outline-distro/certs + cert_cache: /var/lib/shroud/certs acme_http_port: 80 # Obfuscation parameters (must match client config) jc: 4 diff --git a/dist/outline-distro.service b/dist/shroud.service similarity index 73% rename from dist/outline-distro.service rename to dist/shroud.service index a91945fcc25183554a0b0223af7bd8ec738e5c6c..04251b86f161b7504ab665adf6194b74cacb0895 100644 --- a/dist/outline-distro.service +++ b/dist/shroud.service @@ -1,29 +1,29 @@ [Unit] -Description=Outline Distro — Shadowsocks + AmneziaWG VPN Server -Documentation=https://sourcecraft.dev/bigbes/outline-distro +Description=Shroud — Shadowsocks + AmneziaWG VPN Server +Documentation=https://sourcecraft.dev/bigbes/shroud After=network-online.target nss-lookup.target Wants=network-online.target [Service] Type=simple -ExecStart=/usr/local/bin/outline-distro -c /etc/outline-distro/config.yaml +ExecStart=/usr/local/bin/shroud -c /etc/shroud/config.yaml Restart=on-failure RestartSec=5 WatchdogSec=60 # Logging — stdout/stderr go straight to journald. -# View with: journalctl -u outline-distro -f -# Filter errors: journalctl -u outline-distro -p err +# View with: journalctl -u shroud -f +# Filter errors: journalctl -u shroud -p err StandardOutput=journal StandardError=journal -SyslogIdentifier=outline-distro +SyslogIdentifier=shroud # File descriptors LimitNOFILE=65536 # Run as dedicated user (created by install script) -User=outline-distro -Group=outline-distro +User=shroud +Group=shroud # Capabilities — needed for: # CAP_NET_BIND_SERVICE — bind to ports < 1024 (AWG on 443, ACME on 80) @@ -50,7 +50,7 @@ RemoveIPC=yes SystemCallArchitectures=native # Writable paths for state, certs, and TUN device -ReadWritePaths=/var/lib/outline-distro /etc/outline-distro /dev/net/tun +ReadWritePaths=/var/lib/shroud /etc/shroud /dev/net/tun # Allow /dev/net/tun access for AWG DeviceAllow=/dev/net/tun rw diff --git a/go.mod b/go.mod index 1cc36232b2e27f233b4c0101986006f0d437657e..4485bab4f8d1437b26a0555b0bb51ff6ae4ddf1f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module sourcecraft.dev/bigbes/outline-distro +module sourcecraft.dev/bigbes/shroud go 1.26.1 diff --git a/install.sh b/install.sh index 6346502cd8912fa23ca7fc9194c42f17db210664..d34deb91126fa8a01f1c9eed7044e6654b846ff1 100755 --- a/install.sh +++ b/install.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # -# outline-distro installer (Ubuntu) +# shroud installer (Ubuntu) # -# Builds from source and installs outline-distro as a systemd service +# Builds from source and installs shroud as a systemd service # with Shadowsocks + optional AmneziaWG (HTTP/3 mux for DPI resistance). # # Usage: @@ -16,14 +16,14 @@ set -euo pipefail # --- Paths --- -INSTALL_DIR="/opt/outline-distro" -CONFIG_DIR="/etc/outline-distro" -DATA_DIR="/var/lib/outline-distro" -BINARY="/usr/local/bin/outline-distro" -SERVICE_NAME="outline-distro" -SERVICE_USER="outline-distro" +INSTALL_DIR="/opt/shroud" +CONFIG_DIR="/etc/shroud" +DATA_DIR="/var/lib/shroud" +BINARY="/usr/local/bin/shroud" +SERVICE_NAME="shroud" +SERVICE_USER="shroud" UNIT_FILE="/etc/systemd/system/${SERVICE_NAME}.service" -REPO_URL="https://sourcecraft.dev/bigbes/outline-distro.git" +REPO_URL="https://sourcecraft.dev/bigbes/shroud.git" # --- Config defaults --- @@ -110,7 +110,7 @@ fi if ! id "$SERVICE_USER" &>/dev/null; then info "Creating system user $SERVICE_USER..." useradd --system --home-dir "$DATA_DIR" --shell /usr/sbin/nologin \ - --comment "Outline Distro VPN" "$SERVICE_USER" + --comment "Shroud VPN" "$SERVICE_USER" ok "User $SERVICE_USER created" fi @@ -129,7 +129,7 @@ fi VERSION=$(git describe --tags --always 2>/dev/null || echo "dev") info "Building $VERSION..." CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$VERSION" \ - -o "$BINARY" ./cmd/outline-distro/ + -o "$BINARY" ./cmd/shroud/ ok "Binary: $BINARY ($VERSION)" # --- Directories --- @@ -217,14 +217,14 @@ fi info "Installing systemd unit..." # Use the bundled unit file if present, otherwise generate one. -BUNDLED_UNIT="$INSTALL_DIR/dist/outline-distro.service" +BUNDLED_UNIT="$INSTALL_DIR/dist/shroud.service" if [[ -f "$BUNDLED_UNIT" ]]; then cp "$BUNDLED_UNIT" "$UNIT_FILE" else cat > "$UNIT_FILE" </dev/null 2>&1 && ufw status | grep -q "active"; then info "Configuring ufw firewall rules..." - ufw allow "$API_PORT"/tcp comment "outline-distro API" >/dev/null 2>&1 || true + ufw allow "$API_PORT"/tcp comment "shroud API" >/dev/null 2>&1 || true if [[ "$AWG_ENABLED" == "true" ]]; then - ufw allow "$AWG_PORT"/udp comment "outline-distro AWG+HTTP3" >/dev/null 2>&1 || true - ufw allow 80/tcp comment "outline-distro ACME" >/dev/null 2>&1 || true + ufw allow "$AWG_PORT"/udp comment "shroud AWG+HTTP3" >/dev/null 2>&1 || true + ufw allow 80/tcp comment "shroud ACME" >/dev/null 2>&1 || true fi ok "Firewall rules added" else @@ -291,7 +291,7 @@ if [[ "$AWG_ENABLED" == "true" ]]; then info "Enabling IPv4 forwarding..." sysctl -q -w net.ipv4.ip_forward=1 # Persist via sysctl.d drop-in (idempotent). - cat > /etc/sysctl.d/99-outline-distro.conf <<'SYSCTL' + cat > /etc/sysctl.d/99-shroud.conf <<'SYSCTL' # Enable IPv4 forwarding for AmneziaWG VPN net.ipv4.ip_forward=1 SYSCTL @@ -330,7 +330,7 @@ API_SECRET_DISPLAY=$(grep -oP 'secret:\s*"\K[^"]+' "$CONFIG_FILE" 2>/dev/null || cat <