@ebowwa/hetzner 0.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/actions.js +802 -0
- package/actions.ts +1053 -0
- package/auth.js +35 -0
- package/auth.ts +37 -0
- package/bootstrap/FIREWALL.md +326 -0
- package/bootstrap/KERNEL-HARDENING.md +258 -0
- package/bootstrap/SECURITY-INTEGRATION.md +281 -0
- package/bootstrap/TESTING.md +301 -0
- package/bootstrap/cloud-init.js +279 -0
- package/bootstrap/cloud-init.ts +394 -0
- package/bootstrap/firewall.js +279 -0
- package/bootstrap/firewall.ts +342 -0
- package/bootstrap/genesis.js +406 -0
- package/bootstrap/genesis.ts +518 -0
- package/bootstrap/index.js +35 -0
- package/bootstrap/index.ts +71 -0
- package/bootstrap/kernel-hardening.js +266 -0
- package/bootstrap/kernel-hardening.test.ts +230 -0
- package/bootstrap/kernel-hardening.ts +272 -0
- package/bootstrap/security-audit.js +118 -0
- package/bootstrap/security-audit.ts +124 -0
- package/bootstrap/ssh-hardening.js +182 -0
- package/bootstrap/ssh-hardening.ts +192 -0
- package/client.js +137 -0
- package/client.ts +177 -0
- package/config.js +5 -0
- package/config.ts +5 -0
- package/errors.js +270 -0
- package/errors.ts +371 -0
- package/index.js +28 -0
- package/index.ts +55 -0
- package/package.json +56 -0
- package/pricing.js +284 -0
- package/pricing.ts +422 -0
- package/schemas.js +660 -0
- package/schemas.ts +765 -0
- package/server-status.ts +81 -0
- package/servers.js +424 -0
- package/servers.ts +568 -0
- package/ssh-keys.js +90 -0
- package/ssh-keys.ts +122 -0
- package/ssh-setup.ts +218 -0
- package/types.js +96 -0
- package/types.ts +389 -0
- package/volumes.js +172 -0
- package/volumes.ts +229 -0
package/servers.ts
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hetzner server operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type {
|
|
7
|
+
HetznerServer,
|
|
8
|
+
CreateServerOptions,
|
|
9
|
+
HetznerAction,
|
|
10
|
+
CreateServerResponse,
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
import type { HetznerClient } from "./client.js";
|
|
13
|
+
import {
|
|
14
|
+
HetznerListServersResponseSchema,
|
|
15
|
+
HetznerGetServerResponseSchema,
|
|
16
|
+
HetznerCreateServerResponseSchema,
|
|
17
|
+
HetznerActionSchema,
|
|
18
|
+
} from "./schemas.js";
|
|
19
|
+
|
|
20
|
+
export class ServerOperations {
|
|
21
|
+
constructor(private client: HetznerClient) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* List all servers
|
|
25
|
+
*/
|
|
26
|
+
async list(): Promise<HetznerServer[]> {
|
|
27
|
+
const response = await this.client.request<{ servers: HetznerServer[] }>(
|
|
28
|
+
"/servers",
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// Validate response with Zod
|
|
32
|
+
const validated = HetznerListServersResponseSchema.safeParse(response);
|
|
33
|
+
if (!validated.success) {
|
|
34
|
+
console.warn('Hetzner list servers validation warning:', validated.error.issues);
|
|
35
|
+
return response.servers; // Return unvalidated data for backward compatibility
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return validated.data.servers;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get a specific server by ID
|
|
43
|
+
*/
|
|
44
|
+
async get(id: number): Promise<HetznerServer> {
|
|
45
|
+
const response = await this.client.request<{ server: HetznerServer }>(
|
|
46
|
+
`/servers/${id}`,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Validate response with Zod
|
|
50
|
+
const validated = HetznerGetServerResponseSchema.safeParse(response);
|
|
51
|
+
if (!validated.success) {
|
|
52
|
+
console.warn('Hetzner get server validation warning:', validated.error.issues);
|
|
53
|
+
return response.server; // Return unvalidated data for backward compatibility
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return validated.data.server;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a new server
|
|
61
|
+
*
|
|
62
|
+
* @param options - Server creation options
|
|
63
|
+
* @returns Create server response including server, action, and next_actions
|
|
64
|
+
*/
|
|
65
|
+
async create(options: CreateServerOptions): Promise<CreateServerResponse> {
|
|
66
|
+
// Validate input with Zod
|
|
67
|
+
const createServerOptionsSchema = z.object({
|
|
68
|
+
name: z.string().min(1),
|
|
69
|
+
server_type: z.string().min(1).default("cpx11"),
|
|
70
|
+
image: z.string().min(1).default("ubuntu-24.04"),
|
|
71
|
+
location: z.string().min(1).optional(),
|
|
72
|
+
datacenter: z.string().min(1).optional(),
|
|
73
|
+
ssh_keys: z.array(z.union([z.string(), z.number()])).default([]),
|
|
74
|
+
volumes: z.array(z.number()).default([]),
|
|
75
|
+
labels: z.record(z.string(), z.any()).optional(),
|
|
76
|
+
start_after_create: z.boolean().default(true),
|
|
77
|
+
user_data: z.string().optional(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const validatedOptions = createServerOptionsSchema.safeParse(options);
|
|
81
|
+
if (!validatedOptions.success) {
|
|
82
|
+
throw new Error(`Invalid server options: ${validatedOptions.error.issues.map(i => i.message).join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Ensure either location or datacenter, not both
|
|
86
|
+
if (validatedOptions.data.location && validatedOptions.data.datacenter) {
|
|
87
|
+
throw new Error('Cannot specify both location and datacenter');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const body = {
|
|
91
|
+
name: validatedOptions.data.name,
|
|
92
|
+
server_type: validatedOptions.data.server_type,
|
|
93
|
+
image: validatedOptions.data.image,
|
|
94
|
+
...(validatedOptions.data.location && { location: validatedOptions.data.location }),
|
|
95
|
+
...(validatedOptions.data.datacenter && { datacenter: { id: validatedOptions.data.datacenter } }),
|
|
96
|
+
ssh_keys: validatedOptions.data.ssh_keys,
|
|
97
|
+
volumes: validatedOptions.data.volumes,
|
|
98
|
+
...(validatedOptions.data.labels && { labels: validatedOptions.data.labels }),
|
|
99
|
+
start_after_create: validatedOptions.data.start_after_create,
|
|
100
|
+
...(validatedOptions.data.user_data && { user_data: validatedOptions.data.user_data }),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
console.log('[Hetzner] Creating server with body:', JSON.stringify(body, null, 2));
|
|
104
|
+
|
|
105
|
+
const response = await this.client.request<CreateServerResponse>("/servers", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
body: JSON.stringify(body),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Validate response with Zod
|
|
111
|
+
const validatedResponse = HetznerCreateServerResponseSchema.safeParse(response);
|
|
112
|
+
if (!validatedResponse.success) {
|
|
113
|
+
console.warn('Hetzner create server validation warning:', validatedResponse.error.issues);
|
|
114
|
+
return response; // Return unvalidated data for backward compatibility
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return validatedResponse.data as CreateServerResponse;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a new server and wait for it to be ready
|
|
122
|
+
*
|
|
123
|
+
* This convenience method creates a server and waits for the initial action to complete.
|
|
124
|
+
*
|
|
125
|
+
* @param options - Server creation options
|
|
126
|
+
* @param onProgress - Optional progress callback
|
|
127
|
+
* @returns Server once ready
|
|
128
|
+
*/
|
|
129
|
+
async createAndWait(
|
|
130
|
+
options: CreateServerOptions,
|
|
131
|
+
onProgress?: (action: HetznerAction) => void
|
|
132
|
+
): Promise<HetznerServer> {
|
|
133
|
+
const response = await this.create(options);
|
|
134
|
+
|
|
135
|
+
// Build wait options (only include onProgress if defined)
|
|
136
|
+
const waitOptions = onProgress !== undefined ? { onProgress } : {};
|
|
137
|
+
|
|
138
|
+
// Wait for the main create action to complete
|
|
139
|
+
if (response.action.status === 'running') {
|
|
140
|
+
await this.client.actions.waitFor(response.action.id, waitOptions);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Wait for any next actions (e.g., start server)
|
|
144
|
+
if (response.next_actions.length > 0) {
|
|
145
|
+
for (const action of response.next_actions) {
|
|
146
|
+
await this.client.actions.waitFor(action.id, waitOptions);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Return the server object
|
|
151
|
+
const server = await this.get(response.server.id);
|
|
152
|
+
return server;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Delete a server
|
|
157
|
+
*
|
|
158
|
+
* @param id - Server ID
|
|
159
|
+
* @returns Action for server deletion
|
|
160
|
+
*/
|
|
161
|
+
async delete(id: number): Promise<HetznerAction> {
|
|
162
|
+
// Validate ID with Zod
|
|
163
|
+
const serverIdSchema = z.number().int().positive();
|
|
164
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
165
|
+
if (!validatedId.success) {
|
|
166
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
170
|
+
`/servers/${validatedId.data}`,
|
|
171
|
+
{ method: "DELETE" }
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
175
|
+
if (!validated.success) {
|
|
176
|
+
console.warn('Hetzner delete server validation warning:', validated.error.issues);
|
|
177
|
+
return response.action;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return validated.data as HetznerAction;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Delete a server and wait for completion
|
|
185
|
+
*
|
|
186
|
+
* @param id - Server ID
|
|
187
|
+
* @param onProgress - Optional progress callback
|
|
188
|
+
*/
|
|
189
|
+
async deleteAndWait(
|
|
190
|
+
id: number,
|
|
191
|
+
onProgress?: (action: HetznerAction) => void
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
const action = await this.delete(id);
|
|
194
|
+
await this.client.actions.waitFor(action.id, { onProgress });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Power on a server
|
|
199
|
+
*
|
|
200
|
+
* @param id - Server ID
|
|
201
|
+
* @returns Action for server power on
|
|
202
|
+
*/
|
|
203
|
+
async powerOn(id: number): Promise<HetznerAction> {
|
|
204
|
+
// Validate ID with Zod
|
|
205
|
+
const serverIdSchema = z.number().int().positive();
|
|
206
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
207
|
+
if (!validatedId.success) {
|
|
208
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
212
|
+
`/servers/${validatedId.data}/actions/poweron`,
|
|
213
|
+
{ method: "POST" }
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
217
|
+
if (!validated.success) {
|
|
218
|
+
console.warn('Hetzner power on validation warning:', validated.error.issues);
|
|
219
|
+
return response.action;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return validated.data as HetznerAction;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Power on a server and wait for completion
|
|
227
|
+
*
|
|
228
|
+
* @param id - Server ID
|
|
229
|
+
* @param onProgress - Optional progress callback
|
|
230
|
+
*/
|
|
231
|
+
async powerOnAndWait(
|
|
232
|
+
id: number,
|
|
233
|
+
onProgress?: (action: HetznerAction) => void
|
|
234
|
+
): Promise<HetznerAction> {
|
|
235
|
+
const action = await this.powerOn(id);
|
|
236
|
+
return await this.client.actions.waitFor(action.id, { onProgress });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Power off a server
|
|
241
|
+
*
|
|
242
|
+
* @param id - Server ID
|
|
243
|
+
* @returns Action for server power off
|
|
244
|
+
*/
|
|
245
|
+
async powerOff(id: number): Promise<HetznerAction> {
|
|
246
|
+
// Validate ID with Zod
|
|
247
|
+
const serverIdSchema = z.number().int().positive();
|
|
248
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
249
|
+
if (!validatedId.success) {
|
|
250
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
254
|
+
`/servers/${validatedId.data}/actions/poweroff`,
|
|
255
|
+
{ method: "POST" }
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
259
|
+
if (!validated.success) {
|
|
260
|
+
console.warn('Hetzner power off validation warning:', validated.error.issues);
|
|
261
|
+
return response.action;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return validated.data as HetznerAction;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Power off a server and wait for completion
|
|
269
|
+
*
|
|
270
|
+
* @param id - Server ID
|
|
271
|
+
* @param onProgress - Optional progress callback
|
|
272
|
+
*/
|
|
273
|
+
async powerOffAndWait(
|
|
274
|
+
id: number,
|
|
275
|
+
onProgress?: (action: HetznerAction) => void
|
|
276
|
+
): Promise<HetznerAction> {
|
|
277
|
+
const action = await this.powerOff(id);
|
|
278
|
+
return await this.client.actions.waitFor(action.id, { onProgress });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Reboot a server
|
|
283
|
+
*
|
|
284
|
+
* @param id - Server ID
|
|
285
|
+
* @returns Action for server reboot
|
|
286
|
+
*/
|
|
287
|
+
async reboot(id: number): Promise<HetznerAction> {
|
|
288
|
+
// Validate ID with Zod
|
|
289
|
+
const serverIdSchema = z.number().int().positive();
|
|
290
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
291
|
+
if (!validatedId.success) {
|
|
292
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
296
|
+
`/servers/${validatedId.data}/actions/reboot`,
|
|
297
|
+
{ method: "POST" }
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
301
|
+
if (!validated.success) {
|
|
302
|
+
console.warn('Hetzner reboot validation warning:', validated.error.issues);
|
|
303
|
+
return response.action;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return validated.data as HetznerAction;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Reboot a server and wait for completion
|
|
311
|
+
*
|
|
312
|
+
* @param id - Server ID
|
|
313
|
+
* @param onProgress - Optional progress callback
|
|
314
|
+
*/
|
|
315
|
+
async rebootAndWait(
|
|
316
|
+
id: number,
|
|
317
|
+
onProgress?: (action: HetznerAction) => void
|
|
318
|
+
): Promise<HetznerAction> {
|
|
319
|
+
const action = await this.reboot(id);
|
|
320
|
+
return await this.client.actions.waitFor(action.id, { onProgress });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Shutdown a server gracefully
|
|
325
|
+
*
|
|
326
|
+
* @param id - Server ID
|
|
327
|
+
* @returns Action for server shutdown
|
|
328
|
+
*/
|
|
329
|
+
async shutdown(id: number): Promise<HetznerAction> {
|
|
330
|
+
// Validate ID with Zod
|
|
331
|
+
const serverIdSchema = z.number().int().positive();
|
|
332
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
333
|
+
if (!validatedId.success) {
|
|
334
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
338
|
+
`/servers/${validatedId.data}/actions/shutdown`,
|
|
339
|
+
{ method: "POST" }
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
343
|
+
if (!validated.success) {
|
|
344
|
+
console.warn('Hetzner shutdown validation warning:', validated.error.issues);
|
|
345
|
+
return response.action;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return validated.data as HetznerAction;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Shutdown a server and wait for completion
|
|
353
|
+
*
|
|
354
|
+
* @param id - Server ID
|
|
355
|
+
* @param onProgress - Optional progress callback
|
|
356
|
+
*/
|
|
357
|
+
async shutdownAndWait(
|
|
358
|
+
id: number,
|
|
359
|
+
onProgress?: (action: HetznerAction) => void
|
|
360
|
+
): Promise<HetznerAction> {
|
|
361
|
+
const action = await this.shutdown(id);
|
|
362
|
+
return await this.client.actions.waitFor(action.id, { onProgress });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Reset a server
|
|
367
|
+
*
|
|
368
|
+
* @param id - Server ID
|
|
369
|
+
* @returns Action for server reset
|
|
370
|
+
*/
|
|
371
|
+
async reset(id: number): Promise<HetznerAction> {
|
|
372
|
+
// Validate ID with Zod
|
|
373
|
+
const serverIdSchema = z.number().int().positive();
|
|
374
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
375
|
+
if (!validatedId.success) {
|
|
376
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
380
|
+
`/servers/${validatedId.data}/actions/reset`,
|
|
381
|
+
{ method: "POST" }
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
385
|
+
if (!validated.success) {
|
|
386
|
+
console.warn('Hetzner reset validation warning:', validated.error.issues);
|
|
387
|
+
return response.action;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return validated.data as HetznerAction;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Reset a server and wait for completion
|
|
395
|
+
*
|
|
396
|
+
* @param id - Server ID
|
|
397
|
+
* @param onProgress - Optional progress callback
|
|
398
|
+
*/
|
|
399
|
+
async resetAndWait(
|
|
400
|
+
id: number,
|
|
401
|
+
onProgress?: (action: HetznerAction) => void
|
|
402
|
+
): Promise<HetznerAction> {
|
|
403
|
+
const action = await this.reset(id);
|
|
404
|
+
return await this.client.actions.waitFor(action.id, { onProgress });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Rebuild a server from an image
|
|
409
|
+
*
|
|
410
|
+
* @param id - Server ID
|
|
411
|
+
* @param image - Image ID or name
|
|
412
|
+
* @returns Action for server rebuild
|
|
413
|
+
*/
|
|
414
|
+
async rebuild(id: number, image: string): Promise<HetznerAction> {
|
|
415
|
+
// Validate ID with Zod
|
|
416
|
+
const serverIdSchema = z.number().int().positive();
|
|
417
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
418
|
+
if (!validatedId.success) {
|
|
419
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
423
|
+
`/servers/${validatedId.data}/actions/rebuild`,
|
|
424
|
+
{
|
|
425
|
+
method: "POST",
|
|
426
|
+
body: JSON.stringify({ image }),
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
431
|
+
if (!validated.success) {
|
|
432
|
+
console.warn('Hetzner rebuild validation warning:', validated.error.issues);
|
|
433
|
+
return response.action;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return validated.data as HetznerAction;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Enable rescue mode for a server
|
|
441
|
+
*
|
|
442
|
+
* @param id - Server ID
|
|
443
|
+
* @param options - Rescue mode options
|
|
444
|
+
* @returns Action for enabling rescue mode
|
|
445
|
+
*/
|
|
446
|
+
async enableRescue(
|
|
447
|
+
id: number,
|
|
448
|
+
options?: { type?: string; ssh_keys?: number[] }
|
|
449
|
+
): Promise<HetznerAction> {
|
|
450
|
+
// Validate ID with Zod
|
|
451
|
+
const serverIdSchema = z.number().int().positive();
|
|
452
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
453
|
+
if (!validatedId.success) {
|
|
454
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
458
|
+
`/servers/${validatedId.data}/actions/enable_rescue`,
|
|
459
|
+
{
|
|
460
|
+
method: "POST",
|
|
461
|
+
body: JSON.stringify(options || {}),
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
466
|
+
if (!validated.success) {
|
|
467
|
+
console.warn('Hetzner enable rescue validation warning:', validated.error.issues);
|
|
468
|
+
return response.action;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return validated.data as HetznerAction;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Disable rescue mode for a server
|
|
476
|
+
*
|
|
477
|
+
* @param id - Server ID
|
|
478
|
+
* @returns Action for disabling rescue mode
|
|
479
|
+
*/
|
|
480
|
+
async disableRescue(id: number): Promise<HetznerAction> {
|
|
481
|
+
// Validate ID with Zod
|
|
482
|
+
const serverIdSchema = z.number().int().positive();
|
|
483
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
484
|
+
if (!validatedId.success) {
|
|
485
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
489
|
+
`/servers/${validatedId.data}/actions/disable_rescue`,
|
|
490
|
+
{ method: "POST" }
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
494
|
+
if (!validated.success) {
|
|
495
|
+
console.warn('Hetzner disable rescue validation warning:', validated.error.issues);
|
|
496
|
+
return response.action;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return validated.data as HetznerAction;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Change server type
|
|
504
|
+
*
|
|
505
|
+
* @param id - Server ID
|
|
506
|
+
* @param serverType - New server type
|
|
507
|
+
* @param upgradeDisk - Whether to upgrade disk (default: false)
|
|
508
|
+
* @returns Action for changing server type
|
|
509
|
+
*/
|
|
510
|
+
async changeType(
|
|
511
|
+
id: number,
|
|
512
|
+
serverType: string,
|
|
513
|
+
upgradeDisk: boolean = false
|
|
514
|
+
): Promise<HetznerAction> {
|
|
515
|
+
// Validate ID with Zod
|
|
516
|
+
const serverIdSchema = z.number().int().positive();
|
|
517
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
518
|
+
if (!validatedId.success) {
|
|
519
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const response = await this.client.request<{ action: HetznerAction }>(
|
|
523
|
+
`/servers/${validatedId.data}/actions/change_type`,
|
|
524
|
+
{
|
|
525
|
+
method: "POST",
|
|
526
|
+
body: JSON.stringify({ server_type: serverType, upgrade_disk: upgradeDisk }),
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const validated = HetznerActionSchema.safeParse(response.action);
|
|
531
|
+
if (!validated.success) {
|
|
532
|
+
console.warn('Hetzner change type validation warning:', validated.error.issues);
|
|
533
|
+
return response.action;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return validated.data as HetznerAction;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get actions for a specific server
|
|
541
|
+
*
|
|
542
|
+
* @param id - Server ID
|
|
543
|
+
* @param options - Optional filters (status, sort, etc.)
|
|
544
|
+
* @returns Array of server actions
|
|
545
|
+
*/
|
|
546
|
+
async getActions(
|
|
547
|
+
id: number,
|
|
548
|
+
options?: { status?: "running" | "success" | "error"; sort?: string }
|
|
549
|
+
): Promise<HetznerAction[]> {
|
|
550
|
+
// Validate ID with Zod
|
|
551
|
+
const serverIdSchema = z.number().int().positive();
|
|
552
|
+
const validatedId = serverIdSchema.safeParse(id);
|
|
553
|
+
if (!validatedId.success) {
|
|
554
|
+
throw new Error(`Invalid server ID: ${validatedId.error.issues.map(i => i.message).join(', ')}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const params = new URLSearchParams();
|
|
558
|
+
if (options?.status) params.append("status", options.status);
|
|
559
|
+
if (options?.sort) params.append("sort", options.sort);
|
|
560
|
+
|
|
561
|
+
const query = params.toString();
|
|
562
|
+
const response = await this.client.request<{ actions: HetznerAction[] }>(
|
|
563
|
+
`/servers/${validatedId.data}/actions${query ? `?${query}` : ""}`
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
return response.actions;
|
|
567
|
+
}
|
|
568
|
+
}
|
package/ssh-keys.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hetzner SSH key operations
|
|
3
|
+
*/
|
|
4
|
+
import { HetznerListSSHKeysResponseSchema, HetznerGetSSHKeyResponseSchema, HetznerCreateSSHKeyRequestSchema, HetznerCreateSSHKeyResponseSchema, } from "./schemas.js";
|
|
5
|
+
export class SSHKeyOperations {
|
|
6
|
+
client;
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* List all SSH keys
|
|
12
|
+
*/
|
|
13
|
+
async list() {
|
|
14
|
+
const response = await this.client.request("/ssh_keys");
|
|
15
|
+
// Validate response with Zod
|
|
16
|
+
const validated = HetznerListSSHKeysResponseSchema.safeParse(response);
|
|
17
|
+
if (!validated.success) {
|
|
18
|
+
console.warn('Hetzner list SSH keys validation warning:', validated.error.issues);
|
|
19
|
+
return response.ssh_keys; // Return unvalidated data for backward compatibility
|
|
20
|
+
}
|
|
21
|
+
return validated.data.ssh_keys;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get a specific SSH key by ID or name
|
|
25
|
+
*/
|
|
26
|
+
async get(idOrName) {
|
|
27
|
+
const endpoint = typeof idOrName === 'number'
|
|
28
|
+
? `/ssh_keys/${idOrName}`
|
|
29
|
+
: `/ssh_keys?name=${encodeURIComponent(idOrName)}`;
|
|
30
|
+
const response = await this.client.request(endpoint);
|
|
31
|
+
// Validate response with Zod
|
|
32
|
+
const validated = HetznerGetSSHKeyResponseSchema.safeParse(response);
|
|
33
|
+
if (!validated.success) {
|
|
34
|
+
console.warn('Hetzner get SSH key validation warning:', validated.error.issues);
|
|
35
|
+
return response.ssh_key; // Return unvalidated data for backward compatibility
|
|
36
|
+
}
|
|
37
|
+
return validated.data.ssh_key;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a new SSH key
|
|
41
|
+
*
|
|
42
|
+
* @param options - SSH key creation options
|
|
43
|
+
* @returns Created SSH key
|
|
44
|
+
*/
|
|
45
|
+
async create(options) {
|
|
46
|
+
// Validate input with Zod
|
|
47
|
+
const validatedOptions = HetznerCreateSSHKeyRequestSchema.safeParse(options);
|
|
48
|
+
if (!validatedOptions.success) {
|
|
49
|
+
throw new Error(`Invalid SSH key options: ${validatedOptions.error.issues.map(i => i.message).join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
const body = {
|
|
52
|
+
name: validatedOptions.data.name,
|
|
53
|
+
public_key: validatedOptions.data.public_key,
|
|
54
|
+
...(validatedOptions.data.labels && { labels: validatedOptions.data.labels }),
|
|
55
|
+
};
|
|
56
|
+
const response = await this.client.request("/ssh_keys", {
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: JSON.stringify(body),
|
|
59
|
+
});
|
|
60
|
+
// Validate response with Zod
|
|
61
|
+
const validated = HetznerCreateSSHKeyResponseSchema.safeParse(response);
|
|
62
|
+
if (!validated.success) {
|
|
63
|
+
console.warn('Hetzner create SSH key validation warning:', validated.error.issues);
|
|
64
|
+
return response.ssh_key; // Return unvalidated data for backward compatibility
|
|
65
|
+
}
|
|
66
|
+
return validated.data.ssh_key;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Delete an SSH key
|
|
70
|
+
*
|
|
71
|
+
* @param id - SSH key ID
|
|
72
|
+
*/
|
|
73
|
+
async delete(id) {
|
|
74
|
+
await this.client.request(`/ssh_keys/${id}`, { method: "DELETE" });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Find an SSH key by name
|
|
78
|
+
* Returns undefined if not found
|
|
79
|
+
*/
|
|
80
|
+
async findByName(name) {
|
|
81
|
+
try {
|
|
82
|
+
const keys = await this.list();
|
|
83
|
+
return keys.find(key => key.name === name);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=ssh-keys.js.map
|