@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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { colorize, info, success } from "../../shared/formatter.js";
|
|
2
|
+
import { confirmAction, output } from "../../shared/helpers.js";
|
|
3
|
+
import { formatCloudServerDetails, formatCloudServerList, } from "../formatter.js";
|
|
4
|
+
import { cloudAction, parseLabels, resolveIdOrName, } from "../helpers.js";
|
|
5
|
+
function resolveServerId(client, idOrName) {
|
|
6
|
+
return resolveIdOrName(idOrName, "server", (name) => client.listServers({ name }));
|
|
7
|
+
}
|
|
8
|
+
async function handleServerList(client, options) {
|
|
9
|
+
const servers = await client.listServers({
|
|
10
|
+
label_selector: options.labelSelector,
|
|
11
|
+
name: options.name,
|
|
12
|
+
sort: options.sort,
|
|
13
|
+
status: options.status,
|
|
14
|
+
});
|
|
15
|
+
output(servers, formatCloudServerList, options);
|
|
16
|
+
}
|
|
17
|
+
async function handleServerDescribe(client, idOrName, options) {
|
|
18
|
+
const id = await resolveServerId(client, idOrName);
|
|
19
|
+
const srv = await client.getServer(id);
|
|
20
|
+
output(srv, formatCloudServerDetails, options);
|
|
21
|
+
}
|
|
22
|
+
async function handleServerCreate(client, options) {
|
|
23
|
+
const result = await client.createServer({
|
|
24
|
+
name: options.name,
|
|
25
|
+
server_type: options.type,
|
|
26
|
+
image: options.image,
|
|
27
|
+
location: options.location,
|
|
28
|
+
ssh_keys: options.sshKey,
|
|
29
|
+
user_data: options.userData,
|
|
30
|
+
labels: options.labels ? parseLabels(options.labels) : undefined,
|
|
31
|
+
firewalls: options.firewall?.map((id) => ({
|
|
32
|
+
firewall: Number.parseInt(id, 10),
|
|
33
|
+
})),
|
|
34
|
+
placement_group: options.placementGroup
|
|
35
|
+
? Number.parseInt(options.placementGroup, 10)
|
|
36
|
+
: undefined,
|
|
37
|
+
start_after_create: options.startAfterCreate,
|
|
38
|
+
});
|
|
39
|
+
console.log(success(`Server '${result.server.name}' created (ID: ${result.server.id})`));
|
|
40
|
+
if (result.root_password) {
|
|
41
|
+
console.log(`Root password: ${colorize(result.root_password, "yellow")}`);
|
|
42
|
+
}
|
|
43
|
+
console.log(info(`IPv4: ${result.server.public_net.ipv4?.ip || "pending"}`));
|
|
44
|
+
}
|
|
45
|
+
async function handleServerDelete(client, idOrName, options) {
|
|
46
|
+
const id = await resolveServerId(client, idOrName);
|
|
47
|
+
const srv = await client.getServer(id);
|
|
48
|
+
if (!(await confirmAction(`Delete server '${srv.name}' (ID: ${id})?`, options))) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
await client.deleteServer(id);
|
|
52
|
+
console.log(success(`Server '${srv.name}' (ID: ${id}) deleted.`));
|
|
53
|
+
}
|
|
54
|
+
async function handleServerUpdate(client, idOrName, options) {
|
|
55
|
+
const id = await resolveServerId(client, idOrName);
|
|
56
|
+
const { server: srv } = await client.updateServer(id, {
|
|
57
|
+
name: options.name,
|
|
58
|
+
});
|
|
59
|
+
console.log(success(`Server '${srv.name}' updated.`));
|
|
60
|
+
}
|
|
61
|
+
async function handleServerPoweron(client, idOrName) {
|
|
62
|
+
const id = await resolveServerId(client, idOrName);
|
|
63
|
+
await client.powerOnServer(id);
|
|
64
|
+
console.log(success(`Server ${id} powered on.`));
|
|
65
|
+
}
|
|
66
|
+
async function handleServerPoweroff(client, idOrName) {
|
|
67
|
+
const id = await resolveServerId(client, idOrName);
|
|
68
|
+
await client.powerOffServer(id);
|
|
69
|
+
console.log(success(`Server ${id} powered off.`));
|
|
70
|
+
}
|
|
71
|
+
async function handleServerReboot(client, idOrName) {
|
|
72
|
+
const id = await resolveServerId(client, idOrName);
|
|
73
|
+
await client.rebootServer(id);
|
|
74
|
+
console.log(success(`Server ${id} rebooted.`));
|
|
75
|
+
}
|
|
76
|
+
async function handleServerReset(client, idOrName) {
|
|
77
|
+
const id = await resolveServerId(client, idOrName);
|
|
78
|
+
await client.resetServer(id);
|
|
79
|
+
console.log(success(`Server ${id} reset.`));
|
|
80
|
+
}
|
|
81
|
+
async function handleServerShutdown(client, idOrName) {
|
|
82
|
+
const id = await resolveServerId(client, idOrName);
|
|
83
|
+
await client.shutdownServer(id);
|
|
84
|
+
console.log(success(`Server ${id} shutdown initiated.`));
|
|
85
|
+
}
|
|
86
|
+
async function handleServerRebuild(client, idOrName, options) {
|
|
87
|
+
const id = await resolveServerId(client, idOrName);
|
|
88
|
+
const result = await client.rebuildServer(id, options.image);
|
|
89
|
+
console.log(success(`Server ${id} rebuilding.`));
|
|
90
|
+
if (result.root_password) {
|
|
91
|
+
console.log(`Root password: ${colorize(result.root_password, "yellow")}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function handleServerChangeType(client, idOrName, options) {
|
|
95
|
+
const id = await resolveServerId(client, idOrName);
|
|
96
|
+
await client.changeServerType(id, options.type, !!options.upgradeDisk);
|
|
97
|
+
console.log(success(`Server ${id} type change initiated.`));
|
|
98
|
+
}
|
|
99
|
+
async function handleServerEnableRescue(client, idOrName, options) {
|
|
100
|
+
const id = await resolveServerId(client, idOrName);
|
|
101
|
+
const result = await client.enableServerRescue(id, options.type, options.sshKey?.map(Number));
|
|
102
|
+
console.log(success("Rescue mode enabled."));
|
|
103
|
+
console.log(`Root password: ${colorize(result.root_password, "yellow")}`);
|
|
104
|
+
}
|
|
105
|
+
async function handleServerDisableRescue(client, idOrName) {
|
|
106
|
+
const id = await resolveServerId(client, idOrName);
|
|
107
|
+
await client.disableServerRescue(id);
|
|
108
|
+
console.log(success("Rescue mode disabled."));
|
|
109
|
+
}
|
|
110
|
+
async function handleServerEnableBackup(client, idOrName) {
|
|
111
|
+
const id = await resolveServerId(client, idOrName);
|
|
112
|
+
await client.enableServerBackup(id);
|
|
113
|
+
console.log(success(`Backups enabled for server ${id}.`));
|
|
114
|
+
}
|
|
115
|
+
async function handleServerDisableBackup(client, idOrName) {
|
|
116
|
+
const id = await resolveServerId(client, idOrName);
|
|
117
|
+
await client.disableServerBackup(id);
|
|
118
|
+
console.log(success(`Backups disabled for server ${id}.`));
|
|
119
|
+
}
|
|
120
|
+
async function handleServerCreateImage(client, idOrName, options) {
|
|
121
|
+
const id = await resolveServerId(client, idOrName);
|
|
122
|
+
const result = await client.createServerImage(id, {
|
|
123
|
+
description: options.description,
|
|
124
|
+
type: options.type,
|
|
125
|
+
});
|
|
126
|
+
console.log(success(`Image created (ID: ${result.image.id})`));
|
|
127
|
+
}
|
|
128
|
+
async function handleServerAttachIso(client, idOrName, options) {
|
|
129
|
+
const id = await resolveServerId(client, idOrName);
|
|
130
|
+
await client.attachIsoToServer(id, options.iso);
|
|
131
|
+
console.log(success(`ISO attached to server ${id}.`));
|
|
132
|
+
}
|
|
133
|
+
async function handleServerDetachIso(client, idOrName) {
|
|
134
|
+
const id = await resolveServerId(client, idOrName);
|
|
135
|
+
await client.detachIsoFromServer(id);
|
|
136
|
+
console.log(success(`ISO detached from server ${id}.`));
|
|
137
|
+
}
|
|
138
|
+
async function handleServerResetPassword(client, idOrName) {
|
|
139
|
+
const id = await resolveServerId(client, idOrName);
|
|
140
|
+
const result = await client.resetServerPassword(id);
|
|
141
|
+
console.log(success("Root password reset."));
|
|
142
|
+
console.log(`New password: ${colorize(result.root_password, "yellow")}`);
|
|
143
|
+
}
|
|
144
|
+
async function handleServerSetRdns(client, idOrName, options) {
|
|
145
|
+
const id = await resolveServerId(client, idOrName);
|
|
146
|
+
await client.setServerRdns(id, options.ip, options.dnsPtr);
|
|
147
|
+
console.log(success(`rDNS set: ${options.ip} -> ${options.dnsPtr}`));
|
|
148
|
+
}
|
|
149
|
+
async function handleServerEnableProtection(client, idOrName, options) {
|
|
150
|
+
const id = await resolveServerId(client, idOrName);
|
|
151
|
+
await client.enableServerProtection(id, {
|
|
152
|
+
delete: options.delete,
|
|
153
|
+
rebuild: options.rebuild,
|
|
154
|
+
});
|
|
155
|
+
console.log(success(`Protection enabled for server ${id}.`));
|
|
156
|
+
}
|
|
157
|
+
async function handleServerDisableProtection(client, idOrName) {
|
|
158
|
+
const id = await resolveServerId(client, idOrName);
|
|
159
|
+
await client.enableServerProtection(id, {
|
|
160
|
+
delete: false,
|
|
161
|
+
rebuild: false,
|
|
162
|
+
});
|
|
163
|
+
console.log(success(`Protection disabled for server ${id}.`));
|
|
164
|
+
}
|
|
165
|
+
async function handleServerRequestConsole(client, idOrName) {
|
|
166
|
+
const id = await resolveServerId(client, idOrName);
|
|
167
|
+
const result = await client.requestServerConsole(id);
|
|
168
|
+
console.log(success("Console ready."));
|
|
169
|
+
console.log(`WebSocket URL: ${result.wss_url}`);
|
|
170
|
+
console.log(`Password: ${colorize(result.password, "yellow")}`);
|
|
171
|
+
}
|
|
172
|
+
async function handleServerAttachToNetwork(client, idOrName, options) {
|
|
173
|
+
const id = await resolveServerId(client, idOrName);
|
|
174
|
+
await client.attachServerToNetwork(id, Number.parseInt(options.network, 10), options.ip);
|
|
175
|
+
console.log(success(`Server ${id} attached to network ${options.network}.`));
|
|
176
|
+
}
|
|
177
|
+
async function handleServerDetachFromNetwork(client, idOrName, options) {
|
|
178
|
+
const id = await resolveServerId(client, idOrName);
|
|
179
|
+
await client.detachServerFromNetwork(id, Number.parseInt(options.network, 10));
|
|
180
|
+
console.log(success(`Server ${id} detached from network ${options.network}.`));
|
|
181
|
+
}
|
|
182
|
+
async function handleServerAddLabel(client, idOrName, label) {
|
|
183
|
+
const id = await resolveServerId(client, idOrName);
|
|
184
|
+
const srv = await client.getServer(id);
|
|
185
|
+
const [key, value] = label.split("=");
|
|
186
|
+
const labels = { ...srv.labels, [key]: value || "" };
|
|
187
|
+
await client.updateServer(id, { labels });
|
|
188
|
+
console.log(success(`Label '${key}' added to server ${id}.`));
|
|
189
|
+
}
|
|
190
|
+
async function handleServerRemoveLabel(client, idOrName, key) {
|
|
191
|
+
const id = await resolveServerId(client, idOrName);
|
|
192
|
+
const srv = await client.getServer(id);
|
|
193
|
+
const labels = Object.fromEntries(Object.entries(srv.labels).filter(([k]) => k !== key));
|
|
194
|
+
await client.updateServer(id, { labels });
|
|
195
|
+
console.log(success(`Label '${key}' removed from server ${id}.`));
|
|
196
|
+
}
|
|
197
|
+
export function registerCloudServerCommands(parent) {
|
|
198
|
+
const server = parent
|
|
199
|
+
.command("server")
|
|
200
|
+
.description("Cloud server management");
|
|
201
|
+
server
|
|
202
|
+
.command("list")
|
|
203
|
+
.alias("ls")
|
|
204
|
+
.description("List all servers")
|
|
205
|
+
.option("-l, --label-selector <selector>", "Label selector")
|
|
206
|
+
.option("-n, --name <name>", "Filter by name")
|
|
207
|
+
.option("-s, --sort <field>", "Sort by field")
|
|
208
|
+
.option("--status <status>", "Filter by status")
|
|
209
|
+
.action(cloudAction(handleServerList));
|
|
210
|
+
server
|
|
211
|
+
.command("describe <id-or-name>")
|
|
212
|
+
.description("Show server details")
|
|
213
|
+
.action(cloudAction(handleServerDescribe));
|
|
214
|
+
server
|
|
215
|
+
.command("create")
|
|
216
|
+
.description("Create a new server")
|
|
217
|
+
.requiredOption("--name <name>", "Server name")
|
|
218
|
+
.requiredOption("--type <type>", "Server type")
|
|
219
|
+
.requiredOption("--image <image>", "Image to use")
|
|
220
|
+
.option("--location <location>", "Location")
|
|
221
|
+
.option("--ssh-key <keys...>", "SSH key IDs or names")
|
|
222
|
+
.option("--user-data <data>", "Cloud-init user data")
|
|
223
|
+
.option("--labels <labels>", "Labels as key=value pairs (comma-separated)")
|
|
224
|
+
.option("--firewall <ids...>", "Firewall IDs to apply")
|
|
225
|
+
.option("--placement-group <id>", "Placement group ID")
|
|
226
|
+
.option("--start-after-create", "Start server after creation", true)
|
|
227
|
+
.option("--no-start-after-create", "Do not start server after creation")
|
|
228
|
+
.action(cloudAction(handleServerCreate));
|
|
229
|
+
server
|
|
230
|
+
.command("delete <id-or-name>")
|
|
231
|
+
.description("Delete a server")
|
|
232
|
+
.option("-y, --yes", "Skip confirmation")
|
|
233
|
+
.action(cloudAction(handleServerDelete));
|
|
234
|
+
server
|
|
235
|
+
.command("update <id-or-name>")
|
|
236
|
+
.description("Update server")
|
|
237
|
+
.option("--name <name>", "New name")
|
|
238
|
+
.action(cloudAction(handleServerUpdate));
|
|
239
|
+
server
|
|
240
|
+
.command("poweron <id-or-name>")
|
|
241
|
+
.description("Power on a server")
|
|
242
|
+
.action(cloudAction(handleServerPoweron));
|
|
243
|
+
server
|
|
244
|
+
.command("poweroff <id-or-name>")
|
|
245
|
+
.description("Power off a server (hard)")
|
|
246
|
+
.action(cloudAction(handleServerPoweroff));
|
|
247
|
+
server
|
|
248
|
+
.command("reboot <id-or-name>")
|
|
249
|
+
.description("Soft reboot a server")
|
|
250
|
+
.action(cloudAction(handleServerReboot));
|
|
251
|
+
server
|
|
252
|
+
.command("reset <id-or-name>")
|
|
253
|
+
.description("Hard reset a server")
|
|
254
|
+
.action(cloudAction(handleServerReset));
|
|
255
|
+
server
|
|
256
|
+
.command("shutdown <id-or-name>")
|
|
257
|
+
.description("Gracefully shutdown a server (ACPI)")
|
|
258
|
+
.action(cloudAction(handleServerShutdown));
|
|
259
|
+
server
|
|
260
|
+
.command("rebuild <id-or-name>")
|
|
261
|
+
.description("Rebuild a server with a new image")
|
|
262
|
+
.requiredOption("--image <image>", "Image to rebuild with")
|
|
263
|
+
.action(cloudAction(handleServerRebuild));
|
|
264
|
+
server
|
|
265
|
+
.command("change-type <id-or-name>")
|
|
266
|
+
.description("Change server type (resize)")
|
|
267
|
+
.requiredOption("--type <type>", "New server type")
|
|
268
|
+
.option("--upgrade-disk", "Upgrade disk size (irreversible)", false)
|
|
269
|
+
.action(cloudAction(handleServerChangeType));
|
|
270
|
+
server
|
|
271
|
+
.command("enable-rescue <id-or-name>")
|
|
272
|
+
.description("Enable rescue mode")
|
|
273
|
+
.option("--type <type>", "Rescue system type", "linux64")
|
|
274
|
+
.option("--ssh-key <keys...>", "SSH key IDs")
|
|
275
|
+
.action(cloudAction(handleServerEnableRescue));
|
|
276
|
+
server
|
|
277
|
+
.command("disable-rescue <id-or-name>")
|
|
278
|
+
.description("Disable rescue mode")
|
|
279
|
+
.action(cloudAction(handleServerDisableRescue));
|
|
280
|
+
server
|
|
281
|
+
.command("enable-backup <id-or-name>")
|
|
282
|
+
.description("Enable automatic backups")
|
|
283
|
+
.action(cloudAction(handleServerEnableBackup));
|
|
284
|
+
server
|
|
285
|
+
.command("disable-backup <id-or-name>")
|
|
286
|
+
.description("Disable automatic backups")
|
|
287
|
+
.action(cloudAction(handleServerDisableBackup));
|
|
288
|
+
server
|
|
289
|
+
.command("create-image <id-or-name>")
|
|
290
|
+
.description("Create an image (snapshot) from server")
|
|
291
|
+
.option("--description <desc>", "Image description")
|
|
292
|
+
.option("--type <type>", "Image type (snapshot or backup)", "snapshot")
|
|
293
|
+
.action(cloudAction(handleServerCreateImage));
|
|
294
|
+
server
|
|
295
|
+
.command("attach-iso <id-or-name>")
|
|
296
|
+
.description("Attach an ISO to a server")
|
|
297
|
+
.requiredOption("--iso <iso>", "ISO name or ID")
|
|
298
|
+
.action(cloudAction(handleServerAttachIso));
|
|
299
|
+
server
|
|
300
|
+
.command("detach-iso <id-or-name>")
|
|
301
|
+
.description("Detach ISO from a server")
|
|
302
|
+
.action(cloudAction(handleServerDetachIso));
|
|
303
|
+
server
|
|
304
|
+
.command("reset-password <id-or-name>")
|
|
305
|
+
.description("Reset server root password")
|
|
306
|
+
.action(cloudAction(handleServerResetPassword));
|
|
307
|
+
server
|
|
308
|
+
.command("set-rdns <id-or-name>")
|
|
309
|
+
.description("Set reverse DNS for server")
|
|
310
|
+
.requiredOption("--ip <ip>", "IP address")
|
|
311
|
+
.requiredOption("--dns-ptr <ptr>", "DNS pointer")
|
|
312
|
+
.action(cloudAction(handleServerSetRdns));
|
|
313
|
+
server
|
|
314
|
+
.command("enable-protection <id-or-name>")
|
|
315
|
+
.description("Enable server protection")
|
|
316
|
+
.option("--delete", "Enable delete protection", true)
|
|
317
|
+
.option("--rebuild", "Enable rebuild protection", false)
|
|
318
|
+
.action(cloudAction(handleServerEnableProtection));
|
|
319
|
+
server
|
|
320
|
+
.command("disable-protection <id-or-name>")
|
|
321
|
+
.description("Disable server protection")
|
|
322
|
+
.action(cloudAction(handleServerDisableProtection));
|
|
323
|
+
server
|
|
324
|
+
.command("request-console <id-or-name>")
|
|
325
|
+
.description("Request a WebSocket VNC console")
|
|
326
|
+
.action(cloudAction(handleServerRequestConsole));
|
|
327
|
+
server
|
|
328
|
+
.command("attach-to-network <id-or-name>")
|
|
329
|
+
.description("Attach server to a network")
|
|
330
|
+
.requiredOption("--network <network>", "Network ID")
|
|
331
|
+
.option("--ip <ip>", "IP address in network")
|
|
332
|
+
.action(cloudAction(handleServerAttachToNetwork));
|
|
333
|
+
server
|
|
334
|
+
.command("detach-from-network <id-or-name>")
|
|
335
|
+
.description("Detach server from a network")
|
|
336
|
+
.requiredOption("--network <network>", "Network ID")
|
|
337
|
+
.action(cloudAction(handleServerDetachFromNetwork));
|
|
338
|
+
server
|
|
339
|
+
.command("add-label <id-or-name> <label>")
|
|
340
|
+
.description("Add a label (key=value)")
|
|
341
|
+
.action(cloudAction(handleServerAddLabel));
|
|
342
|
+
server
|
|
343
|
+
.command("remove-label <id-or-name> <key>")
|
|
344
|
+
.description("Remove a label by key")
|
|
345
|
+
.action(cloudAction(handleServerRemoveLabel));
|
|
346
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { error, success } from "../../shared/formatter.js";
|
|
3
|
+
import { confirmAction, output } from "../../shared/helpers.js";
|
|
4
|
+
import { formatCloudSshKeyDetails, formatCloudSshKeyList, } from "../formatter.js";
|
|
5
|
+
import { cloudAction, resolveIdOrName, } from "../helpers.js";
|
|
6
|
+
export function registerCloudSshKeyCommands(parent) {
|
|
7
|
+
const sshKey = parent.command("ssh-key").description("SSH key management");
|
|
8
|
+
sshKey
|
|
9
|
+
.command("list")
|
|
10
|
+
.alias("ls")
|
|
11
|
+
.description("List all SSH keys")
|
|
12
|
+
.option("-l, --label-selector <selector>", "Label selector")
|
|
13
|
+
.option("-s, --sort <field>", "Sort by field")
|
|
14
|
+
.action(cloudAction(async (client, options) => {
|
|
15
|
+
const keys = await client.listSshKeys({
|
|
16
|
+
label_selector: options.labelSelector,
|
|
17
|
+
sort: options.sort,
|
|
18
|
+
});
|
|
19
|
+
output(keys, formatCloudSshKeyList, options);
|
|
20
|
+
}));
|
|
21
|
+
sshKey
|
|
22
|
+
.command("describe <id-or-name>")
|
|
23
|
+
.description("Show SSH key details")
|
|
24
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
25
|
+
const id = await resolveIdOrName(idOrName, "SSH key", (name) => client.listSshKeys({ name }));
|
|
26
|
+
const key = await client.getSshKey(id);
|
|
27
|
+
output(key, formatCloudSshKeyDetails, options);
|
|
28
|
+
}));
|
|
29
|
+
sshKey
|
|
30
|
+
.command("create")
|
|
31
|
+
.description("Create an SSH key")
|
|
32
|
+
.requiredOption("--name <name>", "Key name")
|
|
33
|
+
.option("--public-key <key>", "Public key string")
|
|
34
|
+
.option("--public-key-from-file <path>", "Read public key from file")
|
|
35
|
+
.action(cloudAction(async (client, options) => {
|
|
36
|
+
let pubKey = options.publicKey;
|
|
37
|
+
if (options.publicKeyFromFile) {
|
|
38
|
+
pubKey = readFileSync(options.publicKeyFromFile, "utf-8").trim();
|
|
39
|
+
}
|
|
40
|
+
if (!pubKey) {
|
|
41
|
+
console.error(error("Provide --public-key or --public-key-from-file"));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const { ssh_key: key } = await client.createSshKey({
|
|
45
|
+
name: options.name,
|
|
46
|
+
public_key: pubKey,
|
|
47
|
+
});
|
|
48
|
+
console.log(success(`SSH key '${key.name}' created (ID: ${key.id})`));
|
|
49
|
+
}));
|
|
50
|
+
sshKey
|
|
51
|
+
.command("delete <id-or-name>")
|
|
52
|
+
.description("Delete an SSH key")
|
|
53
|
+
.option("-y, --yes", "Skip confirmation")
|
|
54
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
55
|
+
const id = await resolveIdOrName(idOrName, "SSH key", (name) => client.listSshKeys({ name }));
|
|
56
|
+
const key = await client.getSshKey(id);
|
|
57
|
+
if (!(await confirmAction(`Delete SSH key '${key.name}' (ID: ${id})?`, options))) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await client.deleteSshKey(id);
|
|
61
|
+
console.log(success(`SSH key '${key.name}' (ID: ${id}) deleted.`));
|
|
62
|
+
}));
|
|
63
|
+
sshKey
|
|
64
|
+
.command("update <id-or-name>")
|
|
65
|
+
.description("Update SSH key")
|
|
66
|
+
.option("--name <name>", "New name")
|
|
67
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
68
|
+
const id = await resolveIdOrName(idOrName, "SSH key", (name) => client.listSshKeys({ name }));
|
|
69
|
+
await client.updateSshKey(id, { name: options.name });
|
|
70
|
+
console.log(success(`SSH key ${id} updated.`));
|
|
71
|
+
}));
|
|
72
|
+
sshKey
|
|
73
|
+
.command("add-label <id-or-name> <label>")
|
|
74
|
+
.description("Add a label (key=value)")
|
|
75
|
+
.action(cloudAction(async (client, idOrName, label) => {
|
|
76
|
+
const id = await resolveIdOrName(idOrName, "SSH key", (name) => client.listSshKeys({ name }));
|
|
77
|
+
const key = await client.getSshKey(id);
|
|
78
|
+
const [k, v] = label.split("=");
|
|
79
|
+
await client.updateSshKey(id, {
|
|
80
|
+
labels: { ...key.labels, [k]: v || "" },
|
|
81
|
+
});
|
|
82
|
+
console.log(success(`Label '${k}' added.`));
|
|
83
|
+
}));
|
|
84
|
+
sshKey
|
|
85
|
+
.command("remove-label <id-or-name> <key>")
|
|
86
|
+
.description("Remove a label")
|
|
87
|
+
.action(cloudAction(async (client, idOrName, key) => {
|
|
88
|
+
const id = await resolveIdOrName(idOrName, "SSH key", (name) => client.listSshKeys({ name }));
|
|
89
|
+
const sshKeyData = await client.getSshKey(id);
|
|
90
|
+
const labels = Object.fromEntries(Object.entries(sshKeyData.labels).filter(([k]) => k !== key));
|
|
91
|
+
await client.updateSshKey(id, { labels });
|
|
92
|
+
console.log(success(`Label '${key}' removed.`));
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { success } from "../../shared/formatter.js";
|
|
2
|
+
import { confirmAction, output } from "../../shared/helpers.js";
|
|
3
|
+
import { formatVolumeDetails, formatVolumeList } from "../formatter.js";
|
|
4
|
+
import { cloudAction, resolveIdOrName, } from "../helpers.js";
|
|
5
|
+
export function registerVolumeCommands(parent) {
|
|
6
|
+
const volume = parent.command("volume").description("Volume management");
|
|
7
|
+
volume
|
|
8
|
+
.command("list")
|
|
9
|
+
.alias("ls")
|
|
10
|
+
.description("List all volumes")
|
|
11
|
+
.option("-l, --label-selector <selector>", "Label selector")
|
|
12
|
+
.option("-s, --sort <field>", "Sort by field")
|
|
13
|
+
.option("--status <status>", "Filter by status")
|
|
14
|
+
.action(cloudAction(async (client, options) => {
|
|
15
|
+
const volumes = await client.listVolumes({
|
|
16
|
+
label_selector: options.labelSelector,
|
|
17
|
+
sort: options.sort,
|
|
18
|
+
status: options.status,
|
|
19
|
+
});
|
|
20
|
+
output(volumes, formatVolumeList, options);
|
|
21
|
+
}));
|
|
22
|
+
volume
|
|
23
|
+
.command("describe <id-or-name>")
|
|
24
|
+
.description("Show volume details")
|
|
25
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
26
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
27
|
+
const vol = await client.getVolume(id);
|
|
28
|
+
output(vol, formatVolumeDetails, options);
|
|
29
|
+
}));
|
|
30
|
+
volume
|
|
31
|
+
.command("create")
|
|
32
|
+
.description("Create a volume")
|
|
33
|
+
.requiredOption("--name <name>", "Volume name")
|
|
34
|
+
.requiredOption("--size <size>", "Size in GB")
|
|
35
|
+
.option("--location <loc>", "Location")
|
|
36
|
+
.option("--server <id>", "Server to attach to")
|
|
37
|
+
.option("--format <format>", "Filesystem format (ext4, xfs)")
|
|
38
|
+
.option("--automount", "Auto-mount on server")
|
|
39
|
+
.action(cloudAction(async (client, options) => {
|
|
40
|
+
const { volume: vol } = await client.createVolume({
|
|
41
|
+
name: options.name,
|
|
42
|
+
size: Number.parseInt(options.size, 10),
|
|
43
|
+
location: options.location,
|
|
44
|
+
server: options.server
|
|
45
|
+
? Number.parseInt(options.server, 10)
|
|
46
|
+
: undefined,
|
|
47
|
+
format: options.format,
|
|
48
|
+
automount: options.automount,
|
|
49
|
+
});
|
|
50
|
+
console.log(success(`Volume '${vol.name}' created (ID: ${vol.id}, ${vol.size} GB)`));
|
|
51
|
+
}));
|
|
52
|
+
volume
|
|
53
|
+
.command("delete <id-or-name>")
|
|
54
|
+
.description("Delete a volume")
|
|
55
|
+
.option("-y, --yes", "Skip confirmation")
|
|
56
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
57
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
58
|
+
const vol = await client.getVolume(id);
|
|
59
|
+
if (!(await confirmAction(`Delete volume '${vol.name}' (ID: ${id})?`, options))) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await client.deleteVolume(id);
|
|
63
|
+
console.log(success(`Volume '${vol.name}' (ID: ${id}) deleted.`));
|
|
64
|
+
}));
|
|
65
|
+
volume
|
|
66
|
+
.command("update <id-or-name>")
|
|
67
|
+
.description("Update volume")
|
|
68
|
+
.option("--name <name>", "New name")
|
|
69
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
70
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
71
|
+
await client.updateVolume(id, { name: options.name });
|
|
72
|
+
console.log(success(`Volume ${id} updated.`));
|
|
73
|
+
}));
|
|
74
|
+
volume
|
|
75
|
+
.command("attach <id-or-name> <server>")
|
|
76
|
+
.description("Attach volume to server")
|
|
77
|
+
.option("--automount", "Auto-mount on server")
|
|
78
|
+
.action(cloudAction(async (client, idOrName, server, options) => {
|
|
79
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
80
|
+
await client.attachVolume(id, Number.parseInt(server, 10), options.automount);
|
|
81
|
+
console.log(success(`Volume ${id} attached to server ${server}.`));
|
|
82
|
+
}));
|
|
83
|
+
volume
|
|
84
|
+
.command("detach <id-or-name>")
|
|
85
|
+
.description("Detach volume from server")
|
|
86
|
+
.action(cloudAction(async (client, idOrName) => {
|
|
87
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
88
|
+
await client.detachVolume(id);
|
|
89
|
+
console.log(success(`Volume ${id} detached.`));
|
|
90
|
+
}));
|
|
91
|
+
volume
|
|
92
|
+
.command("resize <id-or-name>")
|
|
93
|
+
.description("Resize a volume")
|
|
94
|
+
.requiredOption("--size <size>", "New size in GB")
|
|
95
|
+
.action(cloudAction(async (client, idOrName, options) => {
|
|
96
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
97
|
+
await client.resizeVolume(id, Number.parseInt(options.size, 10));
|
|
98
|
+
console.log(success(`Volume ${id} resized to ${options.size} GB.`));
|
|
99
|
+
}));
|
|
100
|
+
volume
|
|
101
|
+
.command("enable-protection <id-or-name>")
|
|
102
|
+
.description("Enable delete protection")
|
|
103
|
+
.action(cloudAction(async (client, idOrName) => {
|
|
104
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
105
|
+
await client.changeVolumeProtection(id, true);
|
|
106
|
+
console.log(success(`Protection enabled for volume ${id}.`));
|
|
107
|
+
}));
|
|
108
|
+
volume
|
|
109
|
+
.command("disable-protection <id-or-name>")
|
|
110
|
+
.description("Disable delete protection")
|
|
111
|
+
.action(cloudAction(async (client, idOrName) => {
|
|
112
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
113
|
+
await client.changeVolumeProtection(id, false);
|
|
114
|
+
console.log(success(`Protection disabled for volume ${id}.`));
|
|
115
|
+
}));
|
|
116
|
+
volume
|
|
117
|
+
.command("add-label <id-or-name> <label>")
|
|
118
|
+
.description("Add a label (key=value)")
|
|
119
|
+
.action(cloudAction(async (client, idOrName, label) => {
|
|
120
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
121
|
+
const vol = await client.getVolume(id);
|
|
122
|
+
const [key, value] = label.split("=");
|
|
123
|
+
await client.updateVolume(id, {
|
|
124
|
+
labels: { ...vol.labels, [key]: value || "" },
|
|
125
|
+
});
|
|
126
|
+
console.log(success(`Label '${key}' added.`));
|
|
127
|
+
}));
|
|
128
|
+
volume
|
|
129
|
+
.command("remove-label <id-or-name> <key>")
|
|
130
|
+
.description("Remove a label")
|
|
131
|
+
.action(cloudAction(async (client, idOrName, key) => {
|
|
132
|
+
const id = await resolveIdOrName(idOrName, "volume", (name) => client.listVolumes({ name }));
|
|
133
|
+
const vol = await client.getVolume(id);
|
|
134
|
+
const labels = Object.fromEntries(Object.entries(vol.labels).filter(([k]) => k !== key));
|
|
135
|
+
await client.updateVolume(id, { labels });
|
|
136
|
+
console.log(success(`Label '${key}' removed.`));
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new cloud context.
|
|
3
|
+
*/
|
|
4
|
+
export declare function createContext(name: string, token: string, options?: {
|
|
5
|
+
verify?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Set the active context.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useContext(name: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Delete a context.
|
|
13
|
+
*/
|
|
14
|
+
export declare function deleteContext(name: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* List all contexts.
|
|
17
|
+
*/
|
|
18
|
+
export declare function listContexts(): {
|
|
19
|
+
name: string;
|
|
20
|
+
active: boolean;
|
|
21
|
+
}[];
|
|
22
|
+
/**
|
|
23
|
+
* Get the active context name.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getActiveContext(): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the cloud API token.
|
|
28
|
+
* Priority: --token flag > HETZNER_CLOUD_TOKEN env > active context
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveToken(flagToken?: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Validate that a token authenticates against the Hetzner Cloud API.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateCloudToken(token: string): Promise<void>;
|