# vibeproxy A small, self-contained reverse proxy manager, written in Go. Think "Nginx Proxy Manager, but simpler and with no external dependencies" — `vibeproxy` handles HTTP/HTTPS reverse proxying, TCP/UDP streams, TLS termination, and Let's Encrypt certificates itself, using Go's standard networking stack (`net/http`, `httputil.ReverseProxy`, `crypto/tls`, `net`). It never shells out to nginx, HAProxy, or Caddy. The admin UI is server-rendered Go templates + Bootstrap 5 + a little vanilla JavaScript — no React/Vue/Node build step. Everything (templates, CSS, JS, SQL migrations) is embedded into a single binary via `go:embed`, so deployment is just: ``` ./vibeproxy serve ``` ## Features - HTTP and HTTPS reverse proxying, routed by Host header / SNI - Redirect hosts (301/302/307/308, with path & query preservation) - TCP and UDP stream proxying, with optional PROXY protocol v1 to backends - Multiple backends per proxy host: round robin, least-connections, or weighted load balancing, with TCP/HTTP health checks and configurable failure/recovery thresholds — unhealthy backends never receive traffic - TLS termination, HTTP/2, WebSockets, keep-alive, streaming responses - Let's Encrypt (HTTP-01 and DNS-01, via the [lego](https://github.com/go-acme/lego) ACME client library) plus custom/uploaded certificates, with automatic renewal - Reusable Access Lists (CIDR allow/deny + optional HTTP Basic Auth) - Per-host custom request/response headers - Configurable trusted proxy networks — `X-Forwarded-For` is only trusted from networks you explicitly configure - SQLite storage (WAL mode, foreign keys, migrations) with the active configuration cached in memory — the hot request path never touches the database, and configuration changes apply live without a restart - REST API under `/api/v1`, authenticated with revocable API tokens - Single administrator account (HTTP Basic Auth, Argon2id password hashing, CSRF-protected forms) - Request logs (with configurable retention) and an audit log of every configuration change - Prometheus metrics, `/healthz`, `/readyz`, graceful shutdown, automatic and on-demand SQLite backups ## Quick start ``` go build -o vibeproxy ./cmd/vibeproxy sudo setcap 'cap_net_bind_service=+ep' ./vibeproxy # to bind :80/:443 without root ./vibeproxy serve ``` On first run, `vibeproxy` creates the `admin` account and prints a random temporary password to stdout — save it, it's not shown again. Change it under **Settings → Administrator Account** after logging in. By default: - `:80` and `:443` — proxy traffic (HTTP/HTTPS reverse proxy + redirect hosts + Let's Encrypt HTTP-01 challenges) - `:8081` — admin web UI, REST API, `/healthz`, `/readyz`, `/metrics`. **Put this behind a firewall or VPN** — Basic Auth protects it, but it shouldn't be exposed to the internet. All of this is configurable via flags or environment variables: | Flag | Env var | Default | Purpose | |---|---|---|---| | `--data-dir` | `VIBEPROXY_DATA_DIR` | `./data` | database, certs, backups | | `--db-path` | `VIBEPROXY_DB_PATH` | `./data/vibeproxy.db` | SQLite file | | `--http-addr` | `VIBEPROXY_HTTP_ADDR` | `:80` | HTTP proxy listener | | `--https-addr` | `VIBEPROXY_HTTPS_ADDR` | `:443` | HTTPS proxy listener | | `--admin-addr` | `VIBEPROXY_ADMIN_ADDR` | `:8081` | admin UI + API | Everything else (ACME email, renewal window, log retention, trusted proxy CIDRs, backup schedule, ...) is runtime configuration stored in SQLite and editable from **Settings**, so it takes effect without a restart. ## CLI ``` vibeproxy serve # run the proxy, admin UI, and API vibeproxy version # print the version vibeproxy config check # validate config + run migrations, then exit vibeproxy admin reset-password # generate a new admin password vibeproxy database backup # write an on-demand SQLite backup vibeproxy database migrate # apply pending migrations vibeproxy certificates renew # renew any due Let's Encrypt certs ``` ## systemd ``` sudo useradd --system --home /var/lib/vibeproxy --shell /usr/sbin/nologin vibeproxy sudo cp vibeproxy /usr/local/bin/vibeproxy sudo cp systemd/vibeproxy.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now vibeproxy sudo journalctl -u vibeproxy -f # first run prints the admin password here ``` The unit grants `CAP_NET_BIND_SERVICE` so `vibeproxy` can bind `:80`/`:443` without running as root, and sandboxes the process with `ProtectSystem`, `ProtectHome`, `PrivateDevices`, etc. It stores everything under `/var/lib/vibeproxy` (mode `0700`). ## Docker ``` docker compose up -d ``` See `docker-compose.yml` / `Dockerfile`. The container runs as a non-root user, so it listens on `8080`/`8443` internally — `docker-compose.yml` maps host `80`/`443` to those. ## REST API All endpoints are under `/api/v1` on the admin listener, authenticated with a bearer token (create one from **API** in the UI — it's shown once): ``` curl -H "Authorization: Bearer vp_xxxxxxxx" \ http://localhost:8081/api/v1/proxy-hosts ``` Resources: `proxy-hosts`, `streams`, `certificates`, `access-lists`, `settings`, `logs/requests`, `logs/audit`, `stats`, `tokens`. See `internal/api` for the full route list. ## Project layout ``` cmd/vibeproxy/ CLI entry point (cobra commands) internal/ api/ REST API (/api/v1) app/ wires every subsystem together for `serve` auth/ password hashing, Basic Auth, API tokens, CSRF access/ CIDR access lists + Basic Auth evaluation backup/ SQLite backup/restore certificates/ ACME (via lego) + custom cert storage & renewal config/ process bootstrap flags/env database/ SQLite connection, migrations, repositories health/ backend health checking loadbalancer/ backend pools: round robin / least-conn / weighted logging/ buffered request logging + audit log + retention metrics/ Prometheus collectors models/ domain types shared across packages proxy/ the HTTP/HTTPS reverse proxy engine runtime/ in-memory active-configuration snapshot stats/ dashboard/API statistics streams/ TCP/UDP proxying web/ server-rendered admin UI (Go templates + embedded assets) templates/, static/ systemd/ vibeproxy.service ``` ## Design notes - **Hot path stays out of SQLite.** `internal/runtime` holds an immutable, atomically-swapped snapshot of proxy hosts, backends, access lists, and settings. Every proxied request/connection reads that snapshot; database writes only happen when an admin changes configuration, at which point `Store.Reload` rebuilds the snapshot. - **`X-Forwarded-For` is not trusted by default.** The real client IP is taken from the TCP peer address unless the peer is inside a configured "trusted proxy" CIDR (Settings → Trusted Proxy Networks), in which case the right-most untrusted entry in `X-Forwarded-For` is used instead. - **Certificates never touch logs.** Private keys are written to disk with `0600` permissions (directories `0700`) and are never logged or returned by the API. - **Reloads don't drop connections.** Stream (TCP/UDP) reconciliation diffs the desired vs. running listener set and only restarts streams whose configuration actually changed; unaffected streams, and all in-flight HTTP requests on the previous configuration snapshot, are left alone. ## Testing ``` go test ./... ``` Covers load-balancing strategies and health-check thresholds, access-list CIDR evaluation, password/API-token hashing, SQLite migrations and proxy-host CRUD round-trips, an end-to-end TCP stream proxy test, and a render-smoke-test for every admin UI page.