cloudron 5.4.2 → 5.6.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/bin/cloudron-appstore +1 -1
- package/package.json +15 -16
- package/src/actions.js +39 -29
package/bin/cloudron-appstore
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
@@ -10,40 +10,39 @@
|
|
|
10
10
|
"url": "https://git.cloudron.io/cloudron/cloudron-cli.git"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "
|
|
13
|
+
"test": "mocha test/test.js"
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
|
-
"cloudron": "
|
|
16
|
+
"cloudron": "bin/cloudron"
|
|
17
17
|
},
|
|
18
18
|
"author": "Cloudron Developers <support@cloudron.io>",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"async": "^3.2.
|
|
21
|
-
"cloudron-manifestformat": "^5.
|
|
22
|
-
"commander": "^
|
|
23
|
-
"debug": "^4.3.
|
|
24
|
-
"delay": "^5.0.0",
|
|
20
|
+
"async": "^3.2.5",
|
|
21
|
+
"cloudron-manifestformat": "^5.24.0",
|
|
22
|
+
"commander": "^12.1.0",
|
|
23
|
+
"debug": "^4.3.5",
|
|
25
24
|
"easy-table": "^1.2.0",
|
|
26
|
-
"ejs": "^3.1.
|
|
25
|
+
"ejs": "^3.1.10",
|
|
27
26
|
"eventsource": "^2.0.2",
|
|
28
|
-
"micromatch": "^4.0.
|
|
27
|
+
"micromatch": "^4.0.7",
|
|
29
28
|
"once": "^1.4.0",
|
|
30
29
|
"open": "^8.4.0",
|
|
31
30
|
"progress": "^2.0.3",
|
|
32
31
|
"progress-stream": "^2.0.0",
|
|
33
32
|
"readline-sync": "^1.4.10",
|
|
34
|
-
"safetydance": "^2.
|
|
35
|
-
"
|
|
36
|
-
"superagent": "^
|
|
33
|
+
"safetydance": "^2.4.0",
|
|
34
|
+
"split2": "^4.2.0",
|
|
35
|
+
"superagent": "^9.0.2",
|
|
37
36
|
"tar-fs": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz",
|
|
38
37
|
"underscore": "^1.13.6"
|
|
39
38
|
},
|
|
40
39
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
40
|
+
"node": ">= 18.x.x"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"expect.js": "^0.3.1",
|
|
45
44
|
"memorystream": "^0.3.1",
|
|
46
|
-
"mocha": "^10.
|
|
47
|
-
"rimraf": "^
|
|
45
|
+
"mocha": "^10.4.0",
|
|
46
|
+
"rimraf": "^5.0.7"
|
|
48
47
|
}
|
|
49
48
|
}
|
package/src/actions.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('assert'),
|
|
4
4
|
config = require('./config.js'),
|
|
5
|
-
delay = require('delay'),
|
|
6
5
|
ejs = require('ejs'),
|
|
7
6
|
{ exit, locateManifest } = require('./helper.js'),
|
|
8
7
|
EventSource = require('eventsource'),
|
|
@@ -17,10 +16,12 @@ const assert = require('assert'),
|
|
|
17
16
|
readlineSync = require('readline-sync'),
|
|
18
17
|
safe = require('safetydance'),
|
|
19
18
|
spawn = require('child_process').spawn,
|
|
20
|
-
|
|
19
|
+
semver = require('semver'),
|
|
20
|
+
split = require('split2'),
|
|
21
21
|
superagent = require('superagent'),
|
|
22
22
|
Table = require('easy-table'),
|
|
23
23
|
tar = require('tar-fs'),
|
|
24
|
+
timers = require('timers/promises'),
|
|
24
25
|
zlib = require('zlib'),
|
|
25
26
|
_ = require('underscore');
|
|
26
27
|
|
|
@@ -109,9 +110,10 @@ async function selectDomain(location, options) {
|
|
|
109
110
|
|
|
110
111
|
let domain;
|
|
111
112
|
let subdomain = location;
|
|
112
|
-
|
|
113
|
+
// find longest matching domain name. this maps to DNS where subdomain is a zone of it's own
|
|
114
|
+
const matchingDomain = domains
|
|
113
115
|
.map(function (d) { return d.domain; } )
|
|
114
|
-
.sort(function(a, b) { return
|
|
116
|
+
.sort(function(a, b) { return b.length - a.length; })
|
|
115
117
|
.find(function (d) { return location.endsWith(d); });
|
|
116
118
|
|
|
117
119
|
if (matchingDomain) {
|
|
@@ -120,7 +122,7 @@ async function selectDomain(location, options) {
|
|
|
120
122
|
} else { // use the admin domain
|
|
121
123
|
domain = domains
|
|
122
124
|
.map(function (d) { return d.domain; } )
|
|
123
|
-
.sort(function(a, b) { return
|
|
125
|
+
.sort(function(a, b) { return b.length - a.length; })
|
|
124
126
|
.find(function (d) { return adminFqdn.endsWith(d); });
|
|
125
127
|
}
|
|
126
128
|
|
|
@@ -146,7 +148,7 @@ async function selectAppWithRepository(repository, options) {
|
|
|
146
148
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
147
149
|
if (response.statusCode !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
const matchingApps = response.body.apps.filter(function (app) {
|
|
150
152
|
return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
|
|
151
153
|
});
|
|
152
154
|
|
|
@@ -161,7 +163,6 @@ async function selectAppWithRepository(repository, options) {
|
|
|
161
163
|
});
|
|
162
164
|
|
|
163
165
|
let index = -1;
|
|
164
|
-
// eslint-disable-next-line no-constant-condition
|
|
165
166
|
while (true) {
|
|
166
167
|
index = parseInt(readlineSync.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
|
|
167
168
|
if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
|
|
@@ -215,9 +216,8 @@ async function waitForHealthy(appId, options) {
|
|
|
215
216
|
|
|
216
217
|
process.stdout.write('\n => ' + 'Wait for health check ');
|
|
217
218
|
|
|
218
|
-
// eslint-disable-next-line no-constant-condition
|
|
219
219
|
while (true) {
|
|
220
|
-
await
|
|
220
|
+
await timers.setTimeout(1000);
|
|
221
221
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
222
222
|
if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
223
223
|
|
|
@@ -240,7 +240,6 @@ async function waitForTask(taskId, options) {
|
|
|
240
240
|
|
|
241
241
|
let currentMessage = '';
|
|
242
242
|
|
|
243
|
-
// eslint-disable-next-line no-constant-condition
|
|
244
243
|
while (true) {
|
|
245
244
|
const response = await createRequest('GET', `/api/v1/tasks/${taskId}`, options);
|
|
246
245
|
if (response.statusCode !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
|
|
@@ -271,7 +270,7 @@ async function waitForTask(taskId, options) {
|
|
|
271
270
|
|
|
272
271
|
currentMessage = message;
|
|
273
272
|
|
|
274
|
-
await
|
|
273
|
+
await timers.setTimeout(1000);
|
|
275
274
|
}
|
|
276
275
|
}
|
|
277
276
|
|
|
@@ -522,8 +521,13 @@ function parseDebugCommand(cmd) {
|
|
|
522
521
|
}
|
|
523
522
|
|
|
524
523
|
async function downloadManifest(appstoreId) {
|
|
525
|
-
const
|
|
526
|
-
|
|
524
|
+
const [ id, version ] = appstoreId.split('@');
|
|
525
|
+
|
|
526
|
+
if (!manifestFormat.isId(id)) throw new Error('Invalid appstore ID');
|
|
527
|
+
if (!semver.valid(version)) throw new Error('Invalid appstore version, not a semver');
|
|
528
|
+
|
|
529
|
+
// encodeURIComponent required to protect against passing junk in the URL (e.g appstoreId=org.wordpress.cloudronapp?foo=bar)
|
|
530
|
+
const url = config.appStoreOrigin() + '/api/v1/apps/' + id + (version ? '/versions/' + version : '');
|
|
527
531
|
|
|
528
532
|
const [error, response] = await safe(superagent.get(url).ok(() => true));
|
|
529
533
|
if (error) throw new Error(`Failed to list apps from appstore: ${error.message}`);
|
|
@@ -597,7 +601,7 @@ async function install(localOptions, cmd) {
|
|
|
597
601
|
|
|
598
602
|
for (const binding in secondaryDomains) console.log(`Secondary domain ${binding}: ${secondaryDomains[binding].subdomain}.${secondaryDomains[binding].domain}`);
|
|
599
603
|
|
|
600
|
-
|
|
604
|
+
const aliasDomains = [];
|
|
601
605
|
if (options.aliasDomains) {
|
|
602
606
|
for (const aliasDomain of options.aliasDomains.split(',')) {
|
|
603
607
|
aliasDomains.push(await selectDomain(aliasDomain, options));
|
|
@@ -611,7 +615,7 @@ async function install(localOptions, cmd) {
|
|
|
611
615
|
if (typeof options.portBindings === 'string') {
|
|
612
616
|
portBindings = { };
|
|
613
617
|
options.portBindings.split(',').forEach(function (kv) {
|
|
614
|
-
|
|
618
|
+
const tmp = kv.split('=');
|
|
615
619
|
if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
|
|
616
620
|
portBindings[tmp[0]] = parseInt(tmp[1], 10);
|
|
617
621
|
});
|
|
@@ -620,7 +624,7 @@ async function install(localOptions, cmd) {
|
|
|
620
624
|
}
|
|
621
625
|
} else { // just put in defaults
|
|
622
626
|
const allPorts = _.extend({}, manifest.tcpPorts, manifest.udpPorts);
|
|
623
|
-
for (
|
|
627
|
+
for (const portName in allPorts) {
|
|
624
628
|
portBindings[portName] = allPorts[portName].defaultValue;
|
|
625
629
|
}
|
|
626
630
|
}
|
|
@@ -681,7 +685,7 @@ async function configure(localOptions, cmd) {
|
|
|
681
685
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
682
686
|
|
|
683
687
|
if (!options.location) options.location = readlineSync.question(`Enter new location (default: ${app.fqdn}): `, { });
|
|
684
|
-
|
|
688
|
+
const location = options.location || app.subdomain;
|
|
685
689
|
|
|
686
690
|
const domainObject = await selectDomain(location, options);
|
|
687
691
|
|
|
@@ -743,7 +747,7 @@ async function configure(localOptions, cmd) {
|
|
|
743
747
|
portBindings = queryPortBindings(app, app.manifest);
|
|
744
748
|
}
|
|
745
749
|
|
|
746
|
-
for (
|
|
750
|
+
for (const binding in portBindings) {
|
|
747
751
|
console.log('%s: %s', binding, portBindings[binding]);
|
|
748
752
|
}
|
|
749
753
|
|
|
@@ -963,7 +967,7 @@ async function logs(localOptions, cmd) {
|
|
|
963
967
|
}
|
|
964
968
|
|
|
965
969
|
if (tail) {
|
|
966
|
-
const url = `${apiPath}?access_token=${token}&lines=10`;
|
|
970
|
+
const url = `${apiPath}?access_token=${token}&lines=10&format=json`;
|
|
967
971
|
var es = new EventSource(url, { rejectUnauthorized }); // not sure why this is needed
|
|
968
972
|
|
|
969
973
|
es.on('message', function (e) { // e { type, data, lastEventId }. lastEventId is the timestamp
|
|
@@ -976,16 +980,21 @@ async function logs(localOptions, cmd) {
|
|
|
976
980
|
exit(error);
|
|
977
981
|
});
|
|
978
982
|
} else {
|
|
979
|
-
const url = `${apiPath}?access_token=${token}&lines=${lines}`;
|
|
983
|
+
const url = `${apiPath}?access_token=${token}&lines=${lines}&format=json`;
|
|
980
984
|
|
|
981
985
|
const req = superagent.get(url, { rejectUnauthorized });
|
|
982
986
|
req.on('response', function (response) {
|
|
983
987
|
if (response.statusCode !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
|
|
984
988
|
});
|
|
985
|
-
req.
|
|
989
|
+
req.on('error', (error) => exit(`Pipe error: ${error.message}`));
|
|
990
|
+
|
|
991
|
+
const jsonStream = split((line) => JSON.parse(line));
|
|
992
|
+
jsonStream
|
|
986
993
|
.on('data', logPrinter)
|
|
987
|
-
.on('error',
|
|
994
|
+
.on('error', (error) => exit(`JSON parse error: ${error.message}`))
|
|
988
995
|
.on('end', process.exit);
|
|
996
|
+
|
|
997
|
+
req.pipe(jsonStream);
|
|
989
998
|
}
|
|
990
999
|
}
|
|
991
1000
|
|
|
@@ -1012,7 +1021,7 @@ async function inspect(localOptions, cmd) {
|
|
|
1012
1021
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
1013
1022
|
if (response.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
1014
1023
|
|
|
1015
|
-
|
|
1024
|
+
const apps = [];
|
|
1016
1025
|
|
|
1017
1026
|
for (const app of response.body.apps) {
|
|
1018
1027
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
@@ -1171,7 +1180,8 @@ async function importApp(localOptions, cmd) {
|
|
|
1171
1180
|
let data = {};
|
|
1172
1181
|
|
|
1173
1182
|
if (!options.inPlace) {
|
|
1174
|
-
let { remotePath, backupFormat, backupConfig
|
|
1183
|
+
let { remotePath, backupFormat, backupConfig } = options;
|
|
1184
|
+
const { backupPath, backupKey } = options;
|
|
1175
1185
|
let backupFolder;
|
|
1176
1186
|
|
|
1177
1187
|
if (backupPath) {
|
|
@@ -1223,7 +1233,7 @@ async function exportApp(localOptions, cmd) {
|
|
|
1223
1233
|
const app = await getApp(options);
|
|
1224
1234
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1225
1235
|
|
|
1226
|
-
|
|
1236
|
+
const data = {};
|
|
1227
1237
|
|
|
1228
1238
|
if (!options.snapshot) return exit('--snapshot is required');
|
|
1229
1239
|
|
|
@@ -1301,7 +1311,7 @@ function demuxStream(stream, stdout, stderr) {
|
|
|
1301
1311
|
// cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar zxf - -C /tmp" - must extrack ok
|
|
1302
1312
|
async function exec(args, localOptions, cmd) {
|
|
1303
1313
|
let stdin = localOptions._stdin || process.stdin; // hack for 'push', 'pull' to reuse this function
|
|
1304
|
-
|
|
1314
|
+
const stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
|
|
1305
1315
|
|
|
1306
1316
|
const options = cmd.optsWithGlobals();
|
|
1307
1317
|
let tty = !!options.tty;
|
|
@@ -1550,9 +1560,9 @@ async function envSet(envVars, localOptions, cmd) {
|
|
|
1550
1560
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1551
1561
|
if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1552
1562
|
|
|
1553
|
-
|
|
1563
|
+
const env = response.body.env;
|
|
1554
1564
|
envVars.forEach(envVar => {
|
|
1555
|
-
|
|
1565
|
+
const m = envVar.match(/(.*?)=(.*)/);
|
|
1556
1566
|
if (!m) return exit(`Expecting KEY=VALUE pattern. Got ${envVar}`);
|
|
1557
1567
|
env[m[1]] = m[2];
|
|
1558
1568
|
});
|
|
@@ -1572,7 +1582,7 @@ async function envUnset(envNames, localOptions, cmd) {
|
|
|
1572
1582
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1573
1583
|
if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1574
1584
|
|
|
1575
|
-
|
|
1585
|
+
const env = response.body.env;
|
|
1576
1586
|
envNames.forEach(name => delete env[name]);
|
|
1577
1587
|
|
|
1578
1588
|
await setEnv(app, env, options);
|