portunus-client
The edge binary that picks up rule pushes over TLS and binds listeners on the local host.
portunus-client is the edge-side binary. It dials a server endpoint
specified in its bundle, validates the server certificate by SHA-256
fingerprint, and accepts rule pushes over a bidirectional gRPC stream.
Synopsis
# Daemon mode (no subcommand): connect, accept rule pushes, forward.
portunus-client [--bundle <PATH>] [--reconnect-initial-delay-ms <N>]
[--reconnect-max-delay-secs <N>] [--shutdown-drain-timeout-secs <N>]
[--stats-report-interval-secs <N>]
# Enroll: redeem a one-time URI and write a bundle, then exit.
portunus-client enroll <URI> [--out <PATH>]Enroll
portunus-client enroll 'portunus://...' --out ./edge-01.bundle.jsonRedeems a one-time enrollment URI (from portunus-server enroll-client),
verifies the pinned server certificate, and writes the credential bundle
with mode 0600. --out defaults to the normal client bundle location.
On success the bundle path is printed to stdout; on failure the client
exits 1.
Bundle resolution
When --bundle is omitted (since v0.8), the client searches:
$PORTUNUS_CLIENT_BUNDLE$XDG_CONFIG_HOME/portunus/client.bundle.json$HOME/.config/portunus/client.bundle.json./client.bundle.json
If none resolve, the client exits 1 listing every attempted path.
Flags
Daemon-mode flags (no subcommand):
| Flag | Default | Purpose |
|---|---|---|
--bundle <PATH> | (resolution chain) | Explicit bundle path |
--reconnect-initial-delay-ms <N> | 500 | Initial reconnect delay; base of the full-jitter exponential backoff |
--reconnect-max-delay-secs <N> | 30 | Reconnect backoff cap |
--shutdown-drain-timeout-secs <N> | 30 | Drain budget on SIGTERM/SIGINT |
--stats-report-interval-secs <N> | 5 | Stats reporting interval |
Run portunus-client --help for the current authoritative surface.
Environment variables
| Variable | Purpose |
|---|---|
PORTUNUS_CLIENT_BUNDLE | Bundle path (highest precedence in resolution chain) |
RUST_LOG | tracing-subscriber env-filter |
Lifecycle
- Read bundle.
- Resolve
server_endpoint. - TLS dial; pin SHA-256 against
server_cert_sha256. - Send Hello with bearer; receive Welcome with server-side tunables.
- Open bidirectional
RuleUpdate↔RuleStatusstream. - For each
RuleUpdate:- Allocate listener (TCP
bindor UDPbind). - Activate the forwarder loop.
- Reply
Active(orFailed(<reason>)).
- Allocate listener (TCP
- Push
StatsReportevery--stats-report-interval-secs(default 5 s).
Drain on shutdown
SIGTERM / SIGINT triggers drain:
- Listeners stop accepting immediately.
- In-flight forwarded connections finish up to
--shutdown-drain-timeout-secs(default 30 s). - The kernel reaps remaining sockets.
Match TimeoutStopSec= in the systemd unit to avoid SIGKILL mid-drain.
Logging
JSON lines by default:
{"event":"process.start"}
{"event":"control.connecting","endpoint":"…:7443"}
{"event":"control.tls_pinned","fingerprint_sha256":"f5e7c2a1..."}
{"event":"control.connected"}
{"event":"rule.received","rule_id":1,"listen_port":18080}
{"event":"rule.activated","rule_id":1,"listen_port":18080}A pin mismatch:
{"event":"control.tls_pinned_mismatch","expected":"…","got":"…"}…and the client exits non-zero.