Initial vibeproxy implementation

Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go,
with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS
framework), and a REST API under /api/v1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
This commit is contained in:
2026-08-17 10:28:59 -05:00
co-authored by Claude Sonnet 5
commit ba1f7c4c03
105 changed files with 9142 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
# 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.