@ebowwa/hetzner 0.2.2 → 0.3.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 (61) hide show
  1. package/dist/bootstrap/index.js +1126 -0
  2. package/dist/bootstrap/index.js.map +15 -0
  3. package/dist/index.js +3540 -0
  4. package/dist/index.js.map +31 -0
  5. package/dist/onboarding/index.js +460 -0
  6. package/dist/onboarding/index.js.map +14 -0
  7. package/package.json +53 -16
  8. package/actions.js +0 -1084
  9. package/actions.ts +0 -1053
  10. package/auth.js +0 -39
  11. package/auth.ts +0 -37
  12. package/bootstrap/FIREWALL.md +0 -326
  13. package/bootstrap/KERNEL-HARDENING.md +0 -258
  14. package/bootstrap/SECURITY-INTEGRATION.md +0 -281
  15. package/bootstrap/TESTING.md +0 -301
  16. package/bootstrap/cloud-init.js +0 -323
  17. package/bootstrap/cloud-init.ts +0 -394
  18. package/bootstrap/firewall.js +0 -292
  19. package/bootstrap/firewall.ts +0 -342
  20. package/bootstrap/genesis.js +0 -424
  21. package/bootstrap/genesis.ts +0 -518
  22. package/bootstrap/index.js +0 -59
  23. package/bootstrap/index.ts +0 -71
  24. package/bootstrap/kernel-hardening.js +0 -270
  25. package/bootstrap/kernel-hardening.test.js +0 -182
  26. package/bootstrap/kernel-hardening.test.ts +0 -230
  27. package/bootstrap/kernel-hardening.ts +0 -272
  28. package/bootstrap/security-audit.js +0 -122
  29. package/bootstrap/security-audit.ts +0 -124
  30. package/bootstrap/ssh-hardening.js +0 -186
  31. package/bootstrap/ssh-hardening.ts +0 -192
  32. package/client.js +0 -234
  33. package/client.ts +0 -177
  34. package/config.js +0 -7
  35. package/config.ts +0 -5
  36. package/errors.js +0 -345
  37. package/errors.ts +0 -371
  38. package/index.js +0 -73
  39. package/index.ts +0 -59
  40. package/onboarding/doppler.ts +0 -116
  41. package/onboarding/git.ts +0 -133
  42. package/onboarding/index.ts +0 -18
  43. package/onboarding/onboarding.ts +0 -193
  44. package/onboarding/tailscale.ts +0 -159
  45. package/onboarding/types.ts +0 -115
  46. package/pricing.js +0 -387
  47. package/pricing.ts +0 -422
  48. package/schemas.js +0 -667
  49. package/schemas.ts +0 -765
  50. package/server-status.js +0 -122
  51. package/server-status.ts +0 -81
  52. package/servers.js +0 -667
  53. package/servers.ts +0 -568
  54. package/ssh-keys.js +0 -180
  55. package/ssh-keys.ts +0 -122
  56. package/ssh-setup.js +0 -253
  57. package/ssh-setup.ts +0 -218
  58. package/types.js +0 -99
  59. package/types.ts +0 -389
  60. package/volumes.js +0 -295
  61. package/volumes.ts +0 -229
package/ssh-keys.js DELETED
@@ -1,180 +0,0 @@
1
- "use strict";
2
- /**
3
- * Hetzner SSH key operations
4
- */
5
- var __assign = (this && this.__assign) || function () {
6
- __assign = Object.assign || function(t) {
7
- for (var s, i = 1, n = arguments.length; i < n; i++) {
8
- s = arguments[i];
9
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
10
- t[p] = s[p];
11
- }
12
- return t;
13
- };
14
- return __assign.apply(this, arguments);
15
- };
16
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18
- return new (P || (P = Promise))(function (resolve, reject) {
19
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22
- step((generator = generator.apply(thisArg, _arguments || [])).next());
23
- });
24
- };
25
- var __generator = (this && this.__generator) || function (thisArg, body) {
26
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
27
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
28
- function verb(n) { return function (v) { return step([n, v]); }; }
29
- function step(op) {
30
- if (f) throw new TypeError("Generator is already executing.");
31
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
32
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
33
- if (y = 0, t) op = [op[0] & 2, t.value];
34
- switch (op[0]) {
35
- case 0: case 1: t = op; break;
36
- case 4: _.label++; return { value: op[1], done: false };
37
- case 5: _.label++; y = op[1]; op = [0]; continue;
38
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
39
- default:
40
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
41
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
42
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
43
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
44
- if (t[2]) _.ops.pop();
45
- _.trys.pop(); continue;
46
- }
47
- op = body.call(thisArg, _);
48
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
49
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
50
- }
51
- };
52
- Object.defineProperty(exports, "__esModule", { value: true });
53
- exports.SSHKeyOperations = void 0;
54
- var schemas_js_1 = require("./schemas.js");
55
- var SSHKeyOperations = /** @class */ (function () {
56
- function SSHKeyOperations(client) {
57
- this.client = client;
58
- }
59
- /**
60
- * List all SSH keys
61
- */
62
- SSHKeyOperations.prototype.list = function () {
63
- return __awaiter(this, void 0, void 0, function () {
64
- var response, validated;
65
- return __generator(this, function (_a) {
66
- switch (_a.label) {
67
- case 0: return [4 /*yield*/, this.client.request("/ssh_keys")];
68
- case 1:
69
- response = _a.sent();
70
- validated = schemas_js_1.HetznerListSSHKeysResponseSchema.safeParse(response);
71
- if (!validated.success) {
72
- console.warn('Hetzner list SSH keys validation warning:', validated.error.issues);
73
- return [2 /*return*/, response.ssh_keys]; // Return unvalidated data for backward compatibility
74
- }
75
- return [2 /*return*/, validated.data.ssh_keys];
76
- }
77
- });
78
- });
79
- };
80
- /**
81
- * Get a specific SSH key by ID or name
82
- */
83
- SSHKeyOperations.prototype.get = function (idOrName) {
84
- return __awaiter(this, void 0, void 0, function () {
85
- var endpoint, response, validated;
86
- return __generator(this, function (_a) {
87
- switch (_a.label) {
88
- case 0:
89
- endpoint = typeof idOrName === 'number'
90
- ? "/ssh_keys/".concat(idOrName)
91
- : "/ssh_keys?name=".concat(encodeURIComponent(idOrName));
92
- return [4 /*yield*/, this.client.request(endpoint)];
93
- case 1:
94
- response = _a.sent();
95
- validated = schemas_js_1.HetznerGetSSHKeyResponseSchema.safeParse(response);
96
- if (!validated.success) {
97
- console.warn('Hetzner get SSH key validation warning:', validated.error.issues);
98
- return [2 /*return*/, response.ssh_key]; // Return unvalidated data for backward compatibility
99
- }
100
- return [2 /*return*/, validated.data.ssh_key];
101
- }
102
- });
103
- });
104
- };
105
- /**
106
- * Create a new SSH key
107
- *
108
- * @param options - SSH key creation options
109
- * @returns Created SSH key
110
- */
111
- SSHKeyOperations.prototype.create = function (options) {
112
- return __awaiter(this, void 0, void 0, function () {
113
- var validatedOptions, body, response, validated;
114
- return __generator(this, function (_a) {
115
- switch (_a.label) {
116
- case 0:
117
- validatedOptions = schemas_js_1.HetznerCreateSSHKeyRequestSchema.safeParse(options);
118
- if (!validatedOptions.success) {
119
- throw new Error("Invalid SSH key options: ".concat(validatedOptions.error.issues.map(function (i) { return i.message; }).join(', ')));
120
- }
121
- body = __assign({ name: validatedOptions.data.name, public_key: validatedOptions.data.public_key }, (validatedOptions.data.labels && { labels: validatedOptions.data.labels }));
122
- return [4 /*yield*/, this.client.request("/ssh_keys", {
123
- method: "POST",
124
- body: JSON.stringify(body),
125
- })];
126
- case 1:
127
- response = _a.sent();
128
- validated = schemas_js_1.HetznerCreateSSHKeyResponseSchema.safeParse(response);
129
- if (!validated.success) {
130
- console.warn('Hetzner create SSH key validation warning:', validated.error.issues);
131
- return [2 /*return*/, response.ssh_key]; // Return unvalidated data for backward compatibility
132
- }
133
- return [2 /*return*/, validated.data.ssh_key];
134
- }
135
- });
136
- });
137
- };
138
- /**
139
- * Delete an SSH key
140
- *
141
- * @param id - SSH key ID
142
- */
143
- SSHKeyOperations.prototype.delete = function (id) {
144
- return __awaiter(this, void 0, void 0, function () {
145
- return __generator(this, function (_a) {
146
- switch (_a.label) {
147
- case 0: return [4 /*yield*/, this.client.request("/ssh_keys/".concat(id), { method: "DELETE" })];
148
- case 1:
149
- _a.sent();
150
- return [2 /*return*/];
151
- }
152
- });
153
- });
154
- };
155
- /**
156
- * Find an SSH key by name
157
- * Returns undefined if not found
158
- */
159
- SSHKeyOperations.prototype.findByName = function (name) {
160
- return __awaiter(this, void 0, void 0, function () {
161
- var keys, _a;
162
- return __generator(this, function (_b) {
163
- switch (_b.label) {
164
- case 0:
165
- _b.trys.push([0, 2, , 3]);
166
- return [4 /*yield*/, this.list()];
167
- case 1:
168
- keys = _b.sent();
169
- return [2 /*return*/, keys.find(function (key) { return key.name === name; })];
170
- case 2:
171
- _a = _b.sent();
172
- return [2 /*return*/, undefined];
173
- case 3: return [2 /*return*/];
174
- }
175
- });
176
- });
177
- };
178
- return SSHKeyOperations;
179
- }());
180
- exports.SSHKeyOperations = SSHKeyOperations;
package/ssh-keys.ts DELETED
@@ -1,122 +0,0 @@
1
- /**
2
- * Hetzner SSH key operations
3
- */
4
-
5
- import { z } from "zod";
6
- import type {
7
- HetznerSSHKey,
8
- CreateSSHKeyOptions,
9
- } from "./types.js";
10
- import type { HetznerClient } from "./client.js";
11
- import {
12
- HetznerListSSHKeysResponseSchema,
13
- HetznerGetSSHKeyResponseSchema,
14
- HetznerCreateSSHKeyRequestSchema,
15
- HetznerCreateSSHKeyResponseSchema,
16
- } from "./schemas.js";
17
-
18
- export class SSHKeyOperations {
19
- constructor(private client: HetznerClient) {}
20
-
21
- /**
22
- * List all SSH keys
23
- */
24
- async list(): Promise<HetznerSSHKey[]> {
25
- const response = await this.client.request<{ ssh_keys: HetznerSSHKey[] }>(
26
- "/ssh_keys",
27
- );
28
-
29
- // Validate response with Zod
30
- const validated = HetznerListSSHKeysResponseSchema.safeParse(response);
31
- if (!validated.success) {
32
- console.warn('Hetzner list SSH keys validation warning:', validated.error.issues);
33
- return response.ssh_keys; // Return unvalidated data for backward compatibility
34
- }
35
-
36
- return validated.data.ssh_keys;
37
- }
38
-
39
- /**
40
- * Get a specific SSH key by ID or name
41
- */
42
- async get(idOrName: number | string): Promise<HetznerSSHKey> {
43
- const endpoint = typeof idOrName === 'number'
44
- ? `/ssh_keys/${idOrName}`
45
- : `/ssh_keys?name=${encodeURIComponent(idOrName)}`;
46
-
47
- const response = await this.client.request<{ ssh_key: HetznerSSHKey }>(
48
- endpoint,
49
- );
50
-
51
- // Validate response with Zod
52
- const validated = HetznerGetSSHKeyResponseSchema.safeParse(response);
53
- if (!validated.success) {
54
- console.warn('Hetzner get SSH key validation warning:', validated.error.issues);
55
- return response.ssh_key; // Return unvalidated data for backward compatibility
56
- }
57
-
58
- return validated.data.ssh_key;
59
- }
60
-
61
- /**
62
- * Create a new SSH key
63
- *
64
- * @param options - SSH key creation options
65
- * @returns Created SSH key
66
- */
67
- async create(options: CreateSSHKeyOptions): Promise<HetznerSSHKey> {
68
- // Validate input with Zod
69
- const validatedOptions = HetznerCreateSSHKeyRequestSchema.safeParse(options);
70
- if (!validatedOptions.success) {
71
- throw new Error(`Invalid SSH key options: ${validatedOptions.error.issues.map(i => i.message).join(', ')}`);
72
- }
73
-
74
- const body = {
75
- name: validatedOptions.data.name,
76
- public_key: validatedOptions.data.public_key,
77
- ...(validatedOptions.data.labels && { labels: validatedOptions.data.labels }),
78
- };
79
-
80
- const response = await this.client.request<{ ssh_key: HetznerSSHKey }>(
81
- "/ssh_keys",
82
- {
83
- method: "POST",
84
- body: JSON.stringify(body),
85
- }
86
- );
87
-
88
- // Validate response with Zod
89
- const validated = HetznerCreateSSHKeyResponseSchema.safeParse(response);
90
- if (!validated.success) {
91
- console.warn('Hetzner create SSH key validation warning:', validated.error.issues);
92
- return response.ssh_key; // Return unvalidated data for backward compatibility
93
- }
94
-
95
- return validated.data.ssh_key;
96
- }
97
-
98
- /**
99
- * Delete an SSH key
100
- *
101
- * @param id - SSH key ID
102
- */
103
- async delete(id: number): Promise<void> {
104
- await this.client.request(
105
- `/ssh_keys/${id}`,
106
- { method: "DELETE" }
107
- );
108
- }
109
-
110
- /**
111
- * Find an SSH key by name
112
- * Returns undefined if not found
113
- */
114
- async findByName(name: string): Promise<HetznerSSHKey | undefined> {
115
- try {
116
- const keys = await this.list();
117
- return keys.find(key => key.name === name);
118
- } catch {
119
- return undefined;
120
- }
121
- }
122
- }
package/ssh-setup.js DELETED
@@ -1,253 +0,0 @@
1
- "use strict";
2
- /**
3
- * SSH Key Management for Hetzner
4
- *
5
- * This module ensures SSH keys are properly configured between:
6
- * 1. Local machine (~/.ssh/)
7
- * 2. Hetzner Cloud API
8
- *
9
- * PROBLEM:
10
- * - Creating random keys in Hetzner doesn't work because we need the matching private key locally
11
- * - Password auth is unreliable and often disabled
12
- * - IP reuse causes known_hosts conflicts
13
- *
14
- * SOLUTION:
15
- * - Always use existing local keys or create new key pairs
16
- * - Upload public key to Hetzner, keep private key local
17
- */
18
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
- return new (P || (P = Promise))(function (resolve, reject) {
21
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
- step((generator = generator.apply(thisArg, _arguments || [])).next());
25
- });
26
- };
27
- var __generator = (this && this.__generator) || function (thisArg, body) {
28
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
29
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
30
- function verb(n) { return function (v) { return step([n, v]); }; }
31
- function step(op) {
32
- if (f) throw new TypeError("Generator is already executing.");
33
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
34
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
35
- if (y = 0, t) op = [op[0] & 2, t.value];
36
- switch (op[0]) {
37
- case 0: case 1: t = op; break;
38
- case 4: _.label++; return { value: op[1], done: false };
39
- case 5: _.label++; y = op[1]; op = [0]; continue;
40
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
41
- default:
42
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
43
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
44
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
45
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
46
- if (t[2]) _.ops.pop();
47
- _.trys.pop(); continue;
48
- }
49
- op = body.call(thisArg, _);
50
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
51
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
52
- }
53
- };
54
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
55
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
56
- if (ar || !(i in from)) {
57
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
58
- ar[i] = from[i];
59
- }
60
- }
61
- return to.concat(ar || Array.prototype.slice.call(from));
62
- };
63
- Object.defineProperty(exports, "__esModule", { value: true });
64
- exports.getOrCreateHetznerSSHKey = getOrCreateHetznerSSHKey;
65
- exports.ensureHetznerSSHKey = ensureHetznerSSHKey;
66
- exports.clearKnownHosts = clearKnownHosts;
67
- exports.testSSHConnection = testSSHConnection;
68
- exports.prepareSSHKeys = prepareSSHKeys;
69
- var fs_1 = require("fs");
70
- var path_1 = require("path");
71
- var ssh_1 = require("@ebowwa/ssh");
72
- var HETZNER_SSH_DIR = (0, path_1.join)(process.env.HOME || "", ".ssh");
73
- var HETZNER_KEY_PREFIX = "hetzner-codespaces";
74
- /**
75
- * Get or create a local SSH key pair for Hetzner
76
- * Returns the key name to use with Hetzner API
77
- */
78
- function getOrCreateHetznerSSHKey() {
79
- return __awaiter(this, void 0, void 0, function () {
80
- var existingKey;
81
- return __generator(this, function (_a) {
82
- existingKey = findExistingHetznerKey();
83
- if (existingKey) {
84
- console.log("\u2713 Using existing SSH key: ".concat(existingKey.name));
85
- return [2 /*return*/, existingKey];
86
- }
87
- // Create new key pair
88
- console.log("Creating new SSH key pair for Hetzner...");
89
- return [2 /*return*/, createNewKeyPair()];
90
- });
91
- });
92
- }
93
- /**
94
- * Find existing Hetzner SSH key in local ~/.ssh/
95
- */
96
- function findExistingHetznerKey() {
97
- var privateKeyPath = (0, path_1.join)(HETZNER_SSH_DIR, "".concat(HETZNER_KEY_PREFIX));
98
- var publicKeyPath = "".concat(privateKeyPath, ".pub");
99
- if (!(0, fs_1.existsSync)(privateKeyPath) || !(0, fs_1.existsSync)(publicKeyPath)) {
100
- return null;
101
- }
102
- var publicKey = (0, fs_1.readFileSync)(publicKeyPath, "utf-8").trim();
103
- return {
104
- name: HETZNER_KEY_PREFIX,
105
- publicKey: publicKey,
106
- privateKeyPath: privateKeyPath,
107
- };
108
- }
109
- /**
110
- * Create a new SSH key pair for Hetzner
111
- */
112
- function createNewKeyPair() {
113
- var keyName = "".concat(HETZNER_KEY_PREFIX, "-").concat(Date.now());
114
- var privateKeyPath = (0, path_1.join)(HETZNER_SSH_DIR, keyName);
115
- var publicKeyPath = "".concat(privateKeyPath, ".pub");
116
- // Generate new ed25519 key pair
117
- try {
118
- // Bun.spawn automatically escapes arguments to prevent shell injection
119
- Bun.spawnSync(["ssh-keygen", "-t", "ed25519", "-f", privateKeyPath, "-N", "", "-C", keyName], {
120
- stdout: "ignore",
121
- stderr: "ignore",
122
- });
123
- // Set proper permissions
124
- Bun.spawnSync(["chmod", "600", privateKeyPath], { stdout: "ignore", stderr: "ignore" });
125
- Bun.spawnSync(["chmod", "644", publicKeyPath], { stdout: "ignore", stderr: "ignore" });
126
- var publicKey = (0, fs_1.readFileSync)(publicKeyPath, "utf-8").trim();
127
- console.log("\u2713 Created new SSH key: ".concat(keyName));
128
- console.log(" Private: ".concat(privateKeyPath));
129
- console.log(" Public: ".concat(publicKeyPath));
130
- return {
131
- name: keyName,
132
- publicKey: publicKey,
133
- privateKeyPath: privateKeyPath,
134
- };
135
- }
136
- catch (error) {
137
- throw new Error("Failed to create SSH key: ".concat(error));
138
- }
139
- }
140
- /**
141
- * Ensure SSH key exists in Hetzner
142
- * Returns the SSH key name to use when creating servers
143
- */
144
- function ensureHetznerSSHKey(hetznerAPI) {
145
- return __awaiter(this, void 0, void 0, function () {
146
- var localKey, existingKeys, matchingKey, createResponse, error, createdKey;
147
- return __generator(this, function (_a) {
148
- switch (_a.label) {
149
- case 0: return [4 /*yield*/, getOrCreateHetznerSSHKey()];
150
- case 1:
151
- localKey = _a.sent();
152
- return [4 /*yield*/, hetznerAPI("/ssh_keys", "GET", null)
153
- .then(function (r) { return r.json(); })
154
- .then(function (data) { return data.ssh_keys; })];
155
- case 2:
156
- existingKeys = _a.sent();
157
- matchingKey = existingKeys.find(function (k) { return k.public_key.trim() === localKey.publicKey.trim(); });
158
- if (matchingKey) {
159
- console.log("\u2713 SSH key already exists in Hetzner: ".concat(matchingKey.name, " (").concat(matchingKey.id, ")"));
160
- return [2 /*return*/, matchingKey.name]; // Use existing key name
161
- }
162
- // 4. Upload new key to Hetzner
163
- console.log("Uploading SSH key to Hetzner: ".concat(localKey.name, "..."));
164
- return [4 /*yield*/, hetznerAPI("/ssh_keys", "POST", {
165
- name: localKey.name,
166
- public_key: localKey.publicKey,
167
- })];
168
- case 3:
169
- createResponse = _a.sent();
170
- if (!!createResponse.ok) return [3 /*break*/, 5];
171
- return [4 /*yield*/, createResponse.text()];
172
- case 4:
173
- error = _a.sent();
174
- throw new Error("Failed to upload SSH key to Hetzner: ".concat(error));
175
- case 5: return [4 /*yield*/, createResponse.json()];
176
- case 6:
177
- createdKey = (_a.sent());
178
- console.log("\u2713 SSH key uploaded: ".concat(createdKey.name, " (").concat(createdKey.id, ")"));
179
- return [2 /*return*/, createdKey.name];
180
- }
181
- });
182
- });
183
- }
184
- /**
185
- * Clear known_hosts entry for an IP (to fix IP reuse issues)
186
- */
187
- function clearKnownHosts(ip) {
188
- try {
189
- // Bun.spawn automatically escapes arguments to prevent shell injection
190
- Bun.spawnSync(["ssh-keygen", "-R", ip], { stdout: "ignore", stderr: "ignore" });
191
- console.log("\u2713 Cleared known_hosts entry for ".concat(ip));
192
- }
193
- catch (_a) {
194
- // Ignore if entry doesn't exist
195
- }
196
- }
197
- /**
198
- * Test SSH connection to a server using typed flags
199
- */
200
- function testSSHConnection(ip_1, privateKeyPath_1) {
201
- return __awaiter(this, arguments, void 0, function (ip, privateKeyPath, username) {
202
- var flags, sshArgs, proc_1, timeout, _a;
203
- if (username === void 0) { username = "root"; }
204
- return __generator(this, function (_b) {
205
- switch (_b.label) {
206
- case 0:
207
- _b.trys.push([0, 2, , 3]);
208
- flags = __spreadArray(__spreadArray([], ssh_1.SSHPresets.default, true), [
209
- ssh_1.SSHFlags.identity(privateKeyPath),
210
- (0, ssh_1.sshConfig)("ConnectTimeout", "10"),
211
- ], false);
212
- sshArgs = (0, ssh_1.buildSSHArgs)(flags, ip, username);
213
- sshArgs.push("echo", "connected");
214
- proc_1 = Bun.spawn(sshArgs, {
215
- stdout: "ignore",
216
- stderr: "ignore",
217
- });
218
- timeout = setTimeout(function () { return proc_1.kill(); }, 15000);
219
- return [4 /*yield*/, proc_1.exited];
220
- case 1:
221
- _b.sent();
222
- clearTimeout(timeout);
223
- return [2 /*return*/, proc_1.exitCode === 0];
224
- case 2:
225
- _a = _b.sent();
226
- return [2 /*return*/, false];
227
- case 3: return [2 /*return*/];
228
- }
229
- });
230
- });
231
- }
232
- /**
233
- * Full workflow: Prepare SSH keys before creating servers
234
- */
235
- function prepareSSHKeys(hetznerAPI) {
236
- return __awaiter(this, void 0, void 0, function () {
237
- var sshKeyName, localKey;
238
- return __generator(this, function (_a) {
239
- switch (_a.label) {
240
- case 0: return [4 /*yield*/, ensureHetznerSSHKey(hetznerAPI)];
241
- case 1:
242
- sshKeyName = _a.sent();
243
- return [4 /*yield*/, getOrCreateHetznerSSHKey()];
244
- case 2:
245
- localKey = _a.sent();
246
- return [2 /*return*/, {
247
- sshKeyName: sshKeyName,
248
- privateKeyPath: localKey.privateKeyPath,
249
- }];
250
- }
251
- });
252
- });
253
- }