@rollupdate/hctl 0.0.0-stage → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1071 -2
- package/dist/auction/cache.d.ts +6 -0
- package/dist/auction/cache.js +55 -0
- package/dist/auction/client.d.ts +24 -0
- package/dist/auction/client.js +236 -0
- package/dist/auction/commands.d.ts +2 -0
- package/dist/auction/commands.js +440 -0
- package/dist/auction/diff-formatter.d.ts +3 -0
- package/dist/auction/diff-formatter.js +61 -0
- package/dist/auction/diff.d.ts +11 -0
- package/dist/auction/diff.js +28 -0
- package/dist/auction/formatter.d.ts +21 -0
- package/dist/auction/formatter.js +182 -0
- package/dist/auction/store.d.ts +24 -0
- package/dist/auction/store.js +177 -0
- package/dist/auction/types.d.ts +74 -0
- package/dist/auction/types.js +5 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +74 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -0
- package/dist/cloud/browser-bootstrap.d.ts +28 -0
- package/dist/cloud/browser-bootstrap.js +480 -0
- package/dist/cloud/client.d.ts +520 -0
- package/dist/cloud/client.js +755 -0
- package/dist/cloud/commands/certificate.d.ts +2 -0
- package/dist/cloud/commands/certificate.js +108 -0
- package/dist/cloud/commands/context.d.ts +2 -0
- package/dist/cloud/commands/context.js +133 -0
- package/dist/cloud/commands/datacenter.d.ts +2 -0
- package/dist/cloud/commands/datacenter.js +24 -0
- package/dist/cloud/commands/firewall.d.ts +2 -0
- package/dist/cloud/commands/firewall.js +142 -0
- package/dist/cloud/commands/floating-ip.d.ts +2 -0
- package/dist/cloud/commands/floating-ip.js +138 -0
- package/dist/cloud/commands/image.d.ts +2 -0
- package/dist/cloud/commands/image.js +98 -0
- package/dist/cloud/commands/index.d.ts +2 -0
- package/dist/cloud/commands/index.js +47 -0
- package/dist/cloud/commands/inventory.d.ts +2 -0
- package/dist/cloud/commands/inventory.js +13 -0
- package/dist/cloud/commands/iso.d.ts +2 -0
- package/dist/cloud/commands/iso.js +27 -0
- package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
- package/dist/cloud/commands/load-balancer-type.js +24 -0
- package/dist/cloud/commands/load-balancer.d.ts +2 -0
- package/dist/cloud/commands/load-balancer.js +269 -0
- package/dist/cloud/commands/location.d.ts +2 -0
- package/dist/cloud/commands/location.js +24 -0
- package/dist/cloud/commands/network.d.ts +2 -0
- package/dist/cloud/commands/network.js +157 -0
- package/dist/cloud/commands/placement-group.d.ts +2 -0
- package/dist/cloud/commands/placement-group.js +79 -0
- package/dist/cloud/commands/pricing.d.ts +2 -0
- package/dist/cloud/commands/pricing.js +15 -0
- package/dist/cloud/commands/primary-ip.d.ts +2 -0
- package/dist/cloud/commands/primary-ip.js +139 -0
- package/dist/cloud/commands/server-type.d.ts +2 -0
- package/dist/cloud/commands/server-type.js +24 -0
- package/dist/cloud/commands/server.d.ts +2 -0
- package/dist/cloud/commands/server.js +346 -0
- package/dist/cloud/commands/ssh-key.d.ts +2 -0
- package/dist/cloud/commands/ssh-key.js +94 -0
- package/dist/cloud/commands/volume.d.ts +2 -0
- package/dist/cloud/commands/volume.js +138 -0
- package/dist/cloud/context.d.ts +34 -0
- package/dist/cloud/context.js +215 -0
- package/dist/cloud/formatter.d.ts +40 -0
- package/dist/cloud/formatter.js +824 -0
- package/dist/cloud/helpers.d.ts +27 -0
- package/dist/cloud/helpers.js +67 -0
- package/dist/cloud/inventory.d.ts +100 -0
- package/dist/cloud/inventory.js +229 -0
- package/dist/cloud/pricing.d.ts +27 -0
- package/dist/cloud/pricing.js +103 -0
- package/dist/cloud/token-instructions.d.ts +2 -0
- package/dist/cloud/token-instructions.js +21 -0
- package/dist/cloud/types.d.ts +437 -0
- package/dist/cloud/types.js +5 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/formatter.d.ts +3 -0
- package/dist/formatter.js +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +21 -0
- package/dist/robot/client.d.ts +256 -0
- package/dist/robot/client.js +745 -0
- package/dist/robot/commands/auth.d.ts +2 -0
- package/dist/robot/commands/auth.js +54 -0
- package/dist/robot/commands/boot.d.ts +2 -0
- package/dist/robot/commands/boot.js +76 -0
- package/dist/robot/commands/cancel.d.ts +2 -0
- package/dist/robot/commands/cancel.js +37 -0
- package/dist/robot/commands/failover.d.ts +2 -0
- package/dist/robot/commands/failover.js +46 -0
- package/dist/robot/commands/firewall.d.ts +2 -0
- package/dist/robot/commands/firewall.js +72 -0
- package/dist/robot/commands/index.d.ts +2 -0
- package/dist/robot/commands/index.js +36 -0
- package/dist/robot/commands/interactive.d.ts +2 -0
- package/dist/robot/commands/interactive.js +152 -0
- package/dist/robot/commands/ip.d.ts +2 -0
- package/dist/robot/commands/ip.js +55 -0
- package/dist/robot/commands/key.d.ts +2 -0
- package/dist/robot/commands/key.js +68 -0
- package/dist/robot/commands/order.d.ts +2 -0
- package/dist/robot/commands/order.js +33 -0
- package/dist/robot/commands/rdns.d.ts +2 -0
- package/dist/robot/commands/rdns.js +41 -0
- package/dist/robot/commands/reset.d.ts +2 -0
- package/dist/robot/commands/reset.js +77 -0
- package/dist/robot/commands/server.d.ts +2 -0
- package/dist/robot/commands/server.js +32 -0
- package/dist/robot/commands/storagebox.d.ts +2 -0
- package/dist/robot/commands/storagebox.js +126 -0
- package/dist/robot/commands/subnet.d.ts +2 -0
- package/dist/robot/commands/subnet.js +24 -0
- package/dist/robot/commands/traffic.d.ts +2 -0
- package/dist/robot/commands/traffic.js +23 -0
- package/dist/robot/commands/vswitch.d.ts +2 -0
- package/dist/robot/commands/vswitch.js +81 -0
- package/dist/robot/commands/wol.d.ts +2 -0
- package/dist/robot/commands/wol.js +20 -0
- package/dist/robot/formatter.d.ts +57 -0
- package/dist/robot/formatter.js +582 -0
- package/dist/robot/types.d.ts +359 -0
- package/dist/robot/types.js +5 -0
- package/dist/shared/config.d.ts +86 -0
- package/dist/shared/config.js +289 -0
- package/dist/shared/formatter.d.ts +29 -0
- package/dist/shared/formatter.js +121 -0
- package/dist/shared/helpers.d.ts +39 -0
- package/dist/shared/helpers.js +86 -0
- package/dist/shared/init.d.ts +2 -0
- package/dist/shared/init.js +107 -0
- package/dist/shared/reference.d.ts +5 -0
- package/dist/shared/reference.js +689 -0
- package/dist/shared/schemas.d.ts +1 -0
- package/dist/shared/schemas.js +1031 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/package.json +127 -4
package/README.md
CHANGED
|
@@ -1,3 +1,1072 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" alt="hctl logo" width="128" height="128">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
# hctl
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@rollupdate/hctl)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
**[Website](https://hctl.dev/)** · **[npm](https://www.npmjs.com/package/@rollupdate/hctl)** · **[GitHub](https://github.com/p8-public/hetzner-cli)**
|
|
11
|
+
|
|
12
|
+
Unified CLI and Node.js library for Hetzner's three APIs:
|
|
13
|
+
|
|
14
|
+
- **[Robot API](https://robot.hetzner.com)** — dedicated server management (servers, IPs, firewall, storage boxes, etc.)
|
|
15
|
+
- **[Cloud API](https://console.hetzner.cloud)** — cloud infrastructure (servers, networks, volumes, load balancers, etc.)
|
|
16
|
+
- **[Server Auction](https://www.hetzner.com/sb/)** — public server auction browser with rich filtering (no auth required)
|
|
17
|
+
|
|
18
|
+
> **Note:** This is an unofficial, community-built CLI. The official Hetzner Cloud CLI is [`hcloud`](https://github.com/hetznercloud/cli). See [how they differ](#comparison-with-official-cli).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Table of Contents
|
|
23
|
+
|
|
24
|
+
- [Comparison with Official CLI](#comparison-with-official-cli)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
27
|
+
- [Authentication](#authentication)
|
|
28
|
+
- [Robot API Credentials](#robot-api-credentials)
|
|
29
|
+
- [Cloud API Token](#cloud-api-token)
|
|
30
|
+
- [Auction API](#auction-api-no-auth)
|
|
31
|
+
- [CLI Reference](#cli-reference)
|
|
32
|
+
- [Global Options](#global-options)
|
|
33
|
+
- [Robot Commands](#robot-commands)
|
|
34
|
+
- [Cloud Commands](#cloud-commands)
|
|
35
|
+
- [Auction Commands](#auction-commands)
|
|
36
|
+
- [Built-in Reference](#built-in-reference)
|
|
37
|
+
- [Library Usage](#library-usage)
|
|
38
|
+
- [Robot Client](#robot-client)
|
|
39
|
+
- [Cloud Client](#cloud-client)
|
|
40
|
+
- [Auction Client](#auction-client)
|
|
41
|
+
- [Type Exports](#type-exports)
|
|
42
|
+
- [API Reference](#api-reference)
|
|
43
|
+
- [Configuration Files](#configuration-files)
|
|
44
|
+
- [Development](#development)
|
|
45
|
+
- [License](#license)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Comparison with Official CLI
|
|
50
|
+
|
|
51
|
+
Hetzner maintains an official Cloud CLI called [`hcloud`](https://github.com/hetznercloud/cli), written in Go. This project (`hctl`) is a separate, community-built tool. Here's how they compare:
|
|
52
|
+
|
|
53
|
+
| Feature | [`hcloud`](https://github.com/hetznercloud/cli) (official) | `hctl` (this project) |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| **API coverage** | Cloud only | Cloud + Robot + Server Auction |
|
|
56
|
+
| **Language** | Go (single binary) | TypeScript / Node.js |
|
|
57
|
+
| **Install** | Homebrew, apt, binary download | `npm install -g @rollupdate/hctl` |
|
|
58
|
+
| **Library usage** | CLI only | Also usable as a Node.js library (`import { HetznerRobotClient } from '@rollupdate/hctl'`) |
|
|
59
|
+
| **Interactive mode** | No | Yes (`hctl interactive`) |
|
|
60
|
+
| **LLM reference** | No | Yes (`hctl reference` — structured output for LLM context) |
|
|
61
|
+
|
|
62
|
+
**When to use `hcloud`:** You only need Cloud API access and prefer a native binary with no runtime dependency.
|
|
63
|
+
|
|
64
|
+
**When to use `hctl`:** You manage dedicated servers (Robot), browse the server auction, or want a single tool that covers all three Hetzner APIs. Also useful if you need a Node.js library for automation scripts.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Global CLI
|
|
72
|
+
npm install -g @rollupdate/hctl
|
|
73
|
+
|
|
74
|
+
# Or as a project dependency
|
|
75
|
+
npm install @rollupdate/hctl
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Requires Node.js 20.17+, 22.13+, or 23.5+.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# First-time setup wizard for Robot + Cloud credentials
|
|
86
|
+
hctl init
|
|
87
|
+
|
|
88
|
+
# List your dedicated servers
|
|
89
|
+
hctl server list
|
|
90
|
+
|
|
91
|
+
# List your cloud servers
|
|
92
|
+
hctl cloud server list
|
|
93
|
+
|
|
94
|
+
# Browse the server auction (no login needed)
|
|
95
|
+
hctl auction list --max-price 60 --ecc --disk-type nvme --sort price
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Authentication
|
|
101
|
+
|
|
102
|
+
### Robot API Credentials
|
|
103
|
+
|
|
104
|
+
The Robot API uses a **web service username and password** (separate from your main Hetzner login).
|
|
105
|
+
These credentials are created in the Robot UI at [robot.hetzner.com](https://robot.hetzner.com), not in the Hetzner Cloud Console.
|
|
106
|
+
|
|
107
|
+
**To create credentials:**
|
|
108
|
+
|
|
109
|
+
1. Go to [robot.hetzner.com](https://robot.hetzner.com)
|
|
110
|
+
2. Navigate to **Settings > Web service settings**
|
|
111
|
+
3. Create a new web service user
|
|
112
|
+
|
|
113
|
+
**Credential resolution order** (first match wins):
|
|
114
|
+
|
|
115
|
+
| Priority | Method | Details |
|
|
116
|
+
|----------|--------|---------|
|
|
117
|
+
| 1 | CLI flags | `--user <username> --password <password>` |
|
|
118
|
+
| 2 | Environment variables | `HETZNER_ROBOT_USER` and `HETZNER_ROBOT_PASSWORD` |
|
|
119
|
+
| 3 | System keychain | Stored by `hctl auth login` (uses native keytar) |
|
|
120
|
+
| 4 | Config file | `~/.hctl/config.json` |
|
|
121
|
+
| 5 | Interactive prompt | Asks for username/password at runtime |
|
|
122
|
+
|
|
123
|
+
**Secure password passing** (keeps password out of shell history):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# From stdin
|
|
127
|
+
echo "$PASSWORD" | hctl server list -u myuser -p -
|
|
128
|
+
|
|
129
|
+
# With 1Password
|
|
130
|
+
op read "op://vault/item/password" | hctl server list -u $(op read "op://vault/item/user") -p -
|
|
131
|
+
|
|
132
|
+
# From environment
|
|
133
|
+
export HETZNER_ROBOT_USER=myuser
|
|
134
|
+
export HETZNER_ROBOT_PASSWORD=mypassword
|
|
135
|
+
hctl server list
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Cloud API Token
|
|
139
|
+
|
|
140
|
+
The Cloud API uses a **bearer token** from the Hetzner Cloud Console.
|
|
141
|
+
Cloud tokens are project-scoped and are created in the Cloud Console at [console.hetzner.cloud](https://console.hetzner.cloud), not in the Robot UI.
|
|
142
|
+
|
|
143
|
+
**To create a token:**
|
|
144
|
+
|
|
145
|
+
1. Go to [console.hetzner.cloud](https://console.hetzner.cloud)
|
|
146
|
+
2. Select your project, or create one
|
|
147
|
+
3. In the left menu, open **Security**
|
|
148
|
+
4. In the top menu, open **API Tokens**
|
|
149
|
+
5. Click **Generate API Token**
|
|
150
|
+
6. Name the token, for example `hctl`
|
|
151
|
+
7. Choose **Read & Write** for full `hctl` management, or **Read** for list/describe-only usage
|
|
152
|
+
8. Copy the token immediately; Hetzner only shows it once
|
|
153
|
+
|
|
154
|
+
**Token resolution order:**
|
|
155
|
+
|
|
156
|
+
| Priority | Method | Details |
|
|
157
|
+
|----------|--------|---------|
|
|
158
|
+
| 1 | CLI flag | `--token <token>` per command |
|
|
159
|
+
| 2 | Environment variable | `HETZNER_CLOUD_TOKEN` |
|
|
160
|
+
| 3 | Active context | Set via `hctl cloud context use <name>` |
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Interactive Robot + Cloud setup
|
|
164
|
+
hctl init
|
|
165
|
+
|
|
166
|
+
# Set up a named context (saves token)
|
|
167
|
+
hctl cloud context create production -t hcloud_xxxxxx
|
|
168
|
+
|
|
169
|
+
# Verify the resolved token before provisioning
|
|
170
|
+
hctl cloud context test
|
|
171
|
+
|
|
172
|
+
# Explicit browser-assisted token setup
|
|
173
|
+
hctl cloud context bootstrap placeswhere \
|
|
174
|
+
--project Placeswhere \
|
|
175
|
+
--create-project
|
|
176
|
+
|
|
177
|
+
# Switch contexts
|
|
178
|
+
hctl cloud context use production
|
|
179
|
+
|
|
180
|
+
# Or use inline
|
|
181
|
+
hctl cloud server list --token hcloud_xxxxxx
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Auction API (No Auth)
|
|
185
|
+
|
|
186
|
+
The auction commands use the hosted 15-minute Cloudflare cache by default. No credentials required, and normal CLI usage does not hit Hetzner's public JSON endpoint directly.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
hctl auction list
|
|
190
|
+
hctl auction list --direct # bypass the hosted cache and fetch from Hetzner
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Hosted JSON files:
|
|
194
|
+
|
|
195
|
+
- `https://auction.hctl.dev/live_data_sb_EUR.json`
|
|
196
|
+
- `https://auction.hctl.dev/live_data_sb_USD.json`
|
|
197
|
+
- `https://auction.hctl.dev/metadata.json`
|
|
198
|
+
- `https://auction.hctl.dev/health.json`
|
|
199
|
+
|
|
200
|
+
If Hetzner's public JSON endpoint is temporarily unavailable, the Cloudflare Worker keeps serving the last good R2 snapshot and records the refresh failure in `metadata.json` and `health.json`. If the cache is empty and Hetzner cannot be reached, the Worker returns `502` with `Retry-After: 300`.
|
|
201
|
+
|
|
202
|
+
The CLI also keeps a local copy of the most recent successful auction response. If your internet connection is unavailable, `hctl auction list` and `hctl auction show` fall back to that local data and mark it as stale. JSON output remains valid JSON on stdout; stale-cache warnings are written to stderr.
|
|
203
|
+
|
|
204
|
+
`hctl auction status --json` is the stable machine-readable way to check freshness before automation. `auction diff` and `auction watch` require a current fetch and will not create change snapshots from stale local fallback data.
|
|
205
|
+
|
|
206
|
+
The Worker stores the current public files in R2 under `latest/` and keeps timestamped copies under `history/YYYY/MM/DD/HHmm/`. Those historical JSON snapshots are intended for later trend analysis in DuckDB or a database.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## CLI Reference
|
|
211
|
+
|
|
212
|
+
### Global Options
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
-u, --user <username> Robot API username
|
|
216
|
+
-p, --password <password> Robot API password (use "-" to read from stdin)
|
|
217
|
+
--json Output raw JSON instead of formatted tables
|
|
218
|
+
--output-schema Print TypeScript type for --json output and exit
|
|
219
|
+
-V, --version Show version number
|
|
220
|
+
-h, --help Show help for any command
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
All commands support `--json` for machine-readable output. Pipe to `jq` for filtering:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
hctl server list --json | jq '.[].server.server_ip'
|
|
227
|
+
hctl auction list --max-price 50 --json | jq '.[].id'
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Robot Commands
|
|
231
|
+
|
|
232
|
+
Commands for managing Hetzner dedicated servers via the Robot API.
|
|
233
|
+
|
|
234
|
+
#### Authentication
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
hctl auth login # Interactive credential setup
|
|
238
|
+
hctl auth logout # Clear saved credentials
|
|
239
|
+
hctl auth status # Show current auth source
|
|
240
|
+
hctl auth test # Verify credentials work
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### Servers
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
hctl server list # List all servers
|
|
247
|
+
hctl server get <server> # Server details (by ID or IP)
|
|
248
|
+
hctl server rename <server> <name> # Rename a server
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Reset
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
hctl reset options [server] # Show available reset types
|
|
255
|
+
hctl reset execute <servers...> # Reset servers
|
|
256
|
+
-t, --type <type> # sw|hw|man|power|power_long (default: sw)
|
|
257
|
+
-i, --interactive # Select type interactively
|
|
258
|
+
-y, --yes # Skip confirmation
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Boot Configuration
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
hctl boot status <server> # Show all boot config
|
|
265
|
+
|
|
266
|
+
# Rescue system
|
|
267
|
+
hctl boot rescue activate <server> [-o linux|linuxold|vkvm] [-a 64|32] [-k <fingerprints...>]
|
|
268
|
+
hctl boot rescue deactivate <server>
|
|
269
|
+
hctl boot rescue last <server> # Show last rescue (includes password)
|
|
270
|
+
|
|
271
|
+
# Linux installation
|
|
272
|
+
hctl boot linux options <server> # Show available distributions
|
|
273
|
+
hctl boot linux activate <server> -d <dist> [-a 64|32] [-l en] [-k <fingerprints...>]
|
|
274
|
+
hctl boot linux deactivate <server>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### IP & Networking
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
hctl ip list # List all IPs
|
|
281
|
+
hctl ip get <ip> # IP details
|
|
282
|
+
hctl ip update <ip> [--warnings true|false] [--hourly <mb>] [--daily <mb>] [--monthly <gb>]
|
|
283
|
+
hctl ip mac get|generate|delete <ip> # Separate MAC management
|
|
284
|
+
|
|
285
|
+
hctl subnet list # List subnets
|
|
286
|
+
hctl subnet get <subnet>
|
|
287
|
+
|
|
288
|
+
hctl failover list # List failover IPs
|
|
289
|
+
hctl failover get <ip>
|
|
290
|
+
hctl failover switch <failover-ip> <target-server-ip> [-y]
|
|
291
|
+
hctl failover delete <ip> [-y]
|
|
292
|
+
|
|
293
|
+
hctl rdns list # List reverse DNS entries
|
|
294
|
+
hctl rdns get <ip>
|
|
295
|
+
hctl rdns set <ip> <ptr> # Create/update
|
|
296
|
+
hctl rdns delete <ip>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### SSH Keys
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
hctl key list # List all keys
|
|
303
|
+
hctl key get <fingerprint>
|
|
304
|
+
hctl key add <name> [-f <path>] [-d <data>]
|
|
305
|
+
hctl key rename <fingerprint> <name>
|
|
306
|
+
hctl key delete <fingerprint> [-y]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Firewall
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
hctl firewall get <server> # Show config
|
|
313
|
+
hctl firewall enable|disable <server>
|
|
314
|
+
hctl firewall delete <server> [-y] # Delete all rules
|
|
315
|
+
|
|
316
|
+
hctl firewall template list
|
|
317
|
+
hctl firewall template get|delete <id>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### vSwitch
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
hctl vswitch list
|
|
324
|
+
hctl vswitch get <id>
|
|
325
|
+
hctl vswitch servers [id]
|
|
326
|
+
hctl vswitch create <name> <vlan>
|
|
327
|
+
hctl vswitch update <id> [-n <name>] [-v <vlan>]
|
|
328
|
+
hctl vswitch delete <id> [-y] [--date <YYYY-MM-DD>]
|
|
329
|
+
hctl vswitch add-server <vswitch-id> <server>
|
|
330
|
+
hctl vswitch remove-server <vswitch-id> <server>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### Storage Box
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
hctl storagebox list # (alias: storage)
|
|
337
|
+
hctl storagebox get <id>
|
|
338
|
+
hctl storagebox update <id> [-n <name>] [--webdav|--samba|--ssh|--external|--zfs true|false]
|
|
339
|
+
hctl storagebox reset-password <id>
|
|
340
|
+
|
|
341
|
+
# Snapshots
|
|
342
|
+
hctl storagebox snapshot list|create|delete|revert <box-id> [<name>]
|
|
343
|
+
|
|
344
|
+
# Subaccounts
|
|
345
|
+
hctl storagebox subaccount list <box-id>
|
|
346
|
+
hctl storagebox subaccount create <box-id> <home-directory> [--samba|--ssh|--webdav|--external|--readonly true|false]
|
|
347
|
+
hctl storagebox subaccount delete <box-id> <username> [-y]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Traffic, WoL, Cancellation, Ordering
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
hctl traffic query [-i <ips...>] [-s <subnets...>] [--from <date>] [--to <date>] [-t day|month|year]
|
|
354
|
+
hctl wol status|send <server>
|
|
355
|
+
hctl cancel status|request|revoke <server>
|
|
356
|
+
hctl order products|market|transactions
|
|
357
|
+
hctl order transaction <id>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### Interactive Mode
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
hctl interactive # or: hctl i
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Menu-driven interface for common operations (list servers, reset, rescue, failover, SSH keys).
|
|
367
|
+
|
|
368
|
+
### Cloud Commands
|
|
369
|
+
|
|
370
|
+
All cloud commands live under `hctl cloud <resource> <action>`. Add `--token <token>` to any command to override the active context.
|
|
371
|
+
|
|
372
|
+
#### Context Management
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
hctl cloud context create <name> [-t <token>] [--no-verify]
|
|
376
|
+
hctl cloud context test [-t <token>]
|
|
377
|
+
hctl cloud context bootstrap <name> --project <name> [--create-project] [--write-env [path]]
|
|
378
|
+
hctl cloud context use <name>
|
|
379
|
+
hctl cloud context delete <name>
|
|
380
|
+
hctl cloud context list
|
|
381
|
+
hctl cloud context active
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`hctl cloud context bootstrap` is an explicit opt-in assisted browser flow. It
|
|
385
|
+
opens a visible local real-browser session so you can log in to the Hetzner Cloud
|
|
386
|
+
Console yourself, including 2FA. `hctl` attaches to the browser only after you
|
|
387
|
+
press Enter, so no automation runs during login. It does not ask for or store
|
|
388
|
+
your console password and does not scrape the one-time API token from the page.
|
|
389
|
+
After Hetzner
|
|
390
|
+
prepares the token form, `hctl` asks for confirmation again before generating
|
|
391
|
+
the credential. Copy the resulting token yourself and paste it into the hidden
|
|
392
|
+
CLI prompt. The token is then validated and saved as a normal `hctl` cloud
|
|
393
|
+
context. Use `--write-env` only when you also want `HETZNER_CLOUD_TOKEN` written
|
|
394
|
+
to a local env file.
|
|
395
|
+
|
|
396
|
+
#### Cloud Servers
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
hctl cloud server list [-l <label-selector>] [-n <name>] [-s <sort>] [--status <status>]
|
|
400
|
+
hctl cloud server describe <id>
|
|
401
|
+
hctl cloud server create --name <name> --type <type> --image <image> [--location <loc>] [--ssh-key <keys...>]
|
|
402
|
+
hctl cloud server delete <id> [-y]
|
|
403
|
+
hctl cloud server update <id> [--name <name>]
|
|
404
|
+
|
|
405
|
+
# Power management
|
|
406
|
+
hctl cloud server poweron|poweroff|reboot|reset|shutdown <id>
|
|
407
|
+
|
|
408
|
+
# Maintenance
|
|
409
|
+
hctl cloud server rebuild <id> --image <image>
|
|
410
|
+
hctl cloud server change-type <id> --type <type> [--upgrade-disk]
|
|
411
|
+
hctl cloud server enable-rescue <id> [--type linux64] [--ssh-key <ids...>]
|
|
412
|
+
hctl cloud server disable-rescue <id>
|
|
413
|
+
hctl cloud server enable-backup|disable-backup <id>
|
|
414
|
+
hctl cloud server create-image <id> [--description <desc>]
|
|
415
|
+
hctl cloud server attach-iso|detach-iso <id> [--iso <iso>]
|
|
416
|
+
hctl cloud server reset-password <id>
|
|
417
|
+
|
|
418
|
+
# Networking
|
|
419
|
+
hctl cloud server set-rdns <id> --ip <ip> --dns-ptr <ptr>
|
|
420
|
+
hctl cloud server attach-to-network <id> --network <id> [--ip <ip>]
|
|
421
|
+
hctl cloud server detach-from-network <id> --network <id>
|
|
422
|
+
|
|
423
|
+
# Protection & labels
|
|
424
|
+
hctl cloud server enable-protection|disable-protection <id>
|
|
425
|
+
hctl cloud server add-label <id> <key=value>
|
|
426
|
+
hctl cloud server remove-label <id> <key>
|
|
427
|
+
hctl cloud server request-console <id>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Other Cloud Resources
|
|
431
|
+
|
|
432
|
+
Each follows the pattern: `hctl cloud <resource> list|describe|create|delete`
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
hctl cloud network list|describe|create|delete
|
|
436
|
+
hctl cloud firewall list|describe|create|delete
|
|
437
|
+
hctl cloud floating-ip list|describe|create|delete
|
|
438
|
+
hctl cloud primary-ip list|describe|create|delete
|
|
439
|
+
hctl cloud volume list|describe|create|delete
|
|
440
|
+
hctl cloud load-balancer list|describe|create|delete
|
|
441
|
+
hctl cloud image list|describe|update|delete
|
|
442
|
+
hctl cloud ssh-key list|describe|create|delete
|
|
443
|
+
hctl cloud certificate list|describe|create|delete
|
|
444
|
+
hctl cloud placement-group list|describe|create|delete
|
|
445
|
+
|
|
446
|
+
# Read-only reference data
|
|
447
|
+
hctl cloud datacenter list|describe
|
|
448
|
+
hctl cloud location list|describe
|
|
449
|
+
hctl cloud server-type list|describe
|
|
450
|
+
hctl cloud load-balancer-type list|describe
|
|
451
|
+
hctl cloud iso list|describe
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Auction Commands
|
|
455
|
+
|
|
456
|
+
Browse Hetzner's server auction — no authentication required. Data is fetched from the hosted 15-minute cache and filtered/sorted client-side. Add `--direct` when you want to bypass the cache and fetch directly from Hetzner.
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
hctl auction list [options] # List and filter auction servers
|
|
460
|
+
hctl auction show <id> # Detailed view of a single server
|
|
461
|
+
hctl auction status # Show hosted/local cache freshness
|
|
462
|
+
hctl auction diff # Compare current data with the last local snapshot
|
|
463
|
+
hctl auction watch # Poll for local snapshot changes
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
#### Auction List — Full Option Reference
|
|
467
|
+
|
|
468
|
+
**Price filters:**
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
--min-price <n> # Minimum monthly price
|
|
472
|
+
--max-price <n> # Maximum monthly price
|
|
473
|
+
--max-hourly-price <n> # Maximum hourly price
|
|
474
|
+
--max-setup-price <n> # Maximum setup fee
|
|
475
|
+
--no-setup-fee # Only free setup (shorthand for --max-setup-price 0)
|
|
476
|
+
--fixed-price # Only fixed-price servers
|
|
477
|
+
--auction-only # Only auction servers (price decreases over time)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Hardware filters:**
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
--cpu <text> # CPU model substring, case-insensitive (e.g. "Ryzen", "EPYC", "i7-6700")
|
|
484
|
+
--min-cpu-count <n> # Minimum CPU/socket count
|
|
485
|
+
--max-cpu-count <n> # Maximum CPU/socket count
|
|
486
|
+
--min-ram <gb> # Minimum RAM in GB
|
|
487
|
+
--max-ram <gb> # Maximum RAM in GB
|
|
488
|
+
--ecc # Only ECC RAM servers
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Disk filters:**
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
--min-disk-size <gb> # Minimum total disk capacity (sum of all drives)
|
|
495
|
+
--max-disk-size <gb> # Maximum total disk capacity
|
|
496
|
+
--min-disk-count <n> # Minimum number of physical drives
|
|
497
|
+
--max-disk-count <n> # Maximum number of physical drives
|
|
498
|
+
--disk-type <type> # Must have this disk type: nvme, sata, hdd
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Network & feature filters:**
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
--datacenter <text> # Datacenter substring, case-insensitive (e.g. "FSN", "HEL1-DC2", "NBG")
|
|
505
|
+
--min-bandwidth <mbit> # Minimum bandwidth in Mbit/s
|
|
506
|
+
--gpu # Only GPU servers
|
|
507
|
+
--inic # Only Intel NIC servers
|
|
508
|
+
--highio # Only high I/O servers
|
|
509
|
+
--specials <text> # Any special feature, substring match (e.g. "GPU", "ECC")
|
|
510
|
+
--search <text> # Free-text search across description
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Sorting:**
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
--sort <field> # Sort field (default: price). Choices:
|
|
517
|
+
# price, hourly, setup, ram, disk, disk_count,
|
|
518
|
+
# cpu, cpu_count, datacenter, bandwidth, next_reduce
|
|
519
|
+
--desc # Sort descending (default: ascending)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Output:**
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
--currency <EUR|USD> # Price currency (default: EUR)
|
|
526
|
+
--direct # Fetch directly from Hetzner instead of hosted cache
|
|
527
|
+
--limit <n> # Limit number of results
|
|
528
|
+
--json # Output raw JSON
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### Auction Status
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
hctl auction status
|
|
535
|
+
hctl auction status --json
|
|
536
|
+
hctl auction status --currency USD
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
`auction status --json` is the recommended freshness check for scripts and LLM agents. It includes `updatedAt`, `ageSeconds`, `stale`, `source`, `serverCount`, `endpointUrl`, `usingLocalFallback`, and local cache state.
|
|
540
|
+
|
|
541
|
+
#### Auction Examples
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
# Browse all servers, cheapest first
|
|
545
|
+
hctl auction list
|
|
546
|
+
|
|
547
|
+
# Cheap AMD EPYC with NVMe + ECC in Helsinki
|
|
548
|
+
hctl auction list --cpu epyc --disk-type nvme --ecc --datacenter HEL --sort price
|
|
549
|
+
|
|
550
|
+
# GPU servers under 150 EUR
|
|
551
|
+
hctl auction list --gpu --max-price 150
|
|
552
|
+
|
|
553
|
+
# High-RAM servers with lots of drives
|
|
554
|
+
hctl auction list --min-ram 256 --min-disk-count 4 --sort ram --desc
|
|
555
|
+
|
|
556
|
+
# Cheapest 10 fixed-price NVMe servers
|
|
557
|
+
hctl auction list --fixed-price --disk-type nvme --sort price --limit 10
|
|
558
|
+
|
|
559
|
+
# Auction servers about to drop in price
|
|
560
|
+
hctl auction list --auction-only --sort next_reduce --limit 20
|
|
561
|
+
|
|
562
|
+
# Export to JSON for scripting
|
|
563
|
+
hctl auction list --max-price 60 --ecc --json | jq '.[].id'
|
|
564
|
+
|
|
565
|
+
# Check freshness before automation
|
|
566
|
+
hctl auction status --json
|
|
567
|
+
|
|
568
|
+
# Bypass the hosted cache
|
|
569
|
+
hctl auction list --direct --max-price 60
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Built-in Reference
|
|
573
|
+
|
|
574
|
+
For a complete, structured reference optimized for LLM context windows:
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
hctl reference # or: hctl ref
|
|
578
|
+
hctl reference --section auction
|
|
579
|
+
hctl auction status --output-schema
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
This prints every command, option, and example in a structured plaintext format designed for easy parsing by language models. Pipe it into your LLM context:
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
hctl reference | pbcopy # Copy to clipboard (macOS)
|
|
586
|
+
hctl reference > /tmp/hctl-ref.txt # Save to file
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Library Usage
|
|
592
|
+
|
|
593
|
+
### Robot Client
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
import { HetznerRobotClient } from '@rollupdate/hctl';
|
|
597
|
+
|
|
598
|
+
const client = new HetznerRobotClient('username', 'password');
|
|
599
|
+
|
|
600
|
+
// List servers
|
|
601
|
+
const servers = await client.listServers();
|
|
602
|
+
|
|
603
|
+
// Get server details
|
|
604
|
+
const { server } = await client.getServer(123456);
|
|
605
|
+
console.log(server.server_name, server.server_ip);
|
|
606
|
+
|
|
607
|
+
// Reset a server
|
|
608
|
+
await client.resetServer(123456, 'sw');
|
|
609
|
+
|
|
610
|
+
// Activate rescue mode
|
|
611
|
+
const rescue = await client.activateRescue(123456, 'linux', 64, ['ssh-fingerprint']);
|
|
612
|
+
console.log('Password:', rescue.rescue.password);
|
|
613
|
+
|
|
614
|
+
// Linux installation
|
|
615
|
+
await client.activateLinux(123456, 'Debian-1210-bookworm-amd64-base', 64, 'en', ['ssh-fingerprint']);
|
|
616
|
+
|
|
617
|
+
// Failover
|
|
618
|
+
await client.switchFailover('1.2.3.4', '5.6.7.8');
|
|
619
|
+
|
|
620
|
+
// SSH keys
|
|
621
|
+
const keys = await client.listSshKeys();
|
|
622
|
+
await client.createSshKey('my-key', 'ssh-rsa AAAA...');
|
|
623
|
+
|
|
624
|
+
// Storage box
|
|
625
|
+
const boxes = await client.listStorageBoxes();
|
|
626
|
+
await client.createStorageBoxSnapshot(123);
|
|
627
|
+
|
|
628
|
+
// Firewall
|
|
629
|
+
const fw = await client.getFirewall(123456);
|
|
630
|
+
await client.updateFirewall(123456, 'active');
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Cloud Client
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
import { HetznerCloudClient } from '@rollupdate/hctl';
|
|
637
|
+
|
|
638
|
+
const client = new HetznerCloudClient('hcloud_xxxxxx');
|
|
639
|
+
|
|
640
|
+
// List servers
|
|
641
|
+
const { servers } = await client.listServers();
|
|
642
|
+
|
|
643
|
+
// Create a server
|
|
644
|
+
const result = await client.createServer({
|
|
645
|
+
name: 'web1',
|
|
646
|
+
server_type: 'cx22',
|
|
647
|
+
image: 'ubuntu-22.04',
|
|
648
|
+
location: 'fsn1',
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// Networks
|
|
652
|
+
const { networks } = await client.listNetworks();
|
|
653
|
+
await client.createNetwork({ name: 'my-net', ip_range: '10.0.0.0/16' });
|
|
654
|
+
|
|
655
|
+
// Volumes
|
|
656
|
+
const { volumes } = await client.listVolumes();
|
|
657
|
+
await client.createVolume({ name: 'data', size: 50, location: 'fsn1' });
|
|
658
|
+
|
|
659
|
+
// Load Balancers
|
|
660
|
+
const { load_balancers } = await client.listLoadBalancers();
|
|
661
|
+
|
|
662
|
+
// Firewalls, Floating IPs, SSH Keys, Images, etc.
|
|
663
|
+
const { firewalls } = await client.listFirewalls();
|
|
664
|
+
const { floating_ips } = await client.listFloatingIps();
|
|
665
|
+
const { ssh_keys } = await client.listSshKeys();
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Auction Client
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
import {
|
|
672
|
+
fetchAuctionData,
|
|
673
|
+
fetchAuctionServers,
|
|
674
|
+
filterAuctionServers,
|
|
675
|
+
getAuctionDataUrl,
|
|
676
|
+
sortAuctionServers,
|
|
677
|
+
} from '@rollupdate/hctl';
|
|
678
|
+
|
|
679
|
+
// Fetch all servers from the hosted 15-minute cache (EUR pricing)
|
|
680
|
+
const { server: servers } = await fetchAuctionServers('EUR');
|
|
681
|
+
const { metadata } = await fetchAuctionData('EUR');
|
|
682
|
+
console.log(metadata.updatedAt, metadata.ageSeconds, metadata.source);
|
|
683
|
+
|
|
684
|
+
// Or bypass the cache and fetch directly from Hetzner
|
|
685
|
+
const live = await fetchAuctionServers('EUR', { source: 'direct' });
|
|
686
|
+
|
|
687
|
+
console.log(getAuctionDataUrl('EUR'), live.serverCount);
|
|
688
|
+
|
|
689
|
+
// Filter: cheap ECC NVMe servers in Helsinki
|
|
690
|
+
const filtered = filterAuctionServers(servers, {
|
|
691
|
+
maxPrice: 60,
|
|
692
|
+
ecc: true,
|
|
693
|
+
diskType: 'nvme',
|
|
694
|
+
datacenter: 'HEL',
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Sort by price ascending
|
|
698
|
+
const sorted = sortAuctionServers(filtered, 'price', false);
|
|
699
|
+
|
|
700
|
+
console.log(`Found ${sorted.length} servers`);
|
|
701
|
+
for (const s of sorted) {
|
|
702
|
+
console.log(`${s.id}: ${s.cpu} | ${s.ram_size}GB | €${s.price}/mo | ${s.datacenter}`);
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Type Exports
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
import type {
|
|
710
|
+
// Robot types
|
|
711
|
+
Server, ServerDetails, Reset, ResetType,
|
|
712
|
+
RescueConfig, LinuxConfig,
|
|
713
|
+
IP, Subnet, Failover, Rdns,
|
|
714
|
+
SshKey, Firewall, FirewallRule,
|
|
715
|
+
VSwitch, StorageBox,
|
|
716
|
+
Traffic, Wol,
|
|
717
|
+
ServerProduct, ServerMarketProduct,
|
|
718
|
+
|
|
719
|
+
// Cloud types
|
|
720
|
+
CloudServer, CloudAction, CloudFirewall, CloudFirewallRule,
|
|
721
|
+
CloudSshKey, Network, NetworkSubnet, NetworkRoute,
|
|
722
|
+
FloatingIp, PrimaryIp, Volume, Image,
|
|
723
|
+
LoadBalancer, LoadBalancerType, LoadBalancerTarget, LoadBalancerService,
|
|
724
|
+
Certificate, PlacementGroup, Datacenter, Location,
|
|
725
|
+
ServerType, ISO, Labels,
|
|
726
|
+
|
|
727
|
+
// Auction types
|
|
728
|
+
AuctionServer, AuctionDiskData, AuctionIpPrice,
|
|
729
|
+
AuctionFilterOptions, AuctionResponse,
|
|
730
|
+
} from '@rollupdate/hctl';
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## API Reference
|
|
736
|
+
|
|
737
|
+
### HetznerRobotClient Methods
|
|
738
|
+
|
|
739
|
+
#### Servers
|
|
740
|
+
|
|
741
|
+
| Method | Description |
|
|
742
|
+
|--------|-------------|
|
|
743
|
+
| `listServers()` | List all servers |
|
|
744
|
+
| `getServer(id)` | Get server details |
|
|
745
|
+
| `updateServerName(id, name)` | Rename server |
|
|
746
|
+
| `getCancellation(id)` | Get cancellation status |
|
|
747
|
+
| `cancelServer(id, date?, reasons?)` | Cancel server |
|
|
748
|
+
| `revokeCancellation(id)` | Revoke cancellation |
|
|
749
|
+
|
|
750
|
+
#### Reset
|
|
751
|
+
|
|
752
|
+
| Method | Description |
|
|
753
|
+
|--------|-------------|
|
|
754
|
+
| `listResetOptions()` | List all reset options |
|
|
755
|
+
| `getResetOptions(id)` | Get reset options for server |
|
|
756
|
+
| `resetServer(id, type?)` | Reset server (sw/hw/man/power/power_long) |
|
|
757
|
+
|
|
758
|
+
#### Boot
|
|
759
|
+
|
|
760
|
+
| Method | Description |
|
|
761
|
+
|--------|-------------|
|
|
762
|
+
| `getBootConfig(id)` | Get all boot configs |
|
|
763
|
+
| `activateRescue(id, os, arch?, keys?)` | Activate rescue |
|
|
764
|
+
| `deactivateRescue(id)` | Deactivate rescue |
|
|
765
|
+
| `getLastRescue(id)` | Get last rescue activation |
|
|
766
|
+
| `getLinux(id)` | Get available Linux distributions |
|
|
767
|
+
| `activateLinux(id, dist, arch?, lang?, keys?)` | Activate Linux install |
|
|
768
|
+
| `deactivateLinux(id)` | Deactivate Linux |
|
|
769
|
+
| `activateVnc(id, dist, arch?, lang?)` | Activate VNC install |
|
|
770
|
+
| `activateWindows(id, dist, lang?)` | Activate Windows install |
|
|
771
|
+
|
|
772
|
+
#### IPs & Networking
|
|
773
|
+
|
|
774
|
+
| Method | Description |
|
|
775
|
+
|--------|-------------|
|
|
776
|
+
| `listIps()` | List all IPs |
|
|
777
|
+
| `getIp(ip)` | Get IP details |
|
|
778
|
+
| `updateIp(ip, warnings?, hourly?, daily?, monthly?)` | Update IP settings |
|
|
779
|
+
| `generateIpMac(ip)` | Generate separate MAC |
|
|
780
|
+
| `deleteIpMac(ip)` | Delete MAC |
|
|
781
|
+
| `listSubnets()` | List subnets |
|
|
782
|
+
| `getSubnet(ip)` | Get subnet details |
|
|
783
|
+
| `listFailovers()` | List failover IPs |
|
|
784
|
+
| `getFailover(ip)` | Get failover details |
|
|
785
|
+
| `switchFailover(ip, targetIp)` | Switch routing |
|
|
786
|
+
| `deleteFailoverRouting(ip)` | Delete routing |
|
|
787
|
+
| `listRdns()` | List reverse DNS |
|
|
788
|
+
| `getRdns(ip)` | Get rDNS for IP |
|
|
789
|
+
| `createRdns(ip, ptr)` | Create/update rDNS |
|
|
790
|
+
| `deleteRdns(ip)` | Delete rDNS |
|
|
791
|
+
|
|
792
|
+
#### SSH Keys
|
|
793
|
+
|
|
794
|
+
| Method | Description |
|
|
795
|
+
|--------|-------------|
|
|
796
|
+
| `listSshKeys()` | List SSH keys |
|
|
797
|
+
| `getSshKey(fingerprint)` | Get key details |
|
|
798
|
+
| `createSshKey(name, data)` | Add SSH key |
|
|
799
|
+
| `updateSshKey(fingerprint, name)` | Rename key |
|
|
800
|
+
| `deleteSshKey(fingerprint)` | Delete key |
|
|
801
|
+
|
|
802
|
+
#### Firewall
|
|
803
|
+
|
|
804
|
+
| Method | Description |
|
|
805
|
+
|--------|-------------|
|
|
806
|
+
| `getFirewall(id)` | Get firewall config |
|
|
807
|
+
| `updateFirewall(id, status, rules?)` | Update firewall |
|
|
808
|
+
| `deleteFirewall(id)` | Delete all rules |
|
|
809
|
+
| `listFirewallTemplates()` | List templates |
|
|
810
|
+
| `getFirewallTemplate(id)` | Get template |
|
|
811
|
+
| `deleteFirewallTemplate(id)` | Delete template |
|
|
812
|
+
|
|
813
|
+
#### vSwitch
|
|
814
|
+
|
|
815
|
+
| Method | Description |
|
|
816
|
+
|--------|-------------|
|
|
817
|
+
| `listVSwitches()` | List vSwitches |
|
|
818
|
+
| `getVSwitch(id)` | Get details |
|
|
819
|
+
| `listVSwitchesWithServers()` | Get details for every vSwitch, including servers |
|
|
820
|
+
| `addVSwitchServerNames(vswitches)` | Add server names to vSwitch details |
|
|
821
|
+
| `createVSwitch(name, vlan)` | Create |
|
|
822
|
+
| `updateVSwitch(id, name?, vlan?)` | Update |
|
|
823
|
+
| `deleteVSwitch(id, date?)` | Delete |
|
|
824
|
+
| `addServerToVSwitch(vswitchId, serverId)` | Add server |
|
|
825
|
+
| `removeServerFromVSwitch(vswitchId, serverId)` | Remove server |
|
|
826
|
+
|
|
827
|
+
#### Storage Box
|
|
828
|
+
|
|
829
|
+
| Method | Description |
|
|
830
|
+
|--------|-------------|
|
|
831
|
+
| `listStorageBoxes()` | List storage boxes |
|
|
832
|
+
| `getStorageBox(id)` | Get details |
|
|
833
|
+
| `updateStorageBox(id, ...)` | Update settings |
|
|
834
|
+
| `resetStorageBoxPassword(id)` | Reset password |
|
|
835
|
+
| `listStorageBoxSnapshots(id)` | List snapshots |
|
|
836
|
+
| `createStorageBoxSnapshot(id)` | Create snapshot |
|
|
837
|
+
| `deleteStorageBoxSnapshot(id, name)` | Delete snapshot |
|
|
838
|
+
| `revertStorageBoxSnapshot(id, name)` | Revert to snapshot |
|
|
839
|
+
| `listStorageBoxSubaccounts(id)` | List subaccounts |
|
|
840
|
+
| `createStorageBoxSubaccount(id, ...)` | Create subaccount |
|
|
841
|
+
| `deleteStorageBoxSubaccount(id, username)` | Delete subaccount |
|
|
842
|
+
|
|
843
|
+
#### Traffic, WoL, Ordering
|
|
844
|
+
|
|
845
|
+
| Method | Description |
|
|
846
|
+
|--------|-------------|
|
|
847
|
+
| `getTraffic(ips, subnets, from, to, type)` | Query traffic |
|
|
848
|
+
| `getWol(id)` | Get WoL status |
|
|
849
|
+
| `sendWol(id)` | Send Wake-on-LAN |
|
|
850
|
+
| `listServerProducts()` | List products |
|
|
851
|
+
| `listServerMarketProducts()` | List auction servers |
|
|
852
|
+
| `getServerTransaction(id)` | Get transaction |
|
|
853
|
+
| `listServerTransactions()` | List transactions |
|
|
854
|
+
|
|
855
|
+
### HetznerCloudClient Methods
|
|
856
|
+
|
|
857
|
+
#### Context Management
|
|
858
|
+
|
|
859
|
+
| Function | Description |
|
|
860
|
+
|----------|-------------|
|
|
861
|
+
| `createContext(name, token)` | Create a named context (stores token) |
|
|
862
|
+
| `useContext(name)` | Switch active context |
|
|
863
|
+
| `deleteContext(name)` | Delete a context |
|
|
864
|
+
| `listContexts()` | List all contexts |
|
|
865
|
+
| `getActiveContext()` | Get active context name |
|
|
866
|
+
| `resolveToken(opts)` | Resolve token (flag > env > context) |
|
|
867
|
+
|
|
868
|
+
#### Servers
|
|
869
|
+
|
|
870
|
+
| Method | Description |
|
|
871
|
+
|--------|-------------|
|
|
872
|
+
| `listServers(params?)` | List cloud servers |
|
|
873
|
+
| `getServer(id)` | Get server details |
|
|
874
|
+
| `createServer(data)` | Create a server |
|
|
875
|
+
| `deleteServer(id)` | Delete a server |
|
|
876
|
+
| `updateServer(id, data)` | Update server name/labels |
|
|
877
|
+
| `poweronServer(id)` | Power on |
|
|
878
|
+
| `poweroffServer(id)` | Hard power off |
|
|
879
|
+
| `rebootServer(id)` | Soft reboot |
|
|
880
|
+
| `resetServer(id)` | Hard reset |
|
|
881
|
+
| `shutdownServer(id)` | Graceful shutdown |
|
|
882
|
+
| `rebuildServer(id, image)` | Rebuild with image |
|
|
883
|
+
| `changeServerType(id, type, disk?)` | Resize server |
|
|
884
|
+
| `enableRescue(id, type?, keys?)` | Enable rescue mode |
|
|
885
|
+
| `disableRescue(id)` | Disable rescue mode |
|
|
886
|
+
| `enableBackup(id)` | Enable backups |
|
|
887
|
+
| `disableBackup(id)` | Disable backups |
|
|
888
|
+
| `createImage(id, desc?, type?)` | Create snapshot |
|
|
889
|
+
| `attachIso(id, iso)` | Attach ISO |
|
|
890
|
+
| `detachIso(id)` | Detach ISO |
|
|
891
|
+
| `resetPassword(id)` | Reset root password |
|
|
892
|
+
| `setRdns(id, ip, ptr)` | Set reverse DNS |
|
|
893
|
+
| `changeProtection(id, opts)` | Change protection |
|
|
894
|
+
| `requestConsole(id)` | Get VNC console URL |
|
|
895
|
+
| `attachToNetwork(id, network, ip?)` | Attach to network |
|
|
896
|
+
| `detachFromNetwork(id, network)` | Detach from network |
|
|
897
|
+
|
|
898
|
+
#### Networks
|
|
899
|
+
|
|
900
|
+
| Method | Description |
|
|
901
|
+
|--------|-------------|
|
|
902
|
+
| `listNetworks()` | List networks |
|
|
903
|
+
| `getNetwork(id)` | Get network details |
|
|
904
|
+
| `createNetwork(data)` | Create a network |
|
|
905
|
+
| `deleteNetwork(id)` | Delete a network |
|
|
906
|
+
| `addSubnet(id, subnet)` | Add subnet |
|
|
907
|
+
| `deleteSubnet(id, subnet)` | Delete subnet |
|
|
908
|
+
| `addRoute(id, route)` | Add route |
|
|
909
|
+
| `deleteRoute(id, route)` | Delete route |
|
|
910
|
+
| `changeIpRange(id, range)` | Change IP range |
|
|
911
|
+
|
|
912
|
+
#### Firewalls, IPs, Volumes, Load Balancers
|
|
913
|
+
|
|
914
|
+
| Method | Description |
|
|
915
|
+
|--------|-------------|
|
|
916
|
+
| `listFirewalls()` / `getFirewall(id)` / `createFirewall(data)` / `deleteFirewall(id)` | Firewall CRUD |
|
|
917
|
+
| `listFloatingIps()` / `getFloatingIp(id)` / `createFloatingIp(data)` / `deleteFloatingIp(id)` | Floating IP CRUD |
|
|
918
|
+
| `listPrimaryIps()` / `getPrimaryIp(id)` / `createPrimaryIp(data)` / `deletePrimaryIp(id)` | Primary IP CRUD |
|
|
919
|
+
| `listVolumes()` / `getVolume(id)` / `createVolume(data)` / `deleteVolume(id)` | Volume CRUD |
|
|
920
|
+
| `listLoadBalancers()` / `getLoadBalancer(id)` / `createLoadBalancer(data)` / `deleteLoadBalancer(id)` | Load Balancer CRUD |
|
|
921
|
+
| `listImages()` / `getImage(id)` / `updateImage(id, data)` / `deleteImage(id)` | Image management |
|
|
922
|
+
| `listSshKeys()` / `getSshKey(id)` / `createSshKey(data)` / `deleteSshKey(id)` | SSH key CRUD |
|
|
923
|
+
| `listCertificates()` / `getCertificate(id)` / `createCertificate(data)` / `deleteCertificate(id)` | Certificate CRUD |
|
|
924
|
+
| `listPlacementGroups()` / `getPlacementGroup(id)` / `createPlacementGroup(data)` / `deletePlacementGroup(id)` | Placement group CRUD |
|
|
925
|
+
|
|
926
|
+
#### Reference Data (Read-Only)
|
|
927
|
+
|
|
928
|
+
| Method | Description |
|
|
929
|
+
|--------|-------------|
|
|
930
|
+
| `listDatacenters()` / `getDatacenter(id)` | Datacenter info |
|
|
931
|
+
| `listLocations()` / `getLocation(id)` | Location info |
|
|
932
|
+
| `listServerTypes()` / `getServerType(id)` | Server type info |
|
|
933
|
+
| `listLoadBalancerTypes()` / `getLoadBalancerType(id)` | LB type info |
|
|
934
|
+
| `listIsos(params?)` / `getIso(id)` | ISO image info |
|
|
935
|
+
|
|
936
|
+
### Auction Functions
|
|
937
|
+
|
|
938
|
+
| Function | Description |
|
|
939
|
+
|----------|-------------|
|
|
940
|
+
| `fetchAuctionData(currency?, options?)` | Fetch servers plus freshness metadata (`updatedAt`, `ageSeconds`, `source`, `stale`, `url`) |
|
|
941
|
+
| `fetchAuctionServers(currency?, options?)` | Fetch all servers from hosted cache by default, or direct from Hetzner with `{ source: "direct" }` |
|
|
942
|
+
| `filterAuctionServers(servers, filters)` | Apply filter criteria to server array |
|
|
943
|
+
| `getAuctionDataUrl(currency?, options?)` | Resolve the JSON URL used for a cache or direct fetch |
|
|
944
|
+
| `sortAuctionServers(servers, field, desc)` | Sort servers by field |
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
## Configuration Files
|
|
949
|
+
|
|
950
|
+
| File | Purpose |
|
|
951
|
+
|------|---------|
|
|
952
|
+
| `~/.hctl/config.json` | Robot API credentials (file-based fallback) |
|
|
953
|
+
| `~/.hctl/cloud-contexts.json` | Cloud API contexts and tokens |
|
|
954
|
+
|
|
955
|
+
System keychain (via keytar):
|
|
956
|
+
|
|
957
|
+
| Key | Value |
|
|
958
|
+
|-----|-------|
|
|
959
|
+
| Service | `hctl` |
|
|
960
|
+
| Account | `robot-api` |
|
|
961
|
+
|
|
962
|
+
Environment variables:
|
|
963
|
+
|
|
964
|
+
| Variable | Purpose |
|
|
965
|
+
|----------|---------|
|
|
966
|
+
| `HETZNER_ROBOT_USER` | Robot API username |
|
|
967
|
+
| `HETZNER_ROBOT_PASSWORD` | Robot API password |
|
|
968
|
+
| `HETZNER_CLOUD_TOKEN` | Cloud API token (overrides active context) |
|
|
969
|
+
| `HCTL_AUCTION_SOURCE` | Set to `direct` to bypass the hosted auction cache |
|
|
970
|
+
| `HCTL_AUCTION_CACHE_URL` | Override the hosted auction cache base URL |
|
|
971
|
+
|
|
972
|
+
### Cloudflare Deployment
|
|
973
|
+
|
|
974
|
+
The website deploys to Cloudflare Pages via `.github/workflows/deploy-website.yml`. The auction cache deploys as a separate Cloudflare Worker via `.github/workflows/deploy-auction-worker.yml`; it refreshes EUR and USD auction JSON into R2 every 15 minutes with the Cron Trigger in `wrangler.auction.toml`.
|
|
975
|
+
|
|
976
|
+
The Worker validates that Hetzner returns JSON with a `server` array before updating R2. Refresh failures do not overwrite the last good snapshot; they are recorded under the metadata and health endpoints so monitors can detect degraded refreshes.
|
|
977
|
+
|
|
978
|
+
R2 object layout:
|
|
979
|
+
|
|
980
|
+
| Prefix | Purpose |
|
|
981
|
+
|--------|---------|
|
|
982
|
+
| `latest/live_data_sb_EUR.json` | Current EUR auction JSON |
|
|
983
|
+
| `latest/live_data_sb_USD.json` | Current USD auction JSON |
|
|
984
|
+
| `history/YYYY/MM/DD/HHmm/live_data_sb_EUR.json` | Historical EUR snapshots |
|
|
985
|
+
| `history/YYYY/MM/DD/HHmm/live_data_sb_USD.json` | Historical USD snapshots |
|
|
986
|
+
| `failures/EUR.json` | Last EUR refresh failure, when present |
|
|
987
|
+
| `failures/USD.json` | Last USD refresh failure, when present |
|
|
988
|
+
|
|
989
|
+
Required GitHub secret:
|
|
990
|
+
|
|
991
|
+
| Secret | Purpose |
|
|
992
|
+
|--------|---------|
|
|
993
|
+
| `CLOUDFLARE_API_TOKEN` | Token with Pages, Workers, Workers KV, and Workers R2 edit access |
|
|
994
|
+
|
|
995
|
+
Optional GitHub variables:
|
|
996
|
+
|
|
997
|
+
| Variable | Default |
|
|
998
|
+
|----------|---------|
|
|
999
|
+
| `CLOUDFLARE_ACCOUNT_ID` | `fffa2cbba41f8b0e145472fe76c90065` |
|
|
1000
|
+
| `CLOUDFLARE_PAGES_PROJECT_NAME` | `hctl` |
|
|
1001
|
+
| `WEBSITE_ORIGIN` | `https://hctl.dev` |
|
|
1002
|
+
|
|
1003
|
+
Initial setup:
|
|
1004
|
+
|
|
1005
|
+
```bash
|
|
1006
|
+
npx wrangler pages project create hctl --production-branch main
|
|
1007
|
+
npx wrangler r2 bucket create hctl-auction-data
|
|
1008
|
+
npx wrangler deploy --config wrangler.auction.toml
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
Manual deployments:
|
|
1012
|
+
|
|
1013
|
+
```bash
|
|
1014
|
+
gh workflow run deploy-website.yml
|
|
1015
|
+
gh workflow run deploy-auction-worker.yml
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
## Development
|
|
1021
|
+
|
|
1022
|
+
```bash
|
|
1023
|
+
npm install # Install dependencies
|
|
1024
|
+
npm run build # Compile TypeScript
|
|
1025
|
+
npm test # Run tests
|
|
1026
|
+
npm run test:coverage # Run with coverage report
|
|
1027
|
+
npm run test:watch # Watch mode
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
### Project Structure
|
|
1031
|
+
|
|
1032
|
+
```
|
|
1033
|
+
src/
|
|
1034
|
+
├── cli.ts # CLI entry point
|
|
1035
|
+
├── index.ts # Library exports
|
|
1036
|
+
├── shared/
|
|
1037
|
+
│ ├── config.ts # Credential management
|
|
1038
|
+
│ ├── formatter.ts # Colors, tables, output formatting
|
|
1039
|
+
│ ├── helpers.ts # asyncAction, output, confirmAction
|
|
1040
|
+
│ └── reference.ts # Built-in reference documentation
|
|
1041
|
+
├── robot/
|
|
1042
|
+
│ ├── client.ts # Robot API client (HetznerRobotClient)
|
|
1043
|
+
│ ├── types.ts # Robot type definitions
|
|
1044
|
+
│ ├── formatter.ts # Robot-specific formatters
|
|
1045
|
+
│ └── commands/ # CLI command modules (17 files)
|
|
1046
|
+
├── cloud/
|
|
1047
|
+
│ ├── client.ts # Cloud API client
|
|
1048
|
+
│ ├── context.ts # Context/token management
|
|
1049
|
+
│ ├── types.ts # Cloud type definitions
|
|
1050
|
+
│ ├── formatter.ts # Cloud-specific formatters
|
|
1051
|
+
│ ├── helpers.ts # Cloud action wrappers
|
|
1052
|
+
│ └── commands/ # CLI command modules (17 files)
|
|
1053
|
+
└── auction/
|
|
1054
|
+
├── client.ts # Auction fetch/filter/sort
|
|
1055
|
+
├── formatter.ts # Auction formatters
|
|
1056
|
+
└── commands.ts # Auction CLI commands
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
### AI Agent Instructions
|
|
1060
|
+
|
|
1061
|
+
This repo is developed with help from multiple coding agents. Each reads its own instruction file at the repo root:
|
|
1062
|
+
|
|
1063
|
+
- **Claude Code** — `.claude/CLAUDE.md` (project-scoped instructions)
|
|
1064
|
+
- **OpenAI Codex / generic agents** — `AGENTS.md`
|
|
1065
|
+
|
|
1066
|
+
Both files describe the same code-quality conventions (Ultracite + Biome), so agents produce consistent output regardless of which one is driving.
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## License
|
|
1071
|
+
|
|
1072
|
+
MIT
|