cloudron 5.14.10 → 5.16.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 +44 -0
- package/bin/cloudron +3 -1
- package/package.json +9 -9
- package/src/actions.js +15 -2
- package/src/backup-tools.js +22 -8
- package/src/build-actions.js +23 -17
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
[5.14.0]
|
|
3
|
+
* Replace superagent module with own implementation.
|
|
4
|
+
|
|
5
|
+
[5.14.1]
|
|
6
|
+
* fix usage of request.attach
|
|
7
|
+
|
|
8
|
+
[5.14.2]
|
|
9
|
+
* superagent does not return stream anymore
|
|
10
|
+
|
|
11
|
+
[5.14.3]
|
|
12
|
+
* Handle case where info subcommand reveals no published versions
|
|
13
|
+
* Add better error message if a URL with protocol was set as build repository
|
|
14
|
+
|
|
15
|
+
[5.14.4]
|
|
16
|
+
* fixes crash in safetydance when execSync returns null
|
|
17
|
+
|
|
18
|
+
[5.14.5]
|
|
19
|
+
* case insensitive compare
|
|
20
|
+
|
|
21
|
+
[5.14.6]
|
|
22
|
+
* Fix typo in appstore notifications
|
|
23
|
+
|
|
24
|
+
[5.14.7]
|
|
25
|
+
* Update base image in template
|
|
26
|
+
|
|
27
|
+
[5.14.8]
|
|
28
|
+
* Always send a request body on POST
|
|
29
|
+
* Ensure we send an object on uninstall to enforce the expected content-type
|
|
30
|
+
|
|
31
|
+
[5.14.9]
|
|
32
|
+
* check image in docker registry instead of docker hub web page since docker hub is down
|
|
33
|
+
|
|
34
|
+
[5.14.10]
|
|
35
|
+
* Use custom namespace for our packages - @cloudron/manifest-format , @cloudron/superagent
|
|
36
|
+
* add install `--memory-limit`
|
|
37
|
+
|
|
38
|
+
[5.15.0]
|
|
39
|
+
* decrypt-dir: option to disable filename decryption
|
|
40
|
+
* Add `--env` to install
|
|
41
|
+
|
|
42
|
+
[5.16.0]
|
|
43
|
+
* build: output the selected Dockerfile name
|
|
44
|
+
|
package/bin/cloudron
CHANGED
|
@@ -56,6 +56,7 @@ backupCommand.command('decrypt <infile> <outfile>')
|
|
|
56
56
|
backupCommand.command('decrypt-dir <indir> <outdir>')
|
|
57
57
|
.description('Decrypt an encrypted directory')
|
|
58
58
|
.option('--password <password>', 'password')
|
|
59
|
+
.option('--no-decrypt-filenames', 'Decrypt filenames [false]')
|
|
59
60
|
.action(backupTools.decryptDir);
|
|
60
61
|
|
|
61
62
|
backupCommand.command('decrypt-filename <path>')
|
|
@@ -111,7 +112,7 @@ envCommand.command('list')
|
|
|
111
112
|
.action(actions.envList);
|
|
112
113
|
|
|
113
114
|
envCommand.command('set <KEY=value...>')
|
|
114
|
-
.description('Set environment variables')
|
|
115
|
+
.description('Set environment variables. e.g X=1 Y=2')
|
|
115
116
|
.option('--app <id>', 'App id')
|
|
116
117
|
.action(actions.envSet);
|
|
117
118
|
|
|
@@ -173,6 +174,7 @@ program.command('install')
|
|
|
173
174
|
.option('--no-sso', 'Disable Cloudron SSO [false]')
|
|
174
175
|
.option('--debug [cmd...]', 'Enable debug mode', false)
|
|
175
176
|
.option('--readonly', 'Mount filesystem readonly. Default is read/write in debug mode.')
|
|
177
|
+
.option('--env <KEY=value...>', 'Set environment variables. e.g X=1 Y=2')
|
|
176
178
|
.action(actions.install);
|
|
177
179
|
|
|
178
180
|
program.command('list')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.16.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
@@ -17,25 +17,25 @@
|
|
|
17
17
|
},
|
|
18
18
|
"author": "Cloudron Developers <support@cloudron.io>",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@cloudron/manifest-format": "^5.
|
|
20
|
+
"@cloudron/manifest-format": "^5.28.0",
|
|
21
21
|
"@cloudron/superagent": "^1.0.0",
|
|
22
22
|
"commander": "^13.1.0",
|
|
23
|
-
"debug": "^4.4.
|
|
23
|
+
"debug": "^4.4.1",
|
|
24
24
|
"easy-table": "^1.2.0",
|
|
25
25
|
"ejs": "^3.1.10",
|
|
26
|
-
"eventsource": "^3.0.
|
|
26
|
+
"eventsource": "^3.0.7",
|
|
27
27
|
"micromatch": "^4.0.8",
|
|
28
|
-
"open": "^10.
|
|
28
|
+
"open": "^10.2.0",
|
|
29
29
|
"safetydance": "^2.5.1",
|
|
30
|
-
"tar-fs": "^3.0
|
|
30
|
+
"tar-fs": "^3.1.0"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">= 18.x.x"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@eslint/js": "^9.
|
|
37
|
-
"eslint": "^9.
|
|
36
|
+
"@eslint/js": "^9.32.0",
|
|
37
|
+
"eslint": "^9.32.0",
|
|
38
38
|
"expect.js": "^0.3.1",
|
|
39
|
-
"mocha": "^11.1
|
|
39
|
+
"mocha": "^11.7.1"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/actions.js
CHANGED
|
@@ -643,6 +643,16 @@ async function install(localOptions, cmd) {
|
|
|
643
643
|
|
|
644
644
|
for (const port in ports) console.log(`Port ${port}: ${ports[port]}`);
|
|
645
645
|
|
|
646
|
+
// environment variables
|
|
647
|
+
const env = {};
|
|
648
|
+
if (options.env) {
|
|
649
|
+
options.env.forEach(envVar => {
|
|
650
|
+
const m = envVar.match(/(.*?)=(.*)/);
|
|
651
|
+
if (!m) return exit(`Expecting KEY=VALUE pattern. Got ${envVar}`);
|
|
652
|
+
env[m[1]] = m[2];
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
646
656
|
const memoryLimit = options.memoryLimit ? parseMemoryLimit(options.memoryLimit) : 0;
|
|
647
657
|
|
|
648
658
|
const data = {
|
|
@@ -655,7 +665,8 @@ async function install(localOptions, cmd) {
|
|
|
655
665
|
aliasDomains,
|
|
656
666
|
ports,
|
|
657
667
|
accessRestriction: null,
|
|
658
|
-
memoryLimit
|
|
668
|
+
memoryLimit,
|
|
669
|
+
env
|
|
659
670
|
};
|
|
660
671
|
|
|
661
672
|
// the sso only applies for apps which allow optional sso
|
|
@@ -1227,10 +1238,12 @@ async function importApp(localOptions, cmd) {
|
|
|
1227
1238
|
}
|
|
1228
1239
|
|
|
1229
1240
|
if (backupKey) data.backupConfig.key = backupKey;
|
|
1241
|
+
} else {
|
|
1242
|
+
data.inPlace = true;
|
|
1230
1243
|
}
|
|
1231
1244
|
|
|
1232
1245
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/import`, options);
|
|
1233
|
-
const response = await request.send(
|
|
1246
|
+
const response = await request.send(data);
|
|
1234
1247
|
if (response.status !== 202) return exit(`Failed to import app: ${requestError(response)}`);
|
|
1235
1248
|
|
|
1236
1249
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
package/src/backup-tools.js
CHANGED
|
@@ -262,11 +262,13 @@ async function decryptDir(inDir, outDir, options) {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
const encryption = aesKeysFromPassword(options.password);
|
|
265
|
+
const decryptFilenames = options.decryptFilenames;
|
|
265
266
|
|
|
266
267
|
const inDirAbs = path.resolve(process.cwd(), inDir);
|
|
267
268
|
const outDirAbs = path.resolve(process.cwd(), outDir);
|
|
268
269
|
|
|
269
270
|
const tbd = [ '' ]; // only has paths relative to inDirAbs
|
|
271
|
+
let errorCount = 0;
|
|
270
272
|
while (true) {
|
|
271
273
|
const cur = tbd.pop();
|
|
272
274
|
const entries = fs.readdirSync(path.join(inDirAbs, cur), { withFileTypes: true });
|
|
@@ -279,29 +281,41 @@ async function decryptDir(inDir, outDir, options) {
|
|
|
279
281
|
continue;
|
|
280
282
|
}
|
|
281
283
|
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (
|
|
284
|
+
const fullPath = path.join(cur, entry.name);
|
|
285
|
+
|
|
286
|
+
let destPath = fullPath;
|
|
287
|
+
if (decryptFilenames) {
|
|
288
|
+
const { error, decryptedFilePath } = decryptFilePath(fullPath, encryption);
|
|
289
|
+
if (error) {
|
|
290
|
+
console.warn(`Could not decrypt filename: ${error.message}`);
|
|
291
|
+
errorCount++;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
destPath = decryptedFilePath;
|
|
295
|
+
}
|
|
286
296
|
|
|
287
|
-
console.log(`Decrypting ${
|
|
297
|
+
console.log(`Decrypting ${destPath}`);
|
|
288
298
|
|
|
289
299
|
const infile = path.join(inDirAbs, cur, entry.name);
|
|
290
300
|
const inStream = fs.createReadStream(infile);
|
|
291
|
-
fs.mkdirSync(path.dirname(path.join(outDirAbs,
|
|
292
|
-
const outfile = path.join(outDirAbs,
|
|
301
|
+
fs.mkdirSync(path.dirname(path.join(outDirAbs, destPath)), { recursive: true });
|
|
302
|
+
const outfile = path.join(outDirAbs, destPath);
|
|
293
303
|
const outStream = fs.createWriteStream(outfile);
|
|
294
304
|
const decryptStream = new DecryptStream(encryption);
|
|
295
305
|
|
|
296
306
|
const [decryptError] = await safe(stream.pipeline(inStream, decryptStream, outStream));
|
|
297
307
|
if (decryptError) {
|
|
298
308
|
safe.fs.rmSync(outfile);
|
|
299
|
-
|
|
309
|
+
console.warn(`Could not decrypt ${infile}: ${decryptError.message}`);
|
|
310
|
+
++errorCount;
|
|
300
311
|
}
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
if (tbd.length === 0) break;
|
|
304
315
|
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if (errorCount) exit(`Failed to decrypt ${errorCount} files`);
|
|
305
319
|
}
|
|
306
320
|
|
|
307
321
|
async function decryptFilename(filePath, options) {
|
package/src/build-actions.js
CHANGED
|
@@ -25,7 +25,7 @@ const assert = require('assert'),
|
|
|
25
25
|
stream = require('stream/promises'),
|
|
26
26
|
superagent = require('@cloudron/superagent'),
|
|
27
27
|
tar = require('tar-fs'),
|
|
28
|
-
|
|
28
|
+
{ URL } = require('url');
|
|
29
29
|
|
|
30
30
|
function requestError(response) {
|
|
31
31
|
if (response.status === 401 || response.status === 403) return 'Invalid token. Use cloudron build login again.';
|
|
@@ -86,11 +86,16 @@ async function followBuildLog(buildId, raw) {
|
|
|
86
86
|
assert.strictEqual(typeof buildId, 'string');
|
|
87
87
|
assert.strictEqual(typeof raw, 'boolean');
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
let tmp
|
|
91
|
-
|
|
89
|
+
const rawUrl = config.getBuildServiceConfig().url;
|
|
90
|
+
let tmp;
|
|
91
|
+
try {
|
|
92
|
+
tmp = new URL(rawUrl);
|
|
93
|
+
} catch {
|
|
94
|
+
tmp = new URL(`http://${rawUrl}`); // if it fails, prepend http://
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const es = new EventSource(`${tmp.origin}/api/v1/builds/${buildId}/logstream?accessToken=${config.getBuildServiceConfig().token}`);
|
|
92
98
|
|
|
93
|
-
const es = new EventSource(`${tmp.href}api/v1/builds/${buildId}/logstream?accessToken=${config.getBuildServiceConfig().token}`);
|
|
94
99
|
let prevId = null, prevWasStatus = false;
|
|
95
100
|
|
|
96
101
|
es.addEventListener('message', function (e) {
|
|
@@ -195,16 +200,16 @@ async function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
const dockerImage = `${appConfig.repository}:${tag}`;
|
|
198
|
-
|
|
199
|
-
console.log('Building locally as %s', dockerImage);
|
|
200
|
-
console.log();
|
|
201
|
-
|
|
202
203
|
const buildArgsCmdLine = options.buildArg.map(function (a) { return `--build-arg "${a}"`; }).join(' ');
|
|
203
204
|
|
|
204
205
|
let dockerfile = 'Dockerfile';
|
|
205
206
|
if (options.file) dockerfile = options.file;
|
|
206
207
|
else if (fs.existsSync(`${sourceDir}/Dockerfile.cloudron`)) dockerfile = 'Dockerfile.cloudron';
|
|
207
208
|
else if (fs.existsSync(`${sourceDir}/cloudron/Dockerfile`)) dockerfile = 'cloudron/Dockerfile';
|
|
209
|
+
|
|
210
|
+
console.log(`Building ${dockerfile} locally as ${dockerImage}`);
|
|
211
|
+
console.log();
|
|
212
|
+
|
|
208
213
|
execSync(`docker build ${!options.cache ? '--no-cache' : ''} -t ${dockerImage} -f ${dockerfile} ${buildArgsCmdLine} ${sourceDir}`, { stdio: 'inherit' });
|
|
209
214
|
|
|
210
215
|
if (options.push) {
|
|
@@ -240,13 +245,20 @@ async function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
240
245
|
|
|
241
246
|
const dockerImage = `${appConfig.repository}:${tag}`;
|
|
242
247
|
|
|
243
|
-
|
|
248
|
+
let dockerfile = 'Dockerfile';
|
|
249
|
+
if (options.file) dockerfile = options.file;
|
|
250
|
+
else if (fs.existsSync(`${sourceDir}/Dockerfile.cloudron`)) dockerfile = 'Dockerfile.cloudron';
|
|
251
|
+
else if (fs.existsSync(`${sourceDir}/cloudron/Dockerfile`)) dockerfile = 'cloudron/Dockerfile';
|
|
252
|
+
|
|
253
|
+
const buildServiceConfig = config.getBuildServiceConfig();
|
|
254
|
+
|
|
255
|
+
console.log(`Building ${dockerfile} as ${dockerImage}`);
|
|
244
256
|
|
|
245
257
|
const sourceArchiveFilePath = path.join(os.tmpdir(), path.basename(sourceDir) + '.tar.gz');
|
|
246
258
|
const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
|
|
247
259
|
const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
|
|
248
260
|
|
|
249
|
-
console.log(
|
|
261
|
+
console.log(`Uploading source tarball to ${buildServiceConfig.url} ...`);
|
|
250
262
|
|
|
251
263
|
const tarStream = tar.pack(sourceDir, {
|
|
252
264
|
ignore: function (name) {
|
|
@@ -258,11 +270,6 @@ async function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
258
270
|
const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
|
|
259
271
|
if (tarError) return exit(`Could not tar: ${tarError.message}`);
|
|
260
272
|
|
|
261
|
-
let dockerfile = 'Dockerfile';
|
|
262
|
-
if (options.file) dockerfile = options.file;
|
|
263
|
-
else if (fs.existsSync(`${sourceDir}/Dockerfile.cloudron`)) dockerfile = 'Dockerfile.cloudron';
|
|
264
|
-
else if (fs.existsSync(`${sourceDir}/cloudron/Dockerfile`)) dockerfile = 'cloudron/Dockerfile';
|
|
265
|
-
|
|
266
273
|
const buildArgsObject = {};
|
|
267
274
|
options.buildArg.forEach(function (a) {
|
|
268
275
|
const key = a.slice(0, a.indexOf('='));
|
|
@@ -270,7 +277,6 @@ async function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
270
277
|
buildArgsObject[key] = value;
|
|
271
278
|
});
|
|
272
279
|
|
|
273
|
-
const buildServiceConfig = config.getBuildServiceConfig();
|
|
274
280
|
const response = await superagent.post(`${buildServiceConfig.url}/api/v1/builds`)
|
|
275
281
|
.query({ accessToken: buildServiceConfig.token, noCache: !options.cache, dockerfile: dockerfile, noPush: !options.push })
|
|
276
282
|
.field('dockerImageRepo', appConfig.repository)
|