https://github.com/iluxav/xgate
A tiny local reverse proxy for developers. Runs as a system service on port 80, manages /etc/hosts entries for you, and ships with a CLI that adds/removes routes live
https://github.com/iluxav/xgate
developer-tools localhost-management proxy
Last synced: 11 days ago
JSON representation
A tiny local reverse proxy for developers. Runs as a system service on port 80, manages /etc/hosts entries for you, and ships with a CLI that adds/removes routes live
- Host: GitHub
- URL: https://github.com/iluxav/xgate
- Owner: iluxav
- Created: 2026-04-09T22:38:37.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-10T02:38:20.000Z (3 months ago)
- Last Synced: 2026-04-10T04:26:03.702Z (3 months ago)
- Topics: developer-tools, localhost-management, proxy
- Language: Go
- Homepage:
- Size: 34.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# xgate
A tiny local reverse proxy for developers. Runs as a system service on port 80, manages `/etc/hosts` entries for you, and ships with a CLI that adds/removes routes live — no restart needed.
```
sudo xgate add api.localhost http://localhost:8081
sudo xgate add app.localhost http://localhost:5173
sudo xgate ls
```
That's it. `http://api.localhost` now proxies to `localhost:8081`.
## Features
- **Host-based routing** — map `app.localhost`, `api.localhost`, etc. to different upstream ports.
- **Wildcard hosts** — `*.app.localhost` matches any subdomain.
- **Live reload** — `xgate add`/`rm` mutate the running daemon; no restart, no dropped connections.
- **`/etc/hosts` management** — entries are added/removed automatically.
- **One-liner install** — on Linux (systemd) and macOS (launchd).
- **Config-file driven** — routes persist in `/etc/xgate/config.yaml`.
## Install
```bash
curl -fsSL https://raw.githubusercontent.com/iluxav/xgate/main/install.sh | sudo bash
```
The installer:
1. Detects your OS (Linux or macOS) and arch (amd64 or arm64).
2. Downloads the latest release binary from GitHub.
3. Verifies the SHA-256 checksum.
4. Installs the binary to `/usr/local/bin/xgate`.
5. Creates `/etc/xgate/config.yaml` with a default config (if missing).
6. Writes a systemd unit (Linux) or launchd plist (macOS).
7. Enables and starts the service.
After install, `xgate` is running on port 80 with an empty route table.
### Pin a specific version
```bash
curl -fsSL https://raw.githubusercontent.com/iluxav/xgate/main/install.sh | sudo XGATE_VERSION=v0.1.0 bash
```
### Uninstall
```bash
curl -fsSL https://raw.githubusercontent.com/iluxav/xgate/main/uninstall.sh | sudo bash
```
This stops and removes the service and the binary. Your `/etc/xgate/` directory is left in place so your routes aren't silently destroyed — remove it manually with `sudo rm -rf /etc/xgate` if you're done for good.
## Build from source
Requires Go 1.25+.
```bash
git clone https://github.com/iluxav/xgate.git
cd xgate
make build
sudo ./xgate serve
```
## Usage
All mutating CLI commands require `sudo` (the admin socket is root-owned, mode `0600`).
### Add a route
```bash
sudo xgate add gateway.localhost http://localhost:8081
```
Output:
```
added gateway.localhost -> http://localhost:8081 (1 routes)
```
### Wildcard subdomains
```bash
sudo xgate add '*.app.localhost' http://localhost:5173
```
Now `http://anything.app.localhost` proxies to `localhost:5173`. Exact matches always beat wildcard matches, so you can mix them freely.
On macOS, `*.localhost` resolves to `127.0.0.1` automatically. On Linux, you need either individual `/etc/hosts` entries (xgate manages these for exact hosts) or a local resolver like `dnsmasq` configured for `*.localhost`.
### List routes
```bash
sudo xgate ls
```
Output:
```
HOST TARGET
gateway.localhost http://localhost:8081
*.app.localhost http://localhost:5173
```
If the daemon isn't running, `ls` falls back to reading `/etc/xgate/config.yaml` directly — this works without `sudo` because the config file is `0644`.
### Remove a route
```bash
sudo xgate rm gateway.localhost
```
### Reload config from disk
If you hand-edit `/etc/xgate/config.yaml`, tell the daemon to re-read it:
```bash
sudo xgate reload
```
## Configuration
The config file lives at `/etc/xgate/config.yaml`:
```yaml
listen: ":80"
manage_hosts: true
routes:
- host: gateway.localhost
target: http://localhost:8081
- host: "*.api.localhost"
target: http://localhost:8081
- host: app.localhost
target: http://localhost:5173
```
- **`listen`** — address to bind the HTTP server. Default `:80`. Change to `:8080` (or similar) if you don't want to run as root. Changing this requires a service restart: `sudo systemctl restart xgate` / `sudo launchctl unload … && sudo launchctl load …`.
- **`manage_hosts`** — whether xgate should add/remove entries in `/etc/hosts` automatically. Wildcard routes are skipped. Default `true`.
- **`routes`** — list of `host → target` mappings. Prefer managing these via `xgate add`/`rm` rather than editing by hand, because writes from the CLI drop YAML comments on rewrite.
## How it works
xgate is a single Go binary that plays two roles:
- **`xgate serve`** — the daemon. Runs the HTTP reverse proxy on `:80` and an admin Unix socket at `/var/run/xgate.sock` (mode `0600`). This is what the service unit launches.
- **`xgate add|rm|ls|reload`** — the CLI. Dials the admin socket, sends one JSON command, prints the response.
Live reloads work via an `atomic.Pointer[Router]` inside the daemon: when a route is added or removed, the daemon builds a fresh routing table in memory and atomically swaps the pointer. In-flight requests finish on the old table, new requests see the new one — no locks on the hot path.
## Flag and environment overrides
Useful for running a non-root dev daemon against a local config without clobbering your installed instance:
```bash
xgate --config /tmp/dev.yaml --socket /tmp/xgate.sock serve
xgate --config /tmp/dev.yaml --socket /tmp/xgate.sock add test.local http://localhost:3000
```
| Flag | Env var | Default |
|--------------|------------------|---------------------------|
| `--config` | `XGATE_CONFIG` | `/etc/xgate/config.yaml` |
| `--socket` | `XGATE_SOCKET` | `/var/run/xgate.sock` |
Priority: flag > env var > default.
## Logs
**Linux:**
```bash
journalctl -u xgate -f
```
**macOS:**
```bash
tail -f /var/log/xgate.log
```
## Troubleshooting
**"xgate daemon not running"** — the CLI couldn't reach `/var/run/xgate.sock`. Start the service:
- Linux: `sudo systemctl start xgate`
- macOS: `sudo launchctl load /Library/LaunchDaemons/com.xgate.daemon.plist`
**"bind: permission denied" in logs** — the daemon can't bind to port 80. On Linux the systemd unit grants `CAP_NET_BIND_SERVICE`; if you're running outside the unit, use `sudo` or change `listen` in the config to an unprivileged port.
**"no route for host: …"** — you requested a hostname that doesn't match any route. Check `sudo xgate ls`. Remember that exact matches beat wildcards, and wildcards need a `*.` prefix.
**Routes don't resolve** — the browser might be resolving the hostname directly instead of going through xgate. Check `/etc/hosts` — for exact-match hosts the installer keeps a marker block there. For wildcards on Linux you need a DNS-level solution (dnsmasq).
## License
MIT.