cloudron 6.0.0 → 7.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/CHANGELOG.md +4 -0
- package/bin/cloudron +13 -21
- package/bin/cloudron-appstore +4 -7
- package/bin/cloudron-build +18 -8
- package/bin/cloudron-versions +33 -0
- package/eslint.config.js +7 -6
- package/package.json +16 -15
- package/src/actions.js +206 -122
- package/src/appstore-actions.js +31 -59
- package/src/backup-tools.js +22 -24
- package/src/build-actions.js +113 -99
- package/src/completion.js +4 -8
- package/src/config.js +78 -53
- package/src/helper.js +155 -13
- package/src/readline.js +8 -10
- package/src/templates/CloudronManifest.appstore.json.ejs +2 -2
- package/src/versions-actions.js +184 -0
- package/test/test.js +131 -160
- package/src/superagent.js +0 -225
package/src/actions.js
CHANGED
|
@@ -1,59 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
exports = module.exports = {
|
|
24
|
-
list,
|
|
25
|
-
login,
|
|
26
|
-
logout,
|
|
27
|
-
open,
|
|
28
|
-
install,
|
|
29
|
-
setLocation,
|
|
30
|
-
debug,
|
|
31
|
-
update,
|
|
32
|
-
uninstall,
|
|
33
|
-
logs,
|
|
34
|
-
exec,
|
|
35
|
-
status,
|
|
36
|
-
inspect,
|
|
37
|
-
pull,
|
|
38
|
-
push,
|
|
39
|
-
restart,
|
|
40
|
-
start,
|
|
41
|
-
stop,
|
|
42
|
-
repair,
|
|
43
|
-
init,
|
|
44
|
-
restore,
|
|
45
|
-
importApp,
|
|
46
|
-
exportApp,
|
|
47
|
-
clone,
|
|
48
|
-
backupCreate,
|
|
49
|
-
backupList,
|
|
50
|
-
cancel,
|
|
51
|
-
|
|
52
|
-
envGet,
|
|
53
|
-
envSet,
|
|
54
|
-
envList,
|
|
55
|
-
envUnset
|
|
56
|
-
};
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { dockerignoreMatcher } from './build-actions.js';
|
|
3
|
+
import * as config from './config.js';
|
|
4
|
+
import ejs from 'ejs';
|
|
5
|
+
import { exit, locateManifest, performOidcLogin } from './helper.js';
|
|
6
|
+
import { EventSource } from 'eventsource';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import https from 'https';
|
|
9
|
+
import manifestFormat from '@cloudron/manifest-format';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import * as readline from './readline.js';
|
|
13
|
+
import safe from '@cloudron/safetydance';
|
|
14
|
+
import { spawn } from 'child_process';
|
|
15
|
+
import semver from 'semver';
|
|
16
|
+
import stream from 'stream/promises';
|
|
17
|
+
import superagent from '@cloudron/superagent';
|
|
18
|
+
import Table from 'easy-table';
|
|
19
|
+
import tar from 'tar-fs';
|
|
20
|
+
import timers from 'timers/promises';
|
|
21
|
+
import zlib from 'zlib';
|
|
22
|
+
import open from 'open';
|
|
57
23
|
|
|
58
24
|
const NO_APP_FOUND_ERROR_STRING = 'Could not determine app. Use --app to specify the app\n';
|
|
59
25
|
|
|
@@ -158,7 +124,7 @@ async function selectAppWithRepository(repository, options) {
|
|
|
158
124
|
console.log('[%s]\t%s', index, app.fqdn);
|
|
159
125
|
});
|
|
160
126
|
|
|
161
|
-
let index
|
|
127
|
+
let index;
|
|
162
128
|
while (true) {
|
|
163
129
|
index = parseInt(await readline.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
|
|
164
130
|
if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
|
|
@@ -180,7 +146,7 @@ async function getApp(options) {
|
|
|
180
146
|
if (!manifestFilePath) throw new Error(NO_APP_FOUND_ERROR_STRING);
|
|
181
147
|
|
|
182
148
|
const sourceDir = path.dirname(manifestFilePath);
|
|
183
|
-
const appConfig = config.
|
|
149
|
+
const appConfig = config.getAppBuildConfig(sourceDir);
|
|
184
150
|
|
|
185
151
|
if (!appConfig.repository) throw new Error(NO_APP_FOUND_ERROR_STRING);
|
|
186
152
|
|
|
@@ -389,12 +355,24 @@ async function login(adminFqdn, localOptions, cmd) {
|
|
|
389
355
|
}
|
|
390
356
|
|
|
391
357
|
if (!token) {
|
|
392
|
-
|
|
393
|
-
const
|
|
358
|
+
// oidc cli login is possible from 9.1 on
|
|
359
|
+
const request = superagent.get(`https://${adminFqdn}/api/v1/cloudron/status`).ok(() => true);
|
|
360
|
+
if (!rejectUnauthorized) request.disableTLSCerts();
|
|
394
361
|
|
|
395
|
-
const [error,
|
|
396
|
-
if (error) return exit(
|
|
397
|
-
|
|
362
|
+
const [error, response] = await safe(request);
|
|
363
|
+
if (error) return exit(error);
|
|
364
|
+
if (response.status !== 200) return exit(`Failed to get cloudron status: ${requestError(response)}`);
|
|
365
|
+
|
|
366
|
+
if (semver.gte(response.body.version, '9.1.0')) {
|
|
367
|
+
token = await performOidcLogin(adminFqdn, { rejectUnauthorized });
|
|
368
|
+
} else {
|
|
369
|
+
const username = options.username || await readline.question('Username: ', {});
|
|
370
|
+
const password = options.password || await readline.question('Password: ', { noEchoBack: true });
|
|
371
|
+
|
|
372
|
+
const [authError, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
|
|
373
|
+
if (authError) return exit(`Failed to login: ${authError.message}`);
|
|
374
|
+
token = result;
|
|
375
|
+
}
|
|
398
376
|
}
|
|
399
377
|
|
|
400
378
|
config.setActive(adminFqdn);
|
|
@@ -404,6 +382,8 @@ async function login(adminFqdn, localOptions, cmd) {
|
|
|
404
382
|
config.set('cloudrons.default', adminFqdn);
|
|
405
383
|
|
|
406
384
|
console.log('Login successful.');
|
|
385
|
+
|
|
386
|
+
process.exit(0);
|
|
407
387
|
}
|
|
408
388
|
|
|
409
389
|
function logout() {
|
|
@@ -413,15 +393,14 @@ function logout() {
|
|
|
413
393
|
console.log('Logged out.');
|
|
414
394
|
}
|
|
415
395
|
|
|
416
|
-
async function
|
|
396
|
+
async function openApp(localOptions, cmd) {
|
|
417
397
|
const options = cmd.optsWithGlobals();
|
|
418
398
|
const [error, app] = await safe(getApp(options));
|
|
419
399
|
if (error) return exit(error);
|
|
420
400
|
|
|
421
401
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
422
402
|
|
|
423
|
-
|
|
424
|
-
(await import('open')).default(`https://${app.fqdn}`);
|
|
403
|
+
open(`https://${app.fqdn}`);
|
|
425
404
|
}
|
|
426
405
|
|
|
427
406
|
async function list(localOptions, cmd) {
|
|
@@ -448,12 +427,12 @@ async function list(localOptions, cmd) {
|
|
|
448
427
|
responses.forEach(function (result) {
|
|
449
428
|
if (result.status !== 'fulfilled') return exit(`Failed to list app: ${requestError(result.value)}`);
|
|
450
429
|
|
|
451
|
-
const
|
|
430
|
+
const appResponse = result.value;
|
|
452
431
|
|
|
453
|
-
if (
|
|
454
|
-
|
|
432
|
+
if (appResponse.status !== 200) return exit(`Failed to list app: ${requestError(appResponse)}`);
|
|
433
|
+
appResponse.body.location = appResponse.body.location || appResponse.body.subdomain; // LEGACY support
|
|
455
434
|
|
|
456
|
-
const detailedApp =
|
|
435
|
+
const detailedApp = appResponse.body;
|
|
457
436
|
|
|
458
437
|
t.cell('Id', detailedApp.id);
|
|
459
438
|
t.cell('Location', detailedApp.fqdn);
|
|
@@ -578,13 +557,32 @@ async function install(localOptions, cmd) {
|
|
|
578
557
|
const result = await getManifest(options.appstoreId || '');
|
|
579
558
|
const { manifest, manifestFilePath } = result;
|
|
580
559
|
|
|
560
|
+
let sourceArchiveFilePath = null; // will be set if we need to send a tarball
|
|
561
|
+
|
|
581
562
|
if (!manifest.dockerImage) { // not a manifest from appstore
|
|
582
563
|
const sourceDir = path.dirname(manifestFilePath);
|
|
583
|
-
const image = options.image || config.
|
|
564
|
+
const image = options.image || config.getAppBuildConfig(sourceDir).dockerImage;
|
|
565
|
+
|
|
566
|
+
if (!image) {
|
|
567
|
+
console.log('No build detected. This package will be built on the server.');
|
|
568
|
+
|
|
569
|
+
const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
|
|
570
|
+
const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
|
|
571
|
+
|
|
572
|
+
const tarStream = tar.pack(sourceDir, {
|
|
573
|
+
ignore: function (name) {
|
|
574
|
+
return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
|
|
575
|
+
}
|
|
576
|
+
});
|
|
584
577
|
|
|
585
|
-
|
|
578
|
+
sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
|
|
579
|
+
const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
|
|
586
580
|
|
|
587
|
-
|
|
581
|
+
const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
|
|
582
|
+
if (tarError) return exit(`Could not create source archive: ${tarError.message}`);
|
|
583
|
+
} else {
|
|
584
|
+
manifest.dockerImage = image;
|
|
585
|
+
}
|
|
588
586
|
}
|
|
589
587
|
|
|
590
588
|
const location = options.location || await readline.question('Location: ', {});
|
|
@@ -689,8 +687,35 @@ async function install(localOptions, cmd) {
|
|
|
689
687
|
if (!data.icon) return exit(`Could not read icon: ${iconFilename} message: ${safe.error.message}`);
|
|
690
688
|
}
|
|
691
689
|
|
|
692
|
-
const request = createRequest('POST', '/api/v1/apps
|
|
693
|
-
|
|
690
|
+
const request = createRequest('POST', '/api/v1/apps', options);
|
|
691
|
+
|
|
692
|
+
let response;
|
|
693
|
+
if (sourceArchiveFilePath) {
|
|
694
|
+
// Send as multipart form-data with source archive
|
|
695
|
+
request.field('appStoreId', data.appStoreId);
|
|
696
|
+
request.field('manifest', JSON.stringify(data.manifest));
|
|
697
|
+
request.field('location', data.location);
|
|
698
|
+
request.field('subdomain', data.subdomain);
|
|
699
|
+
request.field('domain', data.domain);
|
|
700
|
+
request.field('secondaryDomains', JSON.stringify(data.secondaryDomains));
|
|
701
|
+
request.field('aliasDomains', JSON.stringify(data.aliasDomains));
|
|
702
|
+
request.field('ports', JSON.stringify(data.ports));
|
|
703
|
+
request.field('accessRestriction', JSON.stringify(data.accessRestriction));
|
|
704
|
+
request.field('memoryLimit', String(data.memoryLimit));
|
|
705
|
+
request.field('env', JSON.stringify(data.env));
|
|
706
|
+
if (data.sso !== undefined) request.field('sso', String(data.sso));
|
|
707
|
+
if (data.debugMode) request.field('debugMode', JSON.stringify(data.debugMode));
|
|
708
|
+
if (data.icon) request.field('icon', data.icon);
|
|
709
|
+
request.attach('sourceArchive', sourceArchiveFilePath);
|
|
710
|
+
|
|
711
|
+
response = await request;
|
|
712
|
+
|
|
713
|
+
// Clean up temp file
|
|
714
|
+
safe.fs.unlinkSync(sourceArchiveFilePath);
|
|
715
|
+
} else {
|
|
716
|
+
response = await request.send(data);
|
|
717
|
+
}
|
|
718
|
+
|
|
694
719
|
if (response.status !== 202) return exit(`Failed to install app: ${requestError(response)}`);
|
|
695
720
|
|
|
696
721
|
const appId = response.body.id;
|
|
@@ -766,24 +791,24 @@ async function setLocation(localOptions, cmd) {
|
|
|
766
791
|
|
|
767
792
|
// port bindings
|
|
768
793
|
if (options.portBindings) {
|
|
769
|
-
let
|
|
794
|
+
let userPorts;
|
|
770
795
|
// ask the user for port values if the ports are different in the app and the manifest
|
|
771
796
|
if (typeof options.portBindings === 'string') {
|
|
772
|
-
|
|
797
|
+
userPorts = {};
|
|
773
798
|
options.portBindings.split(',').forEach(function (kv) {
|
|
774
799
|
const tmp = kv.split('=');
|
|
775
800
|
if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
|
|
776
|
-
|
|
801
|
+
userPorts[tmp[0]] = parseInt(tmp[1], 10);
|
|
777
802
|
});
|
|
778
803
|
} else {
|
|
779
|
-
|
|
804
|
+
userPorts = await queryPortBindings(app, app.manifest);
|
|
780
805
|
}
|
|
781
806
|
|
|
782
|
-
for (const port in
|
|
783
|
-
console.log('%s: %s', port,
|
|
807
|
+
for (const port in userPorts) {
|
|
808
|
+
console.log('%s: %s', port, userPorts[port]);
|
|
784
809
|
}
|
|
785
810
|
|
|
786
|
-
data.ports =
|
|
811
|
+
data.ports = userPorts;
|
|
787
812
|
}
|
|
788
813
|
|
|
789
814
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/location`, options);
|
|
@@ -803,20 +828,37 @@ async function update(localOptions, cmd) {
|
|
|
803
828
|
|
|
804
829
|
try {
|
|
805
830
|
const app = await getApp(options);
|
|
806
|
-
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
807
831
|
|
|
808
832
|
const result = await getManifest(options.appstoreId || '');
|
|
809
833
|
const { manifest, manifestFilePath } = result;
|
|
810
834
|
|
|
811
835
|
let data = {};
|
|
836
|
+
let sourceArchiveFilePath = null; // will be set if we need to send a tarball
|
|
812
837
|
|
|
813
838
|
if (!manifest.dockerImage) { // not a manifest from appstore
|
|
814
839
|
const sourceDir = path.dirname(manifestFilePath);
|
|
815
|
-
const image = options.image || config.
|
|
840
|
+
const image = options.image || config.getAppBuildConfig(sourceDir).dockerImage;
|
|
841
|
+
|
|
842
|
+
if (!image) {
|
|
843
|
+
console.log('No docker image detected. Creating source archive from this folder.');
|
|
844
|
+
|
|
845
|
+
const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
|
|
846
|
+
const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
|
|
847
|
+
|
|
848
|
+
const tarStream = tar.pack(sourceDir, {
|
|
849
|
+
ignore: function (name) {
|
|
850
|
+
return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
|
|
851
|
+
}
|
|
852
|
+
});
|
|
816
853
|
|
|
817
|
-
|
|
854
|
+
sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
|
|
855
|
+
const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
|
|
818
856
|
|
|
819
|
-
|
|
857
|
+
const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
|
|
858
|
+
if (tarError) return exit(`Could not create source archive: ${tarError.message}`);
|
|
859
|
+
} else {
|
|
860
|
+
manifest.dockerImage = image;
|
|
861
|
+
}
|
|
820
862
|
|
|
821
863
|
if (manifest.icon) {
|
|
822
864
|
let iconFilename = manifest.icon.slice(0, 7) === 'file://' ? manifest.icon.slice(7) : manifest.icon;
|
|
@@ -826,24 +868,32 @@ async function update(localOptions, cmd) {
|
|
|
826
868
|
}
|
|
827
869
|
}
|
|
828
870
|
|
|
829
|
-
|
|
871
|
+
data = Object.assign(data, {
|
|
872
|
+
appStoreId: options.appstoreId || '', // note case change
|
|
873
|
+
manifest: manifest,
|
|
874
|
+
skipBackup: !options.backup,
|
|
875
|
+
skipNotification: true,
|
|
876
|
+
force: true // permit downgrades
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
const request = createRequest('POST', `/api/v1/apps/${app.id}/update`, options);
|
|
830
880
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
881
|
+
let response;
|
|
882
|
+
if (sourceArchiveFilePath) {
|
|
883
|
+
request.field('appStoreId', data.appStoreId);
|
|
884
|
+
request.field('manifest', JSON.stringify(data.manifest));
|
|
885
|
+
request.field('skipBackup', String(data.skipBackup));
|
|
886
|
+
request.field('skipNotification', String(data.skipNotification));
|
|
887
|
+
request.field('force', String(data.force));
|
|
888
|
+
if (data.icon) request.field('icon', data.icon);
|
|
889
|
+
request.attach('sourceArchive', sourceArchiveFilePath);
|
|
890
|
+
|
|
891
|
+
response = await request;
|
|
892
|
+
|
|
893
|
+
safe.fs.unlinkSync(sourceArchiveFilePath);
|
|
834
894
|
} else {
|
|
835
|
-
|
|
836
|
-
data = Object.assign(data, {
|
|
837
|
-
appStoreId: options.appstoreId || '', // note case change
|
|
838
|
-
manifest: manifest,
|
|
839
|
-
skipBackup: !options.backup,
|
|
840
|
-
skipNotification: true,
|
|
841
|
-
force: true // permit downgrades
|
|
842
|
-
});
|
|
895
|
+
response = await request.send(data);
|
|
843
896
|
}
|
|
844
|
-
|
|
845
|
-
const request = createRequest('POST', apiPath, options);
|
|
846
|
-
const response = await request.send(data);
|
|
847
897
|
if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
|
|
848
898
|
|
|
849
899
|
process.stdout.write('\n => ' + 'Waiting for app to be updated ');
|
|
@@ -851,12 +901,11 @@ async function update(localOptions, cmd) {
|
|
|
851
901
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
852
902
|
console.log('\n\nApp is updated.');
|
|
853
903
|
} catch (error) {
|
|
854
|
-
|
|
855
|
-
exit(`\n\nApp update error: ${error.message}`);
|
|
904
|
+
exit(`\nApp update error: ${error.message}`);
|
|
856
905
|
}
|
|
857
906
|
}
|
|
858
907
|
|
|
859
|
-
async function
|
|
908
|
+
async function debugApp(args, localOptions, cmd) {
|
|
860
909
|
try {
|
|
861
910
|
const options = cmd.optsWithGlobals();
|
|
862
911
|
const app = await getApp(options);
|
|
@@ -1319,21 +1368,21 @@ async function clone(localOptions, cmd) {
|
|
|
1319
1368
|
}
|
|
1320
1369
|
|
|
1321
1370
|
// taken from docker-modem
|
|
1322
|
-
function demuxStream(
|
|
1371
|
+
function demuxStream(inputStream, stdout, stderr) {
|
|
1323
1372
|
let header = null;
|
|
1324
1373
|
|
|
1325
|
-
|
|
1326
|
-
header = header ||
|
|
1374
|
+
inputStream.on('readable', function() {
|
|
1375
|
+
header = header || inputStream.read(8);
|
|
1327
1376
|
while (header !== null) {
|
|
1328
1377
|
const type = header.readUInt8(0);
|
|
1329
|
-
const payload =
|
|
1378
|
+
const payload = inputStream.read(header.readUInt32BE(4));
|
|
1330
1379
|
if (payload === null) break;
|
|
1331
1380
|
if (type == 2) {
|
|
1332
1381
|
stderr.write(payload);
|
|
1333
1382
|
} else {
|
|
1334
1383
|
stdout.write(payload);
|
|
1335
1384
|
}
|
|
1336
|
-
header =
|
|
1385
|
+
header = inputStream.read(8);
|
|
1337
1386
|
}
|
|
1338
1387
|
});
|
|
1339
1388
|
}
|
|
@@ -1344,7 +1393,7 @@ function demuxStream(stream, stdout, stderr) {
|
|
|
1344
1393
|
// echo "sauce" | cloudron exec -- bash -c "cat - > /app/data/sauce" - test with binary files. should disable tty
|
|
1345
1394
|
// cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar xvf - -C /tmp" - must show an error
|
|
1346
1395
|
// cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar zxf - -C /tmp" - must extrack ok
|
|
1347
|
-
async function
|
|
1396
|
+
async function execApp(args, localOptions, cmd) {
|
|
1348
1397
|
let stdin = localOptions._stdin || process.stdin; // hack for 'push', 'pull' to reuse this function
|
|
1349
1398
|
const stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
|
|
1350
1399
|
|
|
@@ -1378,12 +1427,12 @@ async function exec(args, localOptions, cmd) {
|
|
|
1378
1427
|
process.exit(response2.body.exitCode);
|
|
1379
1428
|
}
|
|
1380
1429
|
|
|
1381
|
-
const { adminFqdn, token, rejectUnauthorized } = requestOptions(cmd.optsWithGlobals());
|
|
1430
|
+
const { adminFqdn, token: reqToken, rejectUnauthorized } = requestOptions(cmd.optsWithGlobals());
|
|
1382
1431
|
|
|
1383
1432
|
const searchParams = new URLSearchParams({
|
|
1384
1433
|
rows: stdout.rows || 24,
|
|
1385
1434
|
columns: stdout.columns || 80,
|
|
1386
|
-
access_token:
|
|
1435
|
+
access_token: reqToken,
|
|
1387
1436
|
tty
|
|
1388
1437
|
});
|
|
1389
1438
|
|
|
@@ -1448,7 +1497,7 @@ async function exec(args, localOptions, cmd) {
|
|
|
1448
1497
|
req.end(); // this makes the request
|
|
1449
1498
|
}
|
|
1450
1499
|
|
|
1451
|
-
function
|
|
1500
|
+
function pushApp(localDir, remote, localOptions, cmd) {
|
|
1452
1501
|
// deal with paths prefixed with ~
|
|
1453
1502
|
const local = localDir.replace(/^~(?=$|\/|\\)/, os.homedir());
|
|
1454
1503
|
const stat = fs.existsSync(path.resolve(local)) ? fs.lstatSync(local) : null;
|
|
@@ -1461,7 +1510,7 @@ function push(localDir, remote, localOptions, cmd) {
|
|
|
1461
1510
|
return tarzip.stdout;
|
|
1462
1511
|
};
|
|
1463
1512
|
|
|
1464
|
-
|
|
1513
|
+
execApp(['tar', 'zxvf', '-', '-C', remote], localOptions, cmd);
|
|
1465
1514
|
} else {
|
|
1466
1515
|
if (local === '-') {
|
|
1467
1516
|
localOptions._stdin = process.stdin;
|
|
@@ -1477,7 +1526,7 @@ function push(localDir, remote, localOptions, cmd) {
|
|
|
1477
1526
|
remote = remote + '/' + path.basename(local); // do not use path.join as we want this to be a UNIX path
|
|
1478
1527
|
}
|
|
1479
1528
|
|
|
1480
|
-
|
|
1529
|
+
execApp(['bash', '-c', `cat - > "${remote}"`], localOptions, cmd);
|
|
1481
1530
|
}
|
|
1482
1531
|
}
|
|
1483
1532
|
|
|
@@ -1489,7 +1538,7 @@ function pull(remote, local, localOptions, cmd) {
|
|
|
1489
1538
|
unzip.pipe(untar);
|
|
1490
1539
|
localOptions._stdout = unzip;
|
|
1491
1540
|
|
|
1492
|
-
|
|
1541
|
+
execApp(['tar', 'zcf', '-', '-C', remote, '.'], localOptions, cmd);
|
|
1493
1542
|
} else {
|
|
1494
1543
|
if (fs.existsSync(local) && fs.lstatSync(local).isDirectory()) {
|
|
1495
1544
|
local = path.join(local, path.basename(remote));
|
|
@@ -1502,7 +1551,7 @@ function pull(remote, local, localOptions, cmd) {
|
|
|
1502
1551
|
|
|
1503
1552
|
localOptions._stdout.on('error', function (error) { exit('Error pulling', error); });
|
|
1504
1553
|
|
|
1505
|
-
|
|
1554
|
+
execApp(['cat', remote], localOptions, cmd);
|
|
1506
1555
|
}
|
|
1507
1556
|
}
|
|
1508
1557
|
|
|
@@ -1513,10 +1562,10 @@ function init(localOptions, cmd) {
|
|
|
1513
1562
|
|
|
1514
1563
|
const manifestTemplateFilename = options.appstore ? 'CloudronManifest.appstore.json.ejs' : 'CloudronManifest.json.ejs';
|
|
1515
1564
|
|
|
1516
|
-
const manifestTemplate = fs.readFileSync(path.join(
|
|
1517
|
-
const dockerfileTemplate = fs.readFileSync(path.join(
|
|
1518
|
-
const dockerignoreTemplate = fs.readFileSync(path.join(
|
|
1519
|
-
const startShTemplate = fs.readFileSync(path.join(
|
|
1565
|
+
const manifestTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', manifestTemplateFilename), 'utf8');
|
|
1566
|
+
const dockerfileTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'Dockerfile.ejs'), 'utf8');
|
|
1567
|
+
const dockerignoreTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'dockerignore.ejs'), 'utf8');
|
|
1568
|
+
const startShTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'start.sh.ejs'), 'utf8');
|
|
1520
1569
|
|
|
1521
1570
|
const data = {
|
|
1522
1571
|
version: '0.1.0',
|
|
@@ -1549,7 +1598,7 @@ function init(localOptions, cmd) {
|
|
|
1549
1598
|
fs.chmodSync('start.sh', 0o0775);
|
|
1550
1599
|
}
|
|
1551
1600
|
|
|
1552
|
-
fs.copyFileSync(path.join(
|
|
1601
|
+
fs.copyFileSync(path.join(import.meta.dirname, 'templates/', 'logo.png'), 'logo.png');
|
|
1553
1602
|
|
|
1554
1603
|
if (options.appstore) {
|
|
1555
1604
|
if (!fs.existsSync('.gitignore')) fs.writeFileSync('.gitignore', 'node_modules/\n', 'utf8');
|
|
@@ -1658,3 +1707,38 @@ async function envGet(envName, localOptions, cmd) {
|
|
|
1658
1707
|
exit(error);
|
|
1659
1708
|
}
|
|
1660
1709
|
}
|
|
1710
|
+
|
|
1711
|
+
export default {
|
|
1712
|
+
list,
|
|
1713
|
+
login,
|
|
1714
|
+
logout,
|
|
1715
|
+
open: openApp,
|
|
1716
|
+
install,
|
|
1717
|
+
setLocation,
|
|
1718
|
+
debug: debugApp,
|
|
1719
|
+
update,
|
|
1720
|
+
uninstall,
|
|
1721
|
+
logs,
|
|
1722
|
+
exec: execApp,
|
|
1723
|
+
status,
|
|
1724
|
+
inspect,
|
|
1725
|
+
pull,
|
|
1726
|
+
push: pushApp,
|
|
1727
|
+
restart,
|
|
1728
|
+
start,
|
|
1729
|
+
stop,
|
|
1730
|
+
repair,
|
|
1731
|
+
init,
|
|
1732
|
+
restore,
|
|
1733
|
+
importApp,
|
|
1734
|
+
exportApp,
|
|
1735
|
+
clone,
|
|
1736
|
+
backupCreate,
|
|
1737
|
+
backupList,
|
|
1738
|
+
cancel,
|
|
1739
|
+
|
|
1740
|
+
envGet,
|
|
1741
|
+
envSet,
|
|
1742
|
+
envList,
|
|
1743
|
+
envUnset
|
|
1744
|
+
};
|
package/src/appstore-actions.js
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
readline = require('./readline.js'),
|
|
13
|
-
safe = require('safetydance'),
|
|
14
|
-
superagent = require('@cloudron/superagent'),
|
|
15
|
-
Table = require('easy-table');
|
|
16
|
-
|
|
17
|
-
exports = module.exports = {
|
|
18
|
-
login,
|
|
19
|
-
logout,
|
|
20
|
-
info,
|
|
21
|
-
listVersions,
|
|
22
|
-
submit,
|
|
23
|
-
upload,
|
|
24
|
-
revoke,
|
|
25
|
-
approve,
|
|
26
|
-
verifyManifest,
|
|
27
|
-
|
|
28
|
-
notify
|
|
29
|
-
};
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import * as config from './config.js';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { exit, locateManifest, parseChangelog } from './helper.js';
|
|
6
|
+
import manifestFormat from '@cloudron/manifest-format';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import * as readline from './readline.js';
|
|
9
|
+
import safe from '@cloudron/safetydance';
|
|
10
|
+
import superagent from '@cloudron/superagent';
|
|
11
|
+
import Table from 'easy-table';
|
|
30
12
|
|
|
31
13
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
32
14
|
|
|
@@ -156,29 +138,6 @@ async function listVersions(localOptions, cmd) {
|
|
|
156
138
|
console.log(t.toString());
|
|
157
139
|
}
|
|
158
140
|
|
|
159
|
-
function parseChangelog(file, version) {
|
|
160
|
-
let changelog = '';
|
|
161
|
-
const data = safe.fs.readFileSync(file, 'utf8');
|
|
162
|
-
if (!data) return null;
|
|
163
|
-
const lines = data.split('\n');
|
|
164
|
-
|
|
165
|
-
version = version.replace(/-.*/, ''); // remove any prerelease
|
|
166
|
-
|
|
167
|
-
let i;
|
|
168
|
-
for (i = 0; i < lines.length; i++) {
|
|
169
|
-
if (lines[i] === '[' + version + ']') break;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
for (i = i + 1; i < lines.length; i++) {
|
|
173
|
-
if (lines[i] === '') continue;
|
|
174
|
-
if (lines[i][0] === '[') break;
|
|
175
|
-
|
|
176
|
-
changelog += lines[i] + '\n';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return changelog;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
141
|
async function addVersion(manifest, baseDir, options) {
|
|
183
142
|
assert.strictEqual(typeof manifest, 'object');
|
|
184
143
|
assert.strictEqual(typeof baseDir, 'string');
|
|
@@ -210,8 +169,7 @@ async function addVersion(manifest, baseDir, options) {
|
|
|
210
169
|
}
|
|
211
170
|
|
|
212
171
|
if (manifest.changelog.slice(0, 7) === 'file://') {
|
|
213
|
-
|
|
214
|
-
changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
|
|
172
|
+
const changelogPath = path.isAbsolute(manifest.changelog.slice(7)) ? manifest.changelog.slice(7) : path.join(baseDir, manifest.changelog.slice(7));
|
|
215
173
|
manifest.changelog = parseChangelog(changelogPath, manifest.version);
|
|
216
174
|
if (!manifest.changelog) throw new Error('Bad changelog format or missing changelog for this version');
|
|
217
175
|
}
|
|
@@ -277,7 +235,7 @@ async function verifyManifest(localOptions, cmd) {
|
|
|
277
235
|
const manifest = result.manifest;
|
|
278
236
|
|
|
279
237
|
const sourceDir = path.dirname(manifestFilePath);
|
|
280
|
-
const appConfig = config.
|
|
238
|
+
const appConfig = config.getAppBuildConfig(sourceDir);
|
|
281
239
|
|
|
282
240
|
// image can be passed in options for buildbot
|
|
283
241
|
if (options.image) {
|
|
@@ -324,7 +282,7 @@ async function upload(localOptions, cmd) {
|
|
|
324
282
|
const manifest = result.manifest;
|
|
325
283
|
|
|
326
284
|
const sourceDir = path.dirname(manifestFilePath);
|
|
327
|
-
const appConfig = config.
|
|
285
|
+
const appConfig = config.getAppBuildConfig(sourceDir);
|
|
328
286
|
|
|
329
287
|
// image can be passed in options for buildbot
|
|
330
288
|
if (options.image) {
|
|
@@ -467,11 +425,11 @@ async function notify() {
|
|
|
467
425
|
if (result.error) return exit(new Error(`Invalid CloudronManifest.json: ${result.error.message}`));
|
|
468
426
|
const { manifest } = result;
|
|
469
427
|
|
|
470
|
-
let postContent
|
|
428
|
+
let postContent;
|
|
471
429
|
if (manifest.changelog.slice(0, 7) === 'file://') {
|
|
472
430
|
const baseDir = path.dirname(manifestFilePath);
|
|
473
|
-
|
|
474
|
-
changelogPath = path.isAbsolute(
|
|
431
|
+
const manifestChangelogPath = manifest.changelog.slice(7);
|
|
432
|
+
const changelogPath = path.isAbsolute(manifestChangelogPath) ? manifestChangelogPath : path.join(baseDir, manifestChangelogPath);
|
|
475
433
|
const changelog = parseChangelog(changelogPath, manifest.version);
|
|
476
434
|
if (!changelog) return exit('Bad changelog format or missing changelog for this version');
|
|
477
435
|
postContent = `[${manifest.version}]\n${changelog}\n`;
|
|
@@ -511,3 +469,17 @@ async function notify() {
|
|
|
511
469
|
if (postResponse.status !== 200) return exit(`Unable to create changelog post: ${requestError(postResponse)}`);
|
|
512
470
|
console.log('Posted to forum');
|
|
513
471
|
}
|
|
472
|
+
|
|
473
|
+
export default {
|
|
474
|
+
login,
|
|
475
|
+
logout,
|
|
476
|
+
info,
|
|
477
|
+
listVersions,
|
|
478
|
+
submit,
|
|
479
|
+
upload,
|
|
480
|
+
revoke,
|
|
481
|
+
approve,
|
|
482
|
+
verifyManifest,
|
|
483
|
+
|
|
484
|
+
notify
|
|
485
|
+
};
|