@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.
Files changed (46) hide show
  1. package/actions.js +802 -0
  2. package/actions.ts +1053 -0
  3. package/auth.js +35 -0
  4. package/auth.ts +37 -0
  5. package/bootstrap/FIREWALL.md +326 -0
  6. package/bootstrap/KERNEL-HARDENING.md +258 -0
  7. package/bootstrap/SECURITY-INTEGRATION.md +281 -0
  8. package/bootstrap/TESTING.md +301 -0
  9. package/bootstrap/cloud-init.js +279 -0
  10. package/bootstrap/cloud-init.ts +394 -0
  11. package/bootstrap/firewall.js +279 -0
  12. package/bootstrap/firewall.ts +342 -0
  13. package/bootstrap/genesis.js +406 -0
  14. package/bootstrap/genesis.ts +518 -0
  15. package/bootstrap/index.js +35 -0
  16. package/bootstrap/index.ts +71 -0
  17. package/bootstrap/kernel-hardening.js +266 -0
  18. package/bootstrap/kernel-hardening.test.ts +230 -0
  19. package/bootstrap/kernel-hardening.ts +272 -0
  20. package/bootstrap/security-audit.js +118 -0
  21. package/bootstrap/security-audit.ts +124 -0
  22. package/bootstrap/ssh-hardening.js +182 -0
  23. package/bootstrap/ssh-hardening.ts +192 -0
  24. package/client.js +137 -0
  25. package/client.ts +177 -0
  26. package/config.js +5 -0
  27. package/config.ts +5 -0
  28. package/errors.js +270 -0
  29. package/errors.ts +371 -0
  30. package/index.js +28 -0
  31. package/index.ts +55 -0
  32. package/package.json +56 -0
  33. package/pricing.js +284 -0
  34. package/pricing.ts +422 -0
  35. package/schemas.js +660 -0
  36. package/schemas.ts +765 -0
  37. package/server-status.ts +81 -0
  38. package/servers.js +424 -0
  39. package/servers.ts +568 -0
  40. package/ssh-keys.js +90 -0
  41. package/ssh-keys.ts +122 -0
  42. package/ssh-setup.ts +218 -0
  43. package/types.js +96 -0
  44. package/types.ts +389 -0
  45. package/volumes.js +172 -0
  46. package/volumes.ts +229 -0
package/types.ts ADDED
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Hetzner Cloud API types
3
+ */
4
+
5
+ // ============================================================================
6
+ // Import shared status enums
7
+ // ============================================================================
8
+
9
+ import {
10
+ EnvironmentStatus,
11
+ ActionStatus,
12
+ VolumeStatus,
13
+ } from "@ebowwa/codespaces-types/compile";
14
+
15
+ // Re-export for convenience
16
+ export { EnvironmentStatus, ActionStatus, VolumeStatus };
17
+
18
+ // ============================================================================
19
+ // Server Types
20
+ // ============================================================================
21
+
22
+ export interface HetznerServer {
23
+ id: number;
24
+ name: string;
25
+ status: EnvironmentStatus;
26
+ image?: {
27
+ id: number;
28
+ name: string;
29
+ description: string;
30
+ type: "snapshot" | "backup" | "system";
31
+ } | null;
32
+ public_net: {
33
+ ipv4: {
34
+ ip: string;
35
+ blocked: boolean;
36
+ };
37
+ ipv6?: {
38
+ ip: string;
39
+ blocked: boolean;
40
+ };
41
+ floating_ips: Array<{
42
+ id: number;
43
+ ip: string;
44
+ }>;
45
+ firewalls: Array<{
46
+ id: number;
47
+ name: string;
48
+ status: "applied" | "pending";
49
+ }>;
50
+ };
51
+ server_type: {
52
+ id: number;
53
+ name: string;
54
+ description: string;
55
+ cores: number;
56
+ memory: number;
57
+ disk: number;
58
+ };
59
+ datacenter: {
60
+ id: number;
61
+ name: string;
62
+ description: string;
63
+ location: {
64
+ id: number;
65
+ name: string;
66
+ description: string;
67
+ country: string;
68
+ city: string;
69
+ latitude: number;
70
+ longitude: number;
71
+ network_zone: string;
72
+ };
73
+ supported_server_types?: Array<{
74
+ id: number;
75
+ name: string;
76
+ }> | null;
77
+ };
78
+ labels: Record<string, string>;
79
+ created: string;
80
+ protection: {
81
+ delete: boolean;
82
+ rebuild: boolean;
83
+ };
84
+ volumes: Array<{
85
+ id: number;
86
+ name: string;
87
+ size: number;
88
+ linux_device: string;
89
+ }>;
90
+ }
91
+
92
+ export interface CreateServerOptions {
93
+ name: string;
94
+ server_type?: string;
95
+ image?: string;
96
+ location?: string;
97
+ datacenter?: string;
98
+ ssh_keys?: Array<string | number>;
99
+ volumes?: number[];
100
+ labels?: Record<string, string>;
101
+ start_after_create?: boolean;
102
+ /** Cloud-init user data script for first-boot provisioning */
103
+ user_data?: string;
104
+ }
105
+
106
+ export interface UpdateServerOptions {
107
+ name?: string;
108
+ labels?: Record<string, string>;
109
+ }
110
+
111
+ // ============================================================================
112
+ // Action Types
113
+ // ============================================================================
114
+
115
+ /**
116
+ * Action command types from Hetzner Cloud API
117
+ */
118
+ export enum ActionCommand {
119
+ // Server actions
120
+ CreateServer = "create_server",
121
+ DeleteServer = "delete_server",
122
+ StartServer = "start_server",
123
+ StopServer = "stop_server",
124
+ RebootServer = "reboot_server",
125
+ ResetServer = "reset_server",
126
+ ShutdownServer = "shutdown_server",
127
+ Poweroff = "poweroff",
128
+ ChangeServerType = "change_server_type",
129
+ RebuildServer = "rebuild_server",
130
+ EnableBackup = "enable_backup",
131
+ DisableBackup = "disable_backup",
132
+ CreateImage = "create_image",
133
+ ChangeDnsPtr = "change_dns_ptr",
134
+ AttachToNetwork = "attach_to_network",
135
+ DetachFromNetwork = "detach_from_network",
136
+ ChangeAliasIps = "change_alias_ips",
137
+ EnableRescue = "enable_rescue",
138
+ DisableRescue = "disable_rescue",
139
+ ChangeProtection = "change_protection",
140
+
141
+ // Volume actions
142
+ CreateVolume = "create_volume",
143
+ DeleteVolume = "delete_volume",
144
+ AttachVolume = "attach_volume",
145
+ DetachVolume = "detach_volume",
146
+ ResizeVolume = "resize_volume",
147
+ VolumeChangeProtection = "volume_change_protection",
148
+
149
+ // Network actions
150
+ AddSubnet = "add_subnet",
151
+ DeleteSubnet = "delete_subnet",
152
+ AddRoute = "add_route",
153
+ DeleteRoute = "delete_route",
154
+ ChangeIpRange = "change_ip_range",
155
+ NetworkChangeProtection = "network_change_protection",
156
+
157
+ // Floating IP actions
158
+ AssignFloatingIp = "assign_floating_ip",
159
+ UnassignFloatingIp = "unassign_floating_ip",
160
+ FloatingIpChangeDnsPtr = "floating_ip_change_dns_ptr",
161
+ FloatingIpChangeProtection = "floating_ip_change_protection",
162
+
163
+ // Load Balancer actions
164
+ CreateLoadBalancer = "create_load_balancer",
165
+ DeleteLoadBalancer = "delete_load_balancer",
166
+ AddTarget = "add_target",
167
+ RemoveTarget = "remove_target",
168
+ AddService = "add_service",
169
+ UpdateService = "update_service",
170
+ DeleteService = "delete_service",
171
+ LoadBalancerAttachToNetwork = "load_balancer_attach_to_network",
172
+ LoadBalancerDetachFromNetwork = "load_balancer_detach_from_network",
173
+ ChangeAlgorithm = "change_algorithm",
174
+ ChangeType = "change_type",
175
+ LoadBalancerChangeProtection = "load_balancer_change_protection",
176
+
177
+ // Certificate actions
178
+ IssueCertificate = "issue_certificate",
179
+ RetryCertificate = "retry_certificate",
180
+
181
+ // Firewall actions
182
+ SetFirewallRules = "set_firewall_rules",
183
+ ApplyFirewall = "apply_firewall",
184
+ RemoveFirewall = "remove_firewall",
185
+ FirewallChangeProtection = "firewall_change_protection",
186
+
187
+ // Image actions
188
+ ImageChangeProtection = "image_change_protection",
189
+ }
190
+
191
+ /**
192
+ * Resource types that can be affected by actions
193
+ */
194
+ export enum ResourceType {
195
+ Server = "server",
196
+ Volume = "volume",
197
+ Network = "network",
198
+ FloatingIp = "floating_ip",
199
+ LoadBalancer = "load_balancer",
200
+ Certificate = "certificate",
201
+ Firewall = "firewall",
202
+ Image = "image",
203
+ }
204
+
205
+ /**
206
+ * Action resource reference
207
+ */
208
+ export interface ActionResource {
209
+ id: number;
210
+ type: ResourceType;
211
+ }
212
+
213
+ /**
214
+ * Action error details
215
+ */
216
+ export interface ActionError {
217
+ code: string;
218
+ message: string;
219
+ }
220
+
221
+ /**
222
+ * Base Hetzner Action type matching the API response
223
+ *
224
+ * The API returns actions with:
225
+ * - status: "running" | "success" | "error"
226
+ * - finished: string | null (null when running)
227
+ * - error: ActionError | null (null when not error)
228
+ *
229
+ * Note: Zod validation makes finished and error optional, but the API
230
+ * typically returns them. Use type guards for runtime checks.
231
+ */
232
+ export interface HetznerAction {
233
+ id: number;
234
+ command: ActionCommand | string; // API returns string
235
+ status: ActionStatus;
236
+ started: string;
237
+ finished?: string | null;
238
+ progress: number;
239
+ resources: ActionResource[];
240
+ error?: ActionError | null;
241
+ }
242
+
243
+ /**
244
+ * Server action response (includes server reference)
245
+ */
246
+ export interface ServerActionResponse {
247
+ action: HetznerAction;
248
+ server?: HetznerServer;
249
+ }
250
+
251
+ /**
252
+ * Create server response with actions
253
+ */
254
+ export interface CreateServerResponse {
255
+ server: HetznerServer;
256
+ action: HetznerAction;
257
+ next_actions: HetznerAction[];
258
+ root_password: string | null;
259
+ }
260
+
261
+ // ============================================================================
262
+ // Volume Types
263
+ // ============================================================================
264
+
265
+ export interface HetznerVolume {
266
+ id: number;
267
+ name: string;
268
+ status: VolumeStatus;
269
+ server: number | null;
270
+ size: number;
271
+ linux_device: string | null;
272
+ format: string | null;
273
+ location: {
274
+ id: number;
275
+ name: string;
276
+ description: string;
277
+ country: string;
278
+ city: string;
279
+ latitude: number;
280
+ longitude: number;
281
+ } | null;
282
+ labels: Record<string, string>;
283
+ created: string;
284
+ protection: {
285
+ delete: boolean;
286
+ };
287
+ }
288
+
289
+ export interface CreateVolumeOptions {
290
+ name: string;
291
+ size: number;
292
+ server?: number;
293
+ location?: string;
294
+ format?: string;
295
+ automount?: boolean;
296
+ labels?: Record<string, string>;
297
+ }
298
+
299
+ // ============================================================================
300
+ // Network Types
301
+ // ============================================================================
302
+
303
+ export interface HetznerNetwork {
304
+ id: number;
305
+ name: string;
306
+ ip_range: string;
307
+ subnets: Array<{
308
+ type: "server" | "cloud" | "vswitch";
309
+ ip_range: string;
310
+ network_zone: string;
311
+ gateway: string;
312
+ }>;
313
+ routes: Array<{
314
+ destination: string;
315
+ gateway: string;
316
+ }>;
317
+ servers: number[];
318
+ protection: {
319
+ delete: boolean;
320
+ };
321
+ labels: Record<string, string>;
322
+ created: string;
323
+ }
324
+
325
+ // ============================================================================
326
+ // SSH Key Types
327
+ // ============================================================================
328
+
329
+ export interface HetznerSSHKey {
330
+ id: number;
331
+ name: string;
332
+ fingerprint: string;
333
+ public_key: string;
334
+ labels: Record<string, string>;
335
+ created: string;
336
+ }
337
+
338
+ export interface CreateSSHKeyOptions {
339
+ name: string;
340
+ public_key: string;
341
+ labels?: Record<string, string>;
342
+ }
343
+
344
+ // ============================================================================
345
+ // Rate Limiting Types
346
+ // ============================================================================
347
+
348
+ export interface RateLimitInfo {
349
+ limit: number;
350
+ remaining: number;
351
+ reset: number; // Unix timestamp
352
+ }
353
+
354
+ export interface RateLimitHeaders {
355
+ "RateLimit-Limit": string;
356
+ "RateLimit-Remaining": string;
357
+ "RateLimit-Reset": string;
358
+ }
359
+
360
+ // ============================================================================
361
+ // Polling Options
362
+ // ============================================================================
363
+
364
+ export interface ActionPollingOptions {
365
+ /** Polling interval in milliseconds (default: 2000) */
366
+ pollInterval?: number;
367
+ /** Maximum number of polling attempts (default: 60) */
368
+ maxRetries?: number;
369
+ /** Optional callback for progress updates */
370
+ onProgress?: (action: HetznerAction) => void;
371
+ /** Optional timeout in milliseconds */
372
+ timeout?: number;
373
+ }
374
+
375
+ // ============================================================================
376
+ // Import shared Hetzner types for local use
377
+ // ============================================================================
378
+
379
+ import type {
380
+ HetznerServerType,
381
+ HetznerLocation,
382
+ HetznerDatacenter,
383
+ } from "@ebowwa/codespaces-types/compile";
384
+
385
+ // ============================================================================
386
+ // Re-export shared Hetzner types
387
+ // ============================================================================
388
+
389
+ export type { HetznerServerType, HetznerLocation, HetznerDatacenter };
package/volumes.js ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Hetzner Volume Operations
3
+ *
4
+ * Provides methods for managing Hetzner Cloud volumes.
5
+ * See: https://docs.hetzner.cloud/#volumes
6
+ */
7
+ /**
8
+ * Volume operations for Hetzner Cloud API
9
+ */
10
+ export class VolumeOperations {
11
+ client;
12
+ constructor(client) {
13
+ this.client = client;
14
+ }
15
+ /**
16
+ * List all volumes
17
+ *
18
+ * @param options - List options (name, status, sort, etc.)
19
+ * @returns Array of volumes
20
+ */
21
+ async list(options) {
22
+ const params = new URLSearchParams();
23
+ if (options?.name)
24
+ params.set("name", options.name);
25
+ if (options?.status)
26
+ params.set("status", options.status);
27
+ if (options?.sort)
28
+ params.set("sort", options.sort);
29
+ if (options?.label_selector)
30
+ params.set("label_selector", options.label_selector);
31
+ const endpoint = `/volumes${params.toString() ? `?${params}` : ""}`;
32
+ const response = await this.client.request(endpoint);
33
+ return response.volumes || [];
34
+ }
35
+ /**
36
+ * Get a specific volume by ID
37
+ *
38
+ * @param id - Volume ID
39
+ * @returns Volume details
40
+ */
41
+ async get(id) {
42
+ const response = await this.client.request(`/volumes/${id}`);
43
+ return response.volume;
44
+ }
45
+ /**
46
+ * Create a new volume
47
+ *
48
+ * @param options - Volume creation options
49
+ * @returns Created volume with action info
50
+ */
51
+ async create(options) {
52
+ const response = await this.client.request("/volumes", {
53
+ method: "POST",
54
+ body: JSON.stringify({
55
+ name: options.name,
56
+ size: options.size,
57
+ server: options.server,
58
+ location: options.location,
59
+ automount: options.automount ?? true,
60
+ format: options.format,
61
+ labels: options.labels,
62
+ }),
63
+ });
64
+ return response;
65
+ }
66
+ /**
67
+ * Delete a volume
68
+ *
69
+ * @param id - Volume ID
70
+ * @returns Action response
71
+ */
72
+ async delete(id) {
73
+ const response = await this.client.request(`/volumes/${id}`, {
74
+ method: "DELETE",
75
+ });
76
+ return response.action;
77
+ }
78
+ /**
79
+ * Attach a volume to a server
80
+ *
81
+ * @param volumeId - Volume ID
82
+ * @param serverId - Server ID
83
+ * @param automount - Automatically mount the volume
84
+ * @returns Action response
85
+ */
86
+ async attach(volumeId, serverId, automount = true) {
87
+ const response = await this.client.request(`/volumes/${volumeId}/actions/attach`, {
88
+ method: "POST",
89
+ body: JSON.stringify({
90
+ server: serverId,
91
+ automount,
92
+ }),
93
+ });
94
+ return response.action;
95
+ }
96
+ /**
97
+ * Detach a volume from a server
98
+ *
99
+ * @param volumeId - Volume ID
100
+ * @returns Action response
101
+ */
102
+ async detach(volumeId) {
103
+ const response = await this.client.request(`/volumes/${volumeId}/actions/detach`, {
104
+ method: "POST",
105
+ });
106
+ return response.action;
107
+ }
108
+ /**
109
+ * Resize a volume
110
+ *
111
+ * @param volumeId - Volume ID
112
+ * @param size - New size in GB (must be larger than current)
113
+ * @returns Action response
114
+ */
115
+ async resize(volumeId, size) {
116
+ const response = await this.client.request(`/volumes/${volumeId}/actions/resize`, {
117
+ method: "POST",
118
+ body: JSON.stringify({ size }),
119
+ });
120
+ return response.action;
121
+ }
122
+ /**
123
+ * Change volume protection
124
+ *
125
+ * @param volumeId - Volume ID
126
+ * @param deleteProtection - Enable delete protection
127
+ * @returns Action response
128
+ */
129
+ async changeProtection(volumeId, deleteProtection) {
130
+ const response = await this.client.request(`/volumes/${volumeId}/actions/change_protection`, {
131
+ method: "POST",
132
+ body: JSON.stringify({
133
+ delete: deleteProtection,
134
+ }),
135
+ });
136
+ return response.action;
137
+ }
138
+ /**
139
+ * Update volume labels
140
+ *
141
+ * @param volumeId - Volume ID
142
+ * @param labels - New labels
143
+ * @returns Updated volume
144
+ */
145
+ async updateLabels(volumeId, labels) {
146
+ const response = await this.client.request(`/volumes/${volumeId}`, {
147
+ method: "PUT",
148
+ body: JSON.stringify({ labels }),
149
+ });
150
+ return response.volume;
151
+ }
152
+ /**
153
+ * Get volume pricing information
154
+ * Calculates monthly cost based on size (€0.008/GB per month)
155
+ *
156
+ * @param sizeInGB - Volume size in GB
157
+ * @returns Monthly and hourly pricing
158
+ */
159
+ static calculatePrice(sizeInGB) {
160
+ const PRICE_PER_GB_MONTHLY = 0.008; // €0.008 per GB per month
161
+ const HOURS_PER_MONTH = 730; // Average
162
+ const monthly = sizeInGB * PRICE_PER_GB_MONTHLY;
163
+ const hourly = monthly / HOURS_PER_MONTH;
164
+ return {
165
+ size: sizeInGB,
166
+ monthly: Math.round(monthly * 100) / 100,
167
+ hourly: Math.round(hourly * 10000) / 10000,
168
+ currency: "EUR",
169
+ };
170
+ }
171
+ }
172
+ //# sourceMappingURL=volumes.js.map