hetzner-cli 2.0.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 +907 -0
- package/dist/auction/client.d.ts +4 -0
- package/dist/auction/client.js +103 -0
- package/dist/auction/commands.d.ts +2 -0
- package/dist/auction/commands.js +138 -0
- package/dist/auction/formatter.d.ts +3 -0
- package/dist/auction/formatter.js +87 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +39 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -0
- package/dist/cloud/client.d.ts +511 -0
- package/dist/cloud/client.js +706 -0
- package/dist/cloud/commands/certificate.d.ts +2 -0
- package/dist/cloud/commands/certificate.js +77 -0
- package/dist/cloud/commands/context.d.ts +2 -0
- package/dist/cloud/commands/context.js +78 -0
- package/dist/cloud/commands/datacenter.d.ts +2 -0
- package/dist/cloud/commands/datacenter.js +20 -0
- package/dist/cloud/commands/firewall.d.ts +2 -0
- package/dist/cloud/commands/firewall.js +77 -0
- package/dist/cloud/commands/floating-ip.d.ts +2 -0
- package/dist/cloud/commands/floating-ip.js +83 -0
- package/dist/cloud/commands/image.d.ts +2 -0
- package/dist/cloud/commands/image.js +60 -0
- package/dist/cloud/commands/index.d.ts +2 -0
- package/dist/cloud/commands/index.js +41 -0
- package/dist/cloud/commands/iso.d.ts +2 -0
- package/dist/cloud/commands/iso.js +22 -0
- package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
- package/dist/cloud/commands/load-balancer-type.js +20 -0
- package/dist/cloud/commands/load-balancer.d.ts +2 -0
- package/dist/cloud/commands/load-balancer.js +177 -0
- package/dist/cloud/commands/location.d.ts +2 -0
- package/dist/cloud/commands/location.js +20 -0
- package/dist/cloud/commands/network.d.ts +2 -0
- package/dist/cloud/commands/network.js +96 -0
- package/dist/cloud/commands/placement-group.d.ts +2 -0
- package/dist/cloud/commands/placement-group.js +53 -0
- package/dist/cloud/commands/primary-ip.d.ts +2 -0
- package/dist/cloud/commands/primary-ip.js +83 -0
- package/dist/cloud/commands/server-type.d.ts +2 -0
- package/dist/cloud/commands/server-type.js +20 -0
- package/dist/cloud/commands/server.d.ts +2 -0
- package/dist/cloud/commands/server.js +260 -0
- package/dist/cloud/commands/ssh-key.d.ts +2 -0
- package/dist/cloud/commands/ssh-key.js +63 -0
- package/dist/cloud/commands/volume.d.ts +2 -0
- package/dist/cloud/commands/volume.js +92 -0
- package/dist/cloud/context.d.ts +28 -0
- package/dist/cloud/context.js +172 -0
- package/dist/cloud/formatter.d.ts +37 -0
- package/dist/cloud/formatter.js +413 -0
- package/dist/cloud/helpers.d.ts +18 -0
- package/dist/cloud/helpers.js +48 -0
- package/dist/cloud/types.d.ts +398 -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 +10 -0
- package/dist/index.js +17 -0
- package/dist/robot/client.d.ts +256 -0
- package/dist/robot/client.js +656 -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 +72 -0
- package/dist/robot/commands/cancel.d.ts +2 -0
- package/dist/robot/commands/cancel.js +36 -0
- package/dist/robot/commands/failover.d.ts +2 -0
- package/dist/robot/commands/failover.js +42 -0
- package/dist/robot/commands/firewall.d.ts +2 -0
- package/dist/robot/commands/firewall.js +66 -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 +134 -0
- package/dist/robot/commands/ip.d.ts +2 -0
- package/dist/robot/commands/ip.js +52 -0
- package/dist/robot/commands/key.d.ts +2 -0
- package/dist/robot/commands/key.js +64 -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 +29 -0
- package/dist/robot/commands/storagebox.d.ts +2 -0
- package/dist/robot/commands/storagebox.js +116 -0
- package/dist/robot/commands/subnet.d.ts +2 -0
- package/dist/robot/commands/subnet.js +21 -0
- package/dist/robot/commands/traffic.d.ts +2 -0
- package/dist/robot/commands/traffic.js +20 -0
- package/dist/robot/commands/vswitch.d.ts +2 -0
- package/dist/robot/commands/vswitch.js +64 -0
- package/dist/robot/commands/wol.d.ts +2 -0
- package/dist/robot/commands/wol.js +20 -0
- package/dist/robot/formatter.d.ts +58 -0
- package/dist/robot/formatter.js +500 -0
- package/dist/robot/types.d.ts +352 -0
- package/dist/robot/types.js +5 -0
- package/dist/shared/config.d.ts +86 -0
- package/dist/shared/config.js +273 -0
- package/dist/shared/formatter.d.ts +29 -0
- package/dist/shared/formatter.js +118 -0
- package/dist/shared/helpers.d.ts +17 -0
- package/dist/shared/helpers.js +72 -0
- package/dist/shared/reference.d.ts +2 -0
- package/dist/shared/reference.js +626 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
export interface Server {
|
|
2
|
+
server_ip: string;
|
|
3
|
+
server_ipv6_net: string;
|
|
4
|
+
server_number: number;
|
|
5
|
+
server_name: string;
|
|
6
|
+
product: string;
|
|
7
|
+
dc: string;
|
|
8
|
+
traffic: string;
|
|
9
|
+
status: 'ready' | 'installing' | 'maintenance';
|
|
10
|
+
cancelled: boolean;
|
|
11
|
+
paid_until: string;
|
|
12
|
+
ip: string[];
|
|
13
|
+
subnet: ServerSubnet[];
|
|
14
|
+
}
|
|
15
|
+
export interface ServerSubnet {
|
|
16
|
+
ip: string;
|
|
17
|
+
mask: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ServerDetails extends Server {
|
|
20
|
+
reset: boolean;
|
|
21
|
+
rescue: boolean;
|
|
22
|
+
vnc: boolean;
|
|
23
|
+
windows: boolean;
|
|
24
|
+
plesk: boolean;
|
|
25
|
+
cpanel: boolean;
|
|
26
|
+
wol: boolean;
|
|
27
|
+
hot_swap: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface Cancellation {
|
|
30
|
+
server_ip: string;
|
|
31
|
+
server_ipv6_net: string;
|
|
32
|
+
server_number: number;
|
|
33
|
+
server_name: string;
|
|
34
|
+
earliest_cancellation_date: string;
|
|
35
|
+
cancelled: boolean;
|
|
36
|
+
cancellation_date: string | null;
|
|
37
|
+
cancellation_reason: string[] | null;
|
|
38
|
+
}
|
|
39
|
+
export type ResetType = 'sw' | 'hw' | 'man' | 'power' | 'power_long';
|
|
40
|
+
export interface Reset {
|
|
41
|
+
server_ip: string;
|
|
42
|
+
server_ipv6_net: string;
|
|
43
|
+
server_number: number;
|
|
44
|
+
type: ResetType[];
|
|
45
|
+
operating_status: string;
|
|
46
|
+
}
|
|
47
|
+
export interface BootConfig {
|
|
48
|
+
rescue: RescueConfig | null;
|
|
49
|
+
linux: LinuxConfig | null;
|
|
50
|
+
vnc: VncConfig | null;
|
|
51
|
+
windows: WindowsConfig | null;
|
|
52
|
+
plesk: PleskConfig | null;
|
|
53
|
+
cpanel: CpanelConfig | null;
|
|
54
|
+
}
|
|
55
|
+
interface BaseBootConfig {
|
|
56
|
+
server_ip: string;
|
|
57
|
+
server_ipv6_net: string;
|
|
58
|
+
server_number: number;
|
|
59
|
+
active: boolean;
|
|
60
|
+
password: string | null;
|
|
61
|
+
}
|
|
62
|
+
export interface RescueConfig extends BaseBootConfig {
|
|
63
|
+
os: string[];
|
|
64
|
+
arch: number[];
|
|
65
|
+
authorized_key: string[];
|
|
66
|
+
host_key: string[];
|
|
67
|
+
}
|
|
68
|
+
export interface LinuxConfig extends BaseBootConfig {
|
|
69
|
+
dist: string[];
|
|
70
|
+
arch: number[];
|
|
71
|
+
lang: string[];
|
|
72
|
+
authorized_key: string[];
|
|
73
|
+
host_key: string[];
|
|
74
|
+
}
|
|
75
|
+
export interface VncConfig extends BaseBootConfig {
|
|
76
|
+
dist: string[];
|
|
77
|
+
arch: number[];
|
|
78
|
+
lang: string[];
|
|
79
|
+
}
|
|
80
|
+
export interface WindowsConfig extends BaseBootConfig {
|
|
81
|
+
dist: string[];
|
|
82
|
+
lang: string[];
|
|
83
|
+
}
|
|
84
|
+
export interface PleskConfig extends BaseBootConfig {
|
|
85
|
+
dist: string[];
|
|
86
|
+
arch: number[];
|
|
87
|
+
lang: string[];
|
|
88
|
+
hostname: string | null;
|
|
89
|
+
}
|
|
90
|
+
export interface CpanelConfig extends BaseBootConfig {
|
|
91
|
+
dist: string[];
|
|
92
|
+
arch: number[];
|
|
93
|
+
lang: string[];
|
|
94
|
+
hostname: string | null;
|
|
95
|
+
}
|
|
96
|
+
export interface IP {
|
|
97
|
+
ip: string;
|
|
98
|
+
server_ip: string;
|
|
99
|
+
server_number: number;
|
|
100
|
+
locked: boolean;
|
|
101
|
+
separate_mac: string | null;
|
|
102
|
+
traffic_warnings: boolean;
|
|
103
|
+
traffic_hourly: number;
|
|
104
|
+
traffic_daily: number;
|
|
105
|
+
traffic_monthly: number;
|
|
106
|
+
}
|
|
107
|
+
export interface Mac {
|
|
108
|
+
ip: string;
|
|
109
|
+
mac: string;
|
|
110
|
+
}
|
|
111
|
+
export interface Subnet {
|
|
112
|
+
ip: string;
|
|
113
|
+
mask: string;
|
|
114
|
+
gateway: string;
|
|
115
|
+
server_ip: string;
|
|
116
|
+
server_number: number;
|
|
117
|
+
failover: boolean;
|
|
118
|
+
locked: boolean;
|
|
119
|
+
traffic_warnings: boolean;
|
|
120
|
+
traffic_hourly: number;
|
|
121
|
+
traffic_daily: number;
|
|
122
|
+
traffic_monthly: number;
|
|
123
|
+
}
|
|
124
|
+
export interface Failover {
|
|
125
|
+
ip: string;
|
|
126
|
+
netmask: string;
|
|
127
|
+
server_ip: string;
|
|
128
|
+
server_number: number;
|
|
129
|
+
active_server_ip: string;
|
|
130
|
+
}
|
|
131
|
+
export interface Rdns {
|
|
132
|
+
ip: string;
|
|
133
|
+
ptr: string;
|
|
134
|
+
}
|
|
135
|
+
export interface SshKey {
|
|
136
|
+
name: string;
|
|
137
|
+
fingerprint: string;
|
|
138
|
+
type: string;
|
|
139
|
+
size: number;
|
|
140
|
+
data: string;
|
|
141
|
+
}
|
|
142
|
+
export interface Firewall {
|
|
143
|
+
server_ip: string;
|
|
144
|
+
server_number: number;
|
|
145
|
+
status: 'active' | 'disabled' | 'in process';
|
|
146
|
+
filter_ipv6: boolean;
|
|
147
|
+
whitelist_hos: boolean;
|
|
148
|
+
port: 'main' | 'kvm';
|
|
149
|
+
rules: {
|
|
150
|
+
input: FirewallRule[];
|
|
151
|
+
output?: FirewallRule[];
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export interface FirewallRule {
|
|
155
|
+
ip_version: string;
|
|
156
|
+
name: string;
|
|
157
|
+
dst_ip: string | null;
|
|
158
|
+
dst_port: string | null;
|
|
159
|
+
src_ip: string | null;
|
|
160
|
+
src_port: string | null;
|
|
161
|
+
protocol: string | null;
|
|
162
|
+
tcp_flags: string | null;
|
|
163
|
+
action: 'accept' | 'discard';
|
|
164
|
+
}
|
|
165
|
+
export interface FirewallTemplate {
|
|
166
|
+
id: number;
|
|
167
|
+
name: string;
|
|
168
|
+
filter_ipv6: boolean;
|
|
169
|
+
whitelist_hos: boolean;
|
|
170
|
+
is_default: boolean;
|
|
171
|
+
rules: {
|
|
172
|
+
input: FirewallRule[];
|
|
173
|
+
output?: FirewallRule[];
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export interface VSwitch {
|
|
177
|
+
id: number;
|
|
178
|
+
name: string;
|
|
179
|
+
vlan: number;
|
|
180
|
+
cancelled: boolean;
|
|
181
|
+
server: VSwitchServer[];
|
|
182
|
+
subnet: VSwitchSubnet[];
|
|
183
|
+
cloud_network: VSwitchCloudNetwork[];
|
|
184
|
+
}
|
|
185
|
+
export interface VSwitchServer {
|
|
186
|
+
server_ip: string;
|
|
187
|
+
server_ipv6_net: string;
|
|
188
|
+
server_number: number;
|
|
189
|
+
status: 'ready' | 'in process' | 'failed';
|
|
190
|
+
}
|
|
191
|
+
export interface VSwitchSubnet {
|
|
192
|
+
ip: string;
|
|
193
|
+
mask: number;
|
|
194
|
+
gateway: string;
|
|
195
|
+
}
|
|
196
|
+
export interface VSwitchCloudNetwork {
|
|
197
|
+
id: number;
|
|
198
|
+
ip: string;
|
|
199
|
+
mask: number;
|
|
200
|
+
gateway: string;
|
|
201
|
+
}
|
|
202
|
+
export interface StorageBox {
|
|
203
|
+
id: number;
|
|
204
|
+
login: string;
|
|
205
|
+
name: string;
|
|
206
|
+
product: string;
|
|
207
|
+
cancelled: boolean;
|
|
208
|
+
locked: boolean;
|
|
209
|
+
location: string;
|
|
210
|
+
linked_server: number | null;
|
|
211
|
+
paid_until: string;
|
|
212
|
+
disk_quota: number;
|
|
213
|
+
disk_usage: number;
|
|
214
|
+
disk_usage_data: number;
|
|
215
|
+
disk_usage_snapshots: number;
|
|
216
|
+
webdav: boolean;
|
|
217
|
+
samba: boolean;
|
|
218
|
+
ssh: boolean;
|
|
219
|
+
external_reachability: boolean;
|
|
220
|
+
zfs: boolean;
|
|
221
|
+
server: string;
|
|
222
|
+
host_system: string;
|
|
223
|
+
}
|
|
224
|
+
export interface StorageBoxSnapshot {
|
|
225
|
+
name: string;
|
|
226
|
+
timestamp: string;
|
|
227
|
+
size: number;
|
|
228
|
+
size_formatted: string;
|
|
229
|
+
}
|
|
230
|
+
export interface StorageBoxSnapshotPlan {
|
|
231
|
+
status: 'enabled' | 'disabled';
|
|
232
|
+
minute: number;
|
|
233
|
+
hour: number;
|
|
234
|
+
day_of_week: number;
|
|
235
|
+
day_of_month: number;
|
|
236
|
+
max_snapshots: number;
|
|
237
|
+
}
|
|
238
|
+
export interface StorageBoxSubaccount {
|
|
239
|
+
username: string;
|
|
240
|
+
accountid: string;
|
|
241
|
+
server: string;
|
|
242
|
+
homedirectory: string;
|
|
243
|
+
samba: boolean;
|
|
244
|
+
ssh: boolean;
|
|
245
|
+
external_reachability: boolean;
|
|
246
|
+
webdav: boolean;
|
|
247
|
+
readonly: boolean;
|
|
248
|
+
createtime: string;
|
|
249
|
+
comment: string;
|
|
250
|
+
}
|
|
251
|
+
export interface Traffic {
|
|
252
|
+
ip: string;
|
|
253
|
+
type: 'day' | 'month' | 'year';
|
|
254
|
+
from: string;
|
|
255
|
+
to: string;
|
|
256
|
+
data: TrafficData[];
|
|
257
|
+
}
|
|
258
|
+
export interface TrafficData {
|
|
259
|
+
in: number;
|
|
260
|
+
out: number;
|
|
261
|
+
sum: number;
|
|
262
|
+
date?: string;
|
|
263
|
+
}
|
|
264
|
+
export interface Wol {
|
|
265
|
+
server_ip: string;
|
|
266
|
+
server_ipv6_net: string;
|
|
267
|
+
server_number: number;
|
|
268
|
+
}
|
|
269
|
+
export interface ServerProduct {
|
|
270
|
+
id: string;
|
|
271
|
+
name: string;
|
|
272
|
+
description: string[];
|
|
273
|
+
traffic: string;
|
|
274
|
+
dist: string[];
|
|
275
|
+
arch: number[];
|
|
276
|
+
lang: string[];
|
|
277
|
+
location: string[];
|
|
278
|
+
prices: ProductPrice[];
|
|
279
|
+
orderable_addons: string[];
|
|
280
|
+
}
|
|
281
|
+
export interface ProductPrice {
|
|
282
|
+
location: string;
|
|
283
|
+
price: {
|
|
284
|
+
net: string;
|
|
285
|
+
gross: string;
|
|
286
|
+
};
|
|
287
|
+
price_setup: {
|
|
288
|
+
net: string;
|
|
289
|
+
gross: string;
|
|
290
|
+
};
|
|
291
|
+
price_vat: {
|
|
292
|
+
net: string;
|
|
293
|
+
gross: string;
|
|
294
|
+
};
|
|
295
|
+
price_setup_vat: {
|
|
296
|
+
net: string;
|
|
297
|
+
gross: string;
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
export interface ServerMarketProduct {
|
|
301
|
+
id: number;
|
|
302
|
+
name: string;
|
|
303
|
+
description: string[];
|
|
304
|
+
traffic: string;
|
|
305
|
+
dist: string[];
|
|
306
|
+
arch: number[];
|
|
307
|
+
lang: string[];
|
|
308
|
+
cpu: string;
|
|
309
|
+
cpu_benchmark: number;
|
|
310
|
+
memory_size: number;
|
|
311
|
+
hdd_size: number;
|
|
312
|
+
hdd_text: string;
|
|
313
|
+
hdd_count: number;
|
|
314
|
+
datacenter: string;
|
|
315
|
+
network_speed: string;
|
|
316
|
+
price: string;
|
|
317
|
+
price_setup: string;
|
|
318
|
+
fixed_price: boolean;
|
|
319
|
+
next_reduce: number;
|
|
320
|
+
next_reduce_date: string;
|
|
321
|
+
orderable_addons: string[];
|
|
322
|
+
}
|
|
323
|
+
export interface ServerTransaction {
|
|
324
|
+
id: string;
|
|
325
|
+
date: string;
|
|
326
|
+
status: 'ready' | 'in process' | 'cancelled';
|
|
327
|
+
server_number: number | null;
|
|
328
|
+
server_ip: string | null;
|
|
329
|
+
authorized_key: string[];
|
|
330
|
+
host_key: string[];
|
|
331
|
+
comment: string;
|
|
332
|
+
product: ServerTransactionProduct;
|
|
333
|
+
}
|
|
334
|
+
export interface ServerTransactionProduct {
|
|
335
|
+
id: string;
|
|
336
|
+
name: string;
|
|
337
|
+
description: string[];
|
|
338
|
+
traffic: string;
|
|
339
|
+
dist: string;
|
|
340
|
+
arch: number;
|
|
341
|
+
lang: string;
|
|
342
|
+
location: string;
|
|
343
|
+
}
|
|
344
|
+
export type ApiResponse<T> = Record<string, T>;
|
|
345
|
+
export interface ApiError {
|
|
346
|
+
error: {
|
|
347
|
+
status: number;
|
|
348
|
+
code: string;
|
|
349
|
+
message: string;
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if system keychain is available
|
|
3
|
+
*/
|
|
4
|
+
export declare function hasKeychainSupport(): Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* Get credentials from system keychain
|
|
7
|
+
*/
|
|
8
|
+
export declare function getKeychainCredentials(): Promise<{
|
|
9
|
+
user: string;
|
|
10
|
+
password: string;
|
|
11
|
+
} | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Save credentials to system keychain
|
|
14
|
+
* @returns true if saved successfully, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
export declare function saveToKeychain(user: string, password: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Clear credentials from system keychain
|
|
19
|
+
*/
|
|
20
|
+
export declare function clearKeychain(): Promise<void>;
|
|
21
|
+
export type CredentialSource = 'environment' | 'keychain' | 'file' | null;
|
|
22
|
+
export interface Config {
|
|
23
|
+
user?: string;
|
|
24
|
+
password?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Load configuration from file
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadConfig(): Config;
|
|
30
|
+
/**
|
|
31
|
+
* Save configuration to file
|
|
32
|
+
*/
|
|
33
|
+
export declare function saveConfig(cfg: Config): void;
|
|
34
|
+
/**
|
|
35
|
+
* Clear saved configuration from file
|
|
36
|
+
*/
|
|
37
|
+
export declare function clearConfigFile(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Clear saved configuration from all storage locations
|
|
40
|
+
*/
|
|
41
|
+
export declare function clearConfig(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Get credentials from environment variables only (sync)
|
|
44
|
+
*/
|
|
45
|
+
export declare function getCredentialsFromEnv(): {
|
|
46
|
+
user: string;
|
|
47
|
+
password: string;
|
|
48
|
+
} | null;
|
|
49
|
+
/**
|
|
50
|
+
* Get credentials from config file only (sync)
|
|
51
|
+
*/
|
|
52
|
+
export declare function getCredentialsFromFile(): {
|
|
53
|
+
user: string;
|
|
54
|
+
password: string;
|
|
55
|
+
} | null;
|
|
56
|
+
/**
|
|
57
|
+
* Get credentials from environment variables, keychain, or config file
|
|
58
|
+
* Priority: env vars → keychain → config file
|
|
59
|
+
*/
|
|
60
|
+
export declare function getCredentials(): Promise<{
|
|
61
|
+
user: string;
|
|
62
|
+
password: string;
|
|
63
|
+
source: CredentialSource;
|
|
64
|
+
} | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Check if credentials are configured (sync check - env and file only)
|
|
67
|
+
*/
|
|
68
|
+
export declare function hasCredentialsSync(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Check if credentials are configured (async - includes keychain)
|
|
71
|
+
*/
|
|
72
|
+
export declare function hasCredentials(): Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Interactive login prompt
|
|
75
|
+
*/
|
|
76
|
+
export declare function promptLogin(): Promise<{
|
|
77
|
+
user: string;
|
|
78
|
+
password: string;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Get credentials, prompting if necessary
|
|
82
|
+
*/
|
|
83
|
+
export declare function requireCredentials(): Promise<{
|
|
84
|
+
user: string;
|
|
85
|
+
password: string;
|
|
86
|
+
}>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { input, password as passwordPrompt, confirm } from '@inquirer/prompts';
|
|
6
|
+
config();
|
|
7
|
+
const CONFIG_DIR = join(homedir(), '.hetzner-cli');
|
|
8
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
9
|
+
const KEYCHAIN_SERVICE = 'hetzner-cli';
|
|
10
|
+
const KEYCHAIN_ACCOUNT = 'robot-api';
|
|
11
|
+
// Lazy-loaded keytar module (optional dependency with native bindings)
|
|
12
|
+
let keytarModule = null;
|
|
13
|
+
let keytarLoadAttempted = false;
|
|
14
|
+
/**
|
|
15
|
+
* Try to load keytar module (may fail if native deps unavailable)
|
|
16
|
+
*/
|
|
17
|
+
async function getKeytar() {
|
|
18
|
+
if (keytarLoadAttempted)
|
|
19
|
+
return keytarModule;
|
|
20
|
+
keytarLoadAttempted = true;
|
|
21
|
+
try {
|
|
22
|
+
keytarModule = await import('keytar');
|
|
23
|
+
return keytarModule;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if system keychain is available
|
|
31
|
+
*/
|
|
32
|
+
export async function hasKeychainSupport() {
|
|
33
|
+
const keytar = await getKeytar();
|
|
34
|
+
if (!keytar)
|
|
35
|
+
return false;
|
|
36
|
+
try {
|
|
37
|
+
// Try a read operation to verify keychain access
|
|
38
|
+
await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get credentials from system keychain
|
|
47
|
+
*/
|
|
48
|
+
export async function getKeychainCredentials() {
|
|
49
|
+
const keytar = await getKeytar();
|
|
50
|
+
if (!keytar)
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
const stored = await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
54
|
+
if (!stored)
|
|
55
|
+
return null;
|
|
56
|
+
const parsed = JSON.parse(stored);
|
|
57
|
+
if (parsed.user && parsed.password) {
|
|
58
|
+
return { user: parsed.user, password: parsed.password };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Save credentials to system keychain
|
|
68
|
+
* @returns true if saved successfully, false otherwise
|
|
69
|
+
*/
|
|
70
|
+
export async function saveToKeychain(user, password) {
|
|
71
|
+
const keytar = await getKeytar();
|
|
72
|
+
if (!keytar)
|
|
73
|
+
return false;
|
|
74
|
+
try {
|
|
75
|
+
await keytar.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, JSON.stringify({ user, password }));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Clear credentials from system keychain
|
|
84
|
+
*/
|
|
85
|
+
export async function clearKeychain() {
|
|
86
|
+
const keytar = await getKeytar();
|
|
87
|
+
if (!keytar)
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
await keytar.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Ignore errors when clearing
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Ensure config directory exists
|
|
98
|
+
*/
|
|
99
|
+
function ensureConfigDir() {
|
|
100
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
101
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Load configuration from file
|
|
106
|
+
*/
|
|
107
|
+
export function loadConfig() {
|
|
108
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const data = readFileSync(CONFIG_FILE, 'utf-8');
|
|
113
|
+
return JSON.parse(data);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Save configuration to file
|
|
121
|
+
*/
|
|
122
|
+
export function saveConfig(cfg) {
|
|
123
|
+
ensureConfigDir();
|
|
124
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Clear saved configuration from file
|
|
128
|
+
*/
|
|
129
|
+
export function clearConfigFile() {
|
|
130
|
+
if (existsSync(CONFIG_FILE)) {
|
|
131
|
+
writeFileSync(CONFIG_FILE, '{}', { mode: 0o600 });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Clear saved configuration from all storage locations
|
|
136
|
+
*/
|
|
137
|
+
export async function clearConfig() {
|
|
138
|
+
await clearKeychain();
|
|
139
|
+
clearConfigFile();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get credentials from environment variables only (sync)
|
|
143
|
+
*/
|
|
144
|
+
export function getCredentialsFromEnv() {
|
|
145
|
+
const envUser = process.env.HETZNER_ROBOT_USER;
|
|
146
|
+
const envPassword = process.env.HETZNER_ROBOT_PASSWORD;
|
|
147
|
+
if (envUser && envPassword) {
|
|
148
|
+
return { user: envUser, password: envPassword };
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get credentials from config file only (sync)
|
|
154
|
+
*/
|
|
155
|
+
export function getCredentialsFromFile() {
|
|
156
|
+
const cfg = loadConfig();
|
|
157
|
+
if (cfg.user && cfg.password) {
|
|
158
|
+
return { user: cfg.user, password: cfg.password };
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get credentials from environment variables, keychain, or config file
|
|
164
|
+
* Priority: env vars → keychain → config file
|
|
165
|
+
*/
|
|
166
|
+
export async function getCredentials() {
|
|
167
|
+
// First try environment variables
|
|
168
|
+
const envCreds = getCredentialsFromEnv();
|
|
169
|
+
if (envCreds) {
|
|
170
|
+
return { ...envCreds, source: 'environment' };
|
|
171
|
+
}
|
|
172
|
+
// Then try keychain
|
|
173
|
+
const keychainCreds = await getKeychainCredentials();
|
|
174
|
+
if (keychainCreds) {
|
|
175
|
+
return { ...keychainCreds, source: 'keychain' };
|
|
176
|
+
}
|
|
177
|
+
// Finally try config file
|
|
178
|
+
const fileCreds = getCredentialsFromFile();
|
|
179
|
+
if (fileCreds) {
|
|
180
|
+
return { ...fileCreds, source: 'file' };
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if credentials are configured (sync check - env and file only)
|
|
186
|
+
*/
|
|
187
|
+
export function hasCredentialsSync() {
|
|
188
|
+
return getCredentialsFromEnv() !== null || getCredentialsFromFile() !== null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Check if credentials are configured (async - includes keychain)
|
|
192
|
+
*/
|
|
193
|
+
export async function hasCredentials() {
|
|
194
|
+
return (await getCredentials()) !== null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Interactive login prompt
|
|
198
|
+
*/
|
|
199
|
+
export async function promptLogin() {
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log('Hetzner Robot API Authentication');
|
|
202
|
+
console.log('─'.repeat(40));
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log('To get your API credentials:');
|
|
205
|
+
console.log('1. Go to https://robot.hetzner.com');
|
|
206
|
+
console.log('2. Navigate to: Settings > Web service settings');
|
|
207
|
+
console.log('3. Create a new web service user');
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log('Note: This is separate from your main Hetzner login.');
|
|
210
|
+
console.log('');
|
|
211
|
+
// Check if we should offer migration from file to keychain
|
|
212
|
+
const keychainAvailable = await hasKeychainSupport();
|
|
213
|
+
const existingFileCreds = getCredentialsFromFile();
|
|
214
|
+
if (keychainAvailable && existingFileCreds) {
|
|
215
|
+
const migrate = await confirm({
|
|
216
|
+
message: 'Migrate existing credentials to secure keychain storage?',
|
|
217
|
+
default: true,
|
|
218
|
+
});
|
|
219
|
+
if (migrate) {
|
|
220
|
+
const saved = await saveToKeychain(existingFileCreds.user, existingFileCreds.password);
|
|
221
|
+
if (saved) {
|
|
222
|
+
clearConfigFile();
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log('Credentials migrated to keychain.');
|
|
225
|
+
return existingFileCreds;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const user = await input({
|
|
230
|
+
message: 'Web service username:',
|
|
231
|
+
validate: (v) => v.length > 0 || 'Username is required',
|
|
232
|
+
});
|
|
233
|
+
const password = await passwordPrompt({
|
|
234
|
+
message: 'Web service password:',
|
|
235
|
+
validate: (v) => v.length > 0 || 'Password is required',
|
|
236
|
+
});
|
|
237
|
+
// Determine storage location based on keychain availability
|
|
238
|
+
const storageMessage = keychainAvailable
|
|
239
|
+
? 'Save credentials to secure keychain?'
|
|
240
|
+
: 'Save credentials to ~/.hetzner-cli/config.json?';
|
|
241
|
+
const save = await confirm({
|
|
242
|
+
message: storageMessage,
|
|
243
|
+
default: true,
|
|
244
|
+
});
|
|
245
|
+
if (save) {
|
|
246
|
+
let savedToKeychain = false;
|
|
247
|
+
if (keychainAvailable) {
|
|
248
|
+
savedToKeychain = await saveToKeychain(user, password);
|
|
249
|
+
}
|
|
250
|
+
if (!savedToKeychain) {
|
|
251
|
+
// Fall back to file storage
|
|
252
|
+
saveConfig({ user, password });
|
|
253
|
+
console.log('');
|
|
254
|
+
console.warn('Warning: System keychain unavailable. Credentials stored in plaintext at ~/.hetzner-cli/config.json');
|
|
255
|
+
console.log('Credentials saved to config file.');
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log('Credentials saved to keychain.');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { user, password };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get credentials, prompting if necessary
|
|
266
|
+
*/
|
|
267
|
+
export async function requireCredentials() {
|
|
268
|
+
const creds = await getCredentials();
|
|
269
|
+
if (creds) {
|
|
270
|
+
return { user: creds.user, password: creds.password };
|
|
271
|
+
}
|
|
272
|
+
return promptLogin();
|
|
273
|
+
}
|