cloudron 6.0.0 → 7.0.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/src/actions.js CHANGED
@@ -1,59 +1,25 @@
1
- 'use strict';
2
-
3
- const assert = require('assert'),
4
- config = require('./config.js'),
5
- ejs = require('ejs'),
6
- { exit, locateManifest } = require('./helper.js'),
7
- { EventSource } = require('eventsource'),
8
- fs = require('fs'),
9
- https = require('https'),
10
- manifestFormat = require('@cloudron/manifest-format'),
11
- os = require('os'),
12
- path = require('path'),
13
- readline = require('./readline.js'),
14
- safe = require('safetydance'),
15
- spawn = require('child_process').spawn,
16
- semver = require('semver'),
17
- superagent = require('@cloudron/superagent'),
18
- Table = require('easy-table'),
19
- tar = require('tar-fs'),
20
- timers = require('timers/promises'),
21
- zlib = require('zlib');
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 = -1;
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.getAppConfig(sourceDir);
149
+ const appConfig = config.getAppBuildConfig(sourceDir);
184
150
 
185
151
  if (!appConfig.repository) throw new Error(NO_APP_FOUND_ERROR_STRING);
186
152
 
@@ -300,7 +266,7 @@ async function stopApp(app, options) {
300
266
  const response = await request.send({});
301
267
  if (response.status !== 202) throw `Failed to stop app: ${requestError(response)}`;
302
268
 
303
- await waitForTask(response.body.taskId, options);
269
+ return response.body.taskId;
304
270
  }
305
271
 
306
272
  async function startApp(app, options) {
@@ -311,7 +277,7 @@ async function startApp(app, options) {
311
277
  const response = await request.send({});
312
278
  if (response.status !== 202) throw `Failed to start app: ${requestError(response)}`;
313
279
 
314
- await waitForTask(response.body.taskId, options);
280
+ return response.body.taskId;
315
281
  }
316
282
 
317
283
  async function restartApp(app, options) {
@@ -322,7 +288,7 @@ async function restartApp(app, options) {
322
288
  const response = await request.send({});
323
289
  if (response.status !== 202) throw `Failed to restart app: ${requestError(response)}`;
324
290
 
325
- await waitForTask(response.body.taskId, options);
291
+ return response.body.taskId;
326
292
  }
327
293
 
328
294
  async function authenticate(adminFqdn, username, password, options) {
@@ -389,12 +355,24 @@ async function login(adminFqdn, localOptions, cmd) {
389
355
  }
390
356
 
391
357
  if (!token) {
392
- const username = options.username || await readline.question('Username: ', {});
393
- const password = options.password || await readline.question('Password: ', { noEchoBack: true });
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, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
396
- if (error) return exit(`Failed to login: ${error.message}`);
397
- token = result;
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 open(localOptions, cmd) {
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
- // pure esm module
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 response = result.value;
430
+ const appResponse = result.value;
452
431
 
453
- if (response.status !== 200) return exit(`Failed to list app: ${requestError(response)}`);
454
- response.body.location = response.body.location || response.body.subdomain; // LEGACY support
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 = response.body;
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.getAppConfig(sourceDir).dockerImage;
564
+ const image = options.image || config.getAppBuildConfig(sourceDir).dockerImage;
584
565
 
585
- if (!image) return exit('No image found, please run `cloudron build` first or specify using --image');
566
+ if (!image) {
567
+ console.log('No build detected. This package will be built on the server.');
586
568
 
587
- manifest.dockerImage = image;
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
+ });
577
+
578
+ sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
579
+ const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
580
+
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,14 +687,43 @@ 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/install', options);
693
- const response = await request.send(data);
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;
697
722
 
698
723
  console.log('App is being installed.');
699
724
 
725
+ if (!options.wait) return;
726
+
700
727
  await waitForFinishInstallation(appId, response.body.taskId, options);
701
728
  console.log('\n\nApp is installed.');
702
729
  } catch (error) {
@@ -766,30 +793,32 @@ async function setLocation(localOptions, cmd) {
766
793
 
767
794
  // port bindings
768
795
  if (options.portBindings) {
769
- let ports;
796
+ let userPorts;
770
797
  // ask the user for port values if the ports are different in the app and the manifest
771
798
  if (typeof options.portBindings === 'string') {
772
- ports = {};
799
+ userPorts = {};
773
800
  options.portBindings.split(',').forEach(function (kv) {
774
801
  const tmp = kv.split('=');
775
802
  if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
776
- ports[tmp[0]] = parseInt(tmp[1], 10);
803
+ userPorts[tmp[0]] = parseInt(tmp[1], 10);
777
804
  });
778
805
  } else {
779
- ports = await queryPortBindings(app, app.manifest);
806
+ userPorts = await queryPortBindings(app, app.manifest);
780
807
  }
781
808
 
782
- for (const port in ports) {
783
- console.log('%s: %s', port, ports[port]);
809
+ for (const port in userPorts) {
810
+ console.log('%s: %s', port, userPorts[port]);
784
811
  }
785
812
 
786
- data.ports = ports;
813
+ data.ports = userPorts;
787
814
  }
788
815
 
789
816
  const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/location`, options);
790
817
  const response = await request.send(data);
791
818
  if (response.status !== 202) return exit(`Failed to configure app: ${requestError(response)}`);
792
819
 
820
+ if (!options.wait) return;
821
+
793
822
  await waitForTask(response.body.taskId, options);
794
823
  await waitForHealthy(app.id, options);
795
824
  console.log('\n\nApp configured');
@@ -803,20 +832,37 @@ async function update(localOptions, cmd) {
803
832
 
804
833
  try {
805
834
  const app = await getApp(options);
806
- if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
807
835
 
808
836
  const result = await getManifest(options.appstoreId || '');
809
837
  const { manifest, manifestFilePath } = result;
810
838
 
811
839
  let data = {};
840
+ let sourceArchiveFilePath = null; // will be set if we need to send a tarball
812
841
 
813
842
  if (!manifest.dockerImage) { // not a manifest from appstore
814
843
  const sourceDir = path.dirname(manifestFilePath);
815
- const image = options.image || config.getAppConfig(sourceDir).dockerImage;
844
+ const image = options.image || config.getAppBuildConfig(sourceDir).dockerImage;
816
845
 
817
- if (!image) return exit('No image found, please run `cloudron build` first or use --image');
846
+ if (!image) {
847
+ console.log('No docker image detected. Creating source archive from this folder.');
818
848
 
819
- manifest.dockerImage = image;
849
+ const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
850
+ const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
851
+
852
+ const tarStream = tar.pack(sourceDir, {
853
+ ignore: function (name) {
854
+ return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
855
+ }
856
+ });
857
+
858
+ sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
859
+ const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
860
+
861
+ const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
862
+ if (tarError) return exit(`Could not create source archive: ${tarError.message}`);
863
+ } else {
864
+ manifest.dockerImage = image;
865
+ }
820
866
 
821
867
  if (manifest.icon) {
822
868
  let iconFilename = manifest.icon.slice(0, 7) === 'file://' ? manifest.icon.slice(7) : manifest.icon;
@@ -826,24 +872,32 @@ async function update(localOptions, cmd) {
826
872
  }
827
873
  }
828
874
 
829
- let apiPath;
875
+ data = Object.assign(data, {
876
+ appStoreId: options.appstoreId || '', // note case change
877
+ manifest: manifest,
878
+ skipBackup: !options.backup,
879
+ skipNotification: true,
880
+ force: true // permit downgrades
881
+ });
882
+
883
+ const request = createRequest('POST', `/api/v1/apps/${app.id}/update`, options);
884
+
885
+ let response;
886
+ if (sourceArchiveFilePath) {
887
+ request.field('appStoreId', data.appStoreId);
888
+ request.field('manifest', JSON.stringify(data.manifest));
889
+ request.field('skipBackup', String(data.skipBackup));
890
+ request.field('skipNotification', String(data.skipNotification));
891
+ request.field('force', String(data.force));
892
+ if (data.icon) request.field('icon', data.icon);
893
+ request.attach('sourceArchive', sourceArchiveFilePath);
830
894
 
831
- if (app.error && (app.error.installationState === 'pending_install')) { // install had failed. call repair to re-install
832
- apiPath = `/api/v1/apps/${app.id}/repair`;
833
- data = Object.assign(data, { manifest });
895
+ response = await request;
896
+
897
+ safe.fs.unlinkSync(sourceArchiveFilePath);
834
898
  } else {
835
- apiPath = `/api/v1/apps/${app.id}/update`;
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
- });
899
+ response = await request.send(data);
843
900
  }
844
-
845
- const request = createRequest('POST', apiPath, options);
846
- const response = await request.send(data);
847
901
  if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
848
902
 
849
903
  process.stdout.write('\n => ' + 'Waiting for app to be updated ');
@@ -851,12 +905,11 @@ async function update(localOptions, cmd) {
851
905
  await waitForFinishInstallation(app.id, response.body.taskId, options);
852
906
  console.log('\n\nApp is updated.');
853
907
  } catch (error) {
854
- console.log(error);
855
- exit(`\n\nApp update error: ${error.message}`);
908
+ exit(`\nApp update error: ${error.message}`);
856
909
  }
857
910
  }
858
911
 
859
- async function debug(args, localOptions, cmd) {
912
+ async function debugApp(args, localOptions, cmd) {
860
913
  try {
861
914
  const options = cmd.optsWithGlobals();
862
915
  const app = await getApp(options);
@@ -875,6 +928,8 @@ async function debug(args, localOptions, cmd) {
875
928
  const response = await request.send(data);
876
929
  if (response.status !== 202) return exit(`Failed to set debug mode: ${requestError(response)}`);
877
930
 
931
+ if (!options.wait) return;
932
+
878
933
  await waitForTask(response.body.taskId, options);
879
934
 
880
935
  const memoryLimit = options.limitMemory ? 0 : -1;
@@ -909,6 +964,8 @@ async function repair(localOptions, cmd) {
909
964
  const response = await request.send(data);
910
965
  if (response.status !== 202) return exit(`Failed to set repair mode: ${requestError(response)}`);
911
966
 
967
+ if (!options.wait) return;
968
+
912
969
  process.stdout.write('\n => ' + 'Waiting for app to be repaired ');
913
970
 
914
971
  await waitForFinishInstallation(app.id, response.body.taskId, options);
@@ -943,6 +1000,8 @@ async function uninstall(localOptions, cmd) {
943
1000
  const response = await request.send({});
944
1001
  if (response.status !== 202) return exit(`Failed to uninstall app: ${requestError(response)}`);
945
1002
 
1003
+ if (!options.wait) return;
1004
+
946
1005
  process.stdout.write('\n => ' + 'Waiting for app to be uninstalled ');
947
1006
 
948
1007
  await waitForTask(response.body.taskId, options);
@@ -1068,7 +1127,9 @@ async function restart(localOptions, cmd) {
1068
1127
  const options = cmd.optsWithGlobals();
1069
1128
  const app = await getApp(options);
1070
1129
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1071
- await restartApp(app, options);
1130
+ const taskId = await restartApp(app, options);
1131
+ if (!options.wait) return;
1132
+ await waitForTask(taskId, options);
1072
1133
  await waitForHealthy(app.id, options);
1073
1134
  console.log('\n\nApp restarted');
1074
1135
  } catch (error) {
@@ -1081,7 +1142,9 @@ async function start(localOptions, cmd) {
1081
1142
  const options = cmd.optsWithGlobals();
1082
1143
  const app = await getApp(options);
1083
1144
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1084
- await startApp(app, options);
1145
+ const taskId = await startApp(app, options);
1146
+ if (!options.wait) return;
1147
+ await waitForTask(taskId, options);
1085
1148
  await waitForHealthy(app.id, options);
1086
1149
  console.log('\n\nApp started');
1087
1150
  } catch (error) {
@@ -1094,7 +1157,9 @@ async function stop(localOptions, cmd) {
1094
1157
  const options = cmd.optsWithGlobals();
1095
1158
  const app = await getApp(options);
1096
1159
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1097
- await stopApp(app, options);
1160
+ const taskId = await stopApp(app, options);
1161
+ if (!options.wait) return;
1162
+ await waitForTask(taskId, options);
1098
1163
  console.log('\n\nApp stopped');
1099
1164
  } catch (error) {
1100
1165
  exit(error);
@@ -1120,6 +1185,8 @@ async function backupCreate(localOptions, cmd) {
1120
1185
  const response = await request.send({ backupSiteId });
1121
1186
  if (response.status !== 202) return exit(`Failed to start backup: ${requestError(response)}`);
1122
1187
 
1188
+ if (!options.wait) return;
1189
+
1123
1190
  // FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
1124
1191
  await waitForFinishBackup(app.id, response.body.taskId, options);
1125
1192
  console.log('\n\nApp is backed up');
@@ -1319,21 +1386,21 @@ async function clone(localOptions, cmd) {
1319
1386
  }
1320
1387
 
1321
1388
  // taken from docker-modem
1322
- function demuxStream(stream, stdout, stderr) {
1389
+ function demuxStream(inputStream, stdout, stderr) {
1323
1390
  let header = null;
1324
1391
 
1325
- stream.on('readable', function() {
1326
- header = header || stream.read(8);
1392
+ inputStream.on('readable', function() {
1393
+ header = header || inputStream.read(8);
1327
1394
  while (header !== null) {
1328
1395
  const type = header.readUInt8(0);
1329
- const payload = stream.read(header.readUInt32BE(4));
1396
+ const payload = inputStream.read(header.readUInt32BE(4));
1330
1397
  if (payload === null) break;
1331
1398
  if (type == 2) {
1332
1399
  stderr.write(payload);
1333
1400
  } else {
1334
1401
  stdout.write(payload);
1335
1402
  }
1336
- header = stream.read(8);
1403
+ header = inputStream.read(8);
1337
1404
  }
1338
1405
  });
1339
1406
  }
@@ -1344,7 +1411,7 @@ function demuxStream(stream, stdout, stderr) {
1344
1411
  // echo "sauce" | cloudron exec -- bash -c "cat - > /app/data/sauce" - test with binary files. should disable tty
1345
1412
  // cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar xvf - -C /tmp" - must show an error
1346
1413
  // cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar zxf - -C /tmp" - must extrack ok
1347
- async function exec(args, localOptions, cmd) {
1414
+ async function execApp(args, localOptions, cmd) {
1348
1415
  let stdin = localOptions._stdin || process.stdin; // hack for 'push', 'pull' to reuse this function
1349
1416
  const stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
1350
1417
 
@@ -1378,12 +1445,12 @@ async function exec(args, localOptions, cmd) {
1378
1445
  process.exit(response2.body.exitCode);
1379
1446
  }
1380
1447
 
1381
- const { adminFqdn, token, rejectUnauthorized } = requestOptions(cmd.optsWithGlobals());
1448
+ const { adminFqdn, token: reqToken, rejectUnauthorized } = requestOptions(cmd.optsWithGlobals());
1382
1449
 
1383
1450
  const searchParams = new URLSearchParams({
1384
1451
  rows: stdout.rows || 24,
1385
1452
  columns: stdout.columns || 80,
1386
- access_token: token,
1453
+ access_token: reqToken,
1387
1454
  tty
1388
1455
  });
1389
1456
 
@@ -1448,7 +1515,7 @@ async function exec(args, localOptions, cmd) {
1448
1515
  req.end(); // this makes the request
1449
1516
  }
1450
1517
 
1451
- function push(localDir, remote, localOptions, cmd) {
1518
+ function pushApp(localDir, remote, localOptions, cmd) {
1452
1519
  // deal with paths prefixed with ~
1453
1520
  const local = localDir.replace(/^~(?=$|\/|\\)/, os.homedir());
1454
1521
  const stat = fs.existsSync(path.resolve(local)) ? fs.lstatSync(local) : null;
@@ -1461,7 +1528,7 @@ function push(localDir, remote, localOptions, cmd) {
1461
1528
  return tarzip.stdout;
1462
1529
  };
1463
1530
 
1464
- exec(['tar', 'zxvf', '-', '-C', remote], localOptions, cmd);
1531
+ execApp(['tar', 'zxvf', '-', '-C', remote], localOptions, cmd);
1465
1532
  } else {
1466
1533
  if (local === '-') {
1467
1534
  localOptions._stdin = process.stdin;
@@ -1477,7 +1544,7 @@ function push(localDir, remote, localOptions, cmd) {
1477
1544
  remote = remote + '/' + path.basename(local); // do not use path.join as we want this to be a UNIX path
1478
1545
  }
1479
1546
 
1480
- exec(['bash', '-c', `cat - > "${remote}"`], localOptions, cmd);
1547
+ execApp(['bash', '-c', `cat - > "${remote}"`], localOptions, cmd);
1481
1548
  }
1482
1549
  }
1483
1550
 
@@ -1489,7 +1556,7 @@ function pull(remote, local, localOptions, cmd) {
1489
1556
  unzip.pipe(untar);
1490
1557
  localOptions._stdout = unzip;
1491
1558
 
1492
- exec(['tar', 'zcf', '-', '-C', remote, '.'], localOptions, cmd);
1559
+ execApp(['tar', 'zcf', '-', '-C', remote, '.'], localOptions, cmd);
1493
1560
  } else {
1494
1561
  if (fs.existsSync(local) && fs.lstatSync(local).isDirectory()) {
1495
1562
  local = path.join(local, path.basename(remote));
@@ -1502,7 +1569,7 @@ function pull(remote, local, localOptions, cmd) {
1502
1569
 
1503
1570
  localOptions._stdout.on('error', function (error) { exit('Error pulling', error); });
1504
1571
 
1505
- exec(['cat', remote], localOptions, cmd);
1572
+ execApp(['cat', remote], localOptions, cmd);
1506
1573
  }
1507
1574
  }
1508
1575
 
@@ -1513,10 +1580,10 @@ function init(localOptions, cmd) {
1513
1580
 
1514
1581
  const manifestTemplateFilename = options.appstore ? 'CloudronManifest.appstore.json.ejs' : 'CloudronManifest.json.ejs';
1515
1582
 
1516
- const manifestTemplate = fs.readFileSync(path.join(__dirname, 'templates/', manifestTemplateFilename), 'utf8');
1517
- const dockerfileTemplate = fs.readFileSync(path.join(__dirname, 'templates/', 'Dockerfile.ejs'), 'utf8');
1518
- const dockerignoreTemplate = fs.readFileSync(path.join(__dirname, 'templates/', 'dockerignore.ejs'), 'utf8');
1519
- const startShTemplate = fs.readFileSync(path.join(__dirname, 'templates/', 'start.sh.ejs'), 'utf8');
1583
+ const manifestTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', manifestTemplateFilename), 'utf8');
1584
+ const dockerfileTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'Dockerfile.ejs'), 'utf8');
1585
+ const dockerignoreTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'dockerignore.ejs'), 'utf8');
1586
+ const startShTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'start.sh.ejs'), 'utf8');
1520
1587
 
1521
1588
  const data = {
1522
1589
  version: '0.1.0',
@@ -1549,7 +1616,7 @@ function init(localOptions, cmd) {
1549
1616
  fs.chmodSync('start.sh', 0o0775);
1550
1617
  }
1551
1618
 
1552
- fs.copyFileSync(path.join(__dirname, 'templates/', 'logo.png'), 'logo.png');
1619
+ fs.copyFileSync(path.join(import.meta.dirname, 'templates/', 'logo.png'), 'logo.png');
1553
1620
 
1554
1621
  if (options.appstore) {
1555
1622
  if (!fs.existsSync('.gitignore')) fs.writeFileSync('.gitignore', 'node_modules/\n', 'utf8');
@@ -1658,3 +1725,38 @@ async function envGet(envName, localOptions, cmd) {
1658
1725
  exit(error);
1659
1726
  }
1660
1727
  }
1728
+
1729
+ export default {
1730
+ list,
1731
+ login,
1732
+ logout,
1733
+ open: openApp,
1734
+ install,
1735
+ setLocation,
1736
+ debug: debugApp,
1737
+ update,
1738
+ uninstall,
1739
+ logs,
1740
+ exec: execApp,
1741
+ status,
1742
+ inspect,
1743
+ pull,
1744
+ push: pushApp,
1745
+ restart,
1746
+ start,
1747
+ stop,
1748
+ repair,
1749
+ init,
1750
+ restore,
1751
+ importApp,
1752
+ exportApp,
1753
+ clone,
1754
+ backupCreate,
1755
+ backupList,
1756
+ cancel,
1757
+
1758
+ envGet,
1759
+ envSet,
1760
+ envList,
1761
+ envUnset
1762
+ };