cloudron 8.0.0 → 8.0.2

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.
Files changed (86) hide show
  1. package/bin/cloudron +13 -2
  2. package/package.json +1 -1
  3. package/src/actions.js +105 -34
  4. package/src/helper.js +5 -3
  5. package/src/templates/flask/CloudronManifest.json +25 -0
  6. package/src/templates/flask/Dockerfile +16 -0
  7. package/src/templates/flask/app/__init__.py +5 -0
  8. package/src/templates/flask/app/views.py +5 -0
  9. package/src/templates/flask/dockerignore +5 -0
  10. package/src/templates/flask/requirements.txt +1 -0
  11. package/src/templates/flask/run.py +4 -0
  12. package/src/templates/flask/start.sh +8 -0
  13. package/src/templates/flask/supervisor/uwsgi.conf +12 -0
  14. package/src/templates/flask/template.json +3 -0
  15. package/src/templates/flask/uwsgi.ini +6 -0
  16. package/src/templates/lamp/CloudronManifest.json +26 -0
  17. package/src/templates/lamp/Dockerfile +27 -0
  18. package/src/templates/lamp/apache/app.conf +17 -0
  19. package/src/templates/lamp/apache/mpm_prefork.conf +7 -0
  20. package/src/templates/lamp/dockerignore +4 -0
  21. package/src/templates/lamp/index.php +18 -0
  22. package/src/templates/lamp/start.sh +9 -0
  23. package/src/templates/lamp/template.json +3 -0
  24. package/src/templates/lamp-composer/CloudronManifest.json +26 -0
  25. package/src/templates/lamp-composer/Dockerfile +30 -0
  26. package/src/templates/lamp-composer/apache/app.conf +17 -0
  27. package/src/templates/lamp-composer/apache/mpm_prefork.conf +7 -0
  28. package/src/templates/lamp-composer/composer.json +8 -0
  29. package/src/templates/lamp-composer/dockerignore +5 -0
  30. package/src/templates/lamp-composer/public/index.php +20 -0
  31. package/src/templates/lamp-composer/start.sh +9 -0
  32. package/src/templates/lamp-composer/template.json +3 -0
  33. package/src/templates/lemp/CloudronManifest.json +26 -0
  34. package/src/templates/lemp/Dockerfile +27 -0
  35. package/src/templates/lemp/dockerignore +4 -0
  36. package/src/templates/lemp/index.php +18 -0
  37. package/src/templates/lemp/nginx/app.conf +22 -0
  38. package/src/templates/lemp/start.sh +11 -0
  39. package/src/templates/lemp/template.json +3 -0
  40. package/src/templates/nextjs/CloudronManifest.json +25 -0
  41. package/src/templates/nextjs/Dockerfile +14 -0
  42. package/src/templates/nextjs/app/layout.js +12 -0
  43. package/src/templates/nextjs/app/page.js +7 -0
  44. package/src/templates/nextjs/dockerignore +5 -0
  45. package/src/templates/nextjs/next.config.mjs +6 -0
  46. package/src/templates/nextjs/package.json +15 -0
  47. package/src/templates/nextjs/start.sh +9 -0
  48. package/src/templates/nextjs/template.json +3 -0
  49. package/src/templates/nodejs/CloudronManifest.json +25 -0
  50. package/src/templates/nodejs/Dockerfile +10 -0
  51. package/src/templates/nodejs/dockerignore +4 -0
  52. package/src/templates/nodejs/package.json +11 -0
  53. package/src/templates/nodejs/server.js +12 -0
  54. package/src/templates/nodejs/start.sh +9 -0
  55. package/src/templates/nodejs/template.json +3 -0
  56. package/src/templates/nodejs-mongodb/CloudronManifest.json +26 -0
  57. package/src/templates/nodejs-mongodb/Dockerfile +10 -0
  58. package/src/templates/nodejs-mongodb/dockerignore +4 -0
  59. package/src/templates/nodejs-mongodb/package.json +14 -0
  60. package/src/templates/nodejs-mongodb/server.js +24 -0
  61. package/src/templates/nodejs-mongodb/start.sh +9 -0
  62. package/src/templates/nodejs-mongodb/template.json +3 -0
  63. package/src/templates/rails/CloudronManifest.json +26 -0
  64. package/src/templates/rails/Dockerfile +16 -0
  65. package/src/templates/rails/Gemfile +5 -0
  66. package/src/templates/rails/config/database.yml +4 -0
  67. package/src/templates/rails/config/puma.rb +9 -0
  68. package/src/templates/rails/dockerignore +7 -0
  69. package/src/templates/rails/public/index.html +12 -0
  70. package/src/templates/rails/start.sh +14 -0
  71. package/src/templates/rails/supervisor/puma.conf +12 -0
  72. package/src/templates/rails/template.json +3 -0
  73. package/src/templates/static/CloudronManifest.json +25 -0
  74. package/src/templates/static/Dockerfile +17 -0
  75. package/src/templates/static/apache/app.conf +13 -0
  76. package/src/templates/static/apache/mpm_prefork.conf +7 -0
  77. package/src/templates/static/dockerignore +3 -0
  78. package/src/templates/static/public/index.html +11 -0
  79. package/src/templates/static/start.sh +7 -0
  80. package/src/templates/static/template.json +3 -0
  81. package/test/test.js +1 -1
  82. /package/src/templates/{CloudronManifest.appstore.json.ejs → default/CloudronManifest.json.ejs} +0 -0
  83. /package/src/templates/{CloudronManifest.json.ejs → default/CloudronManifest.minimal.json.ejs} +0 -0
  84. /package/src/templates/{Dockerfile.ejs → default/Dockerfile.ejs} +0 -0
  85. /package/src/templates/{dockerignore.ejs → default/dockerignore.ejs} +0 -0
  86. /package/src/templates/{start.sh.ejs → default/start.sh.ejs} +0 -0
package/bin/cloudron CHANGED
@@ -91,7 +91,7 @@ const buildCommand = program.command('build').description('Build using the build
91
91
  .option('--build-service-url <url>', 'Build service URL')
92
92
  .option('--build-service-token <token>', 'Build service token');
93
93
 
94
- buildCommand.command('build', { isDefault: true })
94
+ const buildBuildCommand = buildCommand.command('build', { isDefault: true })
95
95
  .description('Build an app')
96
96
  .option('--build-arg <namevalue>', 'Build arg passed to docker. Can be used multiple times', collectBuildArgs, [])
97
97
  .option('-f, --file <dockerfile>', 'Name of the Dockerfile')
@@ -137,6 +137,11 @@ buildCommand.command('status')
137
137
  .option('--id <buildid>', 'Build ID')
138
138
  .action(buildActions.status);
139
139
 
140
+ buildCommand.addHelpText('after', (ctx) => {
141
+ const header = '\nDefault subcommand `build` (same as `cloudron build build`; used when no subcommand is given):\n\n';
142
+ return header + buildBuildCommand.helpInformation({ error: ctx.error });
143
+ });
144
+
140
145
  backupCommand.command('create')
141
146
  .description('Create new app backup')
142
147
  .option('--site <siteid>', 'App id')
@@ -259,7 +264,9 @@ program.command('inspect')
259
264
 
260
265
  program.command('init')
261
266
  .description('Creates a new CloudronManifest.json and Dockerfile')
262
- .option('--appstore', 'Appstore template')
267
+ .option('--minimal', 'Minimal template')
268
+ .option('--template <name>', 'Use a project template (use --list-templates to see all)')
269
+ .option('--list-templates', 'List available templates')
263
270
  .action(actions.init);
264
271
 
265
272
  program.command('install')
@@ -276,6 +283,8 @@ program.command('install')
276
283
  .option('--debug [cmd...]', 'Enable debug mode', false)
277
284
  .option('--readonly', 'Mount filesystem readonly. Default is read/write in debug mode.')
278
285
  .option('--env <KEY=value...>', 'Set environment variables. e.g X=1 Y=2')
286
+ .option('--build-arg <namevalue>', 'Build arg passed to docker. Can be used multiple times', collectBuildArgs, [])
287
+ .option('-f, --file <dockerfile>', 'Name of the Dockerfile')
279
288
  .action(actions.install);
280
289
 
281
290
  program.command('list')
@@ -400,6 +409,8 @@ program.command('update')
400
409
  .option('--image <docker image>', 'Docker image')
401
410
  .option('--no-backup', 'Skip backup [false]')
402
411
  .option('--no-force', 'Match appstore id and manifest id before updating', true)
412
+ .option('--build-arg <namevalue>', 'Build arg passed to docker. Can be used multiple times', collectBuildArgs, [])
413
+ .option('-f, --file <dockerfile>', 'Name of the Dockerfile')
403
414
  .action(actions.update);
404
415
 
405
416
  const versionsCommand = program.command('versions').description('Commands for publishing community packages');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudron",
3
- "version": "8.0.0",
3
+ "version": "8.0.2",
4
4
  "license": "MIT",
5
5
  "description": "Cloudron Commandline Tool",
6
6
  "type": "module",
package/src/actions.js CHANGED
@@ -224,7 +224,7 @@ async function waitForFinishInstallation(appId, taskId, options) {
224
224
 
225
225
  if (response.body.installationState !== 'installed') throw new Error(`Installation failed: ${response.body.error ? response.body.error.message : ''}`);
226
226
 
227
- await waitForHealthy(appId, options);
227
+ if (!options.debug) await waitForHealthy(appId, options);
228
228
  }
229
229
 
230
230
  async function waitForFinishBackup(appId, taskId, options) {
@@ -347,6 +347,7 @@ async function login(adminFqdn, localOptions, cmd) {
347
347
 
348
348
  if (semver.gte(response.body.version, '9.1.0')) {
349
349
  token = await performOidcLogin(adminFqdn, { rejectUnauthorized });
350
+ if (!token) process.exit(1);
350
351
  } else {
351
352
  const username = options.username || await readline.question('Username: ', {});
352
353
  const password = options.password || await readline.question('Password: ', { noEchoBack: true });
@@ -682,7 +683,6 @@ async function install(localOptions, cmd) {
682
683
  cmd: debugCmd
683
684
  };
684
685
  data.memoryLimit = -1;
685
- options.wait = false; // in debug mode, health check never succeeds
686
686
  }
687
687
 
688
688
  if (!options.appstoreId && !options.versionsUrl && manifest && manifest.icon) {
@@ -711,6 +711,10 @@ async function install(localOptions, cmd) {
711
711
  if (data.sso !== undefined) request.field('sso', String(data.sso));
712
712
  if (data.debugMode) request.field('debugMode', JSON.stringify(data.debugMode));
713
713
  if (data.icon) request.field('icon', data.icon);
714
+ const buildConfig = {};
715
+ if (options.buildArg && options.buildArg.length) buildConfig.buildArgs = options.buildArg;
716
+ if (options.file) buildConfig.dockerfileName = options.file;
717
+ if (Object.keys(buildConfig).length) request.field('buildConfig', JSON.stringify(buildConfig));
714
718
  request.attach('sourceArchive', sourceArchiveFilePath);
715
719
 
716
720
  response = await request;
@@ -902,6 +906,10 @@ async function update(localOptions, cmd) {
902
906
  request.field('skipNotification', String(data.skipNotification));
903
907
  request.field('force', String(data.force));
904
908
  if (data.icon) request.field('icon', data.icon);
909
+ const buildConfig = {};
910
+ if (options.buildArg && options.buildArg.length) buildConfig.buildArgs = options.buildArg;
911
+ if (options.file) buildConfig.dockerfileName = options.file;
912
+ if (Object.keys(buildConfig).length) request.field('buildConfig', JSON.stringify(buildConfig));
905
913
  request.attach('sourceArchive', sourceArchiveFilePath);
906
914
 
907
915
  response = await request;
@@ -1503,7 +1511,6 @@ function runExecSession(appId, execId, { stdin, stdout, stderr, tty }, options)
1503
1511
  if (stdout !== process.stdout && typeof stdout.end === 'function') {
1504
1512
  stdout.on('close', () => resolve());
1505
1513
  stdout.end();
1506
- stdout.resume(); // drain readable side of Duplex/Transform streams so autoDestroy emits 'close'
1507
1514
  } else {
1508
1515
  setImmediate(resolve);
1509
1516
  }
@@ -1911,52 +1918,116 @@ async function syncPull(remoteDir, localDir, localOptions, cmd) {
1911
1918
  }
1912
1919
  }
1913
1920
 
1921
+ function listTemplates() {
1922
+ const templatesDir = path.join(import.meta.dirname, 'templates');
1923
+ const entries = fs.readdirSync(templatesDir, { withFileTypes: true });
1924
+
1925
+ console.log();
1926
+ console.log('Available templates:');
1927
+ console.log();
1928
+
1929
+ for (const entry of entries) {
1930
+ if (!entry.isDirectory()) continue;
1931
+
1932
+ const metaPath = path.join(templatesDir, entry.name, 'template.json');
1933
+ if (!fs.existsSync(metaPath)) continue;
1934
+
1935
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
1936
+ console.log(` ${entry.name.padEnd(15)} ${meta.description}`);
1937
+ }
1938
+
1939
+ console.log();
1940
+ }
1941
+
1942
+ function copyTemplateDir(srcDir, destDir) {
1943
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
1944
+
1945
+ for (const entry of entries) {
1946
+ const srcPath = path.join(srcDir, entry.name);
1947
+
1948
+ if (entry.name === 'template.json') continue;
1949
+
1950
+ const destName = entry.name === 'dockerignore' ? '.dockerignore' : entry.name;
1951
+ const destPath = path.join(destDir, destName);
1952
+
1953
+ if (entry.isDirectory()) {
1954
+ if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
1955
+ copyTemplateDir(srcPath, destPath);
1956
+ } else {
1957
+ if (fs.existsSync(destPath)) {
1958
+ console.log(`${destName} already exists, skipping`);
1959
+ continue;
1960
+ }
1961
+
1962
+ fs.copyFileSync(srcPath, destPath);
1963
+
1964
+ if (destName === 'start.sh') fs.chmodSync(destPath, 0o0775);
1965
+ }
1966
+ }
1967
+ }
1968
+
1914
1969
  function init(localOptions, cmd) {
1915
1970
  const options = cmd.optsWithGlobals();
1971
+
1972
+ if (options.listTemplates) return listTemplates();
1973
+
1974
+ if (options.template && options.minimal) return exit('--template and --minimal are mutually exclusive');
1975
+
1916
1976
  const manifestFilePath = locateManifest();
1917
1977
  if (manifestFilePath && path.dirname(manifestFilePath) === process.cwd()) return exit('CloudronManifest.json already exists in current directory');
1918
1978
 
1919
- const manifestTemplateFilename = options.appstore ? 'CloudronManifest.appstore.json.ejs' : 'CloudronManifest.json.ejs';
1979
+ if (options.template) {
1980
+ const templateDir = path.join(import.meta.dirname, 'templates', options.template);
1981
+ const metaPath = path.join(templateDir, 'template.json');
1982
+ if (!fs.existsSync(metaPath)) {
1983
+ console.log(`Unknown template "${options.template}".`);
1984
+ return listTemplates();
1985
+ }
1920
1986
 
1921
- const manifestTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', manifestTemplateFilename), 'utf8');
1922
- const dockerfileTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'Dockerfile.ejs'), 'utf8');
1923
- const dockerignoreTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'dockerignore.ejs'), 'utf8');
1924
- const startShTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'start.sh.ejs'), 'utf8');
1987
+ copyTemplateDir(templateDir, process.cwd());
1988
+ } else {
1989
+ const manifestTemplateFilename = !options.minimal ? 'CloudronManifest.json.ejs' : 'CloudronManifest.minimal.json.ejs';
1925
1990
 
1926
- const data = {
1927
- version: '0.1.0',
1928
- title: 'App title',
1929
- httpPort: 3000
1930
- };
1991
+ const manifestTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/default/', manifestTemplateFilename), 'utf8');
1992
+ const dockerfileTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/default/', 'Dockerfile.ejs'), 'utf8');
1993
+ const dockerignoreTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/default/', 'dockerignore.ejs'), 'utf8');
1994
+ const startShTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/default/', 'start.sh.ejs'), 'utf8');
1931
1995
 
1932
- const manifest = ejs.render(manifestTemplate, data);
1933
- fs.writeFileSync('CloudronManifest.json', manifest, 'utf8');
1996
+ const data = {
1997
+ version: '0.1.0',
1998
+ title: 'App title',
1999
+ httpPort: 3000
2000
+ };
1934
2001
 
1935
- if (fs.existsSync('Dockerfile')) {
1936
- console.log('Dockerfile already exists, skipping');
1937
- } else {
1938
- const dockerfile = ejs.render(dockerfileTemplate, data);
1939
- fs.writeFileSync('Dockerfile', dockerfile, 'utf8');
1940
- }
2002
+ const manifest = ejs.render(manifestTemplate, data);
2003
+ fs.writeFileSync('CloudronManifest.json', manifest, 'utf8');
1941
2004
 
1942
- if (fs.existsSync('.dockerignore')) {
1943
- console.log('.dockerignore already exists, skipping');
1944
- } else {
1945
- const dockerignore = ejs.render(dockerignoreTemplate, data);
1946
- fs.writeFileSync('.dockerignore', dockerignore, 'utf8');
1947
- }
2005
+ if (fs.existsSync('Dockerfile')) {
2006
+ console.log('Dockerfile already exists, skipping');
2007
+ } else {
2008
+ const dockerfile = ejs.render(dockerfileTemplate, data);
2009
+ fs.writeFileSync('Dockerfile', dockerfile, 'utf8');
2010
+ }
1948
2011
 
1949
- if (fs.existsSync('start.sh')) {
1950
- console.log('start.sh already exists, skipping');
1951
- } else {
1952
- const dockerignore = ejs.render(startShTemplate, {});
1953
- fs.writeFileSync('start.sh', dockerignore, 'utf8');
1954
- fs.chmodSync('start.sh', 0o0775);
2012
+ if (fs.existsSync('.dockerignore')) {
2013
+ console.log('.dockerignore already exists, skipping');
2014
+ } else {
2015
+ const dockerignore = ejs.render(dockerignoreTemplate, data);
2016
+ fs.writeFileSync('.dockerignore', dockerignore, 'utf8');
2017
+ }
2018
+
2019
+ if (fs.existsSync('start.sh')) {
2020
+ console.log('start.sh already exists, skipping');
2021
+ } else {
2022
+ const startSh = ejs.render(startShTemplate, {});
2023
+ fs.writeFileSync('start.sh', startSh, 'utf8');
2024
+ fs.chmodSync('start.sh', 0o0775);
2025
+ }
1955
2026
  }
1956
2027
 
1957
2028
  fs.copyFileSync(path.join(import.meta.dirname, 'templates/', 'logo.png'), 'logo.png');
1958
2029
 
1959
- if (options.appstore) {
2030
+ if (!options.minimal) {
1960
2031
  if (!fs.existsSync('.gitignore')) fs.writeFileSync('.gitignore', 'node_modules/\n', 'utf8');
1961
2032
  if (!fs.existsSync('DESCRIPTION.md')) fs.writeFileSync('DESCRIPTION.md', '## About\n\nThis app changes everything\n\n', 'utf8');
1962
2033
  if (!fs.existsSync('POSTINSTALL.md')) fs.writeFileSync('POSTINSTALL.md', 'Post installation information\n\n', 'utf8');
package/src/helper.js CHANGED
@@ -87,7 +87,7 @@ async function performOidcLogin(adminFqdn, { rejectUnauthorized = true } = {}) {
87
87
 
88
88
  const { device_code, user_code, verification_uri_complete, verification_uri, interval: pollInterval = 5 } = deviceResponse.body;
89
89
 
90
- console.log(`\nOpen ${verification_uri_complete || verification_uri} in a browser and enter code: ${user_code}\n`);
90
+ console.log(`\nOpen ${verification_uri_complete || verification_uri} in a browser and confirm code: ${user_code}\n`);
91
91
 
92
92
  // try to open browser automatically
93
93
  safe(open(verification_uri_complete || verification_uri));
@@ -119,10 +119,12 @@ async function performOidcLogin(adminFqdn, { rejectUnauthorized = true } = {}) {
119
119
  continue;
120
120
  }
121
121
 
122
- throw new Error(tokenResponse.body.error_description || error);
122
+ console.log('Login failed. Try again.');
123
+ return null;
123
124
  }
124
125
 
125
- throw new Error('Login timed out');
126
+ console.log('Login timed out. Try again.');
127
+ return null;
126
128
  }
127
129
 
128
130
  export {
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "",
3
+ "version": "0.1.0",
4
+ "upstreamVersion": "",
5
+ "minBoxVersion": "9.0.0",
6
+ "title": "",
7
+ "author": "",
8
+ "description": "file://DESCRIPTION.md",
9
+ "tagline": "",
10
+ "website": "",
11
+ "contactEmail": "",
12
+ "iconUrl": "file://logo.png",
13
+ "healthCheckPath": "/",
14
+ "mediaLinks": [],
15
+ "httpPort": 3000,
16
+ "tags": [],
17
+ "changelog": "file://CHANGELOG",
18
+ "postInstallMessage": "file://POSTINSTALL.md",
19
+ "documentationUrl": "",
20
+ "forumUrl": "",
21
+ "addons": {
22
+ "localstorage": {}
23
+ },
24
+ "manifestVersion": 2
25
+ }
@@ -0,0 +1,16 @@
1
+ FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
2
+
3
+ RUN mkdir -p /app/code
4
+ WORKDIR /app/code
5
+
6
+ COPY . /app/code
7
+
8
+ RUN pip install -r requirements.txt
9
+
10
+ ADD supervisor/* /etc/supervisor/conf.d/
11
+ RUN ln -sf /run/supervisord.log /var/log/supervisor/supervisord.log
12
+
13
+ ADD uwsgi.ini /etc/uwsgi/apps-available/flask-uwsgi.ini
14
+ RUN ln -s /etc/uwsgi/apps-available/flask-uwsgi.ini /etc/uwsgi/apps-enabled/flask-uwsgi.ini
15
+
16
+ CMD ["/app/code/start.sh"]
@@ -0,0 +1,5 @@
1
+ from flask import Flask
2
+
3
+ app = Flask(__name__)
4
+
5
+ from app import views
@@ -0,0 +1,5 @@
1
+ from app import app
2
+
3
+ @app.route("/")
4
+ def index():
5
+ return "Hello from Flask"
@@ -0,0 +1,5 @@
1
+ .git
2
+ .gitignore
3
+ .dockerignore
4
+ node_modules
5
+ __pycache__
@@ -0,0 +1 @@
1
+ flask
@@ -0,0 +1,4 @@
1
+ from app import app
2
+
3
+ if __name__ == "__main__":
4
+ app.run()
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ export FLASK_APP=run.py
6
+
7
+ echo "Starting supervisor"
8
+ exec /usr/bin/supervisord --configuration /etc/supervisor/supervisord.conf --nodaemon -i FlaskApp
@@ -0,0 +1,12 @@
1
+ [program:uwsgi]
2
+ autorestart=true
3
+ autostart=true
4
+ command=uwsgi --master --workers 2 --no-orphans --ini /etc/uwsgi/apps-enabled/flask-uwsgi.ini
5
+ stdout_logfile=/dev/stdout
6
+ stdout_logfile_maxbytes=0
7
+ stderr_logfile=/dev/stderr
8
+ stderr_logfile_maxbytes=0
9
+ killasgroup=true
10
+ stopasgroup=true
11
+ stopsignal=QUIT
12
+ priority=100
@@ -0,0 +1,3 @@
1
+ {
2
+ "description": "Python Flask app with uWSGI and Supervisor"
3
+ }
@@ -0,0 +1,6 @@
1
+ [uwsgi]
2
+ plugin = python
3
+ chdir = /app/code
4
+ uid = cloudron
5
+ gid = cloudron
6
+ http-socket = 0.0.0.0:3000
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": "",
3
+ "version": "0.1.0",
4
+ "upstreamVersion": "",
5
+ "minBoxVersion": "9.0.0",
6
+ "title": "",
7
+ "author": "",
8
+ "description": "file://DESCRIPTION.md",
9
+ "tagline": "",
10
+ "website": "",
11
+ "contactEmail": "",
12
+ "iconUrl": "file://logo.png",
13
+ "healthCheckPath": "/",
14
+ "mediaLinks": [],
15
+ "httpPort": 3000,
16
+ "tags": [],
17
+ "changelog": "file://CHANGELOG",
18
+ "postInstallMessage": "file://POSTINSTALL.md",
19
+ "documentationUrl": "",
20
+ "forumUrl": "",
21
+ "addons": {
22
+ "localstorage": {},
23
+ "mysql": {}
24
+ },
25
+ "manifestVersion": 2
26
+ }
@@ -0,0 +1,27 @@
1
+ FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
2
+
3
+ RUN mkdir -p /app/code
4
+ WORKDIR /app/code
5
+
6
+ RUN rm /etc/apache2/sites-enabled/*
7
+ RUN sed -e 's,^ErrorLog.*,ErrorLog "|/bin/cat",' -i /etc/apache2/apache2.conf
8
+ COPY apache/mpm_prefork.conf /etc/apache2/mods-available/mpm_prefork.conf
9
+
10
+ RUN a2disconf other-vhosts-access-log
11
+ COPY apache/app.conf /etc/apache2/sites-enabled/app.conf
12
+ RUN echo "Listen 3000" > /etc/apache2/ports.conf
13
+
14
+ RUN a2enmod php8.3
15
+ RUN crudini --set /etc/php/8.3/apache2/php.ini PHP upload_max_filesize 256M && \
16
+ crudini --set /etc/php/8.3/apache2/php.ini PHP upload_max_size 256M && \
17
+ crudini --set /etc/php/8.3/apache2/php.ini PHP post_max_size 256M && \
18
+ crudini --set /etc/php/8.3/apache2/php.ini PHP memory_limit 256M && \
19
+ crudini --set /etc/php/8.3/apache2/php.ini PHP max_execution_time 200 && \
20
+ crudini --set /etc/php/8.3/apache2/php.ini Session session.save_path /run/app/sessions && \
21
+ crudini --set /etc/php/8.3/apache2/php.ini Session session.gc_probability 1 && \
22
+ crudini --set /etc/php/8.3/apache2/php.ini Session session.gc_divisor 100
23
+
24
+ COPY index.php start.sh /app/code/
25
+ RUN chown -R www-data.www-data /app/code
26
+
27
+ CMD [ "/app/code/start.sh" ]
@@ -0,0 +1,17 @@
1
+ <VirtualHost *:3000>
2
+ DocumentRoot /app/code
3
+
4
+ ErrorLog "|/bin/cat"
5
+ CustomLog "|/bin/cat" combined
6
+
7
+ <Directory /app/code/>
8
+ Options +FollowSymLinks
9
+ AllowOverride All
10
+ Require all granted
11
+
12
+ <Files "config.php">
13
+ Require all denied
14
+ </Files>
15
+ </Directory>
16
+
17
+ </VirtualHost>
@@ -0,0 +1,7 @@
1
+ <IfModule mpm_prefork_module>
2
+ StartServers 2
3
+ MinSpareServers 2
4
+ MaxSpareServers 3
5
+ MaxRequestWorkers 15
6
+ MaxConnectionsPerChild 100
7
+ </IfModule>
@@ -0,0 +1,4 @@
1
+ .git
2
+ .gitignore
3
+ .dockerignore
4
+ node_modules
@@ -0,0 +1,18 @@
1
+ <?php
2
+ $host = getenv("CLOUDRON_MYSQL_HOST");
3
+ $port = getenv("CLOUDRON_MYSQL_PORT");
4
+ $username = getenv("CLOUDRON_MYSQL_USERNAME");
5
+ $password = getenv("CLOUDRON_MYSQL_PASSWORD");
6
+ $database = getenv("CLOUDRON_MYSQL_DATABASE");
7
+
8
+ echo "<h1>Hello from Cloudron!</h1>\n";
9
+
10
+ try {
11
+ $dsn = "mysql:host=$host;port=$port;dbname=$database";
12
+ $pdo = new PDO($dsn, $username, $password);
13
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
14
+ echo "<p>MySQL connection successful.</p>\n";
15
+ } catch (PDOException $e) {
16
+ echo "<p>MySQL connection failed: " . htmlspecialchars($e->getMessage()) . "</p>\n";
17
+ }
18
+ ?>
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ mkdir -p /run/app/sessions
6
+
7
+ APACHE_CONFDIR="" source /etc/apache2/envvars
8
+ rm -f "${APACHE_PID_FILE}"
9
+ exec /usr/sbin/apache2 -DFOREGROUND
@@ -0,0 +1,3 @@
1
+ {
2
+ "description": "LAMP app with Apache, mod_php, and MySQL"
3
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": "",
3
+ "version": "0.1.0",
4
+ "upstreamVersion": "",
5
+ "minBoxVersion": "9.0.0",
6
+ "title": "",
7
+ "author": "",
8
+ "description": "file://DESCRIPTION.md",
9
+ "tagline": "",
10
+ "website": "",
11
+ "contactEmail": "",
12
+ "iconUrl": "file://logo.png",
13
+ "healthCheckPath": "/",
14
+ "mediaLinks": [],
15
+ "httpPort": 3000,
16
+ "tags": [],
17
+ "changelog": "file://CHANGELOG",
18
+ "postInstallMessage": "file://POSTINSTALL.md",
19
+ "documentationUrl": "",
20
+ "forumUrl": "",
21
+ "addons": {
22
+ "localstorage": {},
23
+ "mysql": {}
24
+ },
25
+ "manifestVersion": 2
26
+ }
@@ -0,0 +1,30 @@
1
+ FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
2
+
3
+ RUN mkdir -p /app/code
4
+ WORKDIR /app/code
5
+
6
+ RUN rm /etc/apache2/sites-enabled/*
7
+ RUN sed -e 's,^ErrorLog.*,ErrorLog "|/bin/cat",' -i /etc/apache2/apache2.conf
8
+ COPY apache/mpm_prefork.conf /etc/apache2/mods-available/mpm_prefork.conf
9
+
10
+ RUN a2disconf other-vhosts-access-log
11
+ COPY apache/app.conf /etc/apache2/sites-enabled/app.conf
12
+ RUN echo "Listen 3000" > /etc/apache2/ports.conf
13
+
14
+ RUN a2enmod php8.3 rewrite
15
+ RUN crudini --set /etc/php/8.3/apache2/php.ini PHP upload_max_filesize 256M && \
16
+ crudini --set /etc/php/8.3/apache2/php.ini PHP upload_max_size 256M && \
17
+ crudini --set /etc/php/8.3/apache2/php.ini PHP post_max_size 256M && \
18
+ crudini --set /etc/php/8.3/apache2/php.ini PHP memory_limit 256M && \
19
+ crudini --set /etc/php/8.3/apache2/php.ini PHP max_execution_time 200 && \
20
+ crudini --set /etc/php/8.3/apache2/php.ini Session session.save_path /run/app/sessions && \
21
+ crudini --set /etc/php/8.3/apache2/php.ini Session session.gc_probability 1 && \
22
+ crudini --set /etc/php/8.3/apache2/php.ini Session session.gc_divisor 100
23
+
24
+ COPY . /app/code/
25
+
26
+ RUN composer install --no-dev --optimize-autoloader
27
+
28
+ RUN chown -R www-data.www-data /app/code
29
+
30
+ CMD [ "/app/code/start.sh" ]
@@ -0,0 +1,17 @@
1
+ <VirtualHost *:3000>
2
+ DocumentRoot /app/code/public
3
+
4
+ ErrorLog "|/bin/cat"
5
+ CustomLog "|/bin/cat" combined
6
+
7
+ <Directory /app/code/public/>
8
+ Options +FollowSymLinks
9
+ AllowOverride All
10
+ Require all granted
11
+ </Directory>
12
+
13
+ <Directory /app/code/>
14
+ Require all denied
15
+ </Directory>
16
+
17
+ </VirtualHost>
@@ -0,0 +1,7 @@
1
+ <IfModule mpm_prefork_module>
2
+ StartServers 2
3
+ MinSpareServers 2
4
+ MaxSpareServers 3
5
+ MaxRequestWorkers 15
6
+ MaxConnectionsPerChild 100
7
+ </IfModule>
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "app/cloudron-app",
3
+ "description": "",
4
+ "type": "project",
5
+ "require": {
6
+ "php": ">=8.3"
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ .git
2
+ .gitignore
3
+ .dockerignore
4
+ node_modules
5
+ vendor
@@ -0,0 +1,20 @@
1
+ <?php
2
+ require_once __DIR__ . '/../vendor/autoload.php';
3
+
4
+ $host = getenv("CLOUDRON_MYSQL_HOST");
5
+ $port = getenv("CLOUDRON_MYSQL_PORT");
6
+ $username = getenv("CLOUDRON_MYSQL_USERNAME");
7
+ $password = getenv("CLOUDRON_MYSQL_PASSWORD");
8
+ $database = getenv("CLOUDRON_MYSQL_DATABASE");
9
+
10
+ echo "<h1>Hello from Cloudron!</h1>\n";
11
+
12
+ try {
13
+ $dsn = "mysql:host=$host;port=$port;dbname=$database";
14
+ $pdo = new PDO($dsn, $username, $password);
15
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
16
+ echo "<p>MySQL connection successful.</p>\n";
17
+ } catch (PDOException $e) {
18
+ echo "<p>MySQL connection failed: " . htmlspecialchars($e->getMessage()) . "</p>\n";
19
+ }
20
+ ?>