CLI Walkthrough
Run through the raw CLI flow end to end, from bootstrap to pushing a rule and validating traffic.
This page is the raw end-to-end CLI flow. It mirrors quickstart.md from the
v0.1 spec and doubles as the script the portunus-e2e integration tests
exercise programmatically.
0. Prerequisites
You need two terminals (or two hosts):
- Host A:
portunus-serverinstalled and onPATH. - Host B:
portunus-clientinstalled and onPATH.
For a one-machine trial, install both binaries locally and treat the two terminals as Host A and Host B.
1. Bootstrap an API operator (Host A)
mkdir -p ./srv
portunus-server --data-dir ./srv bootstrap-superadmin --name ops
# → superadmin user_id=_superadmin token=<bearer>This is the legacy API-token bootstrap path for a raw CLI walkthrough. For
browser-first setup, start serve and complete Web UI onboarding with the
setup token printed by the running server.
The bearer is printed exactly once. Capture it now:
export PORTUNUS_OPERATOR_TOKEN=<paste-token-here>Every operator subcommand below reads PORTUNUS_OPERATOR_TOKEN from env.
2. Start the server (Host A)
portunus-server --data-dir ./srv serveOn first server launch the server creates ./srv/server.crt,
./srv/server.key, and any missing sidecar state. Logs are JSON lines:
{"event":"tls.cert_generated","fingerprint_sha256":"…"}
{"event":"server.listening","grpc":"0.0.0.0:7443","operator_http":"127.0.0.1:7080","metrics":"127.0.0.1:7081"}3. Enroll a forwarding client
portunus-server --data-dir ./srv enroll-client edge-01On Host B, install the client then redeem the printed command:
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | sh -s -- client
portunus-client enroll 'portunus://...' --out ./edge-01.bundle.jsonIt verifies the pinned certificate and writes the bundle with mode 0600.
4. Start the client (Host B)
portunus-client --bundle ./edge-01.bundle.jsonWithin a few seconds the server logs client.connected. Verify:
portunus-server --data-dir ./srv list-clients
# CLIENT STATE PROVISIONED_AT ADDRESS
# edge-01 connected 2026-05-06T12:01:30Z -5. Push a forwarding rule
# 18080 on edge-01 → 10.0.0.5:8080
portunus-server push-rule edge-01 18080 10.0.0.5:8080
# → 1push-rule prints only the new rule's bare rule_id on stdout (here 1).
The rule transitions Pending → Active within a 2-second ack budget
(controlled by --ack-timeout, default 2).
The client now binds 0.0.0.0:18080 and forwards every accepted TCP
connection to 10.0.0.5:8080.
6. Stream traffic through the rule
# Send 100 MB (assuming nc -lk 8080 > /tmp/out.bin on the target)
dd if=/dev/urandom of=/tmp/in.bin bs=1M count=100
nc <host-B> 18080 < /tmp/in.bin
sha256sum /tmp/in.bin /tmp/out.bin # hashes must match7. Observe
# Aggregate counters via the operator CLI
portunus-server rule-stats 1
# → rule_id=1 client=edge-01 protocol=tcp bytes_in=104857600 bytes_out=0 active=0 dns_failures=0 target_failovers_total=0 updated_at=2026-05-06T12:05:00Z
# Same numbers via Prometheus
curl -s http://127.0.0.1:7081/metrics | grep '^portunus_rule'8. Tear down
portunus-server remove-rule 1
# Rule drains, in-flight connections finish within shutdown_drain_timeout_secs.SIGTERM either binary; both drain in-flight connections (default 30 s)
then exit 0.
What's next?
| You want… | Read |
|---|---|
| Add UDP rules | UDP Forwarding |
| Forward many ports at once | Port-range Rules |
| Use DNS targets | DNS-name Targets |
| Scope a teammate to one client | Multi-user RBAC |
| Use the browser instead of the CLI | Web UI |
| Cap traffic per rule or per tenant | Rate Limiting & QoS |
| Run in production | Deployment |