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:
@@ -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.
|
||||
Reference in New Issue
Block a user