cloudron 4.13.0-1 → 4.14.1
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 +4 -1
- package/package.json +2 -2
- package/src/actions.js +103 -25
- package/src/appstore-actions.js +14 -0
package/bin/cloudron
CHANGED
|
@@ -27,6 +27,7 @@ function collectArgs(value, collected) {
|
|
|
27
27
|
return collected;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// TODO when updating to commander v8 we require https://github.com/tj/commander.js/pull/1670
|
|
30
31
|
program.version(version)
|
|
31
32
|
.option('--server <server>', 'Cloudron domain')
|
|
32
33
|
.option('--token <token>', 'Cloudron token')
|
|
@@ -73,6 +74,7 @@ program.command('configure')
|
|
|
73
74
|
.option('--no-wait', 'Wait for healthcheck to succeed [false]', false)
|
|
74
75
|
.option('-p, --port-bindings [PORT=port,...]', 'Query port bindings')
|
|
75
76
|
.option('-l, --location <location>', 'Location')
|
|
77
|
+
.option('-s, --secondary-domains [DOMAIN=domain,...]', 'Query/Set secondary domains')
|
|
76
78
|
.action(actions.configure);
|
|
77
79
|
|
|
78
80
|
program.command('debug [cmd...]')
|
|
@@ -128,8 +130,9 @@ program.command('install')
|
|
|
128
130
|
.description('Install or update app')
|
|
129
131
|
.option('--image <docker image>', 'Docker image')
|
|
130
132
|
.option('--no-wait', 'Wait for healthcheck to succeed [false]', false)
|
|
131
|
-
.option('-p, --port-bindings [PORT=port,...]', 'Query port bindings')
|
|
133
|
+
.option('-p, --port-bindings [PORT=port,...]', 'Query/Set port bindings')
|
|
132
134
|
.option('-l, --location <domain>', 'Subdomain or full domain')
|
|
135
|
+
.option('-s, --secondary-domains [DOMAIN=domain,...]', 'Query/Set secondary domains')
|
|
133
136
|
.option('--appstore-id <appid[@version]>', 'Use app from the store')
|
|
134
137
|
.option('--no-sso', 'Disable Cloudron SSO [false]', false)
|
|
135
138
|
.option('--debug [cmd]', 'Enable debug mode')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"author": "Cloudron Developers <support@cloudron.io>",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"async": "^3.2.3",
|
|
21
|
-
"cloudron-manifestformat": "^5.
|
|
21
|
+
"cloudron-manifestformat": "^5.15.0",
|
|
22
22
|
"commander": "^6.1.0",
|
|
23
23
|
"debug": "^4.3.3",
|
|
24
24
|
"delay": "^5.0.0",
|
package/src/actions.js
CHANGED
|
@@ -67,6 +67,10 @@ const NO_APP_FOUND_ERROR_STRING = 'Could not determine app. Use --app to specify
|
|
|
67
67
|
// options for the request module
|
|
68
68
|
function requestOptions(options) {
|
|
69
69
|
const adminFqdn = options.parent.server || config.apiEndpoint();
|
|
70
|
+
|
|
71
|
+
// ensure config can return the correct section
|
|
72
|
+
config.setActive(adminFqdn);
|
|
73
|
+
|
|
70
74
|
const token = options.parent.token || config.token();
|
|
71
75
|
const rejectUnauthorized = !(options.parent.allowSelfsigned || options.parent.acceptSelfsigned || config.allowSelfsigned());
|
|
72
76
|
|
|
@@ -82,7 +86,7 @@ function createRequest(method, apiPath, options) {
|
|
|
82
86
|
if (url.includes('?')) url += '&'; else url += '?';
|
|
83
87
|
url += `access_token=${token}`;
|
|
84
88
|
const request = superagent(method, url);
|
|
85
|
-
if (rejectUnauthorized) request.disableTLSCerts();
|
|
89
|
+
if (!rejectUnauthorized) request.disableTLSCerts();
|
|
86
90
|
request.ok(() => true);
|
|
87
91
|
return request;
|
|
88
92
|
}
|
|
@@ -106,6 +110,7 @@ async function selectDomain(location, options) {
|
|
|
106
110
|
const domains = response.body.domains;
|
|
107
111
|
|
|
108
112
|
let domain;
|
|
113
|
+
let subdomain = location;
|
|
109
114
|
let matchingDomain = domains
|
|
110
115
|
.map(function (d) { return d.domain; } )
|
|
111
116
|
.sort(function(a, b) { return a.length < b.length; })
|
|
@@ -113,7 +118,7 @@ async function selectDomain(location, options) {
|
|
|
113
118
|
|
|
114
119
|
if (matchingDomain) {
|
|
115
120
|
domain = matchingDomain;
|
|
116
|
-
|
|
121
|
+
subdomain = location.slice(0, -matchingDomain.length-1);
|
|
117
122
|
} else { // use the admin domain
|
|
118
123
|
domain = domains
|
|
119
124
|
.map(function (d) { return d.domain; } )
|
|
@@ -121,7 +126,7 @@ async function selectDomain(location, options) {
|
|
|
121
126
|
.find(function (d) { return adminFqdn.endsWith(d); });
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
return {
|
|
129
|
+
return { subdomain, domain };
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
async function stopActiveTask(app, options) {
|
|
@@ -197,7 +202,7 @@ async function getApp(options) {
|
|
|
197
202
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
198
203
|
if (response.statusCode !== 200) throw new Error(`Failed to get apps: ${requestError(response)}`);
|
|
199
204
|
|
|
200
|
-
const match = response.body.apps.filter(function (m) { return m.location === app || m.fqdn === app; });
|
|
205
|
+
const match = response.body.apps.filter(function (m) { return m.subdomain === app || m.location === app || m.fqdn === app; });
|
|
201
206
|
if (match.length == 0) throw new Error(`App at location ${app} not found`);
|
|
202
207
|
|
|
203
208
|
return match[0];
|
|
@@ -363,11 +368,15 @@ async function login(adminFqdn, options) {
|
|
|
363
368
|
|
|
364
369
|
config.setActive(adminFqdn);
|
|
365
370
|
|
|
371
|
+
const rejectUnauthorized = !(options.parent.allowSelfsigned || options.parent.acceptSelfsigned);
|
|
366
372
|
let token = config.token();
|
|
367
373
|
if (token) { // check if the token is not expired
|
|
368
|
-
const
|
|
374
|
+
const request = superagent.get(`https://${adminFqdn}/api/v1/profile?access_token=${token}`)
|
|
369
375
|
.timeout(60000)
|
|
370
|
-
.ok(() => true)
|
|
376
|
+
.ok(() => true);
|
|
377
|
+
if (!rejectUnauthorized) request.disableTLSCerts();
|
|
378
|
+
|
|
379
|
+
const [error, response] = await safe(request);
|
|
371
380
|
if (error) return exit(error);
|
|
372
381
|
if (response.status === 200) {
|
|
373
382
|
console.log('Existing token still valid.');
|
|
@@ -380,7 +389,6 @@ async function login(adminFqdn, options) {
|
|
|
380
389
|
if (!token) {
|
|
381
390
|
const username = options.username || readlineSync.question('Username: ', {});
|
|
382
391
|
const password = options.password || readlineSync.question('Password: ', { noEchoBack: true });
|
|
383
|
-
const rejectUnauthorized = !(options.parent.allowSelfsigned || options.parent.acceptSelfsigned);
|
|
384
392
|
|
|
385
393
|
const [error, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
|
|
386
394
|
if (error) return exit(`Failed to login: ${error.message}`);
|
|
@@ -448,12 +456,25 @@ async function list(options) {
|
|
|
448
456
|
console.log(t.toString());
|
|
449
457
|
}
|
|
450
458
|
|
|
459
|
+
async function querySecondaryDomains(app, manifest, options) {
|
|
460
|
+
const secondaryDomains = {};
|
|
461
|
+
|
|
462
|
+
if(!manifest.httpPorts) return secondaryDomains;
|
|
463
|
+
|
|
464
|
+
for (const env in manifest.httpPorts) {
|
|
465
|
+
const defaultDomain = (app && app.secondaryDomains && app.secondaryDomains[env]) ? app.secondaryDomains[env] : (manifest.httpPorts[env].defaultValue || '');
|
|
466
|
+
const input = readlineSync.question(`${manifest.httpPorts[env].description} (default: "${defaultDomain}"): `, {});
|
|
467
|
+
secondaryDomains[env] = await selectDomain(input, options);
|
|
468
|
+
}
|
|
469
|
+
return secondaryDomains;
|
|
470
|
+
}
|
|
471
|
+
|
|
451
472
|
function queryPortBindings(app, manifest) {
|
|
452
|
-
const portBindings = {
|
|
473
|
+
const portBindings = {};
|
|
453
474
|
const allPorts = _.extend({}, manifest.tcpPorts, manifest.udpPorts);
|
|
454
|
-
for (
|
|
455
|
-
|
|
456
|
-
|
|
475
|
+
for (const env in allPorts) {
|
|
476
|
+
const defaultPort = (app && app.portBindings && app.portBindings[env]) ? app.portBindings[env] : (allPorts[env].defaultValue || '');
|
|
477
|
+
const port = readlineSync.question(allPorts[env].description + ' (default ' + env + '=' + defaultPort + '. "x" to disable): ', {});
|
|
457
478
|
if (port === '') {
|
|
458
479
|
portBindings[env] = defaultPort;
|
|
459
480
|
} else if (isNaN(parseInt(port, 10))) {
|
|
@@ -530,6 +551,27 @@ async function install(options) {
|
|
|
530
551
|
|
|
531
552
|
const domainObject = await selectDomain(location, options);
|
|
532
553
|
|
|
554
|
+
// secondary domains
|
|
555
|
+
let secondaryDomains = {};
|
|
556
|
+
if (options.secondaryDomains) {
|
|
557
|
+
// ask the user for port values if the ports are different in the app and the manifest
|
|
558
|
+
if (typeof options.secondaryDomains === 'string') {
|
|
559
|
+
secondaryDomains = {};
|
|
560
|
+
for (const kv of options.secondaryDomains.split(',')) {
|
|
561
|
+
const tmp = kv.split('=');
|
|
562
|
+
secondaryDomains[tmp[0]] = await selectDomain(tmp[1], options);
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
secondaryDomains = await querySecondaryDomains(null /* existing app */, manifest, options);
|
|
566
|
+
}
|
|
567
|
+
} else if (manifest.httpPorts) { // just put in defaults
|
|
568
|
+
for (const env in manifest.httpPorts) {
|
|
569
|
+
secondaryDomains[env] = await selectDomain(manifest.httpPorts[env].defaultValue, options);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
for (const binding in secondaryDomains) console.log(`Secondary domain ${binding}: ${secondaryDomains[binding].subdomain}.${secondaryDomains[binding].domain}`);
|
|
574
|
+
|
|
533
575
|
// port bindings
|
|
534
576
|
let portBindings = {};
|
|
535
577
|
if (options.portBindings) {
|
|
@@ -551,16 +593,16 @@ async function install(options) {
|
|
|
551
593
|
}
|
|
552
594
|
}
|
|
553
595
|
|
|
554
|
-
for (
|
|
555
|
-
console.log('%s: %s', binding, portBindings[binding]);
|
|
556
|
-
}
|
|
596
|
+
for (const binding in portBindings) console.log(`Port ${binding}: ${portBindings[binding]}`);
|
|
557
597
|
|
|
558
598
|
const data = {
|
|
559
599
|
appStoreId: options.appstoreId || '', // note case change
|
|
560
600
|
manifest: options.appstoreId ? null : manifest, // cloudron ignores manifest anyway if appStoreId is set
|
|
561
|
-
location: domainObject.
|
|
601
|
+
location: domainObject.subdomain, // LEGACY
|
|
602
|
+
subdomain: domainObject.subdomain,
|
|
562
603
|
domain: domainObject.domain,
|
|
563
|
-
|
|
604
|
+
secondaryDomains,
|
|
605
|
+
portBindings,
|
|
564
606
|
accessRestriction: null
|
|
565
607
|
};
|
|
566
608
|
|
|
@@ -610,20 +652,46 @@ async function configure(options) {
|
|
|
610
652
|
|
|
611
653
|
const domainObject = await selectDomain(location, options);
|
|
612
654
|
|
|
655
|
+
const secondaryDomains = {};
|
|
656
|
+
app.secondaryDomains.forEach(sd => {
|
|
657
|
+
secondaryDomains[sd.environmentVariable] = {
|
|
658
|
+
subdomain: sd.subdomain,
|
|
659
|
+
domain: sd.domain
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
|
|
613
663
|
const data = {
|
|
614
|
-
location: domainObject.
|
|
664
|
+
location: domainObject.subdomain, // LEGACY
|
|
665
|
+
subdomain: domainObject.subdomain,
|
|
615
666
|
domain: domainObject.domain,
|
|
616
|
-
portBindings: app.portBindings
|
|
667
|
+
portBindings: app.portBindings,
|
|
668
|
+
secondaryDomains
|
|
617
669
|
};
|
|
618
670
|
|
|
671
|
+
// secondary domains
|
|
672
|
+
if (options.secondaryDomains) {
|
|
673
|
+
// ask the user for port values if the ports are different in the app and the manifest
|
|
674
|
+
if (typeof options.secondaryDomains === 'string') {
|
|
675
|
+
data.secondaryDomains = {};
|
|
676
|
+
for (const kv of options.secondaryDomains.split(',')) {
|
|
677
|
+
const tmp = kv.split('=');
|
|
678
|
+
data.secondaryDomains[tmp[0]] = await selectDomain(tmp[1], options);
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
data.secondaryDomains = await querySecondaryDomains(app, app.manifest, options);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
for (const binding in data.secondaryDomains) console.log(`Secondary domain ${binding}: ${data.secondaryDomains[binding].subdomain}.${data.secondaryDomains[binding].domain}`);
|
|
685
|
+
}
|
|
686
|
+
|
|
619
687
|
// port bindings
|
|
620
688
|
if (options.portBindings) {
|
|
621
|
-
|
|
689
|
+
let portBindings = app.portBindings;
|
|
622
690
|
// ask the user for port values if the ports are different in the app and the manifest
|
|
623
691
|
if (typeof options.portBindings === 'string') {
|
|
624
692
|
portBindings = {};
|
|
625
693
|
options.portBindings.split(',').forEach(function (kv) {
|
|
626
|
-
|
|
694
|
+
const tmp = kv.split('=');
|
|
627
695
|
if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
|
|
628
696
|
portBindings[tmp[0]] = parseInt(tmp[1], 10);
|
|
629
697
|
});
|
|
@@ -733,7 +801,8 @@ async function debug(cmd, options) {
|
|
|
733
801
|
console.log('\n');
|
|
734
802
|
console.log(options.limitMemory ? 'Limiting memory' : 'Setting unlimited memory');
|
|
735
803
|
|
|
736
|
-
const
|
|
804
|
+
const request2 = createRequest('POST', `/api/v1/apps/${app.id}/configure/memory_limit`, options);
|
|
805
|
+
const response2 = await request2.send({ memoryLimit });
|
|
737
806
|
if (response2.statusCode !== 202) return exit(`Failed to set memory limit: ${requestError(response2)}`);
|
|
738
807
|
|
|
739
808
|
await waitForTask(response2.body.taskId, options);
|
|
@@ -907,6 +976,7 @@ async function inspect(options) {
|
|
|
907
976
|
for (const app of response.body.apps) {
|
|
908
977
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
909
978
|
if (response2.statusCode !== 200) return exit(`Failed to list app: ${requestError(response2)}`);
|
|
979
|
+
response2.body.location = response2.body.location || response2.body.subdomain; // LEGACY support
|
|
910
980
|
apps.push(response2.body);
|
|
911
981
|
}
|
|
912
982
|
|
|
@@ -1145,12 +1215,20 @@ async function clone(options) {
|
|
|
1145
1215
|
|
|
1146
1216
|
if (!options.backup) return exit('Use --backup to specify the backup id');
|
|
1147
1217
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1218
|
+
const location = options.location || readlineSync.question('Cloned app location: ', {});
|
|
1219
|
+
const secondaryDomains = await querySecondaryDomains(app, app.manifest, options);
|
|
1220
|
+
const portBindings = queryPortBindings(app, app.manifest);
|
|
1221
|
+
const backupId = options.backup;
|
|
1151
1222
|
|
|
1152
1223
|
const domainObject = await selectDomain(location, options);
|
|
1153
|
-
const data = {
|
|
1224
|
+
const data = {
|
|
1225
|
+
backupId,
|
|
1226
|
+
location: domainObject.subdomain, // LEGACY
|
|
1227
|
+
subdomain: domainObject.subdomain,
|
|
1228
|
+
domain: domainObject.domain,
|
|
1229
|
+
secondaryDomains,
|
|
1230
|
+
portBindings
|
|
1231
|
+
};
|
|
1154
1232
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/clone`, options);
|
|
1155
1233
|
const response = await request.send(data);
|
|
1156
1234
|
if (response.statusCode !== 201) return exit(`Failed to list apps: ${requestError(response)}`);
|
package/src/appstore-actions.js
CHANGED
|
@@ -367,6 +367,19 @@ function approveVersion(appstoreId, version) {
|
|
|
367
367
|
|
|
368
368
|
console.log('Approved.');
|
|
369
369
|
console.log('');
|
|
370
|
+
|
|
371
|
+
superagentEnd(function () {
|
|
372
|
+
return superagent.get(createUrl('/api/v1/developers/apps/' + appstoreId + '/versions/' + version)).query({ accessToken: config.appStoreToken() });
|
|
373
|
+
}, function (error, result) {
|
|
374
|
+
if (error && !error.response) exit(util.format('Failed to list apps: %s', error.message));
|
|
375
|
+
if (result.statusCode !== 200) exit(util.format('Failed to list apps: %s message: %s', result.statusCode, result.text));
|
|
376
|
+
|
|
377
|
+
console.log('Changelog for forum update: ' + result.body.manifest.forumUrl);
|
|
378
|
+
console.log('');
|
|
379
|
+
console.log('[' + version + ']');
|
|
380
|
+
console.log(result.body.manifest.changelog);
|
|
381
|
+
console.log('');
|
|
382
|
+
});
|
|
370
383
|
});
|
|
371
384
|
}
|
|
372
385
|
|
|
@@ -505,6 +518,7 @@ function approve(options) {
|
|
|
505
518
|
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
506
519
|
|
|
507
520
|
console.log('Approving ' + id + '@' + version);
|
|
521
|
+
|
|
508
522
|
approveVersion(id, version);
|
|
509
523
|
});
|
|
510
524
|
}
|