cloudron 5.5.0 → 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 +35 -26
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
|
|
|
@@ -147,7 +148,7 @@ async function selectAppWithRepository(repository, options) {
|
|
|
147
148
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
148
149
|
if (response.statusCode !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
const matchingApps = response.body.apps.filter(function (app) {
|
|
151
152
|
return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
|
|
152
153
|
});
|
|
153
154
|
|
|
@@ -162,7 +163,6 @@ async function selectAppWithRepository(repository, options) {
|
|
|
162
163
|
});
|
|
163
164
|
|
|
164
165
|
let index = -1;
|
|
165
|
-
// eslint-disable-next-line no-constant-condition
|
|
166
166
|
while (true) {
|
|
167
167
|
index = parseInt(readlineSync.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
|
|
168
168
|
if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
|
|
@@ -216,9 +216,8 @@ async function waitForHealthy(appId, options) {
|
|
|
216
216
|
|
|
217
217
|
process.stdout.write('\n => ' + 'Wait for health check ');
|
|
218
218
|
|
|
219
|
-
// eslint-disable-next-line no-constant-condition
|
|
220
219
|
while (true) {
|
|
221
|
-
await
|
|
220
|
+
await timers.setTimeout(1000);
|
|
222
221
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
223
222
|
if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
224
223
|
|
|
@@ -241,7 +240,6 @@ async function waitForTask(taskId, options) {
|
|
|
241
240
|
|
|
242
241
|
let currentMessage = '';
|
|
243
242
|
|
|
244
|
-
// eslint-disable-next-line no-constant-condition
|
|
245
243
|
while (true) {
|
|
246
244
|
const response = await createRequest('GET', `/api/v1/tasks/${taskId}`, options);
|
|
247
245
|
if (response.statusCode !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
|
|
@@ -272,7 +270,7 @@ async function waitForTask(taskId, options) {
|
|
|
272
270
|
|
|
273
271
|
currentMessage = message;
|
|
274
272
|
|
|
275
|
-
await
|
|
273
|
+
await timers.setTimeout(1000);
|
|
276
274
|
}
|
|
277
275
|
}
|
|
278
276
|
|
|
@@ -523,8 +521,13 @@ function parseDebugCommand(cmd) {
|
|
|
523
521
|
}
|
|
524
522
|
|
|
525
523
|
async function downloadManifest(appstoreId) {
|
|
526
|
-
const
|
|
527
|
-
|
|
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 : '');
|
|
528
531
|
|
|
529
532
|
const [error, response] = await safe(superagent.get(url).ok(() => true));
|
|
530
533
|
if (error) throw new Error(`Failed to list apps from appstore: ${error.message}`);
|
|
@@ -598,7 +601,7 @@ async function install(localOptions, cmd) {
|
|
|
598
601
|
|
|
599
602
|
for (const binding in secondaryDomains) console.log(`Secondary domain ${binding}: ${secondaryDomains[binding].subdomain}.${secondaryDomains[binding].domain}`);
|
|
600
603
|
|
|
601
|
-
|
|
604
|
+
const aliasDomains = [];
|
|
602
605
|
if (options.aliasDomains) {
|
|
603
606
|
for (const aliasDomain of options.aliasDomains.split(',')) {
|
|
604
607
|
aliasDomains.push(await selectDomain(aliasDomain, options));
|
|
@@ -612,7 +615,7 @@ async function install(localOptions, cmd) {
|
|
|
612
615
|
if (typeof options.portBindings === 'string') {
|
|
613
616
|
portBindings = { };
|
|
614
617
|
options.portBindings.split(',').forEach(function (kv) {
|
|
615
|
-
|
|
618
|
+
const tmp = kv.split('=');
|
|
616
619
|
if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
|
|
617
620
|
portBindings[tmp[0]] = parseInt(tmp[1], 10);
|
|
618
621
|
});
|
|
@@ -621,7 +624,7 @@ async function install(localOptions, cmd) {
|
|
|
621
624
|
}
|
|
622
625
|
} else { // just put in defaults
|
|
623
626
|
const allPorts = _.extend({}, manifest.tcpPorts, manifest.udpPorts);
|
|
624
|
-
for (
|
|
627
|
+
for (const portName in allPorts) {
|
|
625
628
|
portBindings[portName] = allPorts[portName].defaultValue;
|
|
626
629
|
}
|
|
627
630
|
}
|
|
@@ -682,7 +685,7 @@ async function configure(localOptions, cmd) {
|
|
|
682
685
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
683
686
|
|
|
684
687
|
if (!options.location) options.location = readlineSync.question(`Enter new location (default: ${app.fqdn}): `, { });
|
|
685
|
-
|
|
688
|
+
const location = options.location || app.subdomain;
|
|
686
689
|
|
|
687
690
|
const domainObject = await selectDomain(location, options);
|
|
688
691
|
|
|
@@ -744,7 +747,7 @@ async function configure(localOptions, cmd) {
|
|
|
744
747
|
portBindings = queryPortBindings(app, app.manifest);
|
|
745
748
|
}
|
|
746
749
|
|
|
747
|
-
for (
|
|
750
|
+
for (const binding in portBindings) {
|
|
748
751
|
console.log('%s: %s', binding, portBindings[binding]);
|
|
749
752
|
}
|
|
750
753
|
|
|
@@ -964,7 +967,7 @@ async function logs(localOptions, cmd) {
|
|
|
964
967
|
}
|
|
965
968
|
|
|
966
969
|
if (tail) {
|
|
967
|
-
const url = `${apiPath}?access_token=${token}&lines=10`;
|
|
970
|
+
const url = `${apiPath}?access_token=${token}&lines=10&format=json`;
|
|
968
971
|
var es = new EventSource(url, { rejectUnauthorized }); // not sure why this is needed
|
|
969
972
|
|
|
970
973
|
es.on('message', function (e) { // e { type, data, lastEventId }. lastEventId is the timestamp
|
|
@@ -977,16 +980,21 @@ async function logs(localOptions, cmd) {
|
|
|
977
980
|
exit(error);
|
|
978
981
|
});
|
|
979
982
|
} else {
|
|
980
|
-
const url = `${apiPath}?access_token=${token}&lines=${lines}`;
|
|
983
|
+
const url = `${apiPath}?access_token=${token}&lines=${lines}&format=json`;
|
|
981
984
|
|
|
982
985
|
const req = superagent.get(url, { rejectUnauthorized });
|
|
983
986
|
req.on('response', function (response) {
|
|
984
987
|
if (response.statusCode !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
|
|
985
988
|
});
|
|
986
|
-
req.
|
|
989
|
+
req.on('error', (error) => exit(`Pipe error: ${error.message}`));
|
|
990
|
+
|
|
991
|
+
const jsonStream = split((line) => JSON.parse(line));
|
|
992
|
+
jsonStream
|
|
987
993
|
.on('data', logPrinter)
|
|
988
|
-
.on('error',
|
|
994
|
+
.on('error', (error) => exit(`JSON parse error: ${error.message}`))
|
|
989
995
|
.on('end', process.exit);
|
|
996
|
+
|
|
997
|
+
req.pipe(jsonStream);
|
|
990
998
|
}
|
|
991
999
|
}
|
|
992
1000
|
|
|
@@ -1013,7 +1021,7 @@ async function inspect(localOptions, cmd) {
|
|
|
1013
1021
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
1014
1022
|
if (response.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
1015
1023
|
|
|
1016
|
-
|
|
1024
|
+
const apps = [];
|
|
1017
1025
|
|
|
1018
1026
|
for (const app of response.body.apps) {
|
|
1019
1027
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
@@ -1172,7 +1180,8 @@ async function importApp(localOptions, cmd) {
|
|
|
1172
1180
|
let data = {};
|
|
1173
1181
|
|
|
1174
1182
|
if (!options.inPlace) {
|
|
1175
|
-
let { remotePath, backupFormat, backupConfig
|
|
1183
|
+
let { remotePath, backupFormat, backupConfig } = options;
|
|
1184
|
+
const { backupPath, backupKey } = options;
|
|
1176
1185
|
let backupFolder;
|
|
1177
1186
|
|
|
1178
1187
|
if (backupPath) {
|
|
@@ -1224,7 +1233,7 @@ async function exportApp(localOptions, cmd) {
|
|
|
1224
1233
|
const app = await getApp(options);
|
|
1225
1234
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1226
1235
|
|
|
1227
|
-
|
|
1236
|
+
const data = {};
|
|
1228
1237
|
|
|
1229
1238
|
if (!options.snapshot) return exit('--snapshot is required');
|
|
1230
1239
|
|
|
@@ -1302,7 +1311,7 @@ function demuxStream(stream, stdout, stderr) {
|
|
|
1302
1311
|
// cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar zxf - -C /tmp" - must extrack ok
|
|
1303
1312
|
async function exec(args, localOptions, cmd) {
|
|
1304
1313
|
let stdin = localOptions._stdin || process.stdin; // hack for 'push', 'pull' to reuse this function
|
|
1305
|
-
|
|
1314
|
+
const stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
|
|
1306
1315
|
|
|
1307
1316
|
const options = cmd.optsWithGlobals();
|
|
1308
1317
|
let tty = !!options.tty;
|
|
@@ -1551,9 +1560,9 @@ async function envSet(envVars, localOptions, cmd) {
|
|
|
1551
1560
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1552
1561
|
if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1553
1562
|
|
|
1554
|
-
|
|
1563
|
+
const env = response.body.env;
|
|
1555
1564
|
envVars.forEach(envVar => {
|
|
1556
|
-
|
|
1565
|
+
const m = envVar.match(/(.*?)=(.*)/);
|
|
1557
1566
|
if (!m) return exit(`Expecting KEY=VALUE pattern. Got ${envVar}`);
|
|
1558
1567
|
env[m[1]] = m[2];
|
|
1559
1568
|
});
|
|
@@ -1573,7 +1582,7 @@ async function envUnset(envNames, localOptions, cmd) {
|
|
|
1573
1582
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1574
1583
|
if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1575
1584
|
|
|
1576
|
-
|
|
1585
|
+
const env = response.body.env;
|
|
1577
1586
|
envNames.forEach(name => delete env[name]);
|
|
1578
1587
|
|
|
1579
1588
|
await setEnv(app, env, options);
|