elestio 1.0.3 → 1.2.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 -21
- package/README.md +523 -279
- package/bin/elestio.js +5 -5
- package/package.json +49 -42
- package/src/api.js +143 -105
- package/src/cli.js +32 -838
- package/src/commands/access.js +224 -132
- package/src/commands/actions.js +427 -427
- package/src/commands/auth.js +146 -146
- package/src/commands/backups.js +215 -215
- package/src/commands/billing.js +106 -106
- package/src/commands/cicd-template.js +363 -0
- package/src/commands/cicd.js +509 -486
- package/src/commands/clusters.js +589 -0
- package/src/commands/projects.js +145 -145
- package/src/commands/services.js +306 -294
- package/src/commands/templates.js +188 -188
- package/src/commands/volumes.js +117 -117
- package/src/config.js +104 -84
- package/src/constants.js +76 -0
- package/src/deployment.js +33 -0
- package/src/payloads/pipeline.js +191 -0
- package/src/payloads/server.js +130 -0
- package/src/registry.js +715 -0
- package/src/router.js +188 -0
- package/src/templates/elestio-config.js +188 -0
- package/src/utils.js +174 -162
package/src/commands/access.js
CHANGED
|
@@ -1,132 +1,224 @@
|
|
|
1
|
-
import { apiRequest } from '../api.js';
|
|
2
|
-
import { loadConfig } from '../config.js';
|
|
3
|
-
import {
|
|
4
|
-
import { getServiceDetails } from './services.js';
|
|
5
|
-
|
|
6
|
-
export async function getCredentials(vmID, projectId = null, json = false) {
|
|
7
|
-
const config = loadConfig();
|
|
8
|
-
const pid = projectId || config.defaultProject;
|
|
9
|
-
if (!pid) throw new Error('Project ID required');
|
|
10
|
-
|
|
11
|
-
const serviceInfo = await getServiceDetails(vmID, pid);
|
|
12
|
-
if (!serviceInfo) throw new Error('Failed to get service details');
|
|
13
|
-
|
|
14
|
-
const targetPort = serviceInfo.adminInternalPort || 8080;
|
|
15
|
-
const srvPort = serviceInfo.adminExternalPort || 443;
|
|
16
|
-
|
|
17
|
-
const response = await apiRequest('/api/servers/getAppCredentials', 'POST', {
|
|
18
|
-
vmID: String(vmID), targetPort, srvPort,
|
|
19
|
-
projectID: String(pid), appID: 'CloudVM',
|
|
20
|
-
isServerDeleted: false, mode: 'dbAdmin'
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
if (!response.url) throw new Error(response.message || 'Failed to get credentials');
|
|
24
|
-
|
|
25
|
-
if (json) {
|
|
26
|
-
outputJson({
|
|
27
|
-
service: { name: serviceInfo.displayName, type: serviceInfo.serverType, ip: serviceInfo.ipv4 },
|
|
28
|
-
credentials: { url: response.url, user: response.user, password: response.password },
|
|
29
|
-
database: serviceInfo.managedDBPort ? {
|
|
30
|
-
host: serviceInfo.cname, port: serviceInfo.managedDBPort
|
|
31
|
-
} : null
|
|
32
|
-
});
|
|
33
|
-
return response;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`\n${colors.bold}Service Info${colors.reset}\n`);
|
|
37
|
-
console.log(` Name: ${serviceInfo.displayName}`);
|
|
38
|
-
console.log(` Type: ${serviceInfo.serverType} (${serviceInfo.cores} CPU / ${serviceInfo.ramGB} GB RAM)`);
|
|
39
|
-
console.log(` Status: ${serviceInfo.status}`);
|
|
40
|
-
console.log(` IP: ${serviceInfo.ipv4}`);
|
|
41
|
-
console.log(`\n${colors.bold}App Credentials${colors.reset}\n`);
|
|
42
|
-
console.log(` URL: ${colors.cyan}${response.url}${colors.reset}`);
|
|
43
|
-
console.log(` User: ${response.user || 'N/A'}`);
|
|
44
|
-
console.log(` Password: ${response.password || 'N/A'}`);
|
|
45
|
-
|
|
46
|
-
if (serviceInfo.managedDBPort) {
|
|
47
|
-
console.log(`\n${colors.bold}Database Connection${colors.reset}`);
|
|
48
|
-
console.log(` Host: ${serviceInfo.cname}`);
|
|
49
|
-
console.log(` Port: ${serviceInfo.managedDBPort}`);
|
|
50
|
-
}
|
|
51
|
-
console.log('');
|
|
52
|
-
return response;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function getSSH(vmID, projectId = null, json = false) {
|
|
56
|
-
const config = loadConfig();
|
|
57
|
-
const pid = projectId || config.defaultProject;
|
|
58
|
-
if (!pid) throw new Error('Project ID required');
|
|
59
|
-
|
|
60
|
-
const response = await apiRequest('/api/servers/startSSHDirect', 'POST', {
|
|
61
|
-
vmID: String(vmID), projectID: String(pid), path: '/root/'
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (!response.url) throw new Error(response.message || 'Failed to get SSH access');
|
|
65
|
-
|
|
66
|
-
if (json) { outputJson({ url: response.url }); return response; }
|
|
67
|
-
|
|
68
|
-
console.log(`\n${colors.bold}SSH Access${colors.reset}\n`);
|
|
69
|
-
console.log(` Web Terminal: ${colors.cyan}${response.url}${colors.reset}`);
|
|
70
|
-
console.log('');
|
|
71
|
-
return response;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
1
|
+
import { apiRequest } from '../api.js';
|
|
2
|
+
import { loadConfig } from '../config.js';
|
|
3
|
+
import { colors, outputJson, formatTable, log } from '../utils.js';
|
|
4
|
+
import { getServiceDetails, listServicesRaw } from './services.js';
|
|
5
|
+
|
|
6
|
+
export async function getCredentials(vmID, projectId = null, json = false) {
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const pid = projectId || config.defaultProject;
|
|
9
|
+
if (!pid) throw new Error('Project ID required');
|
|
10
|
+
|
|
11
|
+
const serviceInfo = await getServiceDetails(vmID, pid);
|
|
12
|
+
if (!serviceInfo) throw new Error('Failed to get service details');
|
|
13
|
+
|
|
14
|
+
const targetPort = serviceInfo.adminInternalPort || 8080;
|
|
15
|
+
const srvPort = serviceInfo.adminExternalPort || 443;
|
|
16
|
+
|
|
17
|
+
const response = await apiRequest('/api/servers/getAppCredentials', 'POST', {
|
|
18
|
+
vmID: String(vmID), targetPort, srvPort,
|
|
19
|
+
projectID: String(pid), appID: 'CloudVM',
|
|
20
|
+
isServerDeleted: false, mode: 'dbAdmin'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!response.url) throw new Error(response.message || 'Failed to get credentials');
|
|
24
|
+
|
|
25
|
+
if (json) {
|
|
26
|
+
outputJson({
|
|
27
|
+
service: { name: serviceInfo.displayName, type: serviceInfo.serverType, ip: serviceInfo.ipv4 },
|
|
28
|
+
credentials: { url: response.url, user: response.user, password: response.password },
|
|
29
|
+
database: serviceInfo.managedDBPort ? {
|
|
30
|
+
host: serviceInfo.cname, port: serviceInfo.managedDBPort
|
|
31
|
+
} : null
|
|
32
|
+
});
|
|
33
|
+
return response;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`\n${colors.bold}Service Info${colors.reset}\n`);
|
|
37
|
+
console.log(` Name: ${serviceInfo.displayName}`);
|
|
38
|
+
console.log(` Type: ${serviceInfo.serverType} (${serviceInfo.cores} CPU / ${serviceInfo.ramGB} GB RAM)`);
|
|
39
|
+
console.log(` Status: ${serviceInfo.status}`);
|
|
40
|
+
console.log(` IP: ${serviceInfo.ipv4}`);
|
|
41
|
+
console.log(`\n${colors.bold}App Credentials${colors.reset}\n`);
|
|
42
|
+
console.log(` URL: ${colors.cyan}${response.url}${colors.reset}`);
|
|
43
|
+
console.log(` User: ${response.user || 'N/A'}`);
|
|
44
|
+
console.log(` Password: ${response.password || 'N/A'}`);
|
|
45
|
+
|
|
46
|
+
if (serviceInfo.managedDBPort) {
|
|
47
|
+
console.log(`\n${colors.bold}Database Connection${colors.reset}`);
|
|
48
|
+
console.log(` Host: ${serviceInfo.cname}`);
|
|
49
|
+
console.log(` Port: ${serviceInfo.managedDBPort}`);
|
|
50
|
+
}
|
|
51
|
+
console.log('');
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getSSH(vmID, projectId = null, json = false) {
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
const pid = projectId || config.defaultProject;
|
|
58
|
+
if (!pid) throw new Error('Project ID required');
|
|
59
|
+
|
|
60
|
+
const response = await apiRequest('/api/servers/startSSHDirect', 'POST', {
|
|
61
|
+
vmID: String(vmID), projectID: String(pid), path: '/root/'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!response.url) throw new Error(response.message || 'Failed to get SSH access');
|
|
65
|
+
|
|
66
|
+
if (json) { outputJson({ url: response.url }); return response; }
|
|
67
|
+
|
|
68
|
+
console.log(`\n${colors.bold}SSH Access${colors.reset}\n`);
|
|
69
|
+
console.log(` Web Terminal: ${colors.cyan}${response.url}${colors.reset}`);
|
|
70
|
+
console.log('');
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* `--direct` prints the details for connecting with a local ssh client.
|
|
76
|
+
*
|
|
77
|
+
* These come from the service record: /api/servers/startSSHDirect returns a
|
|
78
|
+
* browser terminal URL, not host/port/user, so it cannot answer this.
|
|
79
|
+
*/
|
|
80
|
+
export async function getSSHDirect(vmID, projectId = null, json = false) {
|
|
81
|
+
const config = loadConfig();
|
|
82
|
+
const pid = projectId || config.defaultProject;
|
|
83
|
+
if (!pid) throw new Error('Project ID required');
|
|
84
|
+
|
|
85
|
+
const service = await getServiceDetails(vmID, pid);
|
|
86
|
+
if (!service) throw new Error(`Service with vmID ${vmID} not found`);
|
|
87
|
+
|
|
88
|
+
const host = service.ipv4 || service.cname;
|
|
89
|
+
if (!host) throw new Error(`Service ${vmID} has no IP yet; it may still be deploying`);
|
|
90
|
+
|
|
91
|
+
const details = { host, port: 22, user: 'root', command: `ssh root@${host}` };
|
|
92
|
+
|
|
93
|
+
if (json) { outputJson(details); return details; }
|
|
94
|
+
|
|
95
|
+
console.log(`\n${colors.bold}Direct SSH${colors.reset}\n`);
|
|
96
|
+
console.log(` Host: ${host}`);
|
|
97
|
+
console.log(` Port: 22`);
|
|
98
|
+
console.log(` User: root`);
|
|
99
|
+
console.log(`\n ${colors.cyan}${details.command}${colors.reset}`);
|
|
100
|
+
console.log(`\n ${colors.dim}Add your key first: elestio ssh-keys add ${vmID} --name <n> --key "$(awk '{print $1" "$2}' ~/.ssh/id_ed25519.pub)"${colors.reset}`);
|
|
101
|
+
console.log('');
|
|
102
|
+
return details;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getVSCode(vmID, projectId = null, json = false) {
|
|
106
|
+
const config = loadConfig();
|
|
107
|
+
const pid = projectId || config.defaultProject;
|
|
108
|
+
if (!pid) throw new Error('Project ID required');
|
|
109
|
+
|
|
110
|
+
const response = await apiRequest('/api/servers/startVSCode', 'POST', {
|
|
111
|
+
vmID: String(vmID), projectID: String(pid)
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!response.url) throw new Error(response.message || 'Failed to get VSCode access');
|
|
115
|
+
|
|
116
|
+
if (json) { outputJson({ url: response.url, user: response.user, password: response.password }); return response; }
|
|
117
|
+
|
|
118
|
+
console.log(`\n${colors.bold}VSCode Web Access${colors.reset}\n`);
|
|
119
|
+
console.log(` URL: ${colors.cyan}${response.url}${colors.reset}`);
|
|
120
|
+
console.log(` User: ${response.user || 'N/A'}`);
|
|
121
|
+
console.log(` Password: ${response.password || 'N/A'}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function getFileExplorer(vmID, projectId = null, json = false) {
|
|
127
|
+
const config = loadConfig();
|
|
128
|
+
const pid = projectId || config.defaultProject;
|
|
129
|
+
if (!pid) throw new Error('Project ID required');
|
|
130
|
+
|
|
131
|
+
const response = await apiRequest('/api/servers/startFileExplorer', 'POST', {
|
|
132
|
+
vmID: String(vmID), projectID: String(pid)
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!response.url) throw new Error(response.message || 'Failed to get File Explorer access');
|
|
136
|
+
|
|
137
|
+
if (json) { outputJson({ url: response.url, user: response.user, password: response.password }); return response; }
|
|
138
|
+
|
|
139
|
+
console.log(`\n${colors.bold}File Explorer Access${colors.reset}\n`);
|
|
140
|
+
console.log(` URL: ${colors.cyan}${response.url}${colors.reset}`);
|
|
141
|
+
console.log(` User: ${response.user || 'N/A'}`);
|
|
142
|
+
console.log(` Password: ${response.password || 'N/A'}`);
|
|
143
|
+
console.log('');
|
|
144
|
+
return response;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* startLogTailView modes, as the backend whitelists them. "syslog" streams the
|
|
149
|
+
* app's `docker-compose logs` and is what the dashboard's Logs tab uses; an
|
|
150
|
+
* empty mode is the install log. "docker" is not accepted.
|
|
151
|
+
*/
|
|
152
|
+
export const LOG_MODES = { app: 'syslog', install: '', resync: 'resyncLog', alerts: 'alertLog', 'db-migration': 'db-migration-logs' };
|
|
153
|
+
const RAW_LOG_MODES = Object.values(LOG_MODES);
|
|
154
|
+
|
|
155
|
+
export function resolveLogMode(mode = 'app') {
|
|
156
|
+
if (mode in LOG_MODES) return LOG_MODES[mode];
|
|
157
|
+
if (RAW_LOG_MODES.includes(mode)) return mode;
|
|
158
|
+
throw new Error(`Unknown log mode "${mode}". Use one of: ${Object.keys(LOG_MODES).join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Live log view. The API opens a temporary web page streaming the logs rather
|
|
163
|
+
* than returning them, like the dashboard's "Logs" tab.
|
|
164
|
+
*/
|
|
165
|
+
export async function getLogsView(vmID, projectId = null, mode = 'app', json = false) {
|
|
166
|
+
const pid = projectId || loadConfig().defaultProject;
|
|
167
|
+
if (!pid) throw new Error('Project ID required');
|
|
168
|
+
|
|
169
|
+
const response = await apiRequest('/api/servers/startLogTailView', 'POST', {
|
|
170
|
+
vmID: String(vmID), projectID: String(pid), mode: resolveLogMode(mode)
|
|
171
|
+
});
|
|
172
|
+
if (!response.url) throw new Error(response.message || 'Failed to open the log view');
|
|
173
|
+
|
|
174
|
+
if (json) { outputJson({ url: response.url, user: response.user, password: response.password }); return response; }
|
|
175
|
+
|
|
176
|
+
console.log(`\n${colors.bold}Live logs (${mode})${colors.reset}\n`);
|
|
177
|
+
console.log(` URL: ${colors.cyan}${response.url}${colors.reset}`);
|
|
178
|
+
if (response.user) console.log(` User: ${response.user}`);
|
|
179
|
+
if (response.password) console.log(` Password: ${response.password}`);
|
|
180
|
+
console.log(`\n ${colors.dim}Temporary page. --mode install for the installation log.${colors.reset}\n`);
|
|
181
|
+
return response;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function formatAudit(a) {
|
|
185
|
+
return {
|
|
186
|
+
when: String(a.time || a.timestamp || 'N/A').replace('T', ' ').slice(0, 19),
|
|
187
|
+
event: [a.event_category, a.event_type].filter(Boolean).join(' / ') || a.event || 'N/A',
|
|
188
|
+
user: a.email || a.userEmail || 'N/A',
|
|
189
|
+
details: String(a.event_details || a.details || '').slice(0, 60)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Audit trail of a service: who did what, over the last N days. */
|
|
194
|
+
export async function getAudits(vmID, projectId = null, days = 30, json = false) {
|
|
195
|
+
const pid = projectId || loadConfig().defaultProject;
|
|
196
|
+
if (!pid) throw new Error('Project ID required');
|
|
197
|
+
|
|
198
|
+
// getAudits takes serverIDs, not vmIDs.
|
|
199
|
+
const service = (await listServicesRaw(pid)).find(s => String(s.vmID) === String(vmID) || String(s.id) === String(vmID));
|
|
200
|
+
if (!service) throw new Error(`Service ${vmID} not found in project ${pid}`);
|
|
201
|
+
|
|
202
|
+
const end = new Date();
|
|
203
|
+
const start = new Date(end.getTime() - Number(days) * 24 * 60 * 60 * 1000);
|
|
204
|
+
const response = await apiRequest('/api/servers/getAudits', 'POST', {
|
|
205
|
+
serverIDs: [String(service.id)], projectID: String(pid),
|
|
206
|
+
startDate: start.toISOString(), endDate: end.toISOString()
|
|
207
|
+
});
|
|
208
|
+
if (response.status === 'KO' || response.status === 'error') throw new Error(response.message || 'Failed to fetch audits');
|
|
209
|
+
|
|
210
|
+
// Entries come back as a bare array in data: {status, count, data: [...]}.
|
|
211
|
+
const audits = Array.isArray(response.data) ? response.data : (response.data?.audits || response.audits || []);
|
|
212
|
+
if (json) { outputJson(audits); return audits; }
|
|
213
|
+
if (audits.length === 0) { log('info', `No audit entries in the last ${days} days`); return []; }
|
|
214
|
+
|
|
215
|
+
console.log(`\n${colors.bold}Audit trail of ${vmID} (last ${days} days)${colors.reset}\n`);
|
|
216
|
+
console.log(formatTable(audits.map(formatAudit), [
|
|
217
|
+
{ key: 'when', label: 'When' },
|
|
218
|
+
{ key: 'event', label: 'Event' },
|
|
219
|
+
{ key: 'user', label: 'User' },
|
|
220
|
+
{ key: 'details', label: 'Details' }
|
|
221
|
+
]));
|
|
222
|
+
console.log('');
|
|
223
|
+
return audits;
|
|
224
|
+
}
|