Ship binaries, systemd units, and install docs
ci/woodpecker/push/woodpecker Pipeline was successful Details

- Allow committing compiled server/client binaries; add systemd units
  (tunnel-client.service, tunnel-server.service) and env examples.
- README: install from git.nixc.us HTTPS raw (curl binary + unit from
  raw/branch/main); document multiple tunnels (one unit/env per tunnel).
- Keys: do not recycle host or shared keys; use a dedicated ed25519 per
  tunnel/host; central server must have that public key in authorized_keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leopere 2026-02-15 19:41:07 -05:00
parent 58092e24c8
commit dc66cbb160
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
8 changed files with 123 additions and 7 deletions

6
.gitignore vendored
View File

@ -1,9 +1,3 @@
# Binaries (root level only)
/tunnel-server
/tunnel-client
/server
/client
# Keys (never commit secrets)
keys/

View File

@ -151,7 +151,7 @@ http:
## Building
```bash
# Build both binaries locally
# Build both binaries locally (commit and push these for remote deployment)
go build -o tunnel-server ./cmd/server/
go build -o tunnel-client ./cmd/client/
@ -160,6 +160,74 @@ docker compose build # server image
docker build --target client -t tunnel-client . # client image
```
## Binaries and systemd (bare metal)
The repo ships compiled `tunnel-server` and `tunnel-client` for remote hosts that run without Docker. Use the included systemd units under `systemd/`.
**Keys:** Do not reuse the hosts SSH keys or share one key between hosts or tunnels. Generate a dedicated ed25519 key per tunnel (or per host). The **central tunnel server** must have that keys **public** half in its `authorized_keys` so the client can connect.
**Multiple tunnels:** Run one systemd instance per tunnel (different env file and optional unit name), e.g. `tunnel-client@app1.service` and `tunnel-client@app2.service` each with their own env and key.
### Install from git.nixc.us (HTTPS raw)
From a host with curl, install the binary and systemd unit directly from the repo (replace `main` with your branch if needed):
```bash
REPO=https://git.nixc.us/colin/better-argo-tunnels/raw/branch/main
sudo curl -o /usr/local/bin/tunnel-client -L "$REPO/client"
sudo chmod +x /usr/local/bin/tunnel-client
sudo curl -o /etc/systemd/system/tunnel-client.service -L "$REPO/systemd/tunnel-client.service"
```
Then add a dedicated key for this tunnel, env file, and authorize on the server:
```bash
sudo mkdir -p /etc/tunnel-client
sudo ssh-keygen -t ed25519 -f /etc/tunnel-client/id_ed25519 -N ""
# Add the public key to the central server's authorized_keys:
sudo cat /etc/tunnel-client/id_ed25519.pub
# (On the central server: append that line to authorized_keys)
sudo cp systemd/tunnel-client.env.example /etc/tunnel-client.env # or curl from $REPO/systemd/tunnel-client.env.example
sudo edit /etc/tunnel-client.env # set TUNNEL_SERVER, TUNNEL_DOMAIN, TUNNEL_KEY=/etc/tunnel-client/id_ed25519
sudo systemctl daemon-reload && sudo systemctl enable --now tunnel-client
```
### Client on a remote host (clone or copy)
1. Install the binary (from repo clone or raw URL above; repo has `client`/`server`):
```bash
sudo cp client /usr/local/bin/tunnel-client && sudo chmod +x /usr/local/bin/tunnel-client
```
2. Copy the systemd unit and create env file:
```bash
sudo cp systemd/tunnel-client.service /etc/systemd/system/
sudo cp systemd/tunnel-client.env.example /etc/tunnel-client.env
sudo edit /etc/tunnel-client.env # set TUNNEL_SERVER, TUNNEL_DOMAIN, TUNNEL_KEY
```
3. Use a **dedicated** ed25519 key for this tunnel (not the hosts keys). Put the private key on the host (e.g. `/etc/tunnel-client/id_ed25519`) and set `TUNNEL_KEY` in env. Ensure the **central server** has the matching **public** key in its `authorized_keys`.
4. Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now tunnel-client
sudo journalctl -u tunnel-client -f
```
For a second tunnel on the same host, use a separate env and key (e.g. `/etc/tunnel-client-app2.env`, `/etc/tunnel-client/app2_id_ed25519`) and a second unit (e.g. copy to `tunnel-client-app2.service` with `EnvironmentFile=/etc/tunnel-client-app2.env`).
### Server (optional, bare metal)
If you run the tunnel server without Docker:
1. Install binary and keys under e.g. `/etc/tunnel-server/` (host_key, authorized_keys, traefik_deploy_key).
2. Copy `systemd/tunnel-server.service` to `/etc/systemd/system/` and `systemd/tunnel-server.env.example` to `/etc/tunnel-server.env`. Set `TRAEFIK_SSH_HOST`, `TRAEFIK_SSH_KEY`, and paths to keys.
3. `systemctl enable --now tunnel-server`.
## Security Notes
- Only clients whose public keys are in `authorized_keys` can connect

BIN
client Executable file

Binary file not shown.

BIN
server Executable file

Binary file not shown.

View File

@ -0,0 +1,10 @@
# Copy to /etc/tunnel-client.env and set values.
# Required:
TUNNEL_SERVER=ingress.example.com:2222
TUNNEL_DOMAIN=myapp.example.com
TUNNEL_KEY=/etc/tunnel-client/id_ed25519
# Optional (defaults shown):
# TUNNEL_PORT=8080
# TUNNEL_AUTH_USER=
# TUNNEL_AUTH_PASS=

View File

@ -0,0 +1,15 @@
[Unit]
Description=Reverse SSH tunnel client for Traefik
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
# Load env from same directory as this unit, or override with drop-in
EnvironmentFile=-/etc/tunnel-client.env
ExecStart=/usr/local/bin/tunnel-client
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,15 @@
# Copy to /etc/tunnel-server.env and set values.
# Required:
TRAEFIK_SSH_HOST=ingress.example.com
TRAEFIK_SSH_KEY=/etc/tunnel-server/traefik_deploy_key
# Optional (defaults shown):
# SSH_PORT=2222
# SSH_HOST_KEY=/etc/tunnel-server/host_key
# AUTHORIZED_KEYS=/etc/tunnel-server/authorized_keys
# PORT_RANGE_START=10000
# PORT_RANGE_END=10100
# TRAEFIK_SSH_USER=root
# SWARM_SERVICE_NAME=better-argo-tunnels_tunnel-server
# TRAEFIK_ENTRYPOINT=websecure
# TRAEFIK_CERT_RESOLVER=letsencryptresolver

View File

@ -0,0 +1,14 @@
[Unit]
Description=Reverse SSH tunnel server for Traefik
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=-/etc/tunnel-server.env
ExecStart=/usr/local/bin/tunnel-server
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target