# portunus-server (https://portunus.bybee.dev/en/docs/cli/portunus-server)



`portunus-server` is the control-plane binary. Most subcommands hit the
loopback HTTP API, so they require `PORTUNUS_OPERATOR_TOKEN` to be set.

```sh
export PORTUNUS_OPERATOR_TOKEN=<paste-token-here>
```

Since v1.1.0, Web UI login uses local passwords and server-side session cookies.
`PORTUNUS_OPERATOR_TOKEN` is still for API/CLI automation only.

Run `portunus-server --help` (or `portunus-server <subcommand> --help`)
for the current authoritative flag surface — the tables below are a
high-level map.

## Server lifecycle [#server-lifecycle]

| Subcommand                           | Purpose                                                                   |
| ------------------------------------ | ------------------------------------------------------------------------- |
| `serve`                              | Start the control plane (gRPC + operator HTTP + metrics)                  |
| `gen-token`                          | Print a fresh URL-safe-base64 token to stdout                             |
| `bootstrap-superadmin --name <name>` | Legacy one-shot superadmin API token mint; bearer printed once            |
| `onboarding-token`                   | Offline rotation of the first-run setup token for an unbootstrapped store |

```sh
portunus-server --data-dir ./srv serve
```

If you need non-default listeners or other overrides, place an optional
`./srv/server.toml`.

For normal first-run setup, start `serve` and use the setup token printed by the
running server to complete Web UI onboarding. `onboarding-token` opens the store
directly and `serve` rotates the token again on start, so it is mainly useful
for offline maintenance and tests.

## Clients [#clients]

| Subcommand                                                 | Purpose                                                                                                                             |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `enroll-client <name> [--address <host>] [--ttl-secs <N>]` | Print a short-lived `portunus-client enroll ...` command. `--ttl-secs` defaults to 600; `--address` overrides the host clients dial |
| `revoke <client_name>`                                     | Revoke a client's bearer; immediate disconnect                                                                                      |
| `list-clients [--format text\|json]`                       | Show connected status for every provisioned client                                                                                  |

## Rules [#rules]

| Subcommand                                              | Purpose                                |
| ------------------------------------------------------- | -------------------------------------- |
| `push-rule <client> <listen_port> <target>`             | Push a single-target TCP rule          |
| `push-rule … --protocol udp`                            | UDP variant                            |
| `push-rule … --target host:port@<priority>`             | Multi-target failover (repeat)         |
| `push-rule … --sni <pattern>`                           | TLS SNI routing                        |
| `push-rule … --bandwidth-in-bps <N>` etc.               | Rate-limit caps (v0.11+)               |
| `push-rule <client> <start>-<end> <host>:<start>-<end>` | Port-range rule                        |
| `push-rule … --prefer-ipv6`                             | DNS targets, AAAA-first                |
| `remove-rule <rule_id>`                                 | Drain + remove a rule                  |
| `list-rules` / `list-rules --client <name>`             | List rules (filter by client)          |
| `rule-stats <id>`                                       | Aggregate per-rule counters            |
| `rule-stats <id> --per-port`                            | Per-port detail (range rules)          |
| `rule-stats <id> --per-target`                          | Per-target detail (multi-target rules) |

## Users (v0.5+) [#users-v05]

| Subcommand                                                                      | Purpose                                      |
| ------------------------------------------------------------------------------- | -------------------------------------------- |
| `user-add <id> --display-name <name> [--role admin\|user]`                      | Add a user (`--role` defaults to `user`)     |
| `user-list`                                                                     | List users                                   |
| `user-get <id>`                                                                 | Show user details                            |
| `user-remove <id>`                                                              | Remove user (cascades grants + rules)        |
| `reset-password <user_id> [--temporary] [--password-stdin] [--keep-api-tokens]` | Local password recovery against the data dir |

`reset-password` opens the SQLite store directly. Stop `serve` first if it is
running against the same `--data-dir`.

```sh
portunus-server --data-dir /var/lib/portunus \
  reset-password admin --temporary
```

Use the actual superadmin user ID. For `bootstrap-superadmin` installs that ID
is `_superadmin`; for Web onboarding it is the ID chosen during setup.

Without `--temporary`, the command prompts twice without echo. `--password-stdin`
reads the new password from the first stdin line for automation. By default,
password reset revokes Web sessions and active API tokens for that user; use
`--keep-api-tokens` only when you are sure the reset is not incident response.

## Credentials [#credentials]

| Subcommand                                    | Purpose                     |
| --------------------------------------------- | --------------------------- |
| `credential-issue <user_id> --label <label>`  | Issue a bearer token        |
| `credential-list <user_id>`                   | List credentials for a user |
| `credential-revoke <user_id> <credential_id>` | Revoke a credential         |
| `credential-rotate <user_id> <credential_id>` | Rotate; old token 401s      |

## Grants [#grants]

| Subcommand                                                                                                     | Purpose                                   |
| -------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `grant-add --user-id <id> --client <name> --listen-port-start <N> --listen-port-end <N> --protocols <tcp,udp>` | Add a grant                               |
| `grant-list`                                                                                                   | List grants                               |
| `grant-revoke <grant_id>`                                                                                      | Revoke a grant (cascades dependent rules) |

## Rate limiting (v0.11+) [#rate-limiting-v011]

| Subcommand                                              | Purpose                              |
| ------------------------------------------------------- | ------------------------------------ |
| `owner-cap set <client> <owner> [--bandwidth-in-bps …]` | Set per-owner envelope               |
| `owner-cap get <client> <owner>`                        | Read per-owner envelope              |
| `owner-cap list <client>`                               | List all owner envelopes on a client |
| `owner-cap delete <client> <owner>`                     | Remove an envelope                   |

Pre-flight rejects an empty `set` envelope with exit 3 +
`validation.rate_limit_no_caps_provided`.

## Storage (v0.8+) [#storage-v08]

| Subcommand                                   | Purpose                                  |
| -------------------------------------------- | ---------------------------------------- |
| `backup --out <path>`                        | Online SQLite backup (WAL-aware)         |
| `restore --in <path> [--force]`              | Restore a backup; runs schema migrations |
| `reset --confirm`                            | Wipe `state.db` + sidecar files          |
| `audit prune --before <RFC3339> [--dry-run]` | Prune the durable audit table            |

## Exit codes [#exit-codes]

| Code | Meaning                                                               |
| ---- | --------------------------------------------------------------------- |
| 0    | Success                                                               |
| 1    | Generic error (config, IO, …)                                         |
| 2    | Validation error                                                      |
| 3    | Pre-flight validation (e.g. `validation.rate_limit_no_caps_provided`) |
| 4    | `PORTUNUS_OPERATOR_TOKEN` missing                                     |
| 6    | Activation failed (e.g. `port_in_use`)                                |
| 8    | User not found for local password recovery                            |
| 75   | Store in use by another process                                       |
| 78   | Schema-version too new (downgrade attempt)                            |

(See per-subcommand `--help` for the complete code table.)

## Common environment variables [#common-environment-variables]

| Variable                  | Purpose                                  |
| ------------------------- | ---------------------------------------- |
| `PORTUNUS_OPERATOR_TOKEN` | Bearer for every operator subcommand     |
| `RUST_LOG`                | Standard `tracing-subscriber` env-filter |
