Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/command-inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,74 @@
"--to-file"
]
},
{
"name": "server",
"path": "nself server",
"short": "Provision, list, resize, and destroy Hetzner Cloud servers",
"hidden": false,
"group_id": "advanced",
"subcommands": [
{
"name": "destroy",
"path": "nself server destroy",
"short": "Delete a Hetzner Cloud server",
"hidden": false,
"flags": [
"--force-no-backup",
"--id",
"--json",
"--release-ip",
"--snapshot",
"--snapshot-timeout",
"--token",
"--token-env"
]
},
{
"name": "list",
"path": "nself server list",
"short": "List Hetzner Cloud servers",
"hidden": false,
"flags": [
"--json",
"--label-selector",
"--token",
"--token-env"
]
},
{
"name": "provision",
"path": "nself server provision",
"short": "Create a new Hetzner Cloud server",
"hidden": false,
"flags": [
"--image",
"--json",
"--label",
"--location",
"--name",
"--ssh-key",
"--token",
"--token-env",
"--type"
]
},
{
"name": "resize",
"path": "nself server resize",
"short": "Change a server's type (CPU/RAM/disk)",
"hidden": false,
"flags": [
"--id",
"--json",
"--token",
"--token-env",
"--type",
"--upgrade-disk"
]
}
]
},
{
"name": "service",
"path": "nself service",
Expand Down
11 changes: 10 additions & 1 deletion .github/command-surface-budget.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@
# teammate's SSH key on an already-deployed host had no CLI path at all.
# `nself access grant/revoke/list` is the command surface named directly
# in #238's proposed shape.
50
#
# 2026-09-11: +1 for `server` (gap G-011). Deliberate exception, not
# creep: `nself access` only manages SSH keys on an already-deployed host
# and `nself security` only audits one — neither can create, list, resize,
# or destroy the server itself, so an operator provisioning or tearing down
# a box had no CLI path and fell back to raw `hcloud server create` /
# `hcloud server delete`, with none of the backup/IP-protection safety this
# command enforces. `nself server provision/list/resize/destroy` is the
# surface G-011 names directly.
51
9 changes: 9 additions & 0 deletions .github/surface-parity.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,15 @@
"env_vars": "n/a",
"openapi": "n/a (see below)"
},
{
"name": "server",
"path": "nself server",
"group_id": "advanced",
"wiki_page": true,
"mcp_tool": false,
"env_vars": "n/a",
"openapi": "n/a (see below)"
},
{
"name": "service",
"path": "nself service",
Expand Down
3 changes: 2 additions & 1 deletion .github/surface-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ One row per top-level command (CLI-R17), scored against the four surfaces a comm
| `nself secrets` | config | yes | no | undocumented: EDITOR | n/a (see below) |
| `nself security` | advanced | yes | no | n/a | n/a (see below) |
| `nself self-heal` | observe | yes | no | n/a | n/a (see below) |
| `nself server` | advanced | yes | no | n/a | n/a (see below) |
| `nself service` | config | yes | yes | n/a | n/a (see below) |
| `nself start` | core | yes | yes | undocumented: NSELF_PROFILE, NSELF_SKIP_DB_INIT | n/a (see below) |
| `nself status` | core | yes | yes | n/a | n/a (see below) |
Expand All @@ -63,4 +64,4 @@ One row per top-level command (CLI-R17), scored against the four surfaces a comm
| `nself verify-sbom` | advanced | yes | no | n/a | n/a (see below) |
| `nself version` | account | yes | no | undocumented: BENCH_RESULTS_FILE | n/a (see below) |

Total: 50 commands. Missing wiki page: 0. No MCP tool: 33. Env vars found but undocumented: 16.
Total: 51 commands. Missing wiki page: 0. No MCP tool: 34. Env vars found but undocumented: 16.
3 changes: 2 additions & 1 deletion .github/wiki/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ tree in `cmd/commands/`. Run `make cmd-inventory` to refresh.
## Complete index

Generated from the cobra registration tree in `cmd/commands/`.
Run `make cmd-inventory` to refresh. **Total top-level commands: 50**
Run `make cmd-inventory` to refresh. **Total top-level commands: 51**

| Command | Short Description | Group | Subcommands |
|---|---|---|---|
Expand Down Expand Up @@ -137,6 +137,7 @@ Run `make cmd-inventory` to refresh. **Total top-level commands: 50**
| `nself secrets` | Manage encrypted project secrets (age encryption) | config | audit, decrypt-on-deploy, edit, get, init, lint, list, list-schedules, rekey, retire, rotate, rotation-log, schedule, set, verify |
| `nself security` | Server security: audit, setup, and status | advanced | audit, setup, status |
| `nself self-heal` | Run targeted self-healing routines for nSelf components | observe | — |
| `nself server` | Provision, list, resize, and destroy Hetzner Cloud servers | advanced | destroy, list, provision, resize |
| `nself service` | Manage optional services | config | add, configure, disable, enable, list, ps, restart, scale, start, stop, update, upgrade |
| `nself start` | Boot your nSelf stack | core | — |
| `nself status` | Show health status of all services | core | — |
Expand Down
4 changes: 2 additions & 2 deletions .github/wiki/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@

<!-- BEGIN GENERATED:command-list -->

**All commands (50)**
**All commands (51)**

- _A:_ [[cmd-access]] · [[cmd-account]] · [[cmd-admin]]
- _B:_ [[cmd-backup]] · [[cmd-build]] · [[cmd-bundle]]
Expand All @@ -295,7 +295,7 @@
- _O:_ [[cmd-oauth]] · [[cmd-ops]]
- _P:_ [[cmd-plugin]] · [[cmd-promote]]
- _R:_ [[cmd-remove]] · [[cmd-reset]] · [[cmd-restart]]
- _S:_ [[cmd-secrets]] · [[cmd-security]] · [[cmd-self-heal]] · [[cmd-service]] · [[cmd-start]] · [[cmd-status]] · [[cmd-stop]]
- _S:_ [[cmd-secrets]] · [[cmd-security]] · [[cmd-self-heal]] · [[cmd-server]] · [[cmd-service]] · [[cmd-start]] · [[cmd-status]] · [[cmd-stop]]
- _T:_ [[cmd-telemetry]] · [[cmd-template]] · [[cmd-trust]]
- _U:_ [[cmd-update]] · [[cmd-urls]]
- _V:_ [[cmd-verify-sbom]] · [[cmd-version]]
Expand Down
149 changes: 149 additions & 0 deletions .github/wiki/cmd-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# nself server

<!-- BEGIN PROSE:summary -->
> Provision, list, resize, and destroy Hetzner Cloud servers.
<!-- END PROSE:summary -->

## Synopsis

```
nself server <subcommand> [flags]
```

## Description

<!-- BEGIN PROSE:description -->
Manage the lifecycle of a Hetzner Cloud server: create one, list what exists,
resize one, or destroy one, all without a raw `hcloud` invocation.

`nself access` manages SSH keys on an already-deployed server, and `nself
security` audits one — neither can create, resize, or destroy the server
itself. `nself server` fills that gap, and encodes the safety checks a manual
`hcloud server create` / `hcloud server delete` does not: `destroy` refuses
to run without a verified backup, protects the server's primary IP(s) from
being deleted along with it, and `resize` explains (rather than raw-errors
on) Hetzner's disk-shrink limitation.

### nself server provision
Create a new server. Every server this command creates is labeled
`managed-by=nself-cli` (unless you pass your own `--label managed-by=...`,
which is respected as-is), so `nself server list` and any future cleanup pass
can tell nself-created servers apart from anything else in the same Hetzner
project.

```bash
nself server provision --name ci-runner-3 --type cx22 --location fsn1 --image ubuntu-24.04
```

Flags: `--name` (required), `--type` (required, e.g. `cx22`), `--location`
(required, e.g. `fsn1`), `--image` (required, e.g. `ubuntu-24.04`),
`--ssh-key` (repeatable, Hetzner SSH key name to authorize), `--label`
(repeatable `key=value`), `--json`.

### nself server list
List servers in the Hetzner project, optionally filtered by label.

```bash
nself server list --label-selector managed-by=nself-cli
```

Flags: `--label-selector`, `--json`.

### nself server resize
Change a server's type (CPU/RAM/disk). Hetzner Cloud has no API to shrink a
server's disk: if `--type` names a type with a smaller disk than the server
currently has, this command refuses and explains the only supported path
(snapshot the current server, provision a new server of the smaller type,
restore from the snapshot, then `nself server destroy` the original) instead
of surfacing Hetzner's raw `invalid_input` error.

```bash
nself server resize --id 12345 --type cx41
```

Flags: `--id` (required), `--type` (required, target server type),
`--upgrade-disk` (also grow the disk to match the new type, irreversible),
`--json`.

### nself server destroy
Delete a server. Safe by default: refuses to run at all unless you pass
`--snapshot` (takes one and waits for it to reach `status=available` before
deleting anything) or `--force-no-backup` (an explicit acknowledgment that no
backup is taken). Hetzner primary IPs default to `auto_delete=true`, so
deleting the server would permanently destroy its IP too; this command sets
`auto_delete=false` on the server's primary IP(s) first and prints which IPs
were retained, unless `--release-ip` says to let them go with the server. If
the snapshot fails or never reaches `status=available` within
`--snapshot-timeout`, the server is NOT deleted.

```bash
nself server destroy --id 12345 --snapshot
```

Flags: `--id` (required), `--snapshot`, `--force-no-backup`, `--release-ip`,
`--snapshot-timeout` (default `10m`), `--json`.

Every subcommand also takes `--token` (Hetzner Cloud API token, overrides
`--token-env`) and `--token-env` (env var to read the token from, default
`HETZNER_NSELF_TOKEN`; falls back to `HCLOUD_TOKEN` if unset). The token is
never logged.
<!-- END PROSE:description -->

## Flags

<!-- BEGIN GENERATED:flags -->
| Flag | Default | Description |
|------|---------|-------------|
| `--help`, `-h` | — | Show help |
<!-- END GENERATED:flags -->

## Subcommands

<!-- BEGIN GENERATED:subcommands -->
| Name | Description |
|------|-------------|
| `destroy` | Delete a Hetzner Cloud server |
| `list` | List Hetzner Cloud servers |
| `provision` | Create a new Hetzner Cloud server |
| `resize` | Change a server's type (CPU/RAM/disk) |
<!-- END GENERATED:subcommands -->

## Examples

<!-- BEGIN PROSE:examples -->
```bash
# Provision a new CI box
nself server provision --name ci-runner-3 --type cx22 --location fsn1 --image ubuntu-24.04 --ssh-key deploy
```

```bash
# List every nself-managed server
nself server list --label-selector managed-by=nself-cli
```

```bash
# Grow a server, keeping the same disk-shrink-safe path
nself server resize --id 12345 --type cx41
```

```bash
# Destroy a server after a verified snapshot, retaining its primary IP
nself server destroy --id 12345 --snapshot
```

```bash
# Destroy a throwaway CI box with no backup, releasing its IP too
nself server destroy --id 12345 --force-no-backup --release-ip
```
<!-- END PROSE:examples -->

## See Also

<!-- BEGIN PROSE:see-also -->
- [[cmd-access]], SSH key access on a server this command already provisioned
- [[cmd-security]], firewall, fail2ban, and sshd hardening for the same server
- [[cmd-deploy]], deploying the nself stack onto a server once it exists
- [[Commands]], full command index
<!-- END PROSE:see-also -->

← [[Commands]] | [[Home]] →
19 changes: 18 additions & 1 deletion .github/wiki/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ nself build # generate docker-compose + nginx
nself start # boot the stack
```

## Commands (50)
## Commands (51)

### nself access

Expand Down Expand Up @@ -858,6 +858,23 @@ Flags:

Full page: [[cmd-self-heal]]

### nself server

Provision, list, resize, and destroy Hetzner Cloud servers

```
nself server <subcommand> [flags]
```

Subcommands:

- `destroy` — Delete a Hetzner Cloud server
- `list` — List Hetzner Cloud servers
- `provision` — Create a new Hetzner Cloud server
- `resize` — Change a server's type (CPU/RAM/disk)

Full page: [[cmd-server]]

### nself service

Manage optional services
Expand Down
5 changes: 5 additions & 0 deletions cmd/commands/error_harness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ var errorHarnessCases = []errorHarnessCase{
{"security", []string{"security", "--no-such-flag-xyz"}, "(b) invalid flag"},
{"security", []string{"security", "unknownsub_xyz"}, "(c) unknown sub"},

// ── server ─────────────────────────────────────────────────────────────
{"server", []string{"server"}, "(a) no project dir"},
{"server", []string{"server", "--no-such-flag-xyz"}, "(b) invalid flag"},
{"server", []string{"server", "unknownsub_xyz"}, "(c) unknown sub"},

// ── service ────────────────────────────────────────────────────────────
{"service", []string{"service"}, "(a) no project dir"},
{"service", []string{"service", "--no-such-flag-xyz"}, "(b) invalid flag"},
Expand Down
1 change: 1 addition & 0 deletions cmd/commands/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ var commandGroupAssignments = map[string]string{
// Advanced & Enterprise.
"access": groupAdvanced,
"security": groupAdvanced,
"server": groupAdvanced,
"verify-sbom": groupAdvanced,
}

Expand Down
Loading
Loading