diff --git a/apps/api/.gitignore b/apps/api/.gitignore index 9d4e0d4e8..8614cdee9 100644 --- a/apps/api/.gitignore +++ b/apps/api/.gitignore @@ -8,4 +8,6 @@ package !.env.example dist dev.db -client \ No newline at end of file +client +testTemplate.yaml +testTags.json \ No newline at end of file diff --git a/apps/api/devTags.json b/apps/api/devTags.json index 5d4ef05cd..a788801e2 100644 --- a/apps/api/devTags.json +++ b/apps/api/devTags.json @@ -1,11 +1,9 @@ [ - { "name": "directus-postgresql", "image": "directus/directus", "tags": ["9.22"] }, - { "name": "whoogle", "image": "benbusby/whoogle-search", "tags": ["0.8.1"] }, - { "name": "libretranslate", "image": "libretranslate/libretranslate", "tags": ["v1.3.8"] }, { "name": "appsmith", "image": "appsmith/appsmith-ce", "tags": [ + "v1.9.3", "v1.9.1", "v1.8.15", "v1.8.12", @@ -34,8 +32,7 @@ "v1.6.5", "v1.6.3", "v1.6.1", - "v1.5.30", - "v1.5.28" + "v1.5.30" ] }, { @@ -74,6 +71,42 @@ "0.3.1" ] }, + { + "name": "directus-postgresql", + "image": "directus/directus", + "tags": [ + "9.22.3", + "9.22.0", + "9.21.0", + "9.20.4", + "9.20.2", + "9.20.0", + "9.19.2", + "9.18.0", + "9.17.4", + "9.17.2", + "9.17.0", + "9.16.0", + "9.15.0", + "9.14.5", + "9.14.3", + "9.14.0", + "9.13.0", + "9.12.2", + "9.12.0", + "9.11.0", + "9.10.0", + "9.9.0", + "9.8.0", + "9.7.0", + "9.6.0", + "9.5.2", + "9.5.0", + "9.4.2", + "9.4.0", + "9.3.0" + ] + }, { "name": "fider", "image": "getfider/fider", @@ -114,6 +147,9 @@ "name": "ghost-mariadb", "image": "bitnami/ghost", "tags": [ + "5.30.1", + "5.30.0", + "5.29.0", "5.28.0", "5.27.0", "5.26.4", @@ -141,9 +177,6 @@ "5.22.4", "5.22.3", "5.22.2", - "5.22.1", - "5.22.0", - "5.21.0", "4.48.8" ] }, @@ -151,6 +184,8 @@ "name": "ghost-mysql", "image": "library/ghost", "tags": [ + "5.30.0", + "5.29.0", "5.28.0", "5.27.0", "5.26.4", @@ -178,15 +213,15 @@ "5.17.2", "5.17.1", "5.17.0", - "5.16.2", - "5.14.2", - "5.14.1" + "5.16.2" ] }, { "name": "ghost-only", "image": "library/ghost", "tags": [ + "5.30.0", + "5.29.0", "5.28.0", "5.27.0", "5.26.4", @@ -214,9 +249,7 @@ "5.17.2", "5.17.1", "5.17.0", - "5.16.2", - "5.14.2", - "5.14.1" + "5.16.2" ] }, { @@ -373,6 +406,7 @@ "7.0.0", "6.0.1", "6.0.0", + "20.0.3", "20.0.2", "20.0.1", "20.0.0", @@ -404,38 +438,12 @@ { "name": "lavalink", "image": "fredboat/lavalink", - "tags": [ - "v3.7", - "v3.6", - "v3-vda0b3a4b3916a7b1a2b79702de1143c3a6939810-SNAPSHOT", - "v3-vc92690c425390bd20f6c51643c67ba79ab85b7e0-SNAPSHOT", - "v3-vab81dcd46adf3e8a961dd57eacd2a1bde1233e6c-SNAPSHOT", - "v3-v9c9432704d6a4badfcbd06a57597c54bed8f4326-SNAPSHOT", - "v3-v3.0", - "v3-v3", - "v3-v124f8fae7dab299f9cdf1cb4c1715be455497286-SNAPSHOT", - "v3-", - "v3", - "v2.0.1", - "v2.0", - "v2", - "update-udpqueue-vb4a439d6147dbd8641ea4f265e8efc9f1e16e2d3-SNAPSHOT", - "update-udpqueue-", - "update-udpqueue", - "revert-713-fix-error-for-loading-jda-nas", - "refactor-github-actions", - "patch-update-lp", - "patch-update-github-actions", - "patch-more-configurable-github-actions", - "patch-lavaplayer-update", - "patch-lavaplayer-bump", - "patch-build-number", - "next-api-vd4db194cac7a839a3899857f1f6d7b910369309d-SNAPSHOT", - "next-api-vc2e018d5ffef54b2d17244b3d213e31723a084d6-SNAPSHOT", - "next-api-v42cb5f7c58e98d1911e87bffb35aee0a235b85f8-SNAPSHOT", - "next-api-v31a243bda80badbd7d643f68fc1f87e99639060f-SNAPSHOT", - "next-api-v17f6884434c2d70d1704b2322a951d9f07af8865-SNAPSHOT" - ] + "tags": ["3.7.0", "3.6.1", "3.5.1", "v2.0.1"] + }, + { + "name": "libretranslate", + "image": "libretranslate/libretranslate", + "tags": ["v1.3.8", "v1.3.6", "v1.3.4", "v1.3.2", "v1.3.0", "v1.2.8"] }, { "name": "meilisearch", @@ -477,6 +485,7 @@ "name": "minio", "image": "minio/minio", "tags": [ + "RELEASE.2023-01-12T02-06-16Z", "RELEASE.2023-01-06T18-11-18Z", "RELEASE.2023-01-02T09-40-09Z", "RELEASE.2022-12-12T19-27-27Z", @@ -505,8 +514,7 @@ "RELEASE.2022-09-01T23-53-36Z.fips", "RELEASE.2022-08-26T19-53-15Z.fips", "RELEASE.2022-08-25T07-17-05Z.fips", - "RELEASE.2022-08-22T23-53-06Z.hotfix.5fa3967bb", - "RELEASE.2022-08-22T23-53-06Z" + "RELEASE.2022-08-22T23-53-06Z.hotfix.5fa3967bb" ] }, { @@ -549,77 +557,56 @@ "name": "nocodb", "image": "nocodb/nocodb", "tags": [ + "0.101.2", + "0.101.0", "0.100.1", - "0.99.1", - "0.98.4", - "0.98.2", - "0.98.0", - "0.96.4", - "0.96.2", - "0.96.0", - "0.92.3", - "0.91.10", - "0.91.9", - "0.91.7", - "0.91.0", - "0.90.10", - "0.90.7", - "0.90.4", - "0.90.2", - "0.90.0", - "0.84.15", - "0.84.12", - "0.84.8", - "0.84.6", - "0.84.2", - "0.84.1", - "0.83.6", - "0.83.3", - "0.83.1", - "0.82.0", - "0.81.0", - "0.11.46" + "0.99.2", + "0.99.0", + "0.98.3", + "0.98.1", + "0.97.0", + "0.96.3", + "0.96.1", + "0.92.4", + "0.92.0", + "0.91.8", + "0.91.6", + "0.91.1", + "0.90.11", + "0.90.8", + "0.90.5", + "0.90.3", + "0.90.1", + "0.84.16", + "0.84.14", + "0.84.10", + "0.84.9", + "0.84.7", + "0.84.3", + "0.83.8", + "0.83.5", + "0.83.2", + "0.83.0" ] }, { "name": "openblocks", "image": "openblocksdev/openblocks-ce", - "tags": ["latest", "heroku", "beta", "1.1.3", "1.1.2", "1.1.1", "1.1.0", "1.0.21"] + "tags": ["1.1.3", "1.1.1", "1.0.21"] }, { "name": "plausibleanalytics-arm", "image": "plausible/analytics", "tags": [ "v1.5.1", - "v1.5.0-rc.2", - "v1.5.0-rc.1", "v1.5.0", - "v1.5", - "v1.4.4", "v1.4.3", - "v1.4.2", "v1.4.1", - "v1.4.0.rc.0", - "v1.4.0-rc.0", "v1.4.0", - "v1.4", - "v1.3.0-rc.1", - "v1.3.0-rc.0", "v1.3.0", - "v1.3", - "v1.2.1", "v1.2.0", - "v1.2-rc.1", - "v1.2-rc.0", - "v1.2", - "v1.1.1", "v1.1.0", - "v1.1", - "v1.0.0", - "v1.0", - "v1", - "stable", - "master" + "v1.0.0" ] }, { @@ -627,55 +614,26 @@ "image": "plausible/analytics", "tags": [ "v1.5.1", - "v1.5.0-rc.2", - "v1.5.0-rc.1", "v1.5.0", - "v1.5", - "v1.4.4", "v1.4.3", - "v1.4.2", "v1.4.1", - "v1.4.0.rc.0", - "v1.4.0-rc.0", "v1.4.0", - "v1.4", - "v1.3.0-rc.1", - "v1.3.0-rc.0", "v1.3.0", - "v1.3", - "v1.2.1", "v1.2.0", - "v1.2-rc.1", - "v1.2-rc.0", - "v1.2", - "v1.1.1", "v1.1.0", - "v1.1", - "v1.0.0", - "v1.0", - "v1", - "stable", - "master" - ] - }, - { - "name": "pocketbase", - "image": "coollabsio/pocketbase", - "tags": [ - "0.8.0-arm64", - "0.8.0-amd64", - "0.8.0-aarch64", - "0.8.0", - "0.10.2-arm64", - "0.10.2-amd64", - "0.10.2-aarch64", - "0.10.2" + "v1.0.0" ] }, + { "name": "pocketbase", "image": "coollabsio/pocketbase", "tags": ["0.11.0", "0.10.2", "0.8.0"] }, { "name": "searxng", "image": "searxng/searxng", "tags": [ + "2023.01.15-52d41559", + "2023.01.15-13b0c251", + "2023.01.14-b720a495", + "2023.01.14-449aebae", + "2023.01.14-18d895ff", "2023.01.09-afd71a6c", "2023.01.09-a90ed481", "2023.01.08-54e63839", @@ -700,18 +658,14 @@ "2022.12.26-0d489617", "2022.12.23-e8f72d70", "2022.12.23-a2d506d4", - "2022.12.22-d75ae7c8", - "2022.12.16-f5bd73d9", - "2022.12.16-b9274821", - "2022.12.16-42ca37a6", - "2022.12.16-2a51c856", - "2022.12.16-0dac581c" + "2022.12.22-d75ae7c8" ] }, { "name": "trilium", "image": "zadam/trilium", "tags": [ + "0.58.4", "0.57.4", "0.57.2", "0.56.1", @@ -740,8 +694,7 @@ "0.45.7", "0.45.5", "0.45.3", - "0.44.8", - "0.44.6" + "0.44.8" ] }, { @@ -907,6 +860,15 @@ "image": "weblate/weblate", "tags": [ "latest", + "edge-2023-01-13-e824b551f23c3679467e38b06366744a06aa3b0c", + "edge-2023-01-13-468b996565e6b62edb78d40b515c476e0d860273", + "edge-2023-01-12-fe3d58b14f119eb5501220e9f096949c2e1ec2d3", + "edge-2023-01-12-112f75f9ee9e118ad493215f89742e6e091be8d0", + "edge-2023-01-11-f7bb190993e329d1529694e8cc7f5e0a80ccd615", + "edge-2023-01-11-e8ef3183aa7723f32c2b60c7c3b89910f2c7c593", + "edge-2023-01-11-155231f6cde18a65e3f35093d66dd0ce93aa7154", + "edge-2023-01-10-e47516e4022f87c019e61998b556b69111187aa9", + "edge-2023-01-10-98c6b38c746165adb27b2a8e93a74fa9ab64f17c", "edge-2023-01-10-1df5c9dd96a6d8650f6881942fecbe33e1884295", "edge-2023-01-09-7029b7b6c630be7cdac07d1629573dd2b81bc05f", "edge-2023-01-09-4b05a878aa25b2c544a4e77027769b5934ec561f", @@ -926,16 +888,24 @@ "edge-2022-12-24-3e1503494ce06ad6ff32f02db1a7d59224e5c860", "edge-2022-12-21-cac4b09f943fe97700e3a33b7caf23277d2fcc11", "edge-2022-12-21-3a8dd1bf66a7295f3512346bc1c97d55c5649dcf", - "edge-2022-12-16-e93caa3b014543b716b946f2c7fbf4a8f9be6099", - "edge-2022-12-16-318a467d2e529a081e9ea9dbad993c1736ff1a00", - "edge-2022-12-16-1af41ec4bd3838f967d88b68dec8195419e01e6f", - "edge-2022-12-16-02e9d020b01d004655c3af20c68a30f6c4645c1a", - "edge-2022-12-15-a6af1384a0831b17c43da7262f80d0cfbc766835", - "edge-2022-12-15-a1c9f77b301a9e23fc05ef2adc4694cceb632c25", - "edge-2022-12-15-1305f7115ef79b75e638b097772680d9cadbd4d0", - "edge-2022-12-14-b400145f05687e647bd4c8192be99f7f04373fb5", - "edge-2022-12-12-c0db193a3baacd107c5f2c28c6e0af89c3d5afa3", - "edge-2022-12-09-647d40c67cf405870ba71a01584a42cfaec5915f" + "edge-2022-12-16-e93caa3b014543b716b946f2c7fbf4a8f9be6099" + ] + }, + { + "name": "whoogle", + "image": "benbusby/whoogle-search", + "tags": [ + "0.8.0", + "0.7.3", + "0.7.1", + "0.6.0", + "0.5.3", + "0.5.1", + "0.4.1", + "0.3.2", + "v0.3.0", + "0.1.2", + "0.1.0" ] }, { diff --git a/apps/api/devTemplates.yaml b/apps/api/devTemplates.yaml index 4cff705db..a3443b727 100644 --- a/apps/api/devTemplates.yaml +++ b/apps/api/devTemplates.yaml @@ -111,7 +111,7 @@ defaultValue: $$generate_password description: "Secret string for the project." showOnConfiguration: true - + - templateVersion: 1.0.0 defaultVersion: v1.3.8 documentation: https://github.com/LibreTranslate/LibreTranslate @@ -166,7 +166,7 @@ defaultValue: "false" description: "Disable or enable web ui. True or false." - templateVersion: 1.0.0 - defaultVersion: 0.8.1 + defaultVersion: 0.8.0 documentation: https://github.com/benbusby/whoogle-search type: whoogle name: Whoogle Search @@ -223,7 +223,7 @@ ports: - "3000" - templateVersion: 1.0.0 - defaultVersion: "0.10.2" + defaultVersion: "0.11.0" documentation: https://pocketbase.io/docs/ type: pocketbase name: Pocketbase @@ -381,7 +381,7 @@ defaultValue: plausible.js description: This is the default script name. - templateVersion: 1.0.0 - defaultVersion: "1.17" + defaultVersion: "1.18" documentation: https://docs.gitea.io type: gitea name: Gitea @@ -594,7 +594,7 @@ defaultValue: $$generate_password required: true - templateVersion: 1.0.0 - defaultVersion: v1.8.9 + defaultVersion: v1.9.3 documentation: https://docs.appsmith.com/getting-started/setup/instance-configuration/ type: appsmith name: Appsmith @@ -627,7 +627,7 @@ defaultValue: "true" description: "" - templateVersion: 1.0.0 - defaultVersion: 0.57.4 + defaultVersion: 0.58.4 documentation: https://hub.docker.com/r/zadam/trilium description: "A hierarchical note taking application with focus on building large personal knowledge bases." labels: @@ -647,7 +647,7 @@ - "8080" variables: [] - templateVersion: 1.0.0 - defaultVersion: 1.18.5 + defaultVersion: 1.19.4 documentation: https://hub.docker.com/r/louislam/uptime-kuma description: A free & fancy self-hosted monitoring tool. labels: @@ -664,7 +664,7 @@ - "3001" variables: [] - templateVersion: 1.0.0 - defaultVersion: "5.8" + defaultVersion: "6.0" documentation: https://hub.docker.com/r/silviof/docker-languagetool description: "A multilingual grammar, style and spell checker." type: languagetool @@ -679,7 +679,7 @@ - "8010" variables: [] - templateVersion: 1.0.0 - defaultVersion: 1.26.0 + defaultVersion: 1.27.0 documentation: https://hub.docker.com/r/vaultwarden/server description: "Bitwarden compatible server written in Rust." type: vaultwarden @@ -697,7 +697,7 @@ - "80" variables: [] - templateVersion: 1.0.0 - defaultVersion: 9.3.1 + defaultVersion: 9.3.2 documentation: https://hub.docker.com/r/grafana/grafana type: grafana name: Grafana @@ -718,7 +718,7 @@ - "3000" variables: [] - templateVersion: 1.0.0 - defaultVersion: 1.1.2 + defaultVersion: 1.2.0 documentation: https://appwrite.io/docs type: appwrite name: Appwrite @@ -1888,7 +1888,7 @@ defaultValue: weblate description: "" - templateVersion: 1.0.0 - defaultVersion: 2022.12.12-966e9c3c + defaultVersion: 2023.01.15-52d41559 documentation: https://docs.searxng.org/ type: searxng name: SearXNG @@ -1961,7 +1961,7 @@ defaultValue: $$generate_password description: "" - templateVersion: 1.0.0 - defaultVersion: v3.0.0 + defaultVersion: v3.0.2 documentation: https://glitchtip.com/documentation type: glitchtip name: GlitchTip @@ -2183,7 +2183,7 @@ defaultValue: glitchtip description: "" - templateVersion: 1.0.0 - defaultVersion: v2.16.0 + defaultVersion: v2.16.1 documentation: https://hasura.io/docs/latest/index/ type: hasura name: Hasura @@ -2663,7 +2663,7 @@ description: "" showOnConfiguration: true - templateVersion: 1.0.0 - defaultVersion: v0.30.1 + defaultVersion: v0.30.5 documentation: https://docs.meilisearch.com/learn/getting_started/quick_start.html type: meilisearch name: MeiliSearch @@ -2693,7 +2693,7 @@ showOnConfiguration: true - templateVersion: 1.0.0 ignore: true - defaultVersion: latest + defaultVersion: 5.30.0 documentation: https://docs.ghost.org arch: amd64 type: ghost-mariadb @@ -2811,7 +2811,7 @@ defaultValue: $$generate_password description: "" - templateVersion: 1.0.0 - defaultVersion: "5.25.3" + defaultVersion: 5.30.0 documentation: https://docs.ghost.org type: ghost-only name: Ghost @@ -2875,7 +2875,7 @@ placeholder: "ghost_db" required: true - templateVersion: 1.0.0 - defaultVersion: "5.25.3" + defaultVersion: 5.30.0 documentation: https://docs.ghost.org type: ghost-mysql name: Ghost @@ -2952,7 +2952,7 @@ defaultValue: $$generate_password description: "" - templateVersion: 1.0.0 - defaultVersion: php8.1 + defaultVersion: php8.2 documentation: https://wordpress.org/ type: wordpress name: WordPress @@ -3042,7 +3042,7 @@ description: "" readOnly: true - templateVersion: 1.0.0 - defaultVersion: php8.1 + defaultVersion: php8.2 documentation: https://wordpress.org/ type: wordpress-only name: WordPress @@ -3116,7 +3116,7 @@ define('WP_DEBUG_DISPLAY', false); @ini_set('display_errors', 0); - templateVersion: 1.0.0 - defaultVersion: 4.9.0 + defaultVersion: 4.9.1 documentation: https://coder.com/docs/coder-oss/latest type: vscodeserver name: VSCode Server @@ -3131,7 +3131,6 @@ depends_on: [] image: "codercom/code-server:$$core_version" volumes: - - "$$id-config-data:/home/coder/.local/share/code-server" - "$$id-vscodeserver-data:/home/coder" - "$$id-keys-directory:/root/.ssh" - "$$id-theme-and-plugin-directory:/root/.local/share/code-server" @@ -3147,7 +3146,7 @@ description: "" showOnConfiguration: true - templateVersion: 1.0.0 - defaultVersion: RELEASE.2022-12-12T19-27-27Z + defaultVersion: RELEASE.2023-01-12T02-06-16Z documentation: https://min.io/docs/minio type: minio name: MinIO @@ -3206,7 +3205,7 @@ description: "" showOnConfiguration: true - templateVersion: 1.0.0 - defaultVersion: 0.21.1 + defaultVersion: stable documentation: https://fider.io/docs type: fider name: Fider @@ -3325,7 +3324,7 @@ defaultValue: $$generate_username description: "" - templateVersion: 1.0.0 - defaultVersion: 0.207.0 + defaultVersion: 0.210.1 documentation: https://docs.n8n.io type: n8n name: n8n.io @@ -3356,7 +3355,7 @@ defaultValue: $$generate_fqdn description: "" - templateVersion: 1.0.0 - defaultVersion: stable + defaultVersion: v1.5.1 documentation: https://plausible.io/doc/ arch: amd64 type: plausibleanalytics @@ -3502,7 +3501,7 @@ defaultValue: plausible.js description: This is the default script name. - templateVersion: 1.0.0 - defaultVersion: 0.99.1 + defaultVersion: 0.101.2 documentation: https://docs.nocodb.com type: nocodb name: NocoDB diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 275b65702..1ab6acd47 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -225,8 +225,22 @@ async function getTagsTemplates() { const { default: got } = await import('got'); try { if (isDev) { - const templates = await fs.readFile('./devTemplates.yaml', 'utf8'); - const tags = await fs.readFile('./devTags.json', 'utf8'); + let templates = await fs.readFile('./devTemplates.yaml', 'utf8'); + let tags = await fs.readFile('./devTags.json', 'utf8'); + try { + if (await fs.stat('./testTemplate.yaml')) { + templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8')); + } + } catch (error) {} + try { + if (await fs.stat('./testTags.json')) { + const testTags = await fs.readFile('./testTags.json', 'utf8'); + if (testTags.length > 0) { + tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags))); + } + } + } catch (error) {} + await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates))); await fs.writeFile('./tags.json', tags); console.log('[004] Tags and templates loaded in dev mode...'); diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index cc3740af4..ee698309f 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -196,7 +196,7 @@ import * as buildpacks from '../lib/buildPacks'; await executeCommand({ debug: true, dockerId: destinationDocker.id, - command: `docker compose --project-directory ${workdir} up -d` + command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d` }); await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId }); } catch (error) { @@ -601,6 +601,7 @@ import * as buildpacks from '../lib/buildPacks'; } if (buildPack === 'compose') { + const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`; try { const { stdout: containers } = await executeCommand({ dockerId: destinationDockerId, @@ -630,7 +631,7 @@ import * as buildpacks from '../lib/buildPacks'; buildId, applicationId, dockerId: destinationDocker.id, - command: `docker compose --project-directory ${workdir} up -d` + command: `docker compose --project-directory ${workdir} -f ${fileYaml} up -d` }); await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId }); await prisma.build.update({ @@ -725,7 +726,7 @@ import * as buildpacks from '../lib/buildPacks'; await executeCommand({ debug, dockerId: destinationDocker.id, - command: `docker compose --project-directory ${workdir} up -d` + command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d` }); await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId }); } catch (error) { diff --git a/apps/api/src/lib/buildPacks/compose.ts b/apps/api/src/lib/buildPacks/compose.ts index c8cbc9f16..6a13c124c 100644 --- a/apps/api/src/lib/buildPacks/compose.ts +++ b/apps/api/src/lib/buildPacks/compose.ts @@ -26,8 +26,10 @@ export default async function (data) { throw 'No Services found in docker-compose file.'; } let envs = []; + let buildEnvs = []; if (secrets.length > 0) { envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)]; + buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)]; } const composeVolumes = []; @@ -43,8 +45,22 @@ export default async function (data) { let networks = {}; for (let [key, value] of Object.entries(dockerComposeYaml.services)) { value['container_name'] = `${applicationId}-${key}`; - let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'] + + let environment = typeof value['environment'] === 'undefined' ? [] : value['environment']; + if (Object.keys(environment).length > 0) { + environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`); + } value['environment'] = [...environment, ...envs]; + + let build = typeof value['build'] === 'undefined' ? [] : value['build']; + if (Object.keys(build).length > 0) { + build = Object.entries(build).map(([key, value]) => `${key}=${value}`); + } + value['build'] = { + ...build, + args: [...(build?.args || []), ...buildEnvs] + }; + value['labels'] = labels; // TODO: If we support separated volume for each service, we need to add it here if (value['volumes']?.length > 0) { @@ -90,12 +106,13 @@ export default async function (data) { dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } }); await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml)); + console.log(yaml.dump(dockerComposeYaml)); await executeCommand({ debug, buildId, applicationId, dockerId, - command: `docker compose --project-directory ${workdir} pull` + command: `docker compose --project-directory ${workdir} -f ${fileYaml} pull` }); await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId }); await executeCommand({ @@ -103,7 +120,7 @@ export default async function (data) { buildId, applicationId, dockerId, - command: `docker compose --project-directory ${workdir} build --progress plain` + command: `docker compose --project-directory ${workdir} -f ${fileYaml} build --progress plain` }); await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId }); } diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 41a8704cd..8dc4981c6 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -19,7 +19,7 @@ import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common import { scheduler } from './scheduler'; import type { ExecaChildProcess } from 'execa'; -export const version = '3.12.10'; +export const version = '3.12.11'; export const isDev = process.env.NODE_ENV === 'development'; export const sentryDSN = 'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216'; @@ -1912,27 +1912,28 @@ export function generateSecrets( secrets: Array, pullmergeRequestId: string, isBuild = false, - port = null + port = null, + compose = false ): Array { const envs = []; const isPRMRSecret = secrets.filter((s) => s.isPRMRSecret); const normalSecrets = secrets.filter((s) => !s.isPRMRSecret); if (pullmergeRequestId && isPRMRSecret.length > 0) { isPRMRSecret.forEach((secret) => { - if (isBuild && !secret.isBuildSecret) { + if ((isBuild && !secret.isBuildSecret) || (!isBuild && secret.isBuildSecret)) { return; } const build = isBuild && secret.isBuildSecret; - envs.push(parseSecret(secret, build)); + envs.push(parseSecret(secret, compose ? false : build)); }); } if (!pullmergeRequestId && normalSecrets.length > 0) { normalSecrets.forEach((secret) => { - if (isBuild && !secret.isBuildSecret) { + if ((isBuild && !secret.isBuildSecret) || (!isBuild && secret.isBuildSecret)) { return; } const build = isBuild && secret.isBuildSecret; - envs.push(parseSecret(secret, build)); + envs.push(parseSecret(secret, compose ? false : build)); }); } const portFound = envs.filter((env) => env.startsWith('PORT')); diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index fb27f2701..494bbe6f0 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -122,6 +122,9 @@ export async function cleanupUnconfiguredApplications(request: FastifyRequest ansi(l)) .filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout); - const sortedLogs = logs.sort((a, b) => - day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1 - ); - return { logs: sortedLogs }; - // } + return { logs: stripLogsStderr.concat(stripLogsStdout) }; } catch (error) { const { statusCode, stderr } = error; if (stderr.startsWith('Error: No such container')) { diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index 20bf285b1..6127ac25e 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -3,550 +3,609 @@ import type { FastifyRequest } from 'fastify'; import { FastifyReply } from 'fastify'; import yaml from 'js-yaml'; import fs from 'fs/promises'; -import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeCommand, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; +import { + ComposeFile, + createDirectories, + decrypt, + defaultComposeConfiguration, + encrypt, + errorHandler, + executeCommand, + generateDatabaseConfiguration, + generatePassword, + getContainerUsage, + getDatabaseImage, + getDatabaseVersions, + getFreePublicPort, + listSettings, + makeLabelForStandaloneDatabase, + prisma, + startTraefikTCPProxy, + stopDatabaseContainer, + stopTcpHttpProxy, + supportedDatabaseTypesAndVersions, + uniqueName, + updatePasswordInDb +} from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; import type { OnlyId } from '../../../../types'; -import type { DeleteDatabase, DeleteDatabaseSecret, GetDatabaseLogs, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveDatabaseType, SaveVersion } from './types'; +import type { + DeleteDatabase, + DeleteDatabaseSecret, + GetDatabaseLogs, + SaveDatabase, + SaveDatabaseDestination, + SaveDatabaseSecret, + SaveDatabaseSettings, + SaveDatabaseType, + SaveVersion +} from './types'; export async function listDatabases(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { teams: true, destinationDocker: true } - }); - return { - databases - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const databases = await prisma.database.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true, destinationDocker: true } + }); + return { + databases + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function newDatabase(request: FastifyRequest, reply: FastifyReply) { - try { - const teamId = request.user.teamId; + try { + const teamId = request.user.teamId; - const name = uniqueName(); - const dbUser = cuid(); - const dbUserPassword = encrypt(generatePassword({})); - const rootUser = cuid(); - const rootUserPassword = encrypt(generatePassword({})); - const defaultDatabase = cuid(); + const name = uniqueName(); + const dbUser = cuid(); + const dbUserPassword = encrypt(generatePassword({})); + const rootUser = cuid(); + const rootUserPassword = encrypt(generatePassword({})); + const defaultDatabase = cuid(); - const { id } = await prisma.database.create({ - data: { - name, - defaultDatabase, - dbUser, - dbUserPassword, - rootUser, - rootUserPassword, - teams: { connect: { id: teamId } }, - settings: { create: { isPublic: false } } - } - }); - return reply.code(201).send({ id }) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const { id } = await prisma.database.create({ + data: { + name, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + teams: { connect: { id: teamId } }, + settings: { create: { isPublic: false } } + } + }); + return reply.code(201).send({ id }); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function cleanupUnconfiguredDatabases(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - let databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { settings: true, destinationDocker: true, teams: true }, - }); - for (const database of databases) { - if (!database?.version) { - const { id } = database; - if (database.destinationDockerId) { - const everStarted = await stopDatabaseContainer(database); - if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); - } - await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); - await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); - await prisma.database.delete({ where: { id } }); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + let databases = await prisma.database.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { settings: true, destinationDocker: true, teams: true } + }); + for (const database of databases) { + if (!database?.version) { + const { id } = database; + if (database.destinationDockerId) { + const everStarted = await stopDatabaseContainer(database); + if (everStarted) + await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); + } + await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); + await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); + await prisma.database.delete({ where: { id } }); + } + } + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseStatus(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - let isRunning = false; + try { + const { id } = request.params; + const teamId = request.user.teamId; + let isRunning = false; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - if (database) { - const { destinationDockerId, destinationDocker } = database; - if (destinationDockerId) { - try { - const { stdout } = await executeCommand({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` }) + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + if (database) { + const { destinationDockerId, destinationDocker } = database; + if (destinationDockerId) { + try { + const { stdout } = await executeCommand({ + dockerId: destinationDocker.id, + command: `docker inspect --format '{{json .State}}' ${id}` + }); - if (JSON.parse(stdout).Running) { - isRunning = true; - } - } catch (error) { - // - } - } - } - return { - isRunning - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (JSON.parse(stdout).Running) { + isRunning = true; + } + } catch (error) { + // + } + } + } + return { + isRunning + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabase(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - if (!database) { - throw { status: 404, message: 'Database not found.' } - } - const settings = await listSettings(); - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - const configuration = generateDatabaseConfiguration(database, settings.arch); - return { - privatePort: configuration?.privatePort, - database, - versions: await getDatabaseVersions(database.type, settings.arch), - settings - }; - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const teamId = request.user.teamId; + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + if (!database) { + throw { status: 404, message: 'Database not found.' }; + } + const settings = await listSettings(); + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + const configuration = generateDatabaseConfiguration(database, settings.arch); + return { + privatePort: configuration?.privatePort, + database, + versions: await getDatabaseVersions(database.type, settings.arch), + settings + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseTypes(request: FastifyRequest) { - try { - return { - types: supportedDatabaseTypesAndVersions - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + return { + types: supportedDatabaseTypesAndVersions + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveDatabaseType(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { type } = request.body; - await prisma.database.update({ - where: { id }, - data: { type } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveDatabaseType( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { type } = request.body; + await prisma.database.update({ + where: { id }, + data: { type } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getVersions(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { type } = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - const { arch } = await listSettings(); - const versions = getDatabaseVersions(type, arch); - return { - versions - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { type } = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + const { arch } = await listSettings(); + const versions = getDatabaseVersions(type, arch); + return { + versions + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveVersion(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { version } = request.body; + try { + const { id } = request.params; + const { version } = request.body; - await prisma.database.update({ - where: { id }, - data: { - version, - } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + await prisma.database.update({ + where: { id }, + data: { + version + } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveDatabaseDestination(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { destinationId } = request.body; +export async function saveDatabaseDestination( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { destinationId } = request.body; - const { arch } = await listSettings(); - await prisma.database.update({ - where: { id }, - data: { destinationDocker: { connect: { id: destinationId } } } - }); + const { arch } = await listSettings(); + await prisma.database.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); - const { - destinationDockerId, - destinationDocker: { engine, id: dockerId }, - version, - type - } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); + const { + destinationDockerId, + destinationDocker: { engine, id: dockerId }, + version, + type + } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); - if (destinationDockerId) { - if (type && version) { - const baseImage = getDatabaseImage(type, arch); - executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` }) - } - } - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (destinationDockerId) { + if (type && version) { + const baseImage = getDatabaseImage(type, arch); + executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` }); + } + } + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseUsage(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - let usage = {}; + try { + const { id } = request.params; + const teamId = request.user.teamId; + let usage = {}; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - if (database.destinationDockerId) { - [usage] = await Promise.all([getContainerUsage(database.destinationDocker.id, id)]); - } - return { - usage - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + if (database.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(database.destinationDocker.id, id)]); + } + return { + usage + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function startDatabase(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; + try { + const teamId = request.user.teamId; + const { id } = request.params; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true, databaseSecret: true } - }); - const { arch } = await listSettings(); - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - const { - type, - destinationDockerId, - destinationDocker, - publicPort, - settings: { isPublic }, - databaseSecret - } = database; - const { privatePort, command, environmentVariables, image, volume, ulimits } = - generateDatabaseConfiguration(database, arch); + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true, databaseSecret: true } + }); + const { arch } = await listSettings(); + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + const { + type, + destinationDockerId, + destinationDocker, + publicPort, + settings: { isPublic }, + databaseSecret + } = database; + const { privatePort, command, environmentVariables, image, volume, ulimits } = + generateDatabaseConfiguration(database, arch); - const network = destinationDockerId && destinationDocker.network; - const volumeName = volume.split(':')[0]; - const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); + const network = destinationDockerId && destinationDocker.network; + const volumeName = volume.split(':')[0]; + const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); - const { workdir } = await createDirectories({ repository: type, buildId: id }); - if (databaseSecret.length > 0) { - databaseSecret.forEach((secret) => { - environmentVariables[secret.name] = decrypt(secret.value); - }); - } - const composeFile: ComposeFile = { - version: '3.8', - services: { - [id]: { - container_name: id, - image, - command, - environment: environmentVariables, - volumes: [volume], - ulimits, - labels, - ...defaultComposeConfiguration(network), - } - }, - networks: { - [network]: { - external: true - } - }, - volumes: { - [volumeName]: { - name: volumeName, - } - } - }; - const composeFileDestination = `${workdir}/docker-compose.yaml`; - await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeCommand({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` }) - if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); - return {}; - - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const { workdir } = await createDirectories({ repository: type, buildId: id }); + if (databaseSecret.length > 0) { + databaseSecret.forEach((secret) => { + environmentVariables[secret.name] = decrypt(secret.value); + }); + } + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image, + command, + environment: environmentVariables, + volumes: [volume], + ulimits, + labels, + ...defaultComposeConfiguration(network) + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [volumeName]: { + name: volumeName + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker compose -f ${composeFileDestination} up -d` + }); + if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function stopDatabase(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - const everStarted = await stopDatabaseContainer(database); - if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); - await prisma.database.update({ - where: { id }, - data: { - settings: { upsert: { update: { isPublic: false }, create: { isPublic: false } } } - } - }); - await prisma.database.update({ where: { id }, data: { publicPort: null } }); - return {}; - - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + const everStarted = await stopDatabaseContainer(database); + if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); + await prisma.database.update({ + where: { id }, + data: { + settings: { upsert: { update: { isPublic: false }, create: { isPublic: false } } } + } + }); + await prisma.database.update({ where: { id }, data: { publicPort: null } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseLogs(request: FastifyRequest) { - try { - const { id } = request.params; - let { since = 0 } = request.query - if (since !== 0) { - since = day(since).unix(); - } - const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ - where: { id }, - include: { destinationDocker: true } - }); - if (destinationDockerId) { - try { - // const found = await checkContainer({ dockerId, container: id }) - // if (found) { - const { default: ansi } = await import('strip-ansi') - const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` }) - const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout) - const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) - return { logs: sortedLogs } - // } - } catch (error) { - const { statusCode } = error; - if (statusCode === 404) { - return { - logs: [] - }; - } - } - } - return { - message: 'No logs found.' - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + let { since = 0 } = request.query; + if (since !== 0) { + since = day(since).unix(); + } + const { + destinationDockerId, + destinationDocker: { id: dockerId } + } = await prisma.database.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (destinationDockerId) { + try { + const { default: ansi } = await import('strip-ansi'); + const { stdout, stderr } = await executeCommand({ + dockerId, + command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` + }); + const stripLogsStdout = stdout + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + const stripLogsStderr = stderr + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + return { logs: stripLogsStderr.concat(stripLogsStdout) }; + } catch (error) { + const { statusCode } = error; + if (statusCode === 404) { + return { + logs: [] + }; + } + } + } + return { + message: 'No logs found.' + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteDatabase(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { force } = request.body; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - if (!force) { - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - if (database.destinationDockerId) { - const everStarted = await stopDatabaseContainer(database); - if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); - } - } - await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); - await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); - await prisma.database.delete({ where: { id } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { force } = request.body; + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + if (!force) { + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + if (database.destinationDockerId) { + const everStarted = await stopDatabaseContainer(database); + if (everStarted) + await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); + } + } + await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); + await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); + await prisma.database.delete({ where: { id } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveDatabase(request: FastifyRequest, reply: FastifyReply) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { - name, - defaultDatabase, - dbUser, - dbUserPassword, - rootUser, - rootUserPassword, - version, - isRunning - } = request.body; - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - if (isRunning) { - if (database.dbUserPassword !== dbUserPassword) { - await updatePasswordInDb(database, dbUser, dbUserPassword, false); - } else if (database.rootUserPassword !== rootUserPassword) { - await updatePasswordInDb(database, rootUser, rootUserPassword, true); - } - } - const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword); - const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword); - await prisma.database.update({ - where: { id }, - data: { - name, - defaultDatabase, - dbUser, - dbUserPassword: encryptedDbUserPassword, - rootUser, - rootUserPassword: encryptedRootUserPassword, - version - } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { + name, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + version, + isRunning + } = request.body; + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + if (isRunning) { + if (database.dbUserPassword !== dbUserPassword) { + await updatePasswordInDb(database, dbUser, dbUserPassword, false); + } else if (database.rootUserPassword !== rootUserPassword) { + await updatePasswordInDb(database, rootUser, rootUserPassword, true); + } + } + const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword); + const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword); + await prisma.database.update({ + where: { id }, + data: { + name, + defaultDatabase, + dbUser, + dbUserPassword: encryptedDbUserPassword, + rootUser, + rootUserPassword: encryptedRootUserPassword, + version + } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveDatabaseSettings(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { isPublic, appendOnly = true } = request.body; + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { isPublic, appendOnly = true } = request.body; - let publicPort = null + let publicPort = null; - const { destinationDocker: { remoteEngine, engine, remoteIpAddress } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }) + const { + destinationDocker: { remoteEngine, engine, remoteIpAddress } + } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); - if (isPublic) { - publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); - } - await prisma.database.update({ - where: { id }, - data: { - settings: { upsert: { update: { isPublic, appendOnly }, create: { isPublic, appendOnly } } } - } - }); - const database = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { destinationDocker: true, settings: true } - }); - const { arch } = await listSettings(); - if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + if (isPublic) { + publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); + } + await prisma.database.update({ + where: { id }, + data: { + settings: { upsert: { update: { isPublic, appendOnly }, create: { isPublic, appendOnly } } } + } + }); + const database = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, settings: true } + }); + const { arch } = await listSettings(); + if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database; - const { privatePort } = generateDatabaseConfiguration(database, arch); + const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database; + const { privatePort } = generateDatabaseConfiguration(database, arch); - if (destinationDockerId) { - if (isPublic) { - await prisma.database.update({ where: { id }, data: { publicPort } }); - await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); - } else { - await prisma.database.update({ where: { id }, data: { publicPort: null } }); - await stopTcpHttpProxy(id, destinationDocker, oldPublicPort); - } - } - return { publicPort } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (destinationDockerId) { + if (isPublic) { + await prisma.database.update({ where: { id }, data: { publicPort } }); + await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); + } else { + await prisma.database.update({ where: { id }, data: { publicPort: null } }); + await stopTcpHttpProxy(id, destinationDocker, oldPublicPort); + } + } + return { publicPort }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseSecrets(request: FastifyRequest) { - try { - const { id } = request.params - let secrets = await prisma.databaseSecret.findMany({ - where: { databaseId: id }, - orderBy: { createdAt: 'desc' } - }); - secrets = secrets.map((secret) => { - secret.value = decrypt(secret.value); - return secret; - }); + try { + const { id } = request.params; + let secrets = await prisma.databaseSecret.findMany({ + where: { databaseId: id }, + orderBy: { createdAt: 'desc' } + }); + secrets = secrets.map((secret) => { + secret.value = decrypt(secret.value); + return secret; + }); - return { - secrets - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + return { + secrets + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveDatabaseSecret(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - let { name, value, isNew } = request.body +export async function saveDatabaseSecret( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + let { name, value, isNew } = request.body; - if (isNew) { - const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } }); - if (found) { - throw `Secret ${name} already exists.` - } else { - value = encrypt(value.trim()); - await prisma.databaseSecret.create({ - data: { name, value, database: { connect: { id } } } - }); - } - } else { - value = encrypt(value.trim()); - const found = await prisma.databaseSecret.findFirst({ where: { databaseId: id, name } }); + if (isNew) { + const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } }); + if (found) { + throw `Secret ${name} already exists.`; + } else { + value = encrypt(value.trim()); + await prisma.databaseSecret.create({ + data: { name, value, database: { connect: { id } } } + }); + } + } else { + value = encrypt(value.trim()); + const found = await prisma.databaseSecret.findFirst({ where: { databaseId: id, name } }); - if (found) { - await prisma.databaseSecret.updateMany({ - where: { databaseId: id, name }, - data: { value } - }); - } else { - await prisma.databaseSecret.create({ - data: { name, value, database: { connect: { id } } } - }); - } - } - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (found) { + await prisma.databaseSecret.updateMany({ + where: { databaseId: id, name }, + data: { value } + }); + } else { + await prisma.databaseSecret.create({ + data: { name, value, database: { connect: { id } } } + }); + } + } + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteDatabaseSecret(request: FastifyRequest) { - try { - const { id } = request.params - const { name } = request.body - await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const { name } = request.body; + await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } diff --git a/apps/api/src/routes/api/v1/handlers.ts b/apps/api/src/routes/api/v1/handlers.ts index 438d8a275..382635800 100644 --- a/apps/api/src/routes/api/v1/handlers.ts +++ b/apps/api/src/routes/api/v1/handlers.ts @@ -1,6 +1,6 @@ -import { compareVersions } from "compare-versions"; -import cuid from "cuid"; -import bcrypt from "bcryptjs"; +import { compareVersions } from 'compare-versions'; +import cuid from 'cuid'; +import bcrypt from 'bcryptjs'; import fs from 'fs/promises'; import yaml from 'js-yaml'; import { @@ -13,12 +13,12 @@ import { uniqueName, version, sentryDSN, - executeCommand, -} from "../../../lib/common"; -import { scheduler } from "../../../lib/scheduler"; -import type { FastifyReply, FastifyRequest } from "fastify"; -import type { Login, Update } from "."; -import type { GetCurrentUser } from "./types"; + executeCommand +} from '../../../lib/common'; +import { scheduler } from '../../../lib/scheduler'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import type { Login, Update } from '.'; +import type { GetCurrentUser } from './types'; export async function hashPassword(password: string): Promise { const saltRounds = 15; @@ -29,9 +29,9 @@ export async function backup(request: FastifyRequest) { try { const { backupData } = request.params; let std = null; - const [id, backupType, type, zipped, storage] = backupData.split(':') - console.log(id, backupType, type, zipped, storage) - const database = await prisma.database.findUnique({ where: { id } }) + const [id, backupType, type, zipped, storage] = backupData.split(':'); + console.log(id, backupType, type, zipped, storage); + const database = await prisma.database.findUnique({ where: { id } }); if (database) { // await executeDockerCmd({ // dockerId: database.destinationDockerId, @@ -40,8 +40,7 @@ export async function backup(request: FastifyRequest) { std = await executeCommand({ dockerId: database.destinationDockerId, command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup` - }) - + }); } if (std.stdout) { return std.stdout; @@ -58,7 +57,7 @@ export async function cleanupManually(request: FastifyRequest) { try { const { serverId } = request.body; const destination = await prisma.destinationDocker.findUnique({ - where: { id: serverId }, + where: { id: serverId } }); await cleanupDockerStorage(destination.id, true, true); return {}; @@ -68,17 +67,25 @@ export async function cleanupManually(request: FastifyRequest) { } export async function refreshTags() { try { - const { default: got } = await import('got') + const { default: got } = await import('got'); try { if (isDev) { - const tags = await fs.readFile('./devTags.json', 'utf8') - await fs.writeFile('./tags.json', tags) + let tags = await fs.readFile('./devTags.json', 'utf8'); + try { + if (await fs.stat('./testTags.json')) { + const testTags = await fs.readFile('./testTags.json', 'utf8'); + if (testTags.length > 0) { + tags = JSON.parse(tags).concat(JSON.parse(testTags)); + } + } + } catch (error) {} + await fs.writeFile('./tags.json', tags); } else { - const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text() - await fs.writeFile('/app/tags.json', tags) + const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text(); + await fs.writeFile('/app/tags.json', tags); } } catch (error) { - console.log(error) + console.log(error); } return {}; @@ -88,17 +95,25 @@ export async function refreshTags() { } export async function refreshTemplates() { try { - const { default: got } = await import('got') + const { default: got } = await import('got'); try { if (isDev) { - const response = await fs.readFile('./devTemplates.yaml', 'utf8') - await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response))) + let templates = await fs.readFile('./devTemplates.yaml', 'utf8'); + try { + if (await fs.stat('./testTemplate.yaml')) { + templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8')); + } + } catch (error) {} + const response = await fs.readFile('./devTemplates.yaml', 'utf8'); + await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response))); } else { - const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text() - await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response))) + const response = await got + .get('https://get.coollabs.io/coolify/service-templates.yaml') + .text(); + await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response))); } } catch (error) { - console.log(error) + console.log(error); } return {}; } catch ({ status, message }) { @@ -107,28 +122,29 @@ export async function refreshTemplates() { } export async function checkUpdate(request: FastifyRequest) { try { - const { default: got } = await import('got') + const { default: got } = await import('got'); const isStaging = - request.hostname === "staging.coolify.io" || - request.hostname === "arm.coolify.io"; + request.hostname === 'staging.coolify.io' || request.hostname === 'arm.coolify.io'; const currentVersion = version; - const { coolify } = await got.get('https://get.coollabs.io/versions.json', { - searchParams: { - appId: process.env['COOLIFY_APP_ID'] || undefined, - version: currentVersion - } - }).json() + const { coolify } = await got + .get('https://get.coollabs.io/versions.json', { + searchParams: { + appId: process.env['COOLIFY_APP_ID'] || undefined, + version: currentVersion + } + }) + .json(); const latestVersion = coolify.main.version; const isUpdateAvailable = compareVersions(latestVersion, currentVersion); if (isStaging) { return { isUpdateAvailable: true, - latestVersion: "next", + latestVersion: 'next' }; } return { isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1, - latestVersion, + latestVersion }; } catch ({ status, message }) { return errorHandler({ status, message }); @@ -142,8 +158,13 @@ export async function update(request: FastifyRequest) { const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` }); await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` }); - await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` }); - await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` }); + await executeCommand({ + command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` + }); + await executeCommand({ + shell: true, + command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` + }); return {}; } else { await asyncSleep(2000); @@ -156,12 +177,12 @@ export async function update(request: FastifyRequest) { export async function resetQueue(request: FastifyRequest) { try { const teamId = request.user.teamId; - if (teamId === "0") { + if (teamId === '0') { await prisma.build.updateMany({ - where: { status: { in: ["queued", "running"] } }, - data: { status: "canceled" }, + where: { status: { in: ['queued', 'running'] } }, + data: { status: 'canceled' } }); - scheduler.workers.get("deployApplication").postMessage("cancel"); + scheduler.workers.get('deployApplication').postMessage('cancel'); } } catch ({ status, message }) { return errorHandler({ status, message }); @@ -170,7 +191,7 @@ export async function resetQueue(request: FastifyRequest) { export async function restartCoolify(request: FastifyRequest) { try { const teamId = request.user.teamId; - if (teamId === "0") { + if (teamId === '0') { if (!isDev) { await executeCommand({ command: `docker restart coolify` }); return {}; @@ -180,7 +201,7 @@ export async function restartCoolify(request: FastifyRequest) { } throw { status: 500, - message: "You are not authorized to restart Coolify.", + message: 'You are not authorized to restart Coolify.' }; } catch ({ status, message }) { return errorHandler({ status, message }); @@ -192,43 +213,52 @@ export async function showDashboard(request: FastifyRequest) { const userId = request.user.userId; const teamId = request.user.teamId; let applications = await prisma.application.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { settings: true, destinationDocker: true, teams: true }, + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { settings: true, destinationDocker: true, teams: true } }); const databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { settings: true, destinationDocker: true, teams: true }, + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { settings: true, destinationDocker: true, teams: true } }); const services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { destinationDocker: true, teams: true }, + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, teams: true } }); const gitSources = await prisma.gitSource.findMany({ - where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] }, - include: { teams: true }, + where: { + OR: [ + { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + { isSystemWide: true } + ] + }, + include: { teams: true } }); const destinations = await prisma.destinationDocker.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { teams: true }, + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true } }); const settings = await listSettings(); let foundUnconfiguredApplication = false; for (const application of applications) { - if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") { - foundUnconfiguredApplication = true + if ( + ((!application.buildPack || !application.branch) && !application.simpleDockerfile) || + !application.destinationDockerId || + (!application.settings?.isBot && !application?.fqdn && application.buildPack !== 'compose') + ) { + foundUnconfiguredApplication = true; } } let foundUnconfiguredService = false; for (const service of services) { if (!service.fqdn) { - foundUnconfiguredService = true + foundUnconfiguredService = true; } } let foundUnconfiguredDatabase = false; for (const database of databases) { if (!database.version) { - foundUnconfiguredDatabase = true + foundUnconfiguredDatabase = true; } } return { @@ -240,101 +270,94 @@ export async function showDashboard(request: FastifyRequest) { services, gitSources, destinations, - settings, + settings }; } catch ({ status, message }) { return errorHandler({ status, message }); } } -export async function login( - request: FastifyRequest, - reply: FastifyReply -) { +export async function login(request: FastifyRequest, reply: FastifyReply) { if (request.user) { - return reply.redirect("/dashboard"); + return reply.redirect('/dashboard'); } else { const { email, password, isLogin } = request.body || {}; if (!email || !password) { - throw { status: 500, message: "Email and password are required." }; + throw { status: 500, message: 'Email and password are required.' }; } const users = await prisma.user.count(); const userFound = await prisma.user.findUnique({ where: { email }, include: { teams: true, permission: true }, - rejectOnNotFound: false, + rejectOnNotFound: false }); if (!userFound && isLogin) { - throw { status: 500, message: "User not found." }; + throw { status: 500, message: 'User not found.' }; } const { isRegistrationEnabled, id } = await prisma.setting.findFirst(); let uid = cuid(); - let permission = "read"; + let permission = 'read'; let isAdmin = false; if (users === 0) { await prisma.setting.update({ where: { id }, - data: { isRegistrationEnabled: false }, + data: { isRegistrationEnabled: false } }); - uid = "0"; + uid = '0'; } if (userFound) { - if (userFound.type === "email") { - if (userFound.password === "RESETME") { + if (userFound.type === 'email') { + if (userFound.password === 'RESETME') { const hashedPassword = await hashPassword(password); if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) { - if (userFound.id === "0") { + if (userFound.id === '0') { await prisma.user.update({ where: { email: userFound.email }, - data: { password: "RESETME" }, + data: { password: 'RESETME' } }); } else { await prisma.user.update({ where: { email: userFound.email }, - data: { password: "RESETTIMEOUT" }, + data: { password: 'RESETTIMEOUT' } }); } throw { status: 500, - message: - "Password reset link has expired. Please request a new one.", + message: 'Password reset link has expired. Please request a new one.' }; } else { await prisma.user.update({ where: { email: userFound.email }, - data: { password: hashedPassword }, + data: { password: hashedPassword } }); return { userId: userFound.id, teamId: userFound.id, permission: userFound.permission, - isAdmin: true, + isAdmin: true }; } } - const passwordMatch = await bcrypt.compare( - password, - userFound.password - ); + const passwordMatch = await bcrypt.compare(password, userFound.password); if (!passwordMatch) { throw { status: 500, - message: "Wrong password or email address.", + message: 'Wrong password or email address.' }; } uid = userFound.id; isAdmin = true; } } else { - permission = "owner"; + permission = 'owner'; isAdmin = true; if (!isRegistrationEnabled) { throw { status: 404, - message: "Registration disabled by administrator.", + message: 'Registration disabled by administrator.' }; } const hashedPassword = await hashPassword(password); @@ -344,17 +367,17 @@ export async function login( id: uid, email, password: hashedPassword, - type: "email", + type: 'email', teams: { create: { id: uid, name: uniqueName(), - destinationDocker: { connect: { network: "coolify" } }, - }, + destinationDocker: { connect: { network: 'coolify' } } + } }, - permission: { create: { teamId: uid, permission: "owner" } }, + permission: { create: { teamId: uid, permission: 'owner' } } }, - include: { teams: true }, + include: { teams: true } }); } else { await prisma.user.create({ @@ -362,16 +385,16 @@ export async function login( id: uid, email, password: hashedPassword, - type: "email", + type: 'email', teams: { create: { id: uid, - name: uniqueName(), - }, + name: uniqueName() + } }, - permission: { create: { teamId: uid, permission: "owner" } }, + permission: { create: { teamId: uid, permission: 'owner' } } }, - include: { teams: true }, + include: { teams: true } }); } } @@ -379,23 +402,20 @@ export async function login( userId: uid, teamId: uid, permission, - isAdmin, + isAdmin }; } } -export async function getCurrentUser( - request: FastifyRequest, - fastify -) { +export async function getCurrentUser(request: FastifyRequest, fastify) { let token = null; const { teamId } = request.query; try { const user = await prisma.user.findUnique({ - where: { id: request.user.userId }, + where: { id: request.user.userId } }); if (!user) { - throw "User not found"; + throw 'User not found'; } } catch (error) { throw { status: 401, message: error }; @@ -404,17 +424,15 @@ export async function getCurrentUser( try { const user = await prisma.user.findFirst({ where: { id: request.user.userId, teams: { some: { id: teamId } } }, - include: { teams: true, permission: true }, + include: { teams: true, permission: true } }); if (user) { - const permission = user.permission.find( - (p) => p.teamId === teamId - ).permission; + const permission = user.permission.find((p) => p.teamId === teamId).permission; const payload = { ...request.user, teamId, permission: permission || null, - isAdmin: permission === "owner" || permission === "admin", + isAdmin: permission === 'owner' || permission === 'admin' }; token = fastify.jwt.sign(payload); } @@ -422,12 +440,14 @@ export async function getCurrentUser( // No new token -> not switching teams } } - const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } }) + const pendingInvitations = await prisma.teamInvitation.findMany({ + where: { uid: request.user.userId } + }); return { - settings: await prisma.setting.findUnique({ where: { id: "0" } }), + settings: await prisma.setting.findUnique({ where: { id: '0' } }), sentryDSN, pendingInvitations, token, - ...request.user, + ...request.user }; } diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 0cf51b4ea..3b1319202 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -4,966 +4,1160 @@ import yaml from 'js-yaml'; import bcrypt from 'bcryptjs'; import cuid from 'cuid'; -import { prisma, uniqueName, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, checkDomainsIsValidInDNS, checkExposedPort, listSettings, generateToken, executeCommand } from '../../../../lib/common'; +import { + prisma, + uniqueName, + getServiceFromDB, + getContainerUsage, + isDomainConfigured, + fixType, + decrypt, + encrypt, + ComposeFile, + getFreePublicPort, + getDomain, + errorHandler, + generatePassword, + isDev, + stopTcpHttpProxy, + checkDomainsIsValidInDNS, + checkExposedPort, + listSettings, + generateToken, + executeCommand +} from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; -import { checkContainer, } from '../../../../lib/docker'; +import { checkContainer } from '../../../../lib/docker'; import { removeService } from '../../../../lib/services/common'; import { getTags, getTemplates } from '../../../../lib/services'; -import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types'; +import type { + ActivateWordpressFtp, + CheckService, + CheckServiceDomain, + DeleteServiceSecret, + DeleteServiceStorage, + GetServiceLogs, + SaveService, + SaveServiceDestination, + SaveServiceSecret, + SaveServiceSettings, + SaveServiceStorage, + SaveServiceType, + SaveServiceVersion, + ServiceStartStop, + SetGlitchTipSettings, + SetWordpressSettings +} from './types'; import type { OnlyId } from '../../../../types'; export async function listServices(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { teams: true, destinationDocker: true } - }); - return { - services - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const services = await prisma.service.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true, destinationDocker: true } + }); + return { + services + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function newService(request: FastifyRequest, reply: FastifyReply) { - try { - const teamId = request.user.teamId; - const name = uniqueName(); + try { + const teamId = request.user.teamId; + const name = uniqueName(); - const { id } = await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } }); - return reply.status(201).send({ id }); - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const { id } = await prisma.service.create({ + data: { name, teams: { connect: { id: teamId } } } + }); + return reply.status(201).send({ id }); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function cleanupUnconfiguredServices(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - let services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { destinationDocker: true, teams: true }, - }); - for (const service of services) { - if (!service.fqdn) { - if (service.destinationDockerId) { - const { stdout: containers } = await executeCommand({ - dockerId: service.destinationDockerId, - command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}` - }) - if (containers) { - const containerArray = containers.split('\n'); - if (containerArray.length > 0) { - for (const container of containerArray) { - await executeCommand({ dockerId: service.destinationDockerId, command: `docker stop -t 0 ${container}` }) - await executeCommand({ dockerId: service.destinationDockerId, command: `docker rm --force ${container}` }) - } - } - } - } - await removeService({ id: service.id }); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + let services = await prisma.service.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, teams: true } + }); + for (const service of services) { + if (!service.fqdn) { + if (service.destinationDockerId) { + const { stdout: containers } = await executeCommand({ + dockerId: service.destinationDockerId, + command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}` + }); + if (containers) { + const containerArray = containers.split('\n'); + if (containerArray.length > 0) { + for (const container of containerArray) { + await executeCommand({ + dockerId: service.destinationDockerId, + command: `docker stop -t 0 ${container}` + }); + await executeCommand({ + dockerId: service.destinationDockerId, + command: `docker rm --force ${container}` + }); + } + } + } + } + await removeService({ id: service.id }); + } + } + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceStatus(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, settings } = service; - let payload = {} - if (destinationDockerId) { - const { stdout: containers } = await executeCommand({ - dockerId: service.destinationDocker.id, - command: - `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'` - }); - if (containers) { - const containersArray = containers.trim().split('\n'); - if (containersArray.length > 0 && containersArray[0] !== '') { - const templates = await getTemplates(); - let template = templates.find(t => t.type === service.type); - const templateStr = JSON.stringify(template) - if (templateStr) { - template = JSON.parse(templateStr.replaceAll('$$id', service.id)); - } - for (const container of containersArray) { - let isRunning = false; - let isExited = false; - let isRestarting = false; - let isExcluded = false; - const containerObj = JSON.parse(container); - const exclude = template?.services[containerObj.Names]?.exclude; - if (exclude) { - payload[containerObj.Names] = { - status: { - isExcluded: true, - isRunning: false, - isExited: false, - isRestarting: false, - } - } - continue; - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const service = await getServiceFromDB({ id, teamId }); + const { destinationDockerId, settings } = service; + let payload = {}; + if (destinationDockerId) { + const { stdout: containers } = await executeCommand({ + dockerId: service.destinationDocker.id, + command: `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'` + }); + if (containers) { + const containersArray = containers.trim().split('\n'); + if (containersArray.length > 0 && containersArray[0] !== '') { + const templates = await getTemplates(); + let template = templates.find((t) => t.type === service.type); + const templateStr = JSON.stringify(template); + if (templateStr) { + template = JSON.parse(templateStr.replaceAll('$$id', service.id)); + } + for (const container of containersArray) { + let isRunning = false; + let isExited = false; + let isRestarting = false; + let isExcluded = false; + const containerObj = JSON.parse(container); + const exclude = template?.services[containerObj.Names]?.exclude; + if (exclude) { + payload[containerObj.Names] = { + status: { + isExcluded: true, + isRunning: false, + isExited: false, + isRestarting: false + } + }; + continue; + } - const status = containerObj.State - if (status === 'running') { - isRunning = true; - } - if (status === 'exited') { - isExited = true; - } - if (status === 'restarting') { - isRestarting = true; - } - payload[containerObj.Names] = { - status: { - isExcluded, - isRunning, - isExited, - isRestarting - } - } - } - } - } - - } - return payload - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const status = containerObj.State; + if (status === 'running') { + isRunning = true; + } + if (status === 'exited') { + isExited = true; + } + if (status === 'restarting') { + isRestarting = true; + } + payload[containerObj.Names] = { + status: { + isExcluded, + isRunning, + isExited, + isRestarting + } + }; + } + } + } + } + return payload; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) { - const templates = await getTemplates() - const foundTemplate = templates.find(t => fixType(t.type) === service.type) - let parsedTemplate = {} - if (foundTemplate) { - if (!isDeploy) { - for (const [key, value] of Object.entries(foundTemplate.services)) { - const realKey = key.replace('$$id', service.id) - let name = value.name - if (!name) { - if (Object.keys(foundTemplate.services).length === 1) { - name = foundTemplate.name || service.name.toLowerCase() - } else { - if (key === '$$id') { - name = foundTemplate.name || key.replaceAll('$$id-', '') || service.name.toLowerCase() - } else { - name = key.replaceAll('$$id-', '') || service.name.toLowerCase() - } - } - } - parsedTemplate[realKey] = { - value, - name, - documentation: value.documentation || foundTemplate.documentation || 'https://docs.coollabs.io', - image: value.image, - files: value?.files, - environment: [], - fqdns: [], - hostPorts: [], - proxy: {} - } - if (value.environment?.length > 0) { - for (const env of value.environment) { - let [envKey, ...envValue] = env.split('=') - envValue = envValue.join("=") - let variable = null - if (foundTemplate?.variables) { - variable = foundTemplate?.variables.find(v => v.name === envKey) || foundTemplate?.variables.find(v => v.id === envValue) - } - if (variable) { - const id = variable.id.replaceAll('$$', '') - const label = variable?.label - const description = variable?.description - const defaultValue = variable?.defaultValue - const main = variable?.main || '$$id' - const type = variable?.type || 'input' - const placeholder = variable?.placeholder || '' - const readOnly = variable?.readOnly || false - const required = variable?.required || false - if (envValue.startsWith('$$config') || variable?.showOnConfiguration) { - if (envValue.startsWith('$$config_coolify')) { - continue - } - parsedTemplate[realKey].environment.push( - { id, name: envKey, value: envValue, main, label, description, defaultValue, type, placeholder, required, readOnly } - ) - } - } +export async function parseAndFindServiceTemplates( + service: any, + workdir?: string, + isDeploy: boolean = false +) { + const templates = await getTemplates(); + const foundTemplate = templates.find((t) => fixType(t.type) === service.type); + let parsedTemplate = {}; + if (foundTemplate) { + if (!isDeploy) { + for (const [key, value] of Object.entries(foundTemplate.services)) { + const realKey = key.replace('$$id', service.id); + let name = value.name; + if (!name) { + if (Object.keys(foundTemplate.services).length === 1) { + name = foundTemplate.name || service.name.toLowerCase(); + } else { + if (key === '$$id') { + name = + foundTemplate.name || key.replaceAll('$$id-', '') || service.name.toLowerCase(); + } else { + name = key.replaceAll('$$id-', '') || service.name.toLowerCase(); + } + } + } + parsedTemplate[realKey] = { + value, + name, + documentation: + value.documentation || foundTemplate.documentation || 'https://docs.coollabs.io', + image: value.image, + files: value?.files, + environment: [], + fqdns: [], + hostPorts: [], + proxy: {} + }; + if (value.environment?.length > 0) { + for (const env of value.environment) { + let [envKey, ...envValue] = env.split('='); + envValue = envValue.join('='); + let variable = null; + if (foundTemplate?.variables) { + variable = + foundTemplate?.variables.find((v) => v.name === envKey) || + foundTemplate?.variables.find((v) => v.id === envValue); + } + if (variable) { + const id = variable.id.replaceAll('$$', ''); + const label = variable?.label; + const description = variable?.description; + const defaultValue = variable?.defaultValue; + const main = variable?.main || '$$id'; + const type = variable?.type || 'input'; + const placeholder = variable?.placeholder || ''; + const readOnly = variable?.readOnly || false; + const required = variable?.required || false; + if (envValue.startsWith('$$config') || variable?.showOnConfiguration) { + if (envValue.startsWith('$$config_coolify')) { + continue; + } + parsedTemplate[realKey].environment.push({ + id, + name: envKey, + value: envValue, + main, + label, + description, + defaultValue, + type, + placeholder, + required, + readOnly + }); + } + } + } + } + if (value?.proxy && value.proxy.length > 0) { + for (const proxyValue of value.proxy) { + if (proxyValue.domain) { + const variable = foundTemplate?.variables.find((v) => v.id === proxyValue.domain); + if (variable) { + const { id, name, label, description, defaultValue, required = false } = variable; + const found = await prisma.serviceSetting.findFirst({ + where: { serviceId: service.id, variableName: proxyValue.domain } + }); + parsedTemplate[realKey].fqdns.push({ + id, + name, + value: found?.value || '', + label, + description, + defaultValue, + required + }); + } + } + if (proxyValue.hostPort) { + const variable = foundTemplate?.variables.find((v) => v.id === proxyValue.hostPort); + if (variable) { + const { id, name, label, description, defaultValue, required = false } = variable; + const found = await prisma.serviceSetting.findFirst({ + where: { serviceId: service.id, variableName: proxyValue.hostPort } + }); + parsedTemplate[realKey].hostPorts.push({ + id, + name, + value: found?.value || '', + label, + description, + defaultValue, + required + }); + } + } + } + } + } + } else { + parsedTemplate = foundTemplate; + } + let strParsedTemplate = JSON.stringify(parsedTemplate); - } - } - if (value?.proxy && value.proxy.length > 0) { - for (const proxyValue of value.proxy) { - if (proxyValue.domain) { - const variable = foundTemplate?.variables.find(v => v.id === proxyValue.domain) - if (variable) { - const { id, name, label, description, defaultValue, required = false } = variable - const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.domain } }) - parsedTemplate[realKey].fqdns.push( - { id, name, value: found?.value || '', label, description, defaultValue, required } - ) - } - } - if (proxyValue.hostPort) { - const variable = foundTemplate?.variables.find(v => v.id === proxyValue.hostPort) - if (variable) { - const { id, name, label, description, defaultValue, required = false } = variable - const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.hostPort } }) - parsedTemplate[realKey].hostPorts.push( - { id, name, value: found?.value || '', label, description, defaultValue, required } - ) - } - } - } - } - } - } else { - parsedTemplate = foundTemplate - } - let strParsedTemplate = JSON.stringify(parsedTemplate) + // replace $$id and $$workdir + strParsedTemplate = strParsedTemplate.replaceAll('$$id', service.id); + strParsedTemplate = strParsedTemplate.replaceAll( + '$$core_version', + service.version || foundTemplate.defaultVersion + ); - // replace $$id and $$workdir - strParsedTemplate = strParsedTemplate.replaceAll('$$id', service.id) - strParsedTemplate = strParsedTemplate.replaceAll('$$core_version', service.version || foundTemplate.defaultVersion) + // replace $$workdir + if (workdir) { + strParsedTemplate = strParsedTemplate.replaceAll('$$workdir', workdir); + } - // replace $$workdir - if (workdir) { - strParsedTemplate = strParsedTemplate.replaceAll('$$workdir', workdir) - } + // replace $$config + if (service.serviceSetting.length > 0) { + for (const setting of service.serviceSetting) { + const { value, variableName } = setting; + const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi'); + if (value === '$$generate_fqdn') { + strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"'); + } else if (value === '$$generate_fqdn_slash') { + strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"'); + } else if (value === '$$generate_domain') { + strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"'); + } else if (service.destinationDocker?.network && value === '$$generate_network') { + strParsedTemplate = strParsedTemplate.replaceAll( + regex, + service.destinationDocker.network + '"' + ); + } else { + strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"'); + } + } + } - // replace $$config - if (service.serviceSetting.length > 0) { - for (const setting of service.serviceSetting) { - const { value, variableName } = setting - const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi') - if (value === '$$generate_fqdn') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"') - } else if (value === '$$generate_fqdn_slash') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"') - } else if (value === '$$generate_domain') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"') - } else if (service.destinationDocker?.network && value === '$$generate_network') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, service.destinationDocker.network + '"') - } else { - strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"') - } - } - } - - // replace $$secret - if (service.serviceSecret.length > 0) { - for (const secret of service.serviceSecret) { - let { name, value } = secret - name = name.toLowerCase() - const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}`, 'gi') - const regex = new RegExp(`\\$\\$secret_${name}`, 'gi') - if (value) { - strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10)) - strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"")) - } else { - strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '') - strParsedTemplate = strParsedTemplate.replaceAll(regex, '') - } - } - } - parsedTemplate = JSON.parse(strParsedTemplate) - } - return parsedTemplate + // replace $$secret + if (service.serviceSecret.length > 0) { + for (const secret of service.serviceSecret) { + let { name, value } = secret; + name = name.toLowerCase(); + const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}`, 'gi'); + const regex = new RegExp(`\\$\\$secret_${name}`, 'gi'); + if (value) { + strParsedTemplate = strParsedTemplate.replaceAll( + regexHashed, + bcrypt.hashSync(value.replaceAll('"', '\\"'), 10) + ); + strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll('"', '\\"')); + } else { + strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, ''); + strParsedTemplate = strParsedTemplate.replaceAll(regex, ''); + } + } + } + parsedTemplate = JSON.parse(strParsedTemplate); + } + return parsedTemplate; } export async function getService(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const service = await getServiceFromDB({ id, teamId }); - if (!service) { - throw { status: 404, message: 'Service not found.' } - } - let template = {} - let tags = [] - if (service.type) { - template = await parseAndFindServiceTemplates(service) - tags = await getTags(service.type) - } - return { - settings: await listSettings(), - service, - template, - tags - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const service = await getServiceFromDB({ id, teamId }); + if (!service) { + throw { status: 404, message: 'Service not found.' }; + } + let template = {}; + let tags = []; + if (service.type) { + template = await parseAndFindServiceTemplates(service); + tags = await getTags(service.type); + } + return { + settings: await listSettings(), + service, + template, + tags + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceType(request: FastifyRequest) { - try { - return { - services: await getTemplates() - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + return { + services: await getTemplates() + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceType(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { type } = request.body; - const templates = await getTemplates() - let foundTemplate = templates.find(t => fixType(t.type) === fixType(type)) - if (foundTemplate) { - foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id)) - if (foundTemplate.variables) { - if (foundTemplate.variables.length > 0) { - for (const variable of foundTemplate.variables) { - const { defaultValue } = variable; - const regex = /^\$\$.*\((\d+)\)$/g; - const length = Number(regex.exec(defaultValue)?.[1]) || undefined - if (variable.defaultValue.startsWith('$$generate_password')) { - variable.value = generatePassword({ length }); - } else if (variable.defaultValue.startsWith('$$generate_hex')) { - variable.value = generatePassword({ length, isHex: true }); - } else if (variable.defaultValue.startsWith('$$generate_username')) { - variable.value = cuid(); - } else if (variable.defaultValue.startsWith('$$generate_token')) { - variable.value = generateToken() - } else { - variable.value = variable.defaultValue || ''; - } - const foundVariableSomewhereElse = foundTemplate.variables.find(v => v.defaultValue.includes(variable.id)) - if (foundVariableSomewhereElse) { - foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(variable.id, variable.value) - } - } - } - for (const variable of foundTemplate.variables) { - if (variable.id.startsWith('$$secret_')) { - const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } }) - if (!found) { - await prisma.serviceSecret.create({ - data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } } - }) - } +export async function saveServiceType( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { type } = request.body; + const templates = await getTemplates(); + let foundTemplate = templates.find((t) => fixType(t.type) === fixType(type)); + if (foundTemplate) { + foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id)); + if (foundTemplate.variables) { + if (foundTemplate.variables.length > 0) { + for (const variable of foundTemplate.variables) { + const { defaultValue } = variable; + const regex = /^\$\$.*\((\d+)\)$/g; + const length = Number(regex.exec(defaultValue)?.[1]) || undefined; + if (variable.defaultValue.startsWith('$$generate_password')) { + variable.value = generatePassword({ length }); + } else if (variable.defaultValue.startsWith('$$generate_hex')) { + variable.value = generatePassword({ length, isHex: true }); + } else if (variable.defaultValue.startsWith('$$generate_username')) { + variable.value = cuid(); + } else if (variable.defaultValue.startsWith('$$generate_token')) { + variable.value = generateToken(); + } else { + variable.value = variable.defaultValue || ''; + } + const foundVariableSomewhereElse = foundTemplate.variables.find((v) => + v.defaultValue.includes(variable.id) + ); + if (foundVariableSomewhereElse) { + foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll( + variable.id, + variable.value + ); + } + } + } + for (const variable of foundTemplate.variables) { + if (variable.id.startsWith('$$secret_')) { + const found = await prisma.serviceSecret.findFirst({ + where: { name: variable.name, serviceId: id } + }); + if (!found) { + await prisma.serviceSecret.create({ + data: { + name: variable.name, + value: encrypt(variable.value) || '', + service: { connect: { id } } + } + }); + } + } + if (variable.id.startsWith('$$config_')) { + const found = await prisma.serviceSetting.findFirst({ + where: { name: variable.name, serviceId: id } + }); + if (!found) { + await prisma.serviceSetting.create({ + data: { + name: variable.name, + value: variable.value.toString(), + variableName: variable.id, + service: { connect: { id } } + } + }); + } + } + } + } + for (const service of Object.keys(foundTemplate.services)) { + if (foundTemplate.services[service].volumes) { + for (const volume of foundTemplate.services[service].volumes) { + const [volumeName, path] = volume.split(':'); + if (!volumeName.startsWith('/')) { + const found = await prisma.servicePersistentStorage.findFirst({ + where: { volumeName, serviceId: id } + }); + if (!found) { + await prisma.servicePersistentStorage.create({ + data: { + volumeName, + path, + containerId: service, + predefined: true, + service: { connect: { id } } + } + }); + } + } + } + } + } + await prisma.service.update({ + where: { id }, + data: { + type, + version: foundTemplate.defaultVersion, + templateVersion: foundTemplate.templateVersion + } + }); - } - if (variable.id.startsWith('$$config_')) { - const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } }) - if (!found) { - await prisma.serviceSetting.create({ - data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } } - }) - } - } - } - } - for (const service of Object.keys(foundTemplate.services)) { - if (foundTemplate.services[service].volumes) { - for (const volume of foundTemplate.services[service].volumes) { - const [volumeName, path] = volume.split(':') - if (!volumeName.startsWith('/')) { - const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } }) - if (!found) { - await prisma.servicePersistentStorage.create({ - data: { volumeName, path, containerId: service, predefined: true, service: { connect: { id } } } - }); - } - } - } - } - } - await prisma.service.update({ where: { id }, data: { type, version: foundTemplate.defaultVersion, templateVersion: foundTemplate.templateVersion } }) - - if (type.startsWith('wordpress')) { - await prisma.service.update({ where: { id }, data: { wordpress: { create: {} } } }) - } - return reply.code(201).send() - } else { - throw { status: 404, message: 'Service type not found.' } - } - - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (type.startsWith('wordpress')) { + await prisma.service.update({ where: { id }, data: { wordpress: { create: {} } } }); + } + return reply.code(201).send(); + } else { + throw { status: 404, message: 'Service type not found.' }; + } + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceVersion(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { version } = request.body; - await prisma.service.update({ - where: { id }, - data: { version } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveServiceVersion( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { version } = request.body; + await prisma.service.update({ + where: { id }, + data: { version } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceDestination(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { destinationId } = request.body; - await prisma.service.update({ - where: { id }, - data: { destinationDocker: { connect: { id: destinationId } } } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveServiceDestination( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { destinationId } = request.body; + await prisma.service.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceUsage(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - let usage = {}; - - const service = await getServiceFromDB({ id, teamId }); - if (service.destinationDockerId) { - [usage] = await Promise.all([getContainerUsage(service.destinationDocker.id, id)]); - } - return { - usage - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + let usage = {}; + const service = await getServiceFromDB({ id, teamId }); + if (service.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(service.destinationDocker.id, id)]); + } + return { + usage + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceLogs(request: FastifyRequest) { - try { - const { id, containerId } = request.params; - let { since = 0 } = request.query - if (since !== 0) { - since = day(since).unix(); - } - const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.service.findUnique({ - where: { id }, - include: { destinationDocker: true } - }); - if (destinationDockerId) { - try { - const { default: ansi } = await import('strip-ansi') - const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` }) - const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout) - const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) - return { logs: sortedLogs } - // } - } catch (error) { - const { statusCode, stderr } = error; - if (stderr.startsWith('Error: No such container')) { - return { logs: [], noContainer: true } - } - if (statusCode === 404) { - return { - logs: [] - }; - } - } - } - return { - message: 'No logs found.' - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id, containerId } = request.params; + let { since = 0 } = request.query; + if (since !== 0) { + since = day(since).unix(); + } + const { + destinationDockerId, + destinationDocker: { id: dockerId } + } = await prisma.service.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (destinationDockerId) { + try { + const { default: ansi } = await import('strip-ansi'); + const { stdout, stderr } = await executeCommand({ + dockerId, + command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` + }); + const stripLogsStdout = stdout + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + const stripLogsStderr = stderr + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + return { logs: stripLogsStderr.concat(stripLogsStdout) }; + // } + } catch (error) { + const { statusCode, stderr } = error; + if (stderr.startsWith('Error: No such container')) { + return { logs: [], noContainer: true }; + } + if (statusCode === 404) { + return { + logs: [] + }; + } + } + } + return { + message: 'No logs found.' + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteService(request: FastifyRequest) { - try { - const { id } = request.params; - await removeService({ id }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + await removeService({ id }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceSettings(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { dualCerts } = request.body; - await prisma.service.update({ - where: { id }, - data: { dualCerts } - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveServiceSettings( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { dualCerts } = request.body; + await prisma.service.update({ + where: { id }, + data: { dualCerts } + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function checkServiceDomain(request: FastifyRequest) { - try { - const { id } = request.params - const { domain } = request.query - const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } }) - // TODO: Disabled this because it is having problems with remote docker engines. - // return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const { domain } = request.query; + const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } }); + // TODO: Disabled this because it is having problems with remote docker engines. + // return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function checkService(request: FastifyRequest) { - try { - const { id } = request.params; - let { fqdn, exposePort, forceSave, dualCerts, otherFqdn = false } = request.body; + try { + const { id } = request.params; + let { fqdn, exposePort, forceSave, dualCerts, otherFqdn = false } = request.body; - const domainsList = await prisma.serviceSetting.findMany({ where: { variableName: { startsWith: '$$config_coolify_fqdn' } } }) + const domainsList = await prisma.serviceSetting.findMany({ + where: { variableName: { startsWith: '$$config_coolify_fqdn' } } + }); - if (fqdn) fqdn = fqdn.toLowerCase(); - if (exposePort) exposePort = Number(exposePort); + if (fqdn) fqdn = fqdn.toLowerCase(); + if (exposePort) exposePort = Number(exposePort); - const { destinationDocker: { remoteIpAddress, remoteEngine, engine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }) - const { isDNSCheckEnabled } = await prisma.setting.findFirst({}); + const { + destinationDocker: { remoteIpAddress, remoteEngine, engine }, + exposePort: configuredPort + } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }); + const { isDNSCheckEnabled } = await prisma.setting.findFirst({}); - let found = await isDomainConfigured({ id, fqdn, remoteIpAddress, checkOwn: otherFqdn }); - if (found) { - throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } - } - if (domainsList.find(d => getDomain(d.value) === getDomain(fqdn))) { - throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } - } - if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress }) - // TODO: Disabled this because it is having problems with remote docker engines. - // if (isDNSCheckEnabled && !isDev && !forceSave) { - // let hostname = request.hostname.split(':')[0]; - // if (remoteEngine) hostname = remoteIpAddress; - // return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }); - // } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + let found = await isDomainConfigured({ id, fqdn, remoteIpAddress, checkOwn: otherFqdn }); + if (found) { + throw { + status: 500, + message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` + }; + } + if (domainsList.find((d) => getDomain(d.value) === getDomain(fqdn))) { + throw { + status: 500, + message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` + }; + } + if (exposePort) + await checkExposedPort({ + id, + configuredPort, + exposePort, + engine, + remoteEngine, + remoteIpAddress + }); + // TODO: Disabled this because it is having problems with remote docker engines. + // if (isDNSCheckEnabled && !isDev && !forceSave) { + // let hostname = request.hostname.split(':')[0]; + // if (remoteEngine) hostname = remoteIpAddress; + // return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }); + // } + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveService(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - let { name, fqdn, exposePort, type, serviceSetting, version } = request.body; - if (fqdn) fqdn = fqdn.toLowerCase(); - if (exposePort) exposePort = Number(exposePort); - type = fixType(type) + try { + const { id } = request.params; + let { name, fqdn, exposePort, type, serviceSetting, version } = request.body; + if (fqdn) fqdn = fqdn.toLowerCase(); + if (exposePort) exposePort = Number(exposePort); + type = fixType(type); - const data = { - fqdn, - name, - exposePort, - version, - } - const templates = await getTemplates() - const service = await prisma.service.findUnique({ where: { id } }) - const foundTemplate = templates.find(t => fixType(t.type) === fixType(service.type)) - for (const setting of serviceSetting) { - let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting - if (value) { - if (changed) { - await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } }) - } - if (isNew) { - if (!variableName) { - variableName = foundTemplate?.variables.find(v => v.name === name).id - } - await prisma.serviceSetting.create({ data: { name, value, variableName, service: { connect: { id } } } }) - } - } - } - await prisma.service.update({ - where: { id }, data - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const data = { + fqdn, + name, + exposePort, + version + }; + const templates = await getTemplates(); + const service = await prisma.service.findUnique({ where: { id } }); + const foundTemplate = templates.find((t) => fixType(t.type) === fixType(service.type)); + for (const setting of serviceSetting) { + let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting; + if (value) { + if (changed) { + await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } }); + } + if (isNew) { + if (!variableName) { + variableName = foundTemplate?.variables.find((v) => v.name === name).id; + } + await prisma.serviceSetting.create({ + data: { name, value, variableName, service: { connect: { id } } } + }); + } + } + } + await prisma.service.update({ + where: { id }, + data + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceSecrets(request: FastifyRequest) { - try { - const { id } = request.params - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - let secrets = await prisma.serviceSecret.findMany({ - where: { serviceId: id }, - orderBy: { createdAt: 'desc' } - }); - const templates = await getTemplates() - const foundTemplate = templates.find(t => fixType(t.type) === service.type) - secrets = secrets.map((secret) => { - const foundVariable = foundTemplate?.variables.find(v => v.name === secret.name) || null - if (foundVariable) { - secret.readOnly = foundVariable.readOnly - } - secret.value = decrypt(secret.value); - return secret; - }); + try { + const { id } = request.params; + const teamId = request.user.teamId; + const service = await getServiceFromDB({ id, teamId }); + let secrets = await prisma.serviceSecret.findMany({ + where: { serviceId: id }, + orderBy: { createdAt: 'desc' } + }); + const templates = await getTemplates(); + const foundTemplate = templates.find((t) => fixType(t.type) === service.type); + secrets = secrets.map((secret) => { + const foundVariable = foundTemplate?.variables.find((v) => v.name === secret.name) || null; + if (foundVariable) { + secret.readOnly = foundVariable.readOnly; + } + secret.value = decrypt(secret.value); + return secret; + }); - return { - secrets - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + return { + secrets + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceSecret(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - let { name, value, isNew } = request.body - if (isNew) { - const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } }); - if (found) { - throw `Secret ${name} already exists.` - } else { - value = encrypt(value.trim()); - await prisma.serviceSecret.create({ - data: { name, value, service: { connect: { id } } } - }); - } - } else { - value = encrypt(value.trim()); - const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } }); +export async function saveServiceSecret( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + let { name, value, isNew } = request.body; + if (isNew) { + const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } }); + if (found) { + throw `Secret ${name} already exists.`; + } else { + value = encrypt(value.trim()); + await prisma.serviceSecret.create({ + data: { name, value, service: { connect: { id } } } + }); + } + } else { + value = encrypt(value.trim()); + const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } }); - if (found) { - await prisma.serviceSecret.updateMany({ - where: { serviceId: id, name }, - data: { value } - }); - } else { - await prisma.serviceSecret.create({ - data: { name, value, service: { connect: { id } } } - }); - } - } - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (found) { + await prisma.serviceSecret.updateMany({ + where: { serviceId: id, name }, + data: { value } + }); + } else { + await prisma.serviceSecret.create({ + data: { name, value, service: { connect: { id } } } + }); + } + } + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteServiceSecret(request: FastifyRequest) { - try { - const { id } = request.params - const { name } = request.body - await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const { name } = request.body; + await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceStorages(request: FastifyRequest) { - try { - const { id } = request.params - const persistentStorages = await prisma.servicePersistentStorage.findMany({ - where: { serviceId: id } - }); - return { - persistentStorages - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const persistentStorages = await prisma.servicePersistentStorage.findMany({ + where: { serviceId: id } + }); + return { + persistentStorages + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceStorage(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const { path, isNewStorage, storageId, containerId } = request.body +export async function saveServiceStorage( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { path, isNewStorage, storageId, containerId } = request.body; - if (isNewStorage) { - const volumeName = `${id}-custom${path.replace(/\//gi, '-')}` - const found = await prisma.servicePersistentStorage.findFirst({ where: { path, containerId } }); - if (found) { - throw { status: 500, message: 'Persistent storage already exists for this container and path.' } - } - await prisma.servicePersistentStorage.create({ - data: { path, volumeName, containerId, service: { connect: { id } } } - }); - } else { - await prisma.servicePersistentStorage.update({ - where: { id: storageId }, - data: { path, containerId } - }); - } - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (isNewStorage) { + const volumeName = `${id}-custom${path.replace(/\//gi, '-')}`; + const found = await prisma.servicePersistentStorage.findFirst({ + where: { path, containerId } + }); + if (found) { + throw { + status: 500, + message: 'Persistent storage already exists for this container and path.' + }; + } + await prisma.servicePersistentStorage.create({ + data: { path, volumeName, containerId, service: { connect: { id } } } + }); + } else { + await prisma.servicePersistentStorage.update({ + where: { id: storageId }, + data: { path, containerId } + }); + } + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteServiceStorage(request: FastifyRequest) { - try { - const { storageId } = request.body - await prisma.servicePersistentStorage.deleteMany({ where: { id: storageId } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { storageId } = request.body; + await prisma.servicePersistentStorage.deleteMany({ where: { id: storageId } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function setSettingsService(request: FastifyRequest, reply: FastifyReply) { - try { - const { type } = request.params - if (type === 'wordpress') { - return await setWordpressSettings(request, reply) - } - if (type === 'glitchtip') { - return await setGlitchTipSettings(request, reply) - } - throw `Service type ${type} not supported.` - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function setSettingsService( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { type } = request.params; + if (type === 'wordpress') { + return await setWordpressSettings(request, reply); + } + if (type === 'glitchtip') { + return await setGlitchTipSettings(request, reply); + } + throw `Service type ${type} not supported.`; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -async function setGlitchTipSettings(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } = request.body - await prisma.glitchTip.update({ - where: { serviceId: id }, - data: { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +async function setGlitchTipSettings( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } = request.body; + await prisma.glitchTip.update({ + where: { serviceId: id }, + data: { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -async function setWordpressSettings(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const { ownMysql } = request.body - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ownMysql } - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +async function setWordpressSettings( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { ownMysql } = request.body; + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ownMysql } + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } - export async function activatePlausibleUsers(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const teamId = request.user.teamId; - const { - destinationDockerId, - destinationDocker, - serviceSecret - } = await getServiceFromDB({ id, teamId }); - if (destinationDockerId) { - const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL'); - if (databaseUrl) { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"` - }) - return await reply.code(201).send() - } - } - throw { status: 500, message: 'Could not activate users.' } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const teamId = request.user.teamId; + const { destinationDockerId, destinationDocker, serviceSecret } = await getServiceFromDB({ + id, + teamId + }); + if (destinationDockerId) { + const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL'); + if (databaseUrl) { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"` + }); + return await reply.code(201).send(); + } + } + throw { status: 500, message: 'Could not activate users.' }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function cleanupPlausibleLogs(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const teamId = request.user.teamId; - const { - destinationDockerId, - destinationDocker, - } = await getServiceFromDB({ id, teamId }); - if (destinationDockerId) { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`, - shell: true - }) - return await reply.code(201).send() - } - throw { status: 500, message: 'Could cleanup logs.' } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const teamId = request.user.teamId; + const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId }); + if (destinationDockerId) { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`, + shell: true + }); + return await reply.code(201).send(); + } + throw { status: 500, message: 'Could cleanup logs.' }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function activateWordpressFtp(request: FastifyRequest, reply: FastifyReply) { - const { id } = request.params - const { ftpEnabled } = request.body; +export async function activateWordpressFtp( + request: FastifyRequest, + reply: FastifyReply +) { + const { id } = request.params; + const { ftpEnabled } = request.body; - const { service: { destinationDocker: { engine, remoteEngine, remoteIpAddress } } } = await prisma.wordpress.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } }) + const { + service: { + destinationDocker: { engine, remoteEngine, remoteIpAddress } + } + } = await prisma.wordpress.findUnique({ + where: { serviceId: id }, + include: { service: { include: { destinationDocker: true } } } + }); - const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); + const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); - let ftpUser = cuid(); - let ftpPassword = generatePassword({}); + let ftpUser = cuid(); + let ftpPassword = generatePassword({}); - const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; - try { - const data = await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpEnabled }, - include: { service: { include: { destinationDocker: true } } } - }); - const { - service: { destinationDockerId, destinationDocker }, - ftpPublicPort, - ftpUser: user, - ftpPassword: savedPassword, - ftpHostKey, - ftpHostKeyPrivate - } = data; - const { network, engine } = destinationDocker; - if (ftpEnabled) { - if (user) ftpUser = user; - if (savedPassword) ftpPassword = decrypt(savedPassword); + const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; + try { + const data = await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpEnabled }, + include: { service: { include: { destinationDocker: true } } } + }); + const { + service: { destinationDockerId, destinationDocker }, + ftpPublicPort, + ftpUser: user, + ftpPassword: savedPassword, + ftpHostKey, + ftpHostKeyPrivate + } = data; + const { network, engine } = destinationDocker; + if (ftpEnabled) { + if (user) ftpUser = user; + if (savedPassword) ftpPassword = decrypt(savedPassword); - // TODO: rewrite these to usable without shell - const { stdout: password } = await executeCommand({ - command: - `echo ${ftpPassword} | openssl passwd -1 -stdin`, - shell: true - } - ); - if (destinationDockerId) { - try { - await fs.stat(hostkeyDir); - } catch (error) { - await executeCommand({ command: `mkdir -p ${hostkeyDir}` }); - } - if (!ftpHostKey) { - await executeCommand({ - command: - `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` - } - ); - const { stdout: ftpHostKey } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.ed25519` }); - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKey: encrypt(ftpHostKey) } - }); - } else { - await executeCommand({ command: `echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`, shell: true }); - } - if (!ftpHostKeyPrivate) { - await executeCommand({ command: `ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa` }); - const { stdout: ftpHostKeyPrivate } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.rsa` }); - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } - }); - } else { - await executeCommand({ command: `echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`, shell: true }); - } + // TODO: rewrite these to usable without shell + const { stdout: password } = await executeCommand({ + command: `echo ${ftpPassword} | openssl passwd -1 -stdin`, + shell: true + }); + if (destinationDockerId) { + try { + await fs.stat(hostkeyDir); + } catch (error) { + await executeCommand({ command: `mkdir -p ${hostkeyDir}` }); + } + if (!ftpHostKey) { + await executeCommand({ + command: `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` + }); + const { stdout: ftpHostKey } = await executeCommand({ + command: `cat ${hostkeyDir}/${id}.ed25519` + }); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKey: encrypt(ftpHostKey) } + }); + } else { + await executeCommand({ + command: `echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`, + shell: true + }); + } + if (!ftpHostKeyPrivate) { + await executeCommand({ + command: `ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa` + }); + const { stdout: ftpHostKeyPrivate } = await executeCommand({ + command: `cat ${hostkeyDir}/${id}.rsa` + }); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } + }); + } else { + await executeCommand({ + command: `echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`, + shell: true + }); + } - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { - ftpPublicPort: publicPort, - ftpUser: user ? undefined : ftpUser, - ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) - } - }); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { + ftpPublicPort: publicPort, + ftpUser: user ? undefined : ftpUser, + ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) + } + }); - try { - const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` }); - if (isRunning) { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, - shell: true - }) - } - } catch (error) { } - const volumes = [ - `${id}-wordpress-data:/home/${ftpUser}/wordpress`, - `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' - }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, - `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' - }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, - `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' - }/${id}.sh:/etc/sftp.d/chmod.sh` - ]; - - const compose: ComposeFile = { - version: '3.8', - services: { - [`${id}-ftp`]: { - image: `atmoz/sftp:alpine`, - command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`, - extra_hosts: ['host.docker.internal:host-gateway'], - container_name: `${id}-ftp`, - volumes, - networks: [network], - depends_on: [], - restart: 'always' - } - }, - networks: { - [network]: { - external: true - } - }, - volumes: { - [`${id}-wordpress-data`]: { - external: true, - name: `${id}-wordpress-data` - } - } - }; - await fs.writeFile( - `${hostkeyDir}/${id}.sh`, - `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/` - ); - await executeCommand({ command: `chmod +x ${hostkeyDir}/${id}.sh` }); - await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` - }) - - } - return reply.code(201).send({ - publicPort, - ftpUser, - ftpPassword - }) - } else { - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpPublicPort: null } - }); - try { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, - shell: true - }) - - } catch (error) { - // - } - await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort); - return { - }; - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } finally { - try { - await executeCommand({ - command: - `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` - } - ); - } catch (error) { } - - } + try { + const { found: isRunning } = await checkContainer({ + dockerId: destinationDocker.id, + container: `${id}-ftp` + }); + if (isRunning) { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, + shell: true + }); + } + } catch (error) {} + const volumes = [ + `${id}-wordpress-data:/home/${ftpUser}/wordpress`, + `${ + isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, + `${ + isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, + `${ + isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.sh:/etc/sftp.d/chmod.sh` + ]; + const compose: ComposeFile = { + version: '3.8', + services: { + [`${id}-ftp`]: { + image: `atmoz/sftp:alpine`, + command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`, + extra_hosts: ['host.docker.internal:host-gateway'], + container_name: `${id}-ftp`, + volumes, + networks: [network], + depends_on: [], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [`${id}-wordpress-data`]: { + external: true, + name: `${id}-wordpress-data` + } + } + }; + await fs.writeFile( + `${hostkeyDir}/${id}.sh`, + `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/` + ); + await executeCommand({ command: `chmod +x ${hostkeyDir}/${id}.sh` }); + await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` + }); + } + return reply.code(201).send({ + publicPort, + ftpUser, + ftpPassword + }); + } else { + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpPublicPort: null } + }); + try { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, + shell: true + }); + } catch (error) { + // + } + await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort); + return {}; + } + } catch ({ status, message }) { + return errorHandler({ status, message }); + } finally { + try { + await executeCommand({ + command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` + }); + } catch (error) {} + } } diff --git a/apps/api/tags.json b/apps/api/tags.json index 5d4ef05cd..a788801e2 100644 --- a/apps/api/tags.json +++ b/apps/api/tags.json @@ -1,11 +1,9 @@ [ - { "name": "directus-postgresql", "image": "directus/directus", "tags": ["9.22"] }, - { "name": "whoogle", "image": "benbusby/whoogle-search", "tags": ["0.8.1"] }, - { "name": "libretranslate", "image": "libretranslate/libretranslate", "tags": ["v1.3.8"] }, { "name": "appsmith", "image": "appsmith/appsmith-ce", "tags": [ + "v1.9.3", "v1.9.1", "v1.8.15", "v1.8.12", @@ -34,8 +32,7 @@ "v1.6.5", "v1.6.3", "v1.6.1", - "v1.5.30", - "v1.5.28" + "v1.5.30" ] }, { @@ -74,6 +71,42 @@ "0.3.1" ] }, + { + "name": "directus-postgresql", + "image": "directus/directus", + "tags": [ + "9.22.3", + "9.22.0", + "9.21.0", + "9.20.4", + "9.20.2", + "9.20.0", + "9.19.2", + "9.18.0", + "9.17.4", + "9.17.2", + "9.17.0", + "9.16.0", + "9.15.0", + "9.14.5", + "9.14.3", + "9.14.0", + "9.13.0", + "9.12.2", + "9.12.0", + "9.11.0", + "9.10.0", + "9.9.0", + "9.8.0", + "9.7.0", + "9.6.0", + "9.5.2", + "9.5.0", + "9.4.2", + "9.4.0", + "9.3.0" + ] + }, { "name": "fider", "image": "getfider/fider", @@ -114,6 +147,9 @@ "name": "ghost-mariadb", "image": "bitnami/ghost", "tags": [ + "5.30.1", + "5.30.0", + "5.29.0", "5.28.0", "5.27.0", "5.26.4", @@ -141,9 +177,6 @@ "5.22.4", "5.22.3", "5.22.2", - "5.22.1", - "5.22.0", - "5.21.0", "4.48.8" ] }, @@ -151,6 +184,8 @@ "name": "ghost-mysql", "image": "library/ghost", "tags": [ + "5.30.0", + "5.29.0", "5.28.0", "5.27.0", "5.26.4", @@ -178,15 +213,15 @@ "5.17.2", "5.17.1", "5.17.0", - "5.16.2", - "5.14.2", - "5.14.1" + "5.16.2" ] }, { "name": "ghost-only", "image": "library/ghost", "tags": [ + "5.30.0", + "5.29.0", "5.28.0", "5.27.0", "5.26.4", @@ -214,9 +249,7 @@ "5.17.2", "5.17.1", "5.17.0", - "5.16.2", - "5.14.2", - "5.14.1" + "5.16.2" ] }, { @@ -373,6 +406,7 @@ "7.0.0", "6.0.1", "6.0.0", + "20.0.3", "20.0.2", "20.0.1", "20.0.0", @@ -404,38 +438,12 @@ { "name": "lavalink", "image": "fredboat/lavalink", - "tags": [ - "v3.7", - "v3.6", - "v3-vda0b3a4b3916a7b1a2b79702de1143c3a6939810-SNAPSHOT", - "v3-vc92690c425390bd20f6c51643c67ba79ab85b7e0-SNAPSHOT", - "v3-vab81dcd46adf3e8a961dd57eacd2a1bde1233e6c-SNAPSHOT", - "v3-v9c9432704d6a4badfcbd06a57597c54bed8f4326-SNAPSHOT", - "v3-v3.0", - "v3-v3", - "v3-v124f8fae7dab299f9cdf1cb4c1715be455497286-SNAPSHOT", - "v3-", - "v3", - "v2.0.1", - "v2.0", - "v2", - "update-udpqueue-vb4a439d6147dbd8641ea4f265e8efc9f1e16e2d3-SNAPSHOT", - "update-udpqueue-", - "update-udpqueue", - "revert-713-fix-error-for-loading-jda-nas", - "refactor-github-actions", - "patch-update-lp", - "patch-update-github-actions", - "patch-more-configurable-github-actions", - "patch-lavaplayer-update", - "patch-lavaplayer-bump", - "patch-build-number", - "next-api-vd4db194cac7a839a3899857f1f6d7b910369309d-SNAPSHOT", - "next-api-vc2e018d5ffef54b2d17244b3d213e31723a084d6-SNAPSHOT", - "next-api-v42cb5f7c58e98d1911e87bffb35aee0a235b85f8-SNAPSHOT", - "next-api-v31a243bda80badbd7d643f68fc1f87e99639060f-SNAPSHOT", - "next-api-v17f6884434c2d70d1704b2322a951d9f07af8865-SNAPSHOT" - ] + "tags": ["3.7.0", "3.6.1", "3.5.1", "v2.0.1"] + }, + { + "name": "libretranslate", + "image": "libretranslate/libretranslate", + "tags": ["v1.3.8", "v1.3.6", "v1.3.4", "v1.3.2", "v1.3.0", "v1.2.8"] }, { "name": "meilisearch", @@ -477,6 +485,7 @@ "name": "minio", "image": "minio/minio", "tags": [ + "RELEASE.2023-01-12T02-06-16Z", "RELEASE.2023-01-06T18-11-18Z", "RELEASE.2023-01-02T09-40-09Z", "RELEASE.2022-12-12T19-27-27Z", @@ -505,8 +514,7 @@ "RELEASE.2022-09-01T23-53-36Z.fips", "RELEASE.2022-08-26T19-53-15Z.fips", "RELEASE.2022-08-25T07-17-05Z.fips", - "RELEASE.2022-08-22T23-53-06Z.hotfix.5fa3967bb", - "RELEASE.2022-08-22T23-53-06Z" + "RELEASE.2022-08-22T23-53-06Z.hotfix.5fa3967bb" ] }, { @@ -549,77 +557,56 @@ "name": "nocodb", "image": "nocodb/nocodb", "tags": [ + "0.101.2", + "0.101.0", "0.100.1", - "0.99.1", - "0.98.4", - "0.98.2", - "0.98.0", - "0.96.4", - "0.96.2", - "0.96.0", - "0.92.3", - "0.91.10", - "0.91.9", - "0.91.7", - "0.91.0", - "0.90.10", - "0.90.7", - "0.90.4", - "0.90.2", - "0.90.0", - "0.84.15", - "0.84.12", - "0.84.8", - "0.84.6", - "0.84.2", - "0.84.1", - "0.83.6", - "0.83.3", - "0.83.1", - "0.82.0", - "0.81.0", - "0.11.46" + "0.99.2", + "0.99.0", + "0.98.3", + "0.98.1", + "0.97.0", + "0.96.3", + "0.96.1", + "0.92.4", + "0.92.0", + "0.91.8", + "0.91.6", + "0.91.1", + "0.90.11", + "0.90.8", + "0.90.5", + "0.90.3", + "0.90.1", + "0.84.16", + "0.84.14", + "0.84.10", + "0.84.9", + "0.84.7", + "0.84.3", + "0.83.8", + "0.83.5", + "0.83.2", + "0.83.0" ] }, { "name": "openblocks", "image": "openblocksdev/openblocks-ce", - "tags": ["latest", "heroku", "beta", "1.1.3", "1.1.2", "1.1.1", "1.1.0", "1.0.21"] + "tags": ["1.1.3", "1.1.1", "1.0.21"] }, { "name": "plausibleanalytics-arm", "image": "plausible/analytics", "tags": [ "v1.5.1", - "v1.5.0-rc.2", - "v1.5.0-rc.1", "v1.5.0", - "v1.5", - "v1.4.4", "v1.4.3", - "v1.4.2", "v1.4.1", - "v1.4.0.rc.0", - "v1.4.0-rc.0", "v1.4.0", - "v1.4", - "v1.3.0-rc.1", - "v1.3.0-rc.0", "v1.3.0", - "v1.3", - "v1.2.1", "v1.2.0", - "v1.2-rc.1", - "v1.2-rc.0", - "v1.2", - "v1.1.1", "v1.1.0", - "v1.1", - "v1.0.0", - "v1.0", - "v1", - "stable", - "master" + "v1.0.0" ] }, { @@ -627,55 +614,26 @@ "image": "plausible/analytics", "tags": [ "v1.5.1", - "v1.5.0-rc.2", - "v1.5.0-rc.1", "v1.5.0", - "v1.5", - "v1.4.4", "v1.4.3", - "v1.4.2", "v1.4.1", - "v1.4.0.rc.0", - "v1.4.0-rc.0", "v1.4.0", - "v1.4", - "v1.3.0-rc.1", - "v1.3.0-rc.0", "v1.3.0", - "v1.3", - "v1.2.1", "v1.2.0", - "v1.2-rc.1", - "v1.2-rc.0", - "v1.2", - "v1.1.1", "v1.1.0", - "v1.1", - "v1.0.0", - "v1.0", - "v1", - "stable", - "master" - ] - }, - { - "name": "pocketbase", - "image": "coollabsio/pocketbase", - "tags": [ - "0.8.0-arm64", - "0.8.0-amd64", - "0.8.0-aarch64", - "0.8.0", - "0.10.2-arm64", - "0.10.2-amd64", - "0.10.2-aarch64", - "0.10.2" + "v1.0.0" ] }, + { "name": "pocketbase", "image": "coollabsio/pocketbase", "tags": ["0.11.0", "0.10.2", "0.8.0"] }, { "name": "searxng", "image": "searxng/searxng", "tags": [ + "2023.01.15-52d41559", + "2023.01.15-13b0c251", + "2023.01.14-b720a495", + "2023.01.14-449aebae", + "2023.01.14-18d895ff", "2023.01.09-afd71a6c", "2023.01.09-a90ed481", "2023.01.08-54e63839", @@ -700,18 +658,14 @@ "2022.12.26-0d489617", "2022.12.23-e8f72d70", "2022.12.23-a2d506d4", - "2022.12.22-d75ae7c8", - "2022.12.16-f5bd73d9", - "2022.12.16-b9274821", - "2022.12.16-42ca37a6", - "2022.12.16-2a51c856", - "2022.12.16-0dac581c" + "2022.12.22-d75ae7c8" ] }, { "name": "trilium", "image": "zadam/trilium", "tags": [ + "0.58.4", "0.57.4", "0.57.2", "0.56.1", @@ -740,8 +694,7 @@ "0.45.7", "0.45.5", "0.45.3", - "0.44.8", - "0.44.6" + "0.44.8" ] }, { @@ -907,6 +860,15 @@ "image": "weblate/weblate", "tags": [ "latest", + "edge-2023-01-13-e824b551f23c3679467e38b06366744a06aa3b0c", + "edge-2023-01-13-468b996565e6b62edb78d40b515c476e0d860273", + "edge-2023-01-12-fe3d58b14f119eb5501220e9f096949c2e1ec2d3", + "edge-2023-01-12-112f75f9ee9e118ad493215f89742e6e091be8d0", + "edge-2023-01-11-f7bb190993e329d1529694e8cc7f5e0a80ccd615", + "edge-2023-01-11-e8ef3183aa7723f32c2b60c7c3b89910f2c7c593", + "edge-2023-01-11-155231f6cde18a65e3f35093d66dd0ce93aa7154", + "edge-2023-01-10-e47516e4022f87c019e61998b556b69111187aa9", + "edge-2023-01-10-98c6b38c746165adb27b2a8e93a74fa9ab64f17c", "edge-2023-01-10-1df5c9dd96a6d8650f6881942fecbe33e1884295", "edge-2023-01-09-7029b7b6c630be7cdac07d1629573dd2b81bc05f", "edge-2023-01-09-4b05a878aa25b2c544a4e77027769b5934ec561f", @@ -926,16 +888,24 @@ "edge-2022-12-24-3e1503494ce06ad6ff32f02db1a7d59224e5c860", "edge-2022-12-21-cac4b09f943fe97700e3a33b7caf23277d2fcc11", "edge-2022-12-21-3a8dd1bf66a7295f3512346bc1c97d55c5649dcf", - "edge-2022-12-16-e93caa3b014543b716b946f2c7fbf4a8f9be6099", - "edge-2022-12-16-318a467d2e529a081e9ea9dbad993c1736ff1a00", - "edge-2022-12-16-1af41ec4bd3838f967d88b68dec8195419e01e6f", - "edge-2022-12-16-02e9d020b01d004655c3af20c68a30f6c4645c1a", - "edge-2022-12-15-a6af1384a0831b17c43da7262f80d0cfbc766835", - "edge-2022-12-15-a1c9f77b301a9e23fc05ef2adc4694cceb632c25", - "edge-2022-12-15-1305f7115ef79b75e638b097772680d9cadbd4d0", - "edge-2022-12-14-b400145f05687e647bd4c8192be99f7f04373fb5", - "edge-2022-12-12-c0db193a3baacd107c5f2c28c6e0af89c3d5afa3", - "edge-2022-12-09-647d40c67cf405870ba71a01584a42cfaec5915f" + "edge-2022-12-16-e93caa3b014543b716b946f2c7fbf4a8f9be6099" + ] + }, + { + "name": "whoogle", + "image": "benbusby/whoogle-search", + "tags": [ + "0.8.0", + "0.7.3", + "0.7.1", + "0.6.0", + "0.5.3", + "0.5.1", + "0.4.1", + "0.3.2", + "v0.3.0", + "0.1.2", + "0.1.0" ] }, { diff --git a/apps/api/templates.json b/apps/api/templates.json index fc378ecb0..aeaa0d0c5 100644 --- a/apps/api/templates.json +++ b/apps/api/templates.json @@ -1 +1 @@ -[{"templateVersion":"1.0.0","defaultVersion":"9.22","documentation":"https://docs.directus.io/getting-started/introduction.html","type":"directus-postgresql","name":"Directus","subname":"(PostgreSQL)","description":"Directus is a free and open-source headless CMS framework for managing custom SQL-based databases.","labels":["CMS","headless"],"services":{"$$id":{"name":"Directus","depends_on":["$$id-postgresql","$$id-redis"],"image":"directus/directus:$$core_version","volumes":["$$id-uploads:/directus/uploads","$$id-database:/directus/database","$$id-extensions:/directus/extensions"],"environment":["KEY=$$secret_key","SECRET=$$secret_secret","DB_CLIENT=pg","DB_CONNECTION_STRING=$$secret_db_connection_string","CACHE_ENABLED=true","CACHE_STORE=redis","CACHE_REDIS=$$secret_cache_redis","ADMIN_EMAIL=$$config_admin_email","ADMIN_PASSWORD=$$secret_admin_password","CACHE_AUTO_PURGE=true","PUBLIC_URL=$$config_public_url"],"ports":["8055"]},"$$id-postgresql":{"name":"Directus PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]},"$$id-redis":{"name":"Directus Redis","depends_on":[],"image":"redis:7.0.4-alpine","command":"--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5","volumes":["$$id-redis:/data"],"environment":[]}},"variables":[{"id":"$$config_public_url","name":"PUBLIC_URL","label":"Public URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_db_connection_string","name":"DB_CONNECTION_STRING","label":"Directus Database Url","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"Database","defaultValue":"directus","description":""},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$secret_cache_redis","name":"CACHE_REDIS","label":"Redis Url","defaultValue":"redis://$$id-redis:6379","description":""},{"id":"$$config_admin_email","name":"ADMIN_EMAIL","label":"Initial Admin Email","defaultValue":"admin@example.com","description":"The email address of the first user that is automatically created. You can change it later in Directus."},{"id":"$$secret_admin_password","name":"ADMIN_PASSWORD","label":"Initial Admin Password","defaultValue":"$$generate_password","description":"The password of the first user that is automatically created.","showOnConfiguration":true},{"id":"$$secret_key","name":"KEY","label":"Key","defaultValue":"$$generate_password","description":"Unique identifier for the project.","showOnConfiguration":true},{"id":"$$secret_secret","name":"SECRET","label":"Secret","defaultValue":"$$generate_password","description":"Secret string for the project.","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"v1.3.8","documentation":"https://github.com/LibreTranslate/LibreTranslate","description":"Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup.","type":"libretranslate","name":"Libretranslate","labels":["translator","argos","python","libretranslate"],"services":{"$$id":{"name":"Libretranslate","image":"libretranslate/libretranslate:$$core_version","environment":["LT_HOST=0.0.0.0","LT_SUGGESTIONS=true","LT_CHAR_LIMIT=$$config_lt_char_limit","LT_REQ_LIMIT=$$config_lt_req_limit","LT_BATCH_LIMIT=$$config_lt_batch_limit","LT_GA_ID=$$config_lt_ga_id","LT_DISABLE_WEB_UI=$$config_lt_web_ui"],"volumes":["$$id-libretranslate:/libretranslate"],"ports":["5000"]}},"variables":[{"id":"$$config_lt_char_limit","name":"LT_CHAR_LIMIT","label":"Char limit","defaultValue":"5000","description":"Set character limit."},{"id":"$$config_lt_req_limit","name":"LT_REQ_LIMIT","label":"Request limit","defaultValue":"5000","description":"Set maximum number of requests per minute per client."},{"id":"$$config_lt_batch_limit","name":"LT_BATCH_LIMIT","label":"Batch Limit","defaultValue":"5000","description":"Set maximum number of texts to translate in a batch request."},{"id":"$$config_lt_ga_id","name":"LT_GA_ID","label":"Google Analytics ID","defaultValue":"","description":"Enable Google Analytics on the API client page by providing an ID"},{"id":"$$config_lt_web_ui","name":"LT_DISABLE_WEB_UI","label":"Web UI","defaultValue":"false","description":"Disable or enable web ui. True or false."}]},{"templateVersion":"1.0.0","defaultVersion":"0.8.1","documentation":"https://github.com/benbusby/whoogle-search","type":"whoogle","name":"Whoogle Search","description":"A self-hosted, ad-free, privacy-respecting metasearch engine","labels":["search","google"],"services":{"$$id":{"name":"Whoogle Search","documentation":"https://github.com/benbusby/whoogle-search","depends_on":[],"image":"benbusby/whoogle-search:$$core_version","cap_drop":["ALL"],"environment":["WHOOGLE_USER=$$config_whoogle_username","WHOOGLE_PASS=$$secret_whoogle_password","WHOOGLE_CONFIG_PREFERENCES_KEY=$$secret_whoogle_preferences_key"],"ulimits":{"nofile":{"soft":262144,"hard":262144}},"ports":["5000"]}},"variables":[{"id":"$$config_whoogle_username","name":"WHOOGLE_USER","label":"Whoogle User","defaultValue":"$$generate_username","description":"Username to log into Whoogle"},{"id":"$$secret_whoogle_password","name":"WHOOGLE_PASSWORD","label":"Whoogle Password","defaultValue":"$$generate_password","description":"Password to log into Whoogle","showOnConfiguration":true},{"id":"$$secret_whoogle_preferences_key","name":"WHOOGLE_CONFIG_PREFERENCES_KEY","label":"Whoogle preferences key","defaultValue":"$$generate_password","description":"password to encrypt preferences"}]},{"templateVersion":"1.0.0","defaultVersion":"1.1.3","documentation":"https://docs.openblocks.dev/","type":"openblocks","name":"Openblocks","description":"The Open Source Retool Alternative","services":{"$$id":{"image":"openblocksdev/openblocks-ce:$$core_version","volumes":["$$id-stacks-data:/openblocks-stacks"],"ports":["3000"]}}},{"templateVersion":"1.0.0","defaultVersion":"0.10.2","documentation":"https://pocketbase.io/docs/","type":"pocketbase","name":"Pocketbase","description":"Open Source realtime backend in 1 file","services":{"$$id":{"image":"coollabsio/pocketbase:$$core_version","volumes":["$$id-data:/app/pb_data"],"ports":["8080"]}}},{"templateVersion":"1.0.0","defaultVersion":"v1.5.1","documentation":"https://plausible.io/doc/","type":"plausibleanalytics-arm","name":"Plausible Analytics (ARM)","description":"A lightweight and open-source website analytics tool.","labels":["analytics","statistics","plausible","gdpr","no-cookie","google analytics"],"services":{"$$id":{"name":"Plausible Analytics","command":"sh -c \"sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run\"","depends_on":["$$id-postgresql","$$id-clickhouse"],"image":"plausible/analytics:$$core_version","environment":["ADMIN_USER_EMAIL=$$config_admin_user_email","ADMIN_USER_NAME=$$config_admin_user_name","ADMIN_USER_PWD=$$secret_admin_user_pwd","BASE_URL=$$config_base_url","SECRET_KEY_BASE=$$secret_secret_key_base","DISABLE_AUTH=$$config_disable_auth","DISABLE_REGISTRATION=$$config_disable_registration","DATABASE_URL=$$secret_database_url","CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url"],"ports":["8000"]},"$$id-postgresql":{"name":"PostgreSQL","image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_USER=$$config_postgres_user","POSTGRES_DB=$$config_postgres_db"]},"$$id-clickhouse":{"name":"Clickhouse","volumes":["$$id-clickhouse-data:/var/lib/clickhouse"],"image":"clickhouse/clickhouse-server:22.6-alpine","ulimits":{"nofile":{"soft":262144,"hard":262144}},"files":[{"location":"/etc/clickhouse-server/users.d/logging.xml","content":"warningtrue"},{"location":"/etc/clickhouse-server/config.d/logging.xml","content":"00"},{"location":"/docker-entrypoint-initdb.d/init.query","content":"CREATE DATABASE IF NOT EXISTS plausible;"},{"location":"/docker-entrypoint-initdb.d/init-db.sh","content":"clickhouse client --queries-file /docker-entrypoint-initdb.d/init.query"}]}},"variables":[{"id":"$$config_base_url","name":"BASE_URL","label":"Base URL","defaultValue":"$$generate_fqdn","description":"You must set this to the FQDN of the Plausible Analytics instance. This is used to generate the links to the Plausible Analytics instance."},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_clickhouse_database_url","name":"CLICKHOUSE_DATABASE_URL","label":"Database URL for Clickhouse","defaultValue":"http://$$id-clickhouse:8123/plausible","description":""},{"id":"$$config_admin_user_email","name":"ADMIN_USER_EMAIL","label":"Admin Email Address","defaultValue":"admin@example.com","description":"This is the admin email. Please change it."},{"id":"$$config_admin_user_name","name":"ADMIN_USER_NAME","label":"Admin User Name","defaultValue":"$$generate_username","description":"This is the admin username. Please change it."},{"id":"$$secret_admin_user_pwd","name":"ADMIN_USER_PWD","label":"Admin User Password","defaultValue":"$$generate_password","description":"This is the admin password. Please change it.","showOnConfiguration":true},{"id":"$$secret_secret_key_base","name":"SECRET_KEY_BASE","label":"Secret Key Base","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_disable_auth","name":"DISABLE_AUTH","label":"Authentication","defaultValue":"false","description":""},{"id":"$$config_disable_registration","name":"DISABLE_REGISTRATION","label":"Registration","defaultValue":"true","description":""},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL Username","defaultValue":"postgresql","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"plausible","description":""},{"id":"$$config_scriptName","name":"SCRIPT_NAME","label":"Custom Script Name","defaultValue":"plausible.js","description":"This is the default script name."}]},{"templateVersion":"1.0.0","defaultVersion":"1.17","documentation":"https://docs.gitea.io","type":"gitea","name":"Gitea","description":"Gitea is a community managed lightweight code hosting solution written in Go.","labels":["storage","git"],"services":{"$$id":{"name":"Gitea","documentation":"https://docs.gitea.io","image":"gitea/gitea:$$core_version","volumes":["$$id-data:/data","/etc/timezone:/etc/timezone:ro","/etc/localtime:/etc/localtime:ro"],"environment":["USER_UID=1000","USER_GID=1000","DOMAIN=$$config_domain","SSH_DOMAIN=$$config_ssh_domain","ROOT_URL=$$config_root_url","SECRET_KEY=$$secret_secret_key","INTERNAL_TOKEN=$$secret_internal_token","SSH_PORT=22","START_SSH_SERVER=$$config_start_ssh_server"],"ports":["3000","22"],"proxy":[{"port":"22","hostPort":"$$config_hostport_ssh"}]}},"variables":[{"id":"$$config_hostport_ssh","name":"SSH_PORT","label":"SSH Port","defaultValue":"8022","description":"","required":true},{"id":"$$config_domain","name":"DOMAIN","label":"Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$config_ssh_domain","name":"SSH_DOMAIN","label":"SSH Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$config_start_ssh_server","name":"START_SSH_SERVER","label":"Start SSH Server","defaultValue":"true","description":""},{"id":"$$config_root_url","name":"ROOT_URL","label":"Root URL of Gitea","defaultValue":"$$generate_fqdn_slash","description":""},{"id":"$$secret_secret_key","name":"SECRET_KEY","label":"Secret Key","defaultValue":"$$generate_hex(32)","description":""},{"id":"$$secret_internal_token","name":"INTERNAL_TOKEN","label":"Internal JWT Token","defaultValue":"$$generate_token","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"20.0","documentation":"https://www.keycloak.org/documentation","type":"keycloak","name":"Keycloak","description":"Keycloak provides user federation, strong authentication, user management, fine-grained authorization, and more.","labels":["authentication","authorization","oidconnect","saml2"],"services":{"$$id":{"name":"Keycloak","command":"start --db=postgres --features=token-exchange --import-realm","depends_on":["$$id-postgresql"],"image":"quay.io/keycloak/keycloak:$$core_version","volumes":["$$id-import:/opt/keycloak/data/import"],"environment":["KC_HEALTH_ENABLED=true","KC_PROXY=edge","KC_DB=postgres","KC_HOSTNAME=$$config_keycloak_domain","KEYCLOAK_ADMIN=$$config_admin_user","KEYCLOAK_ADMIN_PASSWORD=$$secret_keycloak_admin_password","KC_DB_PASSWORD=$$secret_postgres_password","KC_DB_USERNAME=$$config_postgres_user","KC_DB_URL=$$secret_keycloak_database_url"],"ports":["8080"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]}},"variables":[{"id":"$$config_keycloak_domain","name":"KEYCLOAK_DOMAIN","label":"Keycloak Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$secret_keycloak_database_url","name":"KEYCLOAK_DATABASE_URL","label":"Keycloak Database Url","defaultValue":"jdbc:postgresql://$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$config_admin_user","name":"KEYCLOAK_ADMIN","label":"Keycloak Admin User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_keycloak_admin_password","name":"KEYCLOAK_ADMIN_PASSWORD","label":"Keycloak Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"keycloak","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v3.7","documentation":"https://github.com/freyacodes/Lavalink","description":"Standalone audio sending node based on Lavaplayer.","type":"lavalink","name":"Lavalink","labels":["discord","discord bot","audio","lavalink","jda"],"services":{"$$id":{"name":"Lavalink","image":"fredboat/lavalink:$$core_version","environment":[],"volumes":["$$id-lavalink:/lavalink"],"ports":["$$config_port"],"files":[{"location":"/opt/Lavalink/application.yml","content":"server:\n port: 2333\n address: 0.0.0.0\nlavalink:\n server:\n password: \"$$secret_password\"\n sources:\n youtube: true\n bandcamp: true\n soundcloud: true\n twitch: true\n vimeo: true\n http: true\n local: false\n\nlogging:\n file:\n path: ./logs/\n\n level:\n root: INFO\n lavalink: INFO\n\n logback:\n rollingpolicy:\n max-file-size: 1GB\n max-history: 30"}]}},"variables":[{"id":"$$secret_password","name":"PASSWORD","label":"Password","defaultValue":"$$generate_password","required":true}]},{"templateVersion":"1.0.0","defaultVersion":"v1.8.9","documentation":"https://docs.appsmith.com/getting-started/setup/instance-configuration/","type":"appsmith","name":"Appsmith","description":"Fastest way to build internal apps over any database or API.","services":{"$$id":{"image":"appsmith/appsmith-ce:$$core_version","environment":["APPSMITH_MAIL_ENABLED=$$config_appsmith_mail_enabled","APPSMITH_DISABLE_TELEMETRY=$$config_appsmith_disable_telemetry","APPSMITH_DISABLE_INTERCOM=$$config_appsmith_disable_intercom"],"volumes":["$$id-stacks-data:/appsmith-stacks"],"ports":["80"]}},"variables":[{"id":"$$config_appsmith_mail_enabled","name":"APPSMITH_MAIL_ENABLED","label":"Enable Mail","defaultValue":"false","description":""},{"id":"$$config_appsmith_disable_telemetry","name":"APPSMITH_DISABLE_TELEMETRY","label":"Disable Telemetry","defaultValue":"true","description":""},{"id":"$$config_appsmith_disable_intercom","name":"APPSMITH_DISABLE_INTERCOM","label":"Disable Intercom","defaultValue":"true","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"0.57.4","documentation":"https://hub.docker.com/r/zadam/trilium","description":"A hierarchical note taking application with focus on building large personal knowledge bases.","labels":["personal","knowledge","notes","wiki"],"type":"trilium","name":"Trilium Notes","services":{"$$id":{"image":"zadam/trilium:$$core_version","environment":[],"volumes":["$$id-trilium:/home/node/trilium-data"],"ports":["8080"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"1.18.5","documentation":"https://hub.docker.com/r/louislam/uptime-kuma","description":"A free & fancy self-hosted monitoring tool.","labels":["uptime"],"type":"uptimekuma","name":"UptimeKuma","services":{"$$id":{"image":"louislam/uptime-kuma:$$core_version","environment":[],"volumes":["$$id-uptimekuma:/app/data"],"ports":["3001"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"5.8","documentation":"https://hub.docker.com/r/silviof/docker-languagetool","description":"A multilingual grammar, style and spell checker.","type":"languagetool","name":"LanguageTool","services":{"$$id":{"image":"silviof/docker-languagetool:$$core_version","environment":[],"volumes":["$$id-ngrams:/ngrams"],"ports":["8010"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"1.26.0","documentation":"https://hub.docker.com/r/vaultwarden/server","description":"Bitwarden compatible server written in Rust.","type":"vaultwarden","name":"VaultWarden","labels":["bitwarden","password manager"],"services":{"$$id":{"image":"vaultwarden/server:$$core_version","environment":[],"volumes":["$$id-data:/data"],"ports":["80"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"9.3.1","documentation":"https://hub.docker.com/r/grafana/grafana","type":"grafana","name":"Grafana","description":"Grafana allows you to query, visualize, alert on and understand your metrics.","labels":["monitoring","metrics","dashboard"],"services":{"$$id":{"image":"grafana/grafana:$$core_version","environment":[],"volumes":["$$id-config:/etc/grafana","$$id-grafana:/var/lib/grafana"],"ports":["3000"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"1.1.2","documentation":"https://appwrite.io/docs","type":"appwrite","name":"Appwrite","description":"Secure Backend Server for Web, Mobile & Flutter Developers.","labels":["serverless","backend","storage","api"],"services":{"$$id":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_WORKER_PER_CORE=$$config__app_worker_per_core","_APP_LOCALE=$$config__app_locale","_APP_CONSOLE_WHITELIST_ROOT=$$config__app_console_whitelist_root","_APP_CONSOLE_WHITELIST_EMAILS=$$config__app_console_whitelist_emails","_APP_CONSOLE_WHITELIST_IPS=$$config__app_console_whitelist_ips","_APP_SYSTEM_EMAIL_NAME=$$config__app_system_email_name","_APP_SYSTEM_EMAIL_ADDRESS=$$config__app_system_email_address","_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=$$config__app_system_security_email_address","_APP_SYSTEM_RESPONSE_FORMAT=$$config__app_system_response_format","_APP_OPTIONS_ABUSE=$$config__app_options_abuse","_APP_OPTIONS_FORCE_HTTPS=$$config__app_options_force_https","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DOMAIN=$$config__app_domain","_APP_DOMAIN_TARGET=$$config__app_domain_target","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_SMTP_HOST=$$config__app_smtp_host","_APP_SMTP_PORT=$$config__app_smtp_port","_APP_SMTP_SECURE=$$config__app_smtp_secure","_APP_SMTP_USERNAME=$$config__app_smtp_username","_APP_SMTP_PASSWORD=$$secret__app_smtp_password","_APP_USAGE_STATS=$$config__app_usage_stats","_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","_APP_STORAGE_LIMIT=$$config__app_storage_limit","_APP_STORAGE_PREVIEW_LIMIT=$$config__app_storage_preview_limit","_APP_STORAGE_ANTIVIRUS=$$config__app_storage_antivirus_enabled","_APP_STORAGE_ANTIVIRUS_HOST=$$config__app_storage_antivirus_host","_APP_STORAGE_ANTIVIRUS_PORT=$$config__app_storage_antivirus_port","_APP_STORAGE_DEVICE=$$config__app_storage_device","_APP_STORAGE_S3_ACCESS_KEY=$$secret__app_storage_s3_access_key","_APP_STORAGE_S3_SECRET=$$secret__app_storage_s3_secret","_APP_STORAGE_S3_REGION=$$config__app_storage_s3_region","_APP_STORAGE_S3_BUCKET=$$config__app_storage_s3_bucket","_APP_STORAGE_DO_SPACES_ACCESS_KEY=$$secret__app_storage_do_spaces_access_key","_APP_STORAGE_DO_SPACES_SECRET=$$secret__app_storage_do_spaces_secret","_APP_STORAGE_DO_SPACES_REGION=$$config__app_storage_do_spaces_region","_APP_STORAGE_DO_SPACES_BUCKET=$$config__app_storage_do_spaces_bucket","_APP_STORAGE_BACKBLAZE_ACCESS_KEY=$$secret__app_storage_backblaze_access_key","_APP_STORAGE_BACKBLAZE_SECRET=$$secret__app_storage_backblaze_secret","_APP_STORAGE_BACKBLAZE_REGION=$$config__app_storage_backblaze_region","_APP_STORAGE_BACKBLAZE_BUCKET=$$config__app_storage_backblaze_bucket","_APP_STORAGE_LINODE_ACCESS_KEY=$$secret__app_storage_linode_access_key","_APP_STORAGE_LINODE_SECRET=$$secret__app_storage_linode_secret","_APP_STORAGE_LINODE_REGION=$$config__app_storage_linode_region","_APP_STORAGE_LINODE_BUCKET=$$config__app_storage_linode_bucket","_APP_STORAGE_WASABI_ACCESS_KEY=$$secret__app_storage_wasabi_access_key","_APP_STORAGE_WASABI_SECRET=$$secret__app_storage_wasabi_secret","_APP_STORAGE_WASABI_REGION=$$config__app_storage_wasabi_region","_APP_STORAGE_WASABI_BUCKET=$$config__app_storage_wasabi_bucket","_APP_FUNCTIONS_SIZE_LIMIT=$$config__app_functions_size_limit","_APP_FUNCTIONS_TIMEOUT=$$config__app_functions_timeout","_APP_FUNCTIONS_BUILD_TIMEOUT=$$config__app_functions_build_timeout","_APP_FUNCTIONS_CONTAINERS=$$config__app_functions_containers","_APP_FUNCTIONS_CPUS=$$config__app_functions_cpus","_APP_FUNCTIONS_MEMORY=$$config__app_functions_memory_allocated","_APP_FUNCTIONS_MEMORY_SWAP=$$config__app_functions_memory_swap","_APP_FUNCTIONS_RUNTIMES=$$config__app_functions_runtimes","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","_APP_STATSD_HOST=$$config__app_statsd_host","_APP_STATSD_PORT=$$config__app_statsd_port","_APP_MAINTENANCE_INTERVAL=$$config__app_maintenance_interval","_APP_MAINTENANCE_RETENTION_EXECUTION=$$config__app_maintenance_retention_execution","_APP_MAINTENANCE_RETENTION_CACHE=$$config__app_maintenance_retention_cache","_APP_MAINTENANCE_RETENTION_ABUSE=$$config__app_maintenance_retention_abuse","_APP_MAINTENANCE_RETENTION_AUDIT=$$config__app_maintenance_retention_audit","_APP_SMS_PROVIDER=$$config__app_sms_provider","_APP_SMS_FROM=$$config__app_sms_from","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-uploads:/storage/uploads","$$id-cache:/storage/cache","$$id-config:/storage/config","$$id-certificates:/storage/certificates","$$id-functions:/storage/functions"],"ports":["80"],"proxy":[{"port":"80"}]},"$$id-executor":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_FUNCTIONS_TIMEOUT=$$config__app_functions_timeout","_APP_FUNCTIONS_BUILD_TIMEOUT=$$config__app_functions_build_timeout","_APP_FUNCTIONS_CONTAINERS=$$config__app_functions_containers","_APP_FUNCTIONS_RUNTIMES=$$config__app_functions_runtimes","_APP_FUNCTIONS_CPUS=$$config__app_functions_cpus","_APP_FUNCTIONS_MEMORY=$$config__app_functions_memory_allocated","_APP_FUNCTIONS_MEMORY_SWAP=$$config__app_functions_memory_swap","_APP_FUNCTIONS_INACTIVE_THRESHOLD=$$config__app_functions_inactive_threshold","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","_APP_STORAGE_DEVICE=$$config__app_storage_device","_APP_STORAGE_S3_ACCESS_KEY=$$secret__app_storage_s3_access_key","_APP_STORAGE_S3_SECRET=$$secret__app_storage_s3_secret","_APP_STORAGE_S3_REGION=$$config__app_storage_s3_region","_APP_STORAGE_S3_BUCKET=$$config__app_storage_s3_bucket","_APP_STORAGE_DO_SPACES_ACCESS_KEY=$$secret__app_storage_do_spaces_access_key","_APP_STORAGE_DO_SPACES_SECRET=$$secret__app_storage_do_spaces_secret","_APP_STORAGE_DO_SPACES_REGION=$$config__app_storage_do_spaces_region","_APP_STORAGE_DO_SPACES_BUCKET=$$config__app_storage_do_spaces_bucket","_APP_STORAGE_BACKBLAZE_ACCESS_KEY=$$secret__app_storage_backblaze_access_key","_APP_STORAGE_BACKBLAZE_SECRET=$$secret__app_storage_backblaze_secret","_APP_STORAGE_BACKBLAZE_REGION=$$config__app_storage_backblaze_region","_APP_STORAGE_BACKBLAZE_BUCKET=$$config__app_storage_backblaze_bucket","_APP_STORAGE_LINODE_ACCESS_KEY=$$secret__app_storage_linode_access_key","_APP_STORAGE_LINODE_SECRET=$$secret__app_storage_linode_secret","_APP_STORAGE_LINODE_REGION=$$config__app_storage_linode_region","_APP_STORAGE_LINODE_BUCKET=$$config__app_storage_linode_bucket","_APP_STORAGE_WASABI_ACCESS_KEY=$$secret__app_storage_wasabi_access_key","_APP_STORAGE_WASABI_SECRET=$$secret__app_storage_wasabi_secret","_APP_STORAGE_WASABI_REGION=$$config__app_storage_wasabi_region","_APP_STORAGE_WASABI_BUCKET=$$config__app_storage_wasabi_bucket","DOCKERHUB_PULL_USERNAME=$$config_dockerhub_pull_username","DOCKERHUB_PULL_PASSWORD=$$secret_dockerhub_pull_password","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-functions:/storage/functions","$$id-builds:/storage/builds","/var/run/docker.sock:/var/run/docker.sock","/tmp:/tmp:rw"],"entrypoint":"executor"},"$$id-influxdb":{"image":"appwrite/influxdb:1.5.0","environment":[],"volumes":["$$id-influxdb:/var/lib/influxdb"]},"$$id-maintenance":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DOMAIN=$$config__app_domain","_APP_DOMAIN_TARGET=$$config__app_domain_target","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_MAINTENANCE_INTERVAL=$$config__app_maintenance_interval","_APP_MAINTENANCE_RETENTION_EXECUTION=$$config__app_maintenance_retention_execution","_APP_MAINTENANCE_RETENTION_CACHE=$$config__app_maintenance_retention_cache","_APP_MAINTENANCE_RETENTION_ABUSE=$$config__app_maintenance_retention_abuse","_APP_MAINTENANCE_RETENTION_AUDIT=$$config__app_maintenance_retention_audit","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"maintenance"},"$$id-mariadb":{"image":"mariadb:10.7","command":"--innodb-flush-method fsync","environment":["MARIADB_ROOT_PASSWORD=$$secret__app_db_root_pass","MARIADB_DATABASE=$$config__app_db_schema","MARIADB_USER=$$config__app_db_user","MARIADB_PASSWORD=$$secret__app_db_pass","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-mariadb:/var/lib/mysql"]},"$$id-realtime":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_WORKER_PER_CORE=$$config__app_worker_per_core","_APP_OPTIONS_ABUSE=$$config__app_options_abuse","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_USAGE_STATS=$$config__app_usage_stats","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"realtime","proxy":[{"port":"80","pathPrefix":"/v1/realtime"}]},"$$id-redis":{"image":"redis:7.0.4-alpine","command":"--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5","environment":[],"volumes":["$$id-redis:/data"]},"$$id-schedule":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"schedule"},"$$id-telegraf":{"image":"appwrite/telegraf:1.4.0","environment":["_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-influxdb:/var/lib/influxdb"]},"$$id-usage-database":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","_APP_USAGE_TIMESERIES_INTERVAL=$$config__app_usage_timeseries_interval","_APP_USAGE_DATABASE_INTERVAL=$$config__app_usage_database_interval","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"usage --type database"},"$$id-usage":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","_APP_USAGE_TIMESERIES_INTERVAL=$$config__app_usage_timeseries_interval","_APP_USAGE_DATABASE_INTERVAL=$$config__app_usage_database_interval","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"usage --type timeseries"},"$$id-worker-audits":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-audits"},"$$id-worker-builds":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-builds"},"$$id-worker-certificates":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DOMAIN=$$config__app_domain","_APP_DOMAIN_TARGET=$$config__app_domain_target","_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=$$config__app_system_security_email_address","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-config:/storage/config","$$id-certificates:/storage/certificates"],"entrypoint":"worker-certificates"},"$$id-worker-databases":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-databases"},"$$id-worker-deletes":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_STORAGE_DEVICE=$$config__app_storage_device","_APP_STORAGE_S3_ACCESS_KEY=$$secret__app_storage_s3_access_key","_APP_STORAGE_S3_SECRET=$$secret__app_storage_s3_secret","_APP_STORAGE_S3_REGION=$$config__app_storage_s3_region","_APP_STORAGE_S3_BUCKET=$$config__app_storage_s3_bucket","_APP_STORAGE_DO_SPACES_ACCESS_KEY=$$secret__app_storage_do_spaces_access_key","_APP_STORAGE_DO_SPACES_SECRET=$$secret__app_storage_do_spaces_secret","_APP_STORAGE_DO_SPACES_REGION=$$config__app_storage_do_spaces_region","_APP_STORAGE_DO_SPACES_BUCKET=$$config__app_storage_do_spaces_bucket","_APP_STORAGE_BACKBLAZE_ACCESS_KEY=$$secret__app_storage_backblaze_access_key","_APP_STORAGE_BACKBLAZE_SECRET=$$secret__app_storage_backblaze_secret","_APP_STORAGE_BACKBLAZE_REGION=$$config__app_storage_backblaze_region","_APP_STORAGE_BACKBLAZE_BUCKET=$$config__app_storage_backblaze_bucket","_APP_STORAGE_LINODE_ACCESS_KEY=$$secret__app_storage_linode_access_key","_APP_STORAGE_LINODE_SECRET=$$secret__app_storage_linode_secret","_APP_STORAGE_LINODE_REGION=$$config__app_storage_linode_region","_APP_STORAGE_LINODE_BUCKET=$$config__app_storage_linode_bucket","_APP_STORAGE_WASABI_ACCESS_KEY=$$secret__app_storage_wasabi_access_key","_APP_STORAGE_WASABI_SECRET=$$secret__app_storage_wasabi_secret","_APP_STORAGE_WASABI_REGION=$$config__app_storage_wasabi_region","_APP_STORAGE_WASABI_BUCKET=$$config__app_storage_wasabi_bucket","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-uploads:/storage/uploads","$$id-cache:/storage/cache","$$id-functions:/storage/functions","$$id-builds:/storage/builds","$$id-certificates:/storage/certificates"],"entrypoint":"worker-deletes"},"$$id-worker-functions":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_FUNCTIONS_TIMEOUT=$$config__app_functions_timeout","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","_APP_USAGE_STATS=$$config__app_usage_stats","DOCKERHUB_PULL_USERNAME=$$config_dockerhub_pull_username","DOCKERHUB_PULL_PASSWORD=$$secret_dockerhub_pull_password","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-functions"},"$$id-worker-mails":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_SYSTEM_EMAIL_NAME=$$config__app_system_email_name","_APP_SYSTEM_EMAIL_ADDRESS=$$config__app_system_email_address","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_SMTP_HOST=$$config__app_smtp_host","_APP_SMTP_PORT=$$config__app_smtp_port","_APP_SMTP_SECURE=$$config__app_smtp_secure","_APP_SMTP_USERNAME=$$config__app_smtp_username","_APP_SMTP_PASSWORD=$$secret__app_smtp_password","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-mails"},"$$id-worker-messaging":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_SMS_PROVIDER=$$config__app_sms_provider","_APP_SMS_FROM=$$config__app_sms_from","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-messaging"},"$$id-worker-webhooks":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=$$config__app_system_security_email_address","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-webhooks"}},"variables":[{"id":"$$config__app_influxdb_host","name":"_APP_INFLUXDB_HOST","label":"InfluxDB | _APP_INFLUXDB_HOST","defaultValue":"$$id-influxdb","description":""},{"id":"$$config__app_influxdb_port","name":"_APP_INFLUXDB_PORT","label":"InfluxDB | _APP_INFLUXDB_PORT","defaultValue":"8086","description":"InfluxDB server TCP port."},{"id":"$$config__app_env","name":"_APP_ENV","label":"General | _APP_ENV","defaultValue":"production","description":"Set your server running environment."},{"id":"$$config__app_worker_per_core","name":"_APP_WORKER_PER_CORE","label":"General | _APP_WORKER_PER_CORE","defaultValue":"6","description":"Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance."},{"id":"$$config__app_locale","name":"_APP_LOCALE","label":"General | _APP_LOCALE","defaultValue":"en","description":"Set your Appwrite's locale. By default, the locale is set to 'en'."},{"id":"$$config__app_console_whitelist_root","name":"_APP_CONSOLE_WHITELIST_ROOT","label":"General | _APP_CONSOLE_WHITELIST_ROOT","defaultValue":"enabled","description":"This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled."},{"id":"$$config__app_console_whitelist_emails","name":"_APP_CONSOLE_WHITELIST_EMAILS","label":"General | _APP_CONSOLE_WHITELIST_EMAILS","defaultValue":"","description":"This option allows you to limit creation of new users on the Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma."},{"id":"$$config__app_console_whitelist_ips","name":"_APP_CONSOLE_WHITELIST_IPS","label":"General | _APP_CONSOLE_WHITELIST_IPS","defaultValue":"","description":"This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\\n\\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma."},{"id":"$$config__app_system_email_name","name":"_APP_SYSTEM_EMAIL_NAME","label":"General | _APP_SYSTEM_EMAIL_NAME","defaultValue":"Appwrite","description":"This is the sender name value that will appear on email messages sent to developers from the Appwrite console. You can use url encoded strings for spaces and special chars."},{"id":"$$config__app_system_email_address","name":"_APP_SYSTEM_EMAIL_ADDRESS","label":"General | _APP_SYSTEM_EMAIL_ADDRESS","defaultValue":"team@appwrite.io","description":"This is the sender email address that will appear on email messages sent to developers from the Appwrite console. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users' SPAM folders."},{"id":"$$config__app_system_security_email_address","name":"_APP_SYSTEM_SECURITY_EMAIL_ADDRESS","label":"General | _APP_SYSTEM_SECURITY_EMAIL_ADDRESS","defaultValue":"certs@appwrite.io","description":"This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload."},{"id":"$$config__app_system_response_format","name":"_APP_SYSTEM_RESPONSE_FORMAT","label":"General | _APP_SYSTEM_RESPONSE_FORMAT","defaultValue":"","description":"Use this environment variable to set the default Appwrite HTTP response format to support an older version of Appwrite. This option is useful to overcome breaking changes between versions. You can also use the X-Appwrite-Response-Format HTTP request header to overwrite the response for a specific request. This variable accepts any valid Appwrite version. To use the current version format, leave the value of the variable empty."},{"id":"$$config__app_options_abuse","name":"_APP_OPTIONS_ABUSE","label":"General | _APP_OPTIONS_ABUSE","defaultValue":"enabled","description":"Allows you to disable abuse checks and API rate limiting. By default, set to 'enabled'. To cancel the abuse checking, set to 'disabled'. It is not recommended to disable this check-in a production environment."},{"id":"$$config__app_options_force_https","name":"_APP_OPTIONS_FORCE_HTTPS","label":"General | _APP_OPTIONS_FORCE_HTTPS","defaultValue":"disabled","description":"Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the 'Strict-Transport-Security' header to all HTTP responses."},{"id":"$$secret__app_openssl_key_v1","name":"_APP_OPENSSL_KEY_V1","label":"General | _APP_OPENSSL_KEY_V1","defaultValue":"$$generate_hex(256)","description":"This is your server private secret key that is used to encrypt all sensitive data on your server. Appwrite server encrypts all secret data on your server like webhooks, HTTP passwords, user sessions, and storage files. Keep it a secret and have a backup for it."},{"id":"$$config__app_domain","name":"_APP_DOMAIN","label":"General | _APP_DOMAIN","defaultValue":"$$generate_domain","description":"Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is 'localhost'."},{"id":"$$config__app_domain_target","name":"_APP_DOMAIN_TARGET","label":"General | _APP_DOMAIN_TARGET","defaultValue":"$$generate_fqdn","description":"A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite '_APP_DOMAIN' variable. The default value is 'localhost'."},{"id":"$$config__app_redis_host","name":"_APP_REDIS_HOST","label":"Redis | _APP_REDIS_HOST","defaultValue":"$$id-redis","description":""},{"id":"$$config__app_redis_port","name":"_APP_REDIS_PORT","label":"Redis | _APP_REDIS_PORT","defaultValue":"6379","description":"Redis server TCP port."},{"id":"$$config__app_redis_user","name":"_APP_REDIS_USER","label":"Redis | _APP_REDIS_USER","defaultValue":"","description":"Redis server user. This is an optional variable. Default value is an empty string."},{"id":"$$secret__app_redis_pass","name":"_APP_REDIS_PASS","label":"Redis | _APP_REDIS_PASS","defaultValue":"","description":"Redis server password. This is an optional variable. Default value is an empty string."},{"id":"$$config__app_db_host","name":"_APP_DB_HOST","label":"MariaDB | _APP_DB_HOST","defaultValue":"$$id-mariadb","description":""},{"id":"$$config__app_db_port","name":"_APP_DB_PORT","label":"MariaDB | _APP_DB_PORT","defaultValue":"3306","description":"MariaDB server TCP port."},{"id":"$$config__app_db_schema","name":"_APP_DB_SCHEMA","label":"MariaDB | _APP_DB_SCHEMA","defaultValue":"appwrite","description":"MariaDB server database schema."},{"id":"$$config__app_db_user","name":"_APP_DB_USER","label":"MariaDB | _APP_DB_USER","defaultValue":"user","description":"MariaDB server user name."},{"id":"$$secret__app_db_root_pass","name":"MARIADB_ROOT_PASSWORD","label":"MariaDB | MARIADB_ROOT_PASSWORD","defaultValue":"$$generate_hex(16)","description":"MariaDB server root user password."},{"id":"$$secret__app_db_pass","name":"_APP_DB_PASS","label":"MariaDB | _APP_DB_PASS","defaultValue":"$$generate_hex(16)","description":"MariaDB server user password."},{"id":"$$config__app_smtp_host","name":"_APP_SMTP_HOST","label":"SMTP | _APP_SMTP_HOST","defaultValue":"","description":"SMTP server host name address. Use an empty string to disable all mail sending from the server. The default value for this variable is an empty string."},{"id":"$$config__app_smtp_port","name":"_APP_SMTP_PORT","label":"SMTP | _APP_SMTP_PORT","defaultValue":"","description":"SMTP server TCP port. Empty by default."},{"id":"$$config__app_smtp_secure","name":"_APP_SMTP_SECURE","label":"SMTP | _APP_SMTP_SECURE","defaultValue":"","description":"SMTP secure connection protocol. Empty by default, change to 'tls' if running on a secure connection."},{"id":"$$config__app_smtp_username","name":"_APP_SMTP_USERNAME","label":"SMTP | _APP_SMTP_USERNAME","defaultValue":"","description":"SMTP server user name. Empty by default."},{"id":"$$secret__app_smtp_password","name":"_APP_SMTP_PASSWORD","label":"SMTP | _APP_SMTP_PASSWORD","defaultValue":"","description":"SMTP server user password. Empty by default."},{"id":"$$config__app_usage_stats","name":"_APP_USAGE_STATS","label":"General | _APP_USAGE_STATS","defaultValue":"enabled","description":"This variable allows you to disable the collection and displaying of usage stats. This value is set to 'enabled' by default, to disable the usage stats set the value to 'disabled'. When disabled, it's recommended to turn off the Worker Usage, Influxdb and Telegraf containers for better resource usage."},{"id":"$$config__app_storage_limit","name":"_APP_STORAGE_LIMIT","label":"Storage | _APP_STORAGE_LIMIT","defaultValue":"30000000","description":"Maximum file size allowed for file upload. The default value is 30MB. You should pass your size limit value in bytes."},{"id":"$$config__app_storage_preview_limit","name":"_APP_STORAGE_PREVIEW_LIMIT","label":"Storage | _APP_STORAGE_PREVIEW_LIMIT","defaultValue":"20000000","description":"Maximum file size allowed for file image preview. The default value is 20MB. You should pass your size limit value in bytes."},{"id":"$$config__app_storage_antivirus_enabled","name":"_APP_STORAGE_ANTIVIRUS","label":"Storage | _APP_STORAGE_ANTIVIRUS","defaultValue":"disabled","description":"This variable allows you to disable the internal anti-virus scans. This value is set to 'disabled' by default, to enable the scans set the value to 'enabled'. Before enabling, you must add the ClamAV service and depend on it on main Appwrite service."},{"id":"$$config__app_storage_antivirus_host","name":"_APP_STORAGE_ANTIVIRUS_HOST","label":"Storage | _APP_STORAGE_ANTIVIRUS_HOST","defaultValue":"clamav","description":"ClamAV server host name address."},{"id":"$$config__app_storage_antivirus_port","name":"_APP_STORAGE_ANTIVIRUS_PORT","label":"Storage | _APP_STORAGE_ANTIVIRUS_PORT","defaultValue":"3310","description":"ClamAV server TCP port."},{"id":"$$config__app_storage_device","name":"_APP_STORAGE_DEVICE","label":"Storage | _APP_STORAGE_DEVICE","defaultValue":"Local","description":"Select default storage device. The default value is 'Local'. List of supported adapters are 'Local', 'S3', 'DOSpaces', 'Backblaze', 'Linode' and 'Wasabi'."},{"id":"$$secret__app_storage_s3_access_key","name":"_APP_STORAGE_S3_ACCESS_KEY","label":"Storage | _APP_STORAGE_S3_ACCESS_KEY","defaultValue":"","description":"AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console."},{"id":"$$secret__app_storage_s3_secret","name":"_APP_STORAGE_S3_SECRET","label":"Storage | _APP_STORAGE_S3_SECRET","defaultValue":"","description":"AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console."},{"id":"$$config__app_storage_s3_region","name":"_APP_STORAGE_S3_REGION","label":"Storage | _APP_STORAGE_S3_REGION","defaultValue":"us-east-1","description":"AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console."},{"id":"$$config__app_storage_s3_bucket","name":"_APP_STORAGE_S3_BUCKET","label":"Storage | _APP_STORAGE_S3_BUCKET","defaultValue":"","description":"AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console."},{"id":"$$secret__app_storage_do_spaces_access_key","name":"_APP_STORAGE_DO_SPACES_ACCESS_KEY","label":"Storage | _APP_STORAGE_DO_SPACES_ACCESS_KEY","defaultValue":"","description":"DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console."},{"id":"$$secret__app_storage_do_spaces_secret","name":"_APP_STORAGE_DO_SPACES_SECRET","label":"Storage | _APP_STORAGE_DO_SPACES_SECRET","defaultValue":"","description":"DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console."},{"id":"$$config__app_storage_do_spaces_region","name":"_APP_STORAGE_DO_SPACES_REGION","label":"Storage | _APP_STORAGE_DO_SPACES_REGION","defaultValue":"us-east-1","description":"DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console."},{"id":"$$config__app_storage_do_spaces_bucket","name":"_APP_STORAGE_DO_SPACES_BUCKET","label":"Storage | _APP_STORAGE_DO_SPACES_BUCKET","defaultValue":"","description":"DigitalOcean spaces bucket. Required when storage adapter is set to DOSpaces. You can create spaces in your DigitalOcean console."},{"id":"$$secret__app_storage_backblaze_access_key","name":"_APP_STORAGE_BACKBLAZE_ACCESS_KEY","label":"Storage | _APP_STORAGE_BACKBLAZE_ACCESS_KEY","defaultValue":"","description":"Backblaze access key. Required when the storage adapter is set to Backblaze. Your Backblaze keyID will be your access key. You can get your keyID from your Backblaze console."},{"id":"$$secret__app_storage_backblaze_secret","name":"_APP_STORAGE_BACKBLAZE_SECRET","label":"Storage | _APP_STORAGE_BACKBLAZE_SECRET","defaultValue":"","description":"Backblaze secret key. Required when the storage adapter is set to Backblaze. Your Backblaze applicationKey will be your secret key. You can get your applicationKey from your Backblaze console."},{"id":"$$config__app_storage_backblaze_region","name":"_APP_STORAGE_BACKBLAZE_REGION","label":"Storage | _APP_STORAGE_BACKBLAZE_REGION","defaultValue":"us-west-004","description":"Backblaze region. Required when storage adapter is set to Backblaze. You can find your region info from your Backblaze console."},{"id":"$$config__app_storage_backblaze_bucket","name":"_APP_STORAGE_BACKBLAZE_BUCKET","label":"Storage | _APP_STORAGE_BACKBLAZE_BUCKET","defaultValue":"","description":"Backblaze bucket. Required when storage adapter is set to Backblaze. You can create your bucket from your Backblaze console."},{"id":"$$secret__app_storage_linode_access_key","name":"_APP_STORAGE_LINODE_ACCESS_KEY","label":"Storage | _APP_STORAGE_LINODE_ACCESS_KEY","defaultValue":"","description":"Linode object storage access key. Required when the storage adapter is set to Linode. You can get your access key from your Linode console."},{"id":"$$secret__app_storage_linode_secret","name":"_APP_STORAGE_LINODE_SECRET","label":"Storage | _APP_STORAGE_LINODE_SECRET","defaultValue":"","description":"Linode object storage secret key. Required when the storage adapter is set to Linode. You can get your secret key from your Linode console."},{"id":"$$config__app_storage_linode_region","name":"_APP_STORAGE_LINODE_REGION","label":"Storage | _APP_STORAGE_LINODE_REGION","defaultValue":"eu-central-1","description":"Linode object storage region. Required when storage adapter is set to Linode. You can find your region info from your Linode console."},{"id":"$$config__app_storage_linode_bucket","name":"_APP_STORAGE_LINODE_BUCKET","label":"Storage | _APP_STORAGE_LINODE_BUCKET","defaultValue":"","description":"Linode object storage bucket. Required when storage adapter is set to Linode. You can create buckets in your Linode console."},{"id":"$$secret__app_storage_wasabi_access_key","name":"_APP_STORAGE_WASABI_ACCESS_KEY","label":"Storage | _APP_STORAGE_WASABI_ACCESS_KEY","defaultValue":"","description":"Wasabi access key. Required when the storage adapter is set to Wasabi. You can get your access key from your Wasabi console."},{"id":"$$secret__app_storage_wasabi_secret","name":"_APP_STORAGE_WASABI_SECRET","label":"Storage | _APP_STORAGE_WASABI_SECRET","defaultValue":"","description":"Wasabi secret key. Required when the storage adapter is set to Wasabi. You can get your secret key from your Wasabi console."},{"id":"$$config__app_storage_wasabi_region","name":"_APP_STORAGE_WASABI_REGION","label":"Storage | _APP_STORAGE_WASABI_REGION","defaultValue":"eu-central-1","description":"Wasabi region. Required when storage adapter is set to Wasabi. You can find your region info from your Wasabi console."},{"id":"$$config__app_storage_wasabi_bucket","name":"_APP_STORAGE_WASABI_BUCKET","label":"Storage | _APP_STORAGE_WASABI_BUCKET","defaultValue":"","description":"Wasabi bucket. Required when storage adapter is set to Wasabi. You can create buckets in your Wasabi console."},{"id":"$$config__app_functions_size_limit","name":"_APP_FUNCTIONS_SIZE_LIMIT","label":"Functions | _APP_FUNCTIONS_SIZE_LIMIT","defaultValue":"30000000","description":"The maximum size deployment in bytes. The default value is 30MB."},{"id":"$$config__app_functions_timeout","name":"_APP_FUNCTIONS_TIMEOUT","label":"Functions | _APP_FUNCTIONS_TIMEOUT","defaultValue":"900","description":"The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds."},{"id":"$$config__app_functions_build_timeout","name":"_APP_FUNCTIONS_BUILD_TIMEOUT","label":"Functions | _APP_FUNCTIONS_BUILD_TIMEOUT","defaultValue":"900","description":"The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds."},{"id":"$$config__app_functions_containers","name":"_APP_FUNCTIONS_CONTAINERS","label":"Functions | _APP_FUNCTIONS_CONTAINERS","defaultValue":"10","description":"The maximum number of containers Appwrite is allowed to keep alive in the background for function environments. Running containers allow faster execution time as there is no need to recreate each container every time a function gets executed. The default value is 10."},{"id":"$$config__app_functions_cpus","name":"_APP_FUNCTIONS_CPUS","label":"Functions | _APP_FUNCTIONS_CPUS","defaultValue":"","description":"The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it's empty, CPU limit will be disabled."},{"id":"$$config__app_functions_memory_allocated","name":"_APP_FUNCTIONS_MEMORY","label":"Functions | _APP_FUNCTIONS_MEMORY","defaultValue":"","description":"The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it's empty, memory limit will be disabled."},{"id":"$$config__app_functions_memory_swap","name":"_APP_FUNCTIONS_MEMORY_SWAP","label":"Functions | _APP_FUNCTIONS_MEMORY_SWAP","defaultValue":"","description":"The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is empty. When it's empty, swap memory limit will be disabled."},{"id":"$$config__app_functions_runtimes","name":"_APP_FUNCTIONS_RUNTIMES","label":"Functions | _APP_FUNCTIONS_RUNTIMES","defaultValue":"node-18.0","description":"This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\nCurrently, supported environments are: node-14.5, node-16.0, node-18.0, php-8.0, php-8.1, ruby-3.0, ruby-3.1, python-3.8, python-3.9, python-3.10, deno-1.21, deno-1.24, dart-2.15, dart-2.16, dart-2.17, dotnet-3.1, dotnet-6.0, java-8.0, java-11.0, java-17.0, java-18.0, swift-5.5, kotlin-1.6, cpp-17.0"},{"id":"$$secret__app_executor_secret","name":"_APP_EXECUTOR_SECRET","label":"Functions | _APP_EXECUTOR_SECRET","defaultValue":"$$generate_hex(16)","description":"The secret key used by Appwrite to communicate with the function executor."},{"id":"$$config__app_executor_host","name":"_APP_EXECUTOR_HOST","label":"","defaultValue":"http://$$id-executor/v1","description":""},{"id":"$$config__app_logging_provider","name":"_APP_LOGGING_PROVIDER","label":"General | _APP_LOGGING_PROVIDER","defaultValue":"","description":"This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of 'sentry', 'raygun', 'appsignal', 'logowl'"},{"id":"$$config__app_logging_config","name":"_APP_LOGGING_CONFIG","label":"General | _APP_LOGGING_CONFIG","defaultValue":"","description":"This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be 'SENTRY_API_KEY;SENTRY_APP_ID'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket."},{"id":"$$config__app_statsd_host","name":"_APP_STATSD_HOST","label":"","defaultValue":"$$id-telegraf","description":""},{"id":"$$config__app_statsd_port","name":"_APP_STATSD_PORT","label":"StatsD | _APP_STATSD_PORT","defaultValue":"8125","description":"StatsD server TCP port."},{"id":"$$config__app_maintenance_interval","name":"_APP_MAINTENANCE_INTERVAL","label":"Functions | _APP_MAINTENANCE_INTERVAL","defaultValue":"86400","description":"Interval value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 86400 seconds (1 day)."},{"id":"$$config__app_maintenance_retention_execution","name":"_APP_MAINTENANCE_RETENTION_EXECUTION","label":"Functions | _APP_MAINTENANCE_RETENTION_EXECUTION","defaultValue":"1209600","description":"The maximum duration (in seconds) upto which to retain execution logs. The default value is 1209600 seconds (14 days)."},{"id":"$$config__app_maintenance_retention_cache","name":"_APP_MAINTENANCE_RETENTION_CACHE","label":"Functions | _APP_MAINTENANCE_RETENTION_CACHE","defaultValue":"2592000","description":"The maximum duration (in seconds) upto which to retain cached files. The default value is 2592000 seconds (30 days)."},{"id":"$$config__app_maintenance_retention_abuse","name":"_APP_MAINTENANCE_RETENTION_ABUSE","label":"Functions | _APP_MAINTENANCE_RETENTION_ABUSE","defaultValue":"86400","description":"The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day)."},{"id":"$$config__app_maintenance_retention_audit","name":"_APP_MAINTENANCE_RETENTION_AUDIT","label":"Functions | _APP_MAINTENANCE_RETENTION_AUDIT","defaultValue":"1209600","description":"The maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days)."},{"id":"$$config__app_sms_provider","name":"_APP_SMS_PROVIDER","label":"Phone | _APP_SMS_PROVIDER","defaultValue":"","description":"Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. Available providers are twilio, text-magic, telesign, msg91, and vonage."},{"id":"$$config__app_sms_from","name":"_APP_SMS_FROM","label":"Phone | _APP_SMS_FROM","defaultValue":"","description":"Phone number used for sending out messages. Must start with a leading '+' and maximum of 15 digits without spaces (+123456789)."},{"id":"$$config__app_functions_inactive_threshold","name":"_APP_FUNCTIONS_INACTIVE_THRESHOLD","label":"Functions | _APP_FUNCTIONS_INACTIVE_THRESHOLD","defaultValue":"60","description":"The minimum time a function can be inactive before it's container is shutdown and put to sleep. The default value is 60 seconds"},{"id":"$$config_open_runtimes_network","name":"OPEN_RUNTIMES_NETWORK","label":"","defaultValue":"$$generate_network","description":""},{"id":"$$config_dockerhub_pull_username","name":"DOCKERHUB_PULL_USERNAME","label":"Functions | DOCKERHUB_PULL_USERNAME","defaultValue":"","description":"The username for hub.docker.com. This variable is used to pull images from hub.docker.com."},{"id":"$$secret_dockerhub_pull_password","name":"DOCKERHUB_PULL_PASSWORD","label":"Functions | DOCKERHUB_PULL_PASSWORD","defaultValue":"","description":"The password for hub.docker.com. This variable is used to pull images from hub.docker.com."},{"id":"$$config__app_usage_timeseries_interval","name":"_APP_USAGE_TIMESERIES_INTERVAL","label":"General | _APP_USAGE_TIMESERIES_INTERVAL","defaultValue":"30","description":"Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to mariadb from InfluxDB. The default value is 30 seconds."},{"id":"$$config__app_usage_database_interval","name":"_APP_USAGE_DATABASE_INTERVAL","label":"General | _APP_USAGE_DATABASE_INTERVAL","defaultValue":"900","description":"Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats from data in Appwrite Database. The default value is 15 minutes."}]},{"templateVersion":"1.0.0","defaultVersion":"latest","documentation":"https://docs.weblate.org/en/latest/admin/install/docker.html","description":"A copylefted libre software web-based continuous localization system.","type":"weblate","name":"Weblate","labels":["translate","localization"],"services":{"$$id":{"name":"Weblate","depends_on":["$$id-postgresql","$$id-redis"],"image":"weblate/weblate:$$core_version","volumes":["$$id-data:/app/data"],"environment":["WEBLATE_SITE_DOMAIN=$$config_weblate_site_domain","WEBLATE_ADMIN_PASSWORD=$$secret_weblate_admin_password","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_USER=$$config_postgres_user","POSTGRES_DATABASE=$$config_postgres_db","POSTGRES_HOST=$$id-postgresql","POSTGRES_PORT=5432","REDIS_HOST=$$id-redis"],"ports":["8080"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]},"$$id-redis":{"name":"Redis","depends_on":[],"image":"redis:7-alpine","volumes":["$$id-redis-data:/data"],"environment":[],"ports":[]}},"variables":[{"id":"$$config_weblate_site_domain","name":"WEBLATE_SITE_DOMAIN","label":"Weblate Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$secret_weblate_admin_password","name":"WEBLATE_ADMIN_PASSWORD","label":"Weblate Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"weblate","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"2022.12.12-966e9c3c","documentation":"https://docs.searxng.org/","type":"searxng","name":"SearXNG","description":"Free internet metasearch engine which aggregates results from more than 70 search services.","services":{"$$id":{"name":"SearXNG","depends_on":["$$id-redis"],"image":"searxng/searxng:$$core_version","volumes":["$$id-searxng:/etc/searxng"],"environment":["SEARXNG_BASE_URL=$$config_searxng_base_url"],"ports":["8080"],"cap_drop":["ALL"],"cap_add":["CHOWN","SETGID","SETUID","DAC_OVERRIDE"],"files":[{"location":"/etc/searxng/settings.yml","content":"\n # see https://docs.searxng.org/admin/engines/settings.html#use-default-settings\n use_default_settings: true\n server:\n secret_key: $$secret_secret_key\n limiter: true\n image_proxy: true\n ui:\n static_use_hash: true\n redis:\n url: redis://:$$secret_redis_password@$$id-redis:6379/0"}]},"$$id-redis":{"name":"Redis","command":"redis-server --requirepass $$secret_redis_password --save \"\" --appendonly \"no\"","depends_on":[],"image":"redis:7-alpine","volumes":["$$id-redis-data:/data"],"environment":["REDIS_PASSWORD=$$secret_redis_password"],"ports":[],"cap_drop":["ALL"],"cap_add":["SETGID","SETUID","DAC_OVERRIDE"]}},"variables":[{"id":"$$config_searxng_base_url","name":"SEARXNG_BASE_URL","label":"SearXNG Base URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_secret_key","name":"SECRET_KEY","label":"Secret Key","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$secret_redis_password","name":"REDIS_PASSWORD","label":"Redis Password","defaultValue":"$$generate_password","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v3.0.0","documentation":"https://glitchtip.com/documentation","type":"glitchtip","name":"GlitchTip","description":"Simple, open source error tracking.","labels":["sentry","bugsnag"],"services":{"$$id":{"name":"GlitchTip","depends_on":["$$id-postgresql","$$id-redis"],"image":"glitchtip/glitchtip:$$core_version","volumes":[],"environment":["PORT=$$config_port","GLITCHTIP_DOMAIN=$$config_glitchtip_domain","SECRET_KEY=$$secret_secret_key","DATABASE_URL=$$secret_database_url","REDIS_URL=$$secret_redis_url","DEFAULT_FROM_EMAIL=$$config_default_from_email","EMAIL_URL=$$secret_email_url","EMAIL_HOST=$$config_email_host","EMAIL_PORT=$$config_email_port","EMAIL_HOST_USER=$$config_email_host_user","EMAIL_HOST_PASSWORD=$$secret_email_host_password","EMAIL_USE_TLS=$$config_email_use_tls","EMAIL_USE_SSL=$$config_email_use_ssl","EMAIL_BACKEND=$$config_email_backend","MAILGUN_API_KEY=$$secret_mailgun_api_key","SENDGRID_API_KEY=$$secret_sendgrid_api_key","ENABLE_OPEN_USER_REGISTRATION=$$config_enable_open_user_registration","DJANGO_SUPERUSER_EMAIL=$$config_django_superuser_email","DJANGO_SUPERUSER_PASSWORD=$$secret_django_superuser_password","DJANGO_SUPERUSER_USERNAME=$$config_django_superuser_username","CELERY_WORKER_CONCURRENCY=$$config_celery_worker_concurrency"],"ports":["8000"]},"$$id-worker":{"name":"Celery Worker","command":"./bin/run-celery-with-beat.sh","depends_on":["$$id-postgresql","$$id-redis"],"image":"glitchtip/glitchtip:$$core_version","environment":["GLITCHTIP_DOMAIN=$$config_glitchtip_domain","SECRET_KEY=$$secret_secret_key","DATABASE_URL=$$secret_database_url","REDIS_URL=$$secret_redis_url","DEFAULT_FROM_EMAIL=$$config_default_from_email","EMAIL_URL=$$secret_email_url","CELERY_WORKER_CONCURRENCY=$$config_celery_worker_concurrency"],"ports":[]},"$$id-migrate":{"exclude":true,"name":"Migrate","command":"./manage.py migrate","depends_on":["$$id-postgresql","$$id-redis"],"image":"glitchtip/glitchtip:$$core_version","environment":["GLITCHTIP_DOMAIN=$$config_glitchtip_domain","SECRET_KEY=$$secret_secret_key","DATABASE_URL=$$secret_database_url","REDIS_URL=$$secret_redis_url","DEFAULT_FROM_EMAIL=$$config_default_from_email","EMAIL_URL=$$secret_email_url"],"ports":[]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]},"$$id-redis":{"name":"Redis","depends_on":[],"image":"redis:7-alpine","volumes":["$$id-postgresql-redis-data:/data"],"environment":[],"ports":[]}},"variables":[{"id":"$$config_django_superuser_username","name":"DJANGO_SUPERUSER_USERNAME","label":"Django Superuser Username","defaultValue":"$$generate_username","description":""},{"id":"$$secret_django_superuser_password","name":"DJANGO_SUPERUSER_PASSWORD","label":"Django Superuser Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_port","name":"PORT","label":"GlitchTip Port","defaultValue":"8000","description":""},{"id":"$$config_celery_worker_concurrency","main":"$$id-worker","name":"CELERY_WORKER_CONCURRENCY","label":"Celery Worker Concurrency","defaultValue":"2","description":""},{"id":"$$config_glitchtip_domain","name":"GLITCHTIP_DOMAIN","label":"GlitchTip Domain","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_email_url","name":"EMAIL_URL","label":"SMTP Email URL","defaultValue":"smtp://$$config_email_host_user:$$secret_email_host_password@$$config_email_host:$$config_email_port","description":""},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_redis_url","name":"REDIS_URL","label":"Redis URL","defaultValue":"redis://$$id-redis:6379/0","description":""},{"id":"$$config_default_from_email","name":"DEFAULT_FROM_EMAIL","label":"Default Email Address","defaultValue":"noreply@example.com","description":""},{"id":"$$config_email_host","name":"EMAIL_HOST","label":"Email SMTP Host","defaultValue":"","description":""},{"id":"$$config_email_port","name":"EMAIL_PORT","label":"Email SMTP Port","defaultValue":"25","description":""},{"id":"$$config_email_host_user","name":"EMAIL_HOST_USER","label":"Email SMTP User","defaultValue":"","description":""},{"id":"$$secret_email_host_password","name":"EMAIL_HOST_PASSWORD","label":"Email SMTP Password","defaultValue":"","description":""},{"id":"$$config_email_use_tls","name":"EMAIL_USE_TLS","label":"Email Use TLS","defaultValue":"false","description":""},{"id":"$$config_email_use_ssl","name":"EMAIL_USE_SSL","label":"Email Use SSL","defaultValue":"false","description":""},{"id":"$$secret_email_smtp_password","name":"EMAIL_SMTP_PASSWORD","label":"SMTP Password","defaultValue":"","description":""},{"id":"$$config_email_backend","name":"EMAIL_BACKEND","label":"Email Backend","defaultValue":"","description":""},{"id":"$$secret_mailgun_api_key","name":"MAILGUN_API_KEY","label":"Mailgun API Key","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$secret_sendgrid_api_key","name":"SENDGRID_API_KEY","label":"Sendgrid API Key","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$config_enable_open_user_registration","name":"ENABLE_OPEN_USER_REGISTRATION","label":"Enable Open User Registration","defaultValue":"true","description":""},{"id":"$$config_django_superuser_email","name":"DJANGO_SUPERUSER_EMAIL","label":"Django Superuser Email","defaultValue":"noreply@example.com","description":""},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"glitchtip","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v2.16.0","documentation":"https://hasura.io/docs/latest/index/","type":"hasura","name":"Hasura","description":"Instant realtime GraphQL APIs on any Postgres application, existing or new.","labels":["graphql","database"],"services":{"$$id":{"name":"Hasura","depends_on":["$$id-postgresql"],"image":"hasura/graphql-engine:$$core_version","volumes":[],"environment":["HASURA_GRAPHQL_ENABLE_CONSOLE=$$config_hasura_graphql_enable_console","HASURA_GRAPHQL_METADATA_DATABASE_URL=$$secret_hasura_graphql_metadata_database_url","HASURA_GRAPHQL_ADMIN_SECRET=$$secret_hasura_graphql_admin_secret"],"ports":["8080"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]}},"variables":[{"id":"$$config_hasura_graphql_enable_console","name":"HASURA_GRAPHQL_ENABLE_CONSOLE","label":"Enable Hasura Console","defaultValue":"true","description":""},{"id":"$$secret_hasura_graphql_metadata_database_url","name":"HASURA_GRAPHQL_METADATA_DATABASE_URL","label":"Hasura Metadata Database URL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_hasura_graphql_admin_secret","name":"HASURA_GRAPHQL_ADMIN_SECRET","label":"Hasura Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"hasura","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"postgresql-v1.39.5","documentation":"https://umami.is/docs/getting-started","type":"umami-postgresql","name":"Umami","subname":"(PostgreSQL)","description":"A simple, easy to use, self-hosted web analytics solution.","services":{"$$id":{"name":"Umami","depends_on":["$$id-postgresql"],"image":"ghcr.io/umami-software/umami:$$core_version","volumes":[],"environment":["ADMIN_PASSWORD=$$secret_admin_password","DATABASE_URL=$$secret_database_url","DATABASE_TYPE=$$config_database_type","HASH_SALT=$$secret_hash_salt"],"ports":["3000"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[],"files":[{"location":"/docker-entrypoint-initdb.d/schema.postgresql.sql","content":"\n -- CreateTable\n CREATE TABLE \"account\" (\n \"user_id\" SERIAL NOT NULL,\n \"username\" VARCHAR(255) NOT NULL,\n \"password\" VARCHAR(60) NOT NULL,\n \"is_admin\" BOOLEAN NOT NULL DEFAULT false,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"updated_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"user_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"event\" (\n \"event_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"event_type\" VARCHAR(50) NOT NULL,\n \"event_value\" VARCHAR(50) NOT NULL,\n\n PRIMARY KEY (\"event_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"pageview\" (\n \"view_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"referrer\" VARCHAR(500),\n\n PRIMARY KEY (\"view_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"session\" (\n \"session_id\" SERIAL NOT NULL,\n \"session_uuid\" UUID NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"hostname\" VARCHAR(100),\n \"browser\" VARCHAR(20),\n \"os\" VARCHAR(20),\n \"device\" VARCHAR(20),\n \"screen\" VARCHAR(11),\n \"language\" VARCHAR(35),\n \"country\" CHAR(2),\n\n PRIMARY KEY (\"session_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"website\" (\n \"website_id\" SERIAL NOT NULL,\n \"website_uuid\" UUID NOT NULL,\n \"user_id\" INTEGER NOT NULL,\n \"name\" VARCHAR(100) NOT NULL,\n \"domain\" VARCHAR(500),\n \"share_id\" VARCHAR(64),\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"website_id\")\n );\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"account.username_unique\" ON \"account\"(\"username\");\n\n -- CreateIndex\n CREATE INDEX \"event_created_at_idx\" ON \"event\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"event_session_id_idx\" ON \"event\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"event_website_id_idx\" ON \"event\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_created_at_idx\" ON \"pageview\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_session_id_idx\" ON \"pageview\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_idx\" ON \"pageview\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_session_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"session_id\", \"created_at\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"session.session_uuid_unique\" ON \"session\"(\"session_uuid\");\n\n -- CreateIndex\n CREATE INDEX \"session_created_at_idx\" ON \"session\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"session_website_id_idx\" ON \"session\"(\"website_id\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.website_uuid_unique\" ON \"website\"(\"website_uuid\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.share_id_unique\" ON \"website\"(\"share_id\");\n\n -- CreateIndex\n CREATE INDEX \"website_user_id_idx\" ON \"website\"(\"user_id\");\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"session\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"website\" ADD FOREIGN KEY (\"user_id\") REFERENCES \"account\"(\"user_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n insert into account (username, password, is_admin) values ('admin', '$$hashed$$secret_admin_password', true);"}]}},"variables":[{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_hash_salt","name":"HASH_SALT","label":"Hash Salt","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_database_type","name":"DATABASE_TYPE","label":"Database Type","defaultValue":"postgresql","description":""},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"umami","description":""},{"id":"$$secret_admin_password","name":"ADMIN_PASSWORD","label":"Initial Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","ignore":true,"defaultVersion":"postgresql-v1.39.5","documentation":"https://umami.is/docs/getting-started","type":"umami","name":"Umami","subname":"(PostgreSQL)","description":"A simple, easy to use, self-hosted web analytics solution.","services":{"$$id":{"name":"Umami","depends_on":["$$id-postgresql"],"image":"ghcr.io/umami-software/umami:$$core_version","volumes":[],"environment":["ADMIN_PASSWORD=$$secret_admin_password","DATABASE_URL=$$secret_database_url","DATABASE_TYPE=$$config_database_type","HASH_SALT=$$secret_hash_salt"],"ports":["3000"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[],"files":[{"location":"/docker-entrypoint-initdb.d/schema.postgresql.sql","content":"\n -- CreateTable\n CREATE TABLE \"account\" (\n \"user_id\" SERIAL NOT NULL,\n \"username\" VARCHAR(255) NOT NULL,\n \"password\" VARCHAR(60) NOT NULL,\n \"is_admin\" BOOLEAN NOT NULL DEFAULT false,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"updated_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"user_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"event\" (\n \"event_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"event_type\" VARCHAR(50) NOT NULL,\n \"event_value\" VARCHAR(50) NOT NULL,\n\n PRIMARY KEY (\"event_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"pageview\" (\n \"view_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"referrer\" VARCHAR(500),\n\n PRIMARY KEY (\"view_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"session\" (\n \"session_id\" SERIAL NOT NULL,\n \"session_uuid\" UUID NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"hostname\" VARCHAR(100),\n \"browser\" VARCHAR(20),\n \"os\" VARCHAR(20),\n \"device\" VARCHAR(20),\n \"screen\" VARCHAR(11),\n \"language\" VARCHAR(35),\n \"country\" CHAR(2),\n\n PRIMARY KEY (\"session_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"website\" (\n \"website_id\" SERIAL NOT NULL,\n \"website_uuid\" UUID NOT NULL,\n \"user_id\" INTEGER NOT NULL,\n \"name\" VARCHAR(100) NOT NULL,\n \"domain\" VARCHAR(500),\n \"share_id\" VARCHAR(64),\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"website_id\")\n );\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"account.username_unique\" ON \"account\"(\"username\");\n\n -- CreateIndex\n CREATE INDEX \"event_created_at_idx\" ON \"event\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"event_session_id_idx\" ON \"event\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"event_website_id_idx\" ON \"event\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_created_at_idx\" ON \"pageview\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_session_id_idx\" ON \"pageview\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_idx\" ON \"pageview\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_session_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"session_id\", \"created_at\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"session.session_uuid_unique\" ON \"session\"(\"session_uuid\");\n\n -- CreateIndex\n CREATE INDEX \"session_created_at_idx\" ON \"session\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"session_website_id_idx\" ON \"session\"(\"website_id\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.website_uuid_unique\" ON \"website\"(\"website_uuid\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.share_id_unique\" ON \"website\"(\"share_id\");\n\n -- CreateIndex\n CREATE INDEX \"website_user_id_idx\" ON \"website\"(\"user_id\");\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"session\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"website\" ADD FOREIGN KEY (\"user_id\") REFERENCES \"account\"(\"user_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n insert into account (username, password, is_admin) values ('admin', '$$hashed$$secret_admin_password', true);"}]}},"variables":[{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_hash_salt","name":"HASH_SALT","label":"Hash Salt","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_database_type","name":"DATABASE_TYPE","label":"Database Type","defaultValue":"postgresql","description":""},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"umami","description":""},{"id":"$$secret_admin_password","name":"ADMIN_PASSWORD","label":"Initial Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"v0.30.1","documentation":"https://docs.meilisearch.com/learn/getting_started/quick_start.html","type":"meilisearch","name":"MeiliSearch","description":"A lightning Fast, Ultra Relevant, and Typo-Tolerant Search Engine.","services":{"$$id":{"name":"MeiliSearch","documentation":"https://docs.meilisearch.com/","depends_on":[],"image":"getmeili/meilisearch:$$core_version","volumes":["$$id-datams:/meili_data/data.ms","$$id-data:/meili_data","$$id-snapshot:/snapshot","$$id-dump:/dumps"],"environment":["MEILI_MASTER_KEY=$$secret_meili_master_key"],"ports":["7700"]}},"variables":[{"id":"$$secret_meili_master_key","name":"MEILI_MASTER_KEY","label":"Master Key","defaultValue":"$$generate_hex(64)","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","ignore":true,"defaultVersion":"latest","documentation":"https://docs.ghost.org","arch":"amd64","type":"ghost-mariadb","name":"Ghost","subname":"(MariaDB)","description":"Free and open source blogging platform.","labels":["cms","blog"],"services":{"$$id":{"name":"Ghost","depends_on":["$$id-mariadb"],"image":"bitnami/ghost:$$core_version","volumes":["$$id-ghost:/bitnami/ghost"],"environment":["url=$$config_url","GHOST_HOST=$$config_ghost_host","GHOST_ENABLE_HTTPS=$$config_ghost_enable_https","GHOST_EMAIL=$$config_ghost_email","GHOST_PASSWORD=$$secret_ghost_password","GHOST_DATABASE_HOST=$$config_ghost_database_host","GHOST_DATABASE_USER=$$config_mariadb_user","GHOST_DATABASE_PASSWORD=$$secret_ghost_database_password","GHOST_DATABASE_NAME=$$config_mariadb_database","GHOST_DATABASE_PORT_NUMBER=3306"],"ports":["2368"]},"$$id-mariadb":{"name":"MariaDB","depends_on":[],"image":"bitnami/mariadb:latest","volumes":["$$id-mariadb:/bitnami/mariadb"],"environment":["MARIADB_USER=$$config_mariadb_user","MARIADB_PASSWORD=$$secret_mariadb_password","MARIADB_DATABASE=$$config_mariadb_database","MARIADB_ROOT_USER=$$config_mariadb_root_user","MARIADB_ROOT_PASSWORD=$$secret_mariadb_root_password"],"ports":[]}},"variables":[{"id":"$$config_url","name":"url","label":"URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_ghost_host","name":"GHOST_HOST","label":"Ghost Host","defaultValue":"$$generate_domain","description":""},{"id":"$$config_ghost_enable_https","name":"GHOST_ENABLE_HTTPS","label":"Ghost Enable HTTPS","defaultValue":"no","description":""},{"id":"$$config_ghost_email","name":"GHOST_EMAIL","label":"Ghost Default Email","defaultValue":"admin@example.com","description":""},{"id":"$$secret_ghost_password","name":"GHOST_PASSWORD","label":"Ghost Default Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_ghost_database_host","name":"GHOST_DATABASE_HOST","label":"Ghost Database Host","defaultValue":"$$id-mariadb","description":""},{"id":"$$config_ghost_database_user","name":"GHOST_DATABASE_USER","label":"MariaDB User","defaultValue":"$$config_mariadb_user","description":""},{"id":"$$secret_ghost_database_password","name":"GHOST_DATABASE_PASSWORD","label":"MariaDB Password","defaultValue":"$$secret_mariadb_password","description":""},{"id":"$$config_ghost_database_name","name":"GHOST_DATABASE_NAME","label":"MariaDB Database","defaultValue":"$$config_mariadb_database","description":""},{"id":"$$config_mariadb_user","name":"MARIADB_USER","label":"MariaDB User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_mariadb_password","name":"MARIADB_PASSWORD","label":"MariaDB Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_mariadb_database","name":"MARIADB_DATABASE","label":"MariaDB Database","defaultValue":"ghost","description":""},{"id":"$$config_mariadb_root_user","name":"MARIADB_ROOT_USER","label":"MariaDB Root User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_mariadb_root_password","name":"MARIADB_ROOT_PASSWORD","label":"MariaDB Root Password","defaultValue":"$$generate_password","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"5.25.3","documentation":"https://docs.ghost.org","type":"ghost-only","name":"Ghost","subname":"(without Database)","description":"Free and open source blogging platform.","services":{"$$id":{"name":"Ghost","image":"ghost:$$core_version","volumes":["$$id-ghost:/var/lib/ghost/content"],"environment":["url=$$config_url","database__client=$$config_database__client","database__connection__host=$$config_database__connection__host","database__connection__user=$$config_database__connection__user","database__connection__password=$$secret_database__connection__password","database__connection__database=$$config_database__connection__database"],"ports":["2368"]}},"variables":[{"id":"$$config_url","name":"url","label":"URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_database__client","name":"database__client","label":"Database Client","defaultValue":"mysql","description":"","required":true},{"id":"$$config_database__connection__host","name":"database__connection__host","label":"Database Host","defaultValue":"","description":"","required":true,"placeholder":"db.coolify.io"},{"id":"$$config_database__connection__user","name":"database__connection__user","label":"Database User","defaultValue":"","description":"","placeholder":"ghost","required":true},{"id":"$$secret_database__connection__password","name":"database__connection__password","label":"Database Password","defaultValue":"","description":"","placeholder":"superSecretP4ssword","showOnConfiguration":true,"required":true},{"id":"$$config_database__connection__database","name":"database__connection__database","label":"Database Name","defaultValue":"","description":"","placeholder":"ghost_db","required":true}]},{"templateVersion":"1.0.0","defaultVersion":"5.25.3","documentation":"https://docs.ghost.org","type":"ghost-mysql","name":"Ghost","subname":"(MySQL)","description":"Ghost is a free and open source blogging platform.","services":{"$$id":{"name":"Ghost","depends_on":["$$id-mysql"],"image":"ghost:$$core_version","volumes":["$$id-ghost:/var/lib/ghost/content"],"environment":["url=$$config_url","database__client=$$config_database__client","database__connection__host=$$config_database__connection__host","database__connection__user=$$config_mysql_user","database__connection__password=$$secret_mysql_password","database__connection__database=$$config_mysql_database"],"ports":["2368"]},"$$id-mysql":{"name":"MySQL","depends_on":[],"image":"mysql:8.0","volumes":["$$id-mysql:/var/lib/mysql"],"environment":["MYSQL_USER=$$config_mysql_user","MYSQL_PASSWORD=$$secret_mysql_password","MYSQL_DATABASE=$$config_mysql_database","MYSQL_ROOT_PASSWORD=$$secret_mysql_root_password"],"ports":[]}},"variables":[{"id":"$$config_url","name":"url","label":"URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_database__client","name":"database__client","label":"Database Client","defaultValue":"mysql","description":"","readOnly":true},{"id":"$$config_database__connection__host","name":"database__connection__host","label":"Database Host","defaultValue":"$$id-mysql","description":""},{"id":"$$config_mysql_user","main":"$$id-mysql","name":"MYSQL_USER","label":"MySQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_mysql_password","main":"$$id-mysql","name":"MYSQL_PASSWORD","label":"MySQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_mysql_database","main":"$$id-mysql","name":"MYSQL_DATABASE","label":"MySQL Database","defaultValue":"ghost","description":""},{"id":"$$secret_mysql_root_password","name":"MYSQL_ROOT_PASSWORD","label":"MySQL Root Password","defaultValue":"$$generate_password","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"php8.1","documentation":"https://wordpress.org/","type":"wordpress","name":"WordPress","subname":"(MySQL)","description":"A content management system based on PHP.","labels":["wordpress","php","cms"],"services":{"$$id":{"name":"WordPress","depends_on":["$$id-mysql"],"image":"wordpress:$$core_version","volumes":["$$id-wordpress-data:/var/www/html"],"environment":["WORDPRESS_DB_HOST=$$config_wordpress_db_host","WORDPRESS_DB_USER=$$config_mysql_user","WORDPRESS_DB_PASSWORD=$$secret_mysql_password","WORDPRESS_DB_NAME=$$config_mysql_database","WORDPRESS_CONFIG_EXTRA=$$config_wordpress_config_extra"],"ports":["80"]},"$$id-mysql":{"name":"MySQL","depends_on":[],"image":"bitnami/mysql:5.7","imageArm":"mysql:8.0","volumes":["$$id-mysql-data:/bitnami/mysql/data"],"volumesArm":["$$id-mysql-data:/var/lib/mysql"],"environment":["MYSQL_ROOT_PASSWORD=$$secret_mysql_root_password","MYSQL_ROOT_USER=$$config_mysql_root_user","MYSQL_DATABASE=$$config_mysql_database","MYSQL_USER=$$config_mysql_user","MYSQL_PASSWORD=$$secret_mysql_password"]}},"variables":[{"id":"$$config_wordpress_db_host","name":"WORDPRESS_DB_HOST","label":"Database Host","defaultValue":"$$id-mysql","description":"","readOnly":true},{"id":"$$config_wordpress_config_extra","name":"WORDPRESS_CONFIG_EXTRA","label":"WordPress Config Extra","defaultValue":"","description":"","type":"textarea","placeholder":"define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);\n@ini_set('display_errors', 0);\n"},{"id":"$$secret_mysql_root_password","name":"MYSQL_ROOT_PASSWORD","label":"MySQL Root Password","defaultValue":"$$generate_password","description":"","readOnly":true},{"id":"$$config_mysql_root_user","name":"MYSQL_ROOT_USER","label":"MySQL Root User","defaultValue":"$$generate_username","description":"","readOnly":true},{"id":"$$config_mysql_database","name":"MYSQL_DATABASE","label":"MySQL Database","defaultValue":"wordpress","description":"","readOnly":true},{"id":"$$config_mysql_user","name":"MYSQL_USER","label":"MySQL User","defaultValue":"$$generate_username","description":"","readOnly":true},{"id":"$$secret_mysql_password","name":"MYSQL_PASSWORD","label":"MySQL Password","defaultValue":"$$generate_password","description":"","readOnly":true}]},{"templateVersion":"1.0.0","defaultVersion":"php8.1","documentation":"https://wordpress.org/","type":"wordpress-only","name":"WordPress","subname":"(without DB)","description":"A content management system based on PHP.","labels":["wordpress","php","cms"],"services":{"$$id":{"name":"WordPress","image":"wordpress:$$core_version","volumes":["$$id-wordpress-data:/var/www/html"],"environment":["WORDPRESS_DB_HOST=$$config_wordpress_db_host","WORDPRESS_DB_PORT=$$config_wordpress_db_port","WORDPRESS_DB_USER=$$config_wordpress_db_user","WORDPRESS_DB_PASSWORD=$$secret_wordpress_db_password","WORDPRESS_DB_NAME=$$config_wordpress_db_name","WORDPRESS_CONFIG_EXTRA=$$config_wordpress_config_extra"],"ports":["80"]}},"variables":[{"id":"$$config_wordpress_db_host","name":"WORDPRESS_DB_HOST","label":"Database Host","defaultValue":"","description":"","placeholder":"db.coollabs.io","required":true},{"id":"$$config_wordpress_db_port","name":"WORDPRESS_DB_PORT","label":"Database Port","defaultValue":"","description":"","placeholder":"3306","required":true},{"id":"$$config_wordpress_db_user","name":"WORDPRESS_DB_USER","label":"Database User","defaultValue":"","description":"","placeholder":"wordpress","required":true},{"id":"$$secret_wordpress_db_password","name":"WORDPRESS_DB_PASSWORD","label":"Database Password","defaultValue":"","description":"","placeholder":"supers3cr3tpassw0rd!","required":true,"showOnConfiguration":true},{"id":"$$config_wordpress_db_name","name":"WORDPRESS_DB_NAME","label":"Database Name","defaultValue":"","description":"","placeholder":"wordpress","required":true},{"id":"$$config_wordpress_config_extra","name":"WORDPRESS_CONFIG_EXTRA","label":"Extra Config","defaultValue":"","description":"","type":"textarea","placeholder":"define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);\n@ini_set('display_errors', 0);\n"}]},{"templateVersion":"1.0.0","defaultVersion":"4.9.0","documentation":"https://coder.com/docs/coder-oss/latest","type":"vscodeserver","name":"VSCode Server","description":"Visual Studio Code on a remote server, accessible through the browser.","labels":["vscode","ide"],"services":{"$$id":{"name":"VSCode Server","depends_on":[],"image":"codercom/code-server:$$core_version","volumes":["$$id-config-data:/home/coder/.local/share/code-server","$$id-vscodeserver-data:/home/coder","$$id-keys-directory:/root/.ssh","$$id-theme-and-plugin-directory:/root/.local/share/code-server"],"environment":["PASSWORD=$$secret_password"],"ports":["8080"]}},"variables":[{"id":"$$secret_password","name":"PASSWORD","label":"Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"RELEASE.2022-12-12T19-27-27Z","documentation":"https://min.io/docs/minio","type":"minio","name":"MinIO","description":"A cloud storage server compatible with Amazon S3.","labels":["storage","s3"],"services":{"$$id":{"name":"MinIO","command":"server /data --console-address :9001","depends_on":[],"image":"minio/minio:$$core_version","volumes":["$$id-minio-data:/data","$$id-data-write:/files"],"environment":["MINIO_SERVER_URL=$$config_coolify_fqdn_minio_console","MINIO_BROWSER_REDIRECT_URL=$$config_minio_browser_redirect_url","MINIO_DOMAIN=$$config_minio_domain","MINIO_ROOT_USER=$$config_minio_root_user","MINIO_ROOT_PASSWORD=$$secret_minio_root_password"],"ports":["9000","9001"],"proxy":[{"port":"9000","domain":"$$config_coolify_fqdn_minio_console"},{"port":"9001"}]}},"variables":[{"id":"$$config_coolify_fqdn_minio_console","name":"MINIO_SERVER_URL","label":"MinIO Server URL","defaultValue":"","description":"Specify the URL hostname the MinIO Console should use for connecting to the MinIO Server.","required":true},{"id":"$$config_minio_browser_redirect_url","name":"MINIO_BROWSER_REDIRECT_URL","label":"Browser Redirect URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_minio_domain","name":"MINIO_DOMAIN","label":"Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$config_minio_root_user","name":"MINIO_ROOT_USER","label":"Root User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_minio_root_password","name":"MINIO_ROOT_PASSWORD","label":"Root User Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"0.21.1","documentation":"https://fider.io/docs","type":"fider","name":"Fider","description":"A platform to collect and organize customer feedback.","labels":["suggestion","feedback"],"services":{"$$id":{"name":"Fider","image":"getfider/fider:$$core_version","depends_on":["$$id-postgresql"],"environment":["BASE_URL=$$config_base_url","DATABASE_URL=$$secret_database_url","JWT_SECRET=$$secret_jwt_secret","EMAIL_NOREPLY=$$config_email_noreply","EMAIL_MAILGUN_API=$$secret_email_mailgun_api","EMAIL_MAILGUN_REGION=$$config_email_mailgun_region","EMAIL_MAILGUN_DOMAIN=$$config_email_mailgun_domain","EMAIL_SMTP_HOST=$$config_email_smtp_host","EMAIL_SMTP_PORT=$$config_email_smtp_port","EMAIL_SMTP_USER=$$config_email_smtp_user","EMAIL_SMTP_PASSWORD=$$secret_email_smtp_password","EMAIL_SMTP_ENABLE_STARTTLS=$$config_email_smtp_enable_starttls"],"ports":["3000"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"]}},"variables":[{"id":"$$config_base_url","name":"BASE_URL","label":"Base URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db?sslmode=disable","description":""},{"id":"$$secret_jwt_secret","name":"JWT_SECRET","label":"JWT Secret","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_email_noreply","name":"EMAIL_NOREPLY","label":"No Reply Email Address","defaultValue":"noreply@example.com","description":""},{"id":"$$secret_email_mailgun_api","name":"EMAIL_MAILGUN_API","label":"Mailgun API Key","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$config_email_mailgun_region","name":"EMAIL_MAILGUN_REGION","label":"Mailgun Region","defaultValue":"EU","description":""},{"id":"$$config_email_mailgun_domain","name":"EMAIL_MAILGUN_DOMAIN","label":"Mailgun Domain","defaultValue":"","description":""},{"id":"$$config_email_smtp_host","name":"EMAIL_SMTP_HOST","label":"SMTP Host","defaultValue":"","description":""},{"id":"$$config_email_smtp_port","name":"EMAIL_SMTP_PORT","label":"SMTP Port","defaultValue":"587","description":""},{"id":"$$config_email_smtp_user","name":"EMAIL_SMTP_USER","label":"SMTP User","defaultValue":"","description":""},{"id":"$$secret_email_smtp_password","name":"EMAIL_SMTP_PASSWORD","label":"SMTP Password","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$config_email_smtp_enable_starttls","name":"EMAIL_SMTP_ENABLE_STARTTLS","label":"SMTP Enable StartTLS","defaultValue":"false","description":""},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"$$generate_username","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"0.207.0","documentation":"https://docs.n8n.io","type":"n8n","name":"n8n.io","description":"A free and open node based Workflow Automation Tool.","labels":["workflow","automation","ifttt","zapier","nodered"],"services":{"$$id":{"name":"N8n","depends_on":[],"image":"n8nio/n8n:$$core_version","volumes":["$$id-data:/root/.n8n","$$id-data-write:/files","/var/run/docker.sock:/var/run/docker.sock"],"environment":["WEBHOOK_URL=$$config_webhook_url"],"ports":["5678"]}},"variables":[{"id":"$$config_webhook_url","name":"WEBHOOK_URL","label":"Webhook URL","defaultValue":"$$generate_fqdn","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"stable","documentation":"https://plausible.io/doc/","arch":"amd64","type":"plausibleanalytics","name":"Plausible Analytics","description":"A lightweight and open-source website analytics tool.","labels":["analytics","statistics","plausible","gdpr","no-cookie","google analytics"],"services":{"$$id":{"name":"Plausible Analytics","command":"sh -c \"sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run\"","depends_on":["$$id-postgresql","$$id-clickhouse"],"image":"plausible/analytics:$$core_version","environment":["ADMIN_USER_EMAIL=$$config_admin_user_email","ADMIN_USER_NAME=$$config_admin_user_name","ADMIN_USER_PWD=$$secret_admin_user_pwd","BASE_URL=$$config_base_url","SECRET_KEY_BASE=$$secret_secret_key_base","DISABLE_AUTH=$$config_disable_auth","DISABLE_REGISTRATION=$$config_disable_registration","DATABASE_URL=$$secret_database_url","CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url"],"ports":["8000"]},"$$id-postgresql":{"name":"PostgreSQL","image":"bitnami/postgresql:13","volumes":["$$id-postgresql-data:/bitnami/postgresql"],"environment":["POSTGRESQL_PASSWORD=$$secret_postgresql_password","POSTGRESQL_USERNAME=$$config_postgresql_username","POSTGRESQL_DATABASE=$$config_postgresql_database"]},"$$id-clickhouse":{"name":"Clickhouse","volumes":["$$id-clickhouse-data:/var/lib/clickhouse"],"image":"clickhouse/clickhouse-server:22.6-alpine","ulimits":{"nofile":{"soft":262144,"hard":262144}},"files":[{"location":"/etc/clickhouse-server/users.d/logging.xml","content":"warningtrue"},{"location":"/etc/clickhouse-server/config.d/logging.xml","content":"00"},{"location":"/docker-entrypoint-initdb.d/init.query","content":"CREATE DATABASE IF NOT EXISTS plausible;"},{"location":"/docker-entrypoint-initdb.d/init-db.sh","content":"clickhouse client --queries-file /docker-entrypoint-initdb.d/init.query"}]}},"variables":[{"id":"$$config_base_url","name":"BASE_URL","label":"Base URL","defaultValue":"$$generate_fqdn","description":"You must set this to the FQDN of the Plausible Analytics instance. This is used to generate the links to the Plausible Analytics instance."},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgresql_username:$$secret_postgresql_password@$$id-postgresql:5432/$$config_postgresql_database","description":""},{"id":"$$secret_clickhouse_database_url","name":"CLICKHOUSE_DATABASE_URL","label":"Database URL for Clickhouse","defaultValue":"http://$$id-clickhouse:8123/plausible","description":""},{"id":"$$config_admin_user_email","name":"ADMIN_USER_EMAIL","label":"Admin Email Address","defaultValue":"admin@example.com","description":"This is the admin email. Please change it."},{"id":"$$config_admin_user_name","name":"ADMIN_USER_NAME","label":"Admin User Name","defaultValue":"$$generate_username","description":"This is the admin username. Please change it."},{"id":"$$secret_admin_user_pwd","name":"ADMIN_USER_PWD","label":"Admin User Password","defaultValue":"$$generate_password","description":"This is the admin password. Please change it.","showOnConfiguration":true},{"id":"$$secret_secret_key_base","name":"SECRET_KEY_BASE","label":"Secret Key Base","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_disable_auth","name":"DISABLE_AUTH","label":"Authentication","defaultValue":"false","description":""},{"id":"$$config_disable_registration","name":"DISABLE_REGISTRATION","label":"Registration","defaultValue":"true","description":""},{"id":"$$config_postgresql_username","main":"$$id-postgresql","name":"POSTGRESQL_USERNAME","label":"PostgreSQL Username","defaultValue":"postgresql","description":""},{"id":"$$secret_postgresql_password","main":"$$id-postgresql","name":"POSTGRESQL_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgresql_database","main":"$$id-postgresql","name":"POSTGRESQL_DATABASE","label":"PostgreSQL Database","defaultValue":"plausible","description":""},{"id":"$$config_scriptName","name":"SCRIPT_NAME","label":"Custom Script Name","defaultValue":"plausible.js","description":"This is the default script name."}]},{"templateVersion":"1.0.0","defaultVersion":"0.99.1","documentation":"https://docs.nocodb.com","type":"nocodb","name":"NocoDB","description":"Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart-spreadsheet.","labels":["database","airtable","spreadsheet"],"services":{"$$id":{"name":"NocoDB","image":"nocodb/nocodb:$$core_version","environment":["PORT=$$config_port","NC_DB=$$config_nc_db","DATABASE_URL=$$secret_database_url","NC_PUBLIC_URL=$$config_public_url","NC_AUTH_JWT_SECRET=$$secret_auth_jwt_secret","NC_SENTRY_DSN=$$secret_sentry_dsn","NC_CONNECT_TO_EXTERNAL_DB_DISABLED=$$config_connect_to_external_db_disabled","NC_DISABLE_TELE=$$config_disable_tele"],"volumes":["$$id-data:/usr/app/data"],"ports":["8080"]}},"variables":[{"id":"$$config_nc_db","name":"NC_DB","label":"Database","defaultValue":"","description":"MySQL, PostgreSQL and MSSQL connection urls supported. If absent: A local SQLite will be created in root folder."},{"id":"$$config_port","name":"PORT","label":"Port","defaultValue":"8080","description":""},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL","defaultValue":"","description":"JDBC URL Format. Can be used instead of NC_DB. Used in 1-Click Heroku deployment."},{"id":"$$config_public_url","name":"NC_PUBLIC_URL","label":"Public URL","defaultValue":"","description":"Used for sending Email invitations. If absent: Best guess from http request params."},{"id":"$$secret_auth_jwt_secret","name":"NC_AUTH_JWT_SECRET","label":"Auth JWT Secret","defaultValue":"$$generate_hex(64)","description":"JWT secret used for auth and storing other secrets. If absent: A Random secret will be generated."},{"id":"$$secret_sentry_dsn","name":"NC_SENTRY_DSN","label":"Sentry DSN","defaultValue":"","description":"For Sentry monitoring."},{"id":"$$config_connect_to_external_db_disabled","name":"NC_CONNECT_TO_EXTERNAL_DB_DISABLED","label":"Disable External Database","defaultValue":"0","description":"Disable Project creation with external database. (Enter \"1\" to disable)."},{"id":"$$config_disable_tele","name":"NC_DISABLE_TELE","label":"NocoDB Disable Telemetry","defaultValue":"1","description":"Disable telemetry (Enter \"1\" to disable)."}]}] \ No newline at end of file +[{"templateVersion":"1.0.0","defaultVersion":"9.22","documentation":"https://docs.directus.io/getting-started/introduction.html","type":"directus-postgresql","name":"Directus","subname":"(PostgreSQL)","description":"Directus is a free and open-source headless CMS framework for managing custom SQL-based databases.","labels":["CMS","headless"],"services":{"$$id":{"name":"Directus","depends_on":["$$id-postgresql","$$id-redis"],"image":"directus/directus:$$core_version","volumes":["$$id-uploads:/directus/uploads","$$id-database:/directus/database","$$id-extensions:/directus/extensions"],"environment":["KEY=$$secret_key","SECRET=$$secret_secret","DB_CLIENT=pg","DB_CONNECTION_STRING=$$secret_db_connection_string","CACHE_ENABLED=true","CACHE_STORE=redis","CACHE_REDIS=$$secret_cache_redis","ADMIN_EMAIL=$$config_admin_email","ADMIN_PASSWORD=$$secret_admin_password","CACHE_AUTO_PURGE=true","PUBLIC_URL=$$config_public_url"],"ports":["8055"]},"$$id-postgresql":{"name":"Directus PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]},"$$id-redis":{"name":"Directus Redis","depends_on":[],"image":"redis:7.0.4-alpine","command":"--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5","volumes":["$$id-redis:/data"],"environment":[]}},"variables":[{"id":"$$config_public_url","name":"PUBLIC_URL","label":"Public URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_db_connection_string","name":"DB_CONNECTION_STRING","label":"Directus Database Url","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"Database","defaultValue":"directus","description":""},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$secret_cache_redis","name":"CACHE_REDIS","label":"Redis Url","defaultValue":"redis://$$id-redis:6379","description":""},{"id":"$$config_admin_email","name":"ADMIN_EMAIL","label":"Initial Admin Email","defaultValue":"admin@example.com","description":"The email address of the first user that is automatically created. You can change it later in Directus."},{"id":"$$secret_admin_password","name":"ADMIN_PASSWORD","label":"Initial Admin Password","defaultValue":"$$generate_password","description":"The password of the first user that is automatically created.","showOnConfiguration":true},{"id":"$$secret_key","name":"KEY","label":"Key","defaultValue":"$$generate_password","description":"Unique identifier for the project.","showOnConfiguration":true},{"id":"$$secret_secret","name":"SECRET","label":"Secret","defaultValue":"$$generate_password","description":"Secret string for the project.","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"v1.3.8","documentation":"https://github.com/LibreTranslate/LibreTranslate","description":"Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup.","type":"libretranslate","name":"Libretranslate","labels":["translator","argos","python","libretranslate"],"services":{"$$id":{"name":"Libretranslate","image":"libretranslate/libretranslate:$$core_version","environment":["LT_HOST=0.0.0.0","LT_SUGGESTIONS=true","LT_CHAR_LIMIT=$$config_lt_char_limit","LT_REQ_LIMIT=$$config_lt_req_limit","LT_BATCH_LIMIT=$$config_lt_batch_limit","LT_GA_ID=$$config_lt_ga_id","LT_DISABLE_WEB_UI=$$config_lt_web_ui"],"volumes":["$$id-libretranslate:/libretranslate"],"ports":["5000"]}},"variables":[{"id":"$$config_lt_char_limit","name":"LT_CHAR_LIMIT","label":"Char limit","defaultValue":"5000","description":"Set character limit."},{"id":"$$config_lt_req_limit","name":"LT_REQ_LIMIT","label":"Request limit","defaultValue":"5000","description":"Set maximum number of requests per minute per client."},{"id":"$$config_lt_batch_limit","name":"LT_BATCH_LIMIT","label":"Batch Limit","defaultValue":"5000","description":"Set maximum number of texts to translate in a batch request."},{"id":"$$config_lt_ga_id","name":"LT_GA_ID","label":"Google Analytics ID","defaultValue":"","description":"Enable Google Analytics on the API client page by providing an ID"},{"id":"$$config_lt_web_ui","name":"LT_DISABLE_WEB_UI","label":"Web UI","defaultValue":"false","description":"Disable or enable web ui. True or false."}]},{"templateVersion":"1.0.0","defaultVersion":"0.8.0","documentation":"https://github.com/benbusby/whoogle-search","type":"whoogle","name":"Whoogle Search","description":"A self-hosted, ad-free, privacy-respecting metasearch engine","labels":["search","google"],"services":{"$$id":{"name":"Whoogle Search","documentation":"https://github.com/benbusby/whoogle-search","depends_on":[],"image":"benbusby/whoogle-search:$$core_version","cap_drop":["ALL"],"environment":["WHOOGLE_USER=$$config_whoogle_username","WHOOGLE_PASS=$$secret_whoogle_password","WHOOGLE_CONFIG_PREFERENCES_KEY=$$secret_whoogle_preferences_key"],"ulimits":{"nofile":{"soft":262144,"hard":262144}},"ports":["5000"]}},"variables":[{"id":"$$config_whoogle_username","name":"WHOOGLE_USER","label":"Whoogle User","defaultValue":"$$generate_username","description":"Username to log into Whoogle"},{"id":"$$secret_whoogle_password","name":"WHOOGLE_PASSWORD","label":"Whoogle Password","defaultValue":"$$generate_password","description":"Password to log into Whoogle","showOnConfiguration":true},{"id":"$$secret_whoogle_preferences_key","name":"WHOOGLE_CONFIG_PREFERENCES_KEY","label":"Whoogle preferences key","defaultValue":"$$generate_password","description":"password to encrypt preferences"}]},{"templateVersion":"1.0.0","defaultVersion":"1.1.3","documentation":"https://docs.openblocks.dev/","type":"openblocks","name":"Openblocks","description":"The Open Source Retool Alternative","services":{"$$id":{"image":"openblocksdev/openblocks-ce:$$core_version","volumes":["$$id-stacks-data:/openblocks-stacks"],"ports":["3000"]}}},{"templateVersion":"1.0.0","defaultVersion":"0.11.0","documentation":"https://pocketbase.io/docs/","type":"pocketbase","name":"Pocketbase","description":"Open Source realtime backend in 1 file","services":{"$$id":{"image":"coollabsio/pocketbase:$$core_version","volumes":["$$id-data:/app/pb_data"],"ports":["8080"]}}},{"templateVersion":"1.0.0","defaultVersion":"v1.5.1","documentation":"https://plausible.io/doc/","type":"plausibleanalytics-arm","name":"Plausible Analytics (ARM)","description":"A lightweight and open-source website analytics tool.","labels":["analytics","statistics","plausible","gdpr","no-cookie","google analytics"],"services":{"$$id":{"name":"Plausible Analytics","command":"sh -c \"sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run\"","depends_on":["$$id-postgresql","$$id-clickhouse"],"image":"plausible/analytics:$$core_version","environment":["ADMIN_USER_EMAIL=$$config_admin_user_email","ADMIN_USER_NAME=$$config_admin_user_name","ADMIN_USER_PWD=$$secret_admin_user_pwd","BASE_URL=$$config_base_url","SECRET_KEY_BASE=$$secret_secret_key_base","DISABLE_AUTH=$$config_disable_auth","DISABLE_REGISTRATION=$$config_disable_registration","DATABASE_URL=$$secret_database_url","CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url"],"ports":["8000"]},"$$id-postgresql":{"name":"PostgreSQL","image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_USER=$$config_postgres_user","POSTGRES_DB=$$config_postgres_db"]},"$$id-clickhouse":{"name":"Clickhouse","volumes":["$$id-clickhouse-data:/var/lib/clickhouse"],"image":"clickhouse/clickhouse-server:22.6-alpine","ulimits":{"nofile":{"soft":262144,"hard":262144}},"files":[{"location":"/etc/clickhouse-server/users.d/logging.xml","content":"warningtrue"},{"location":"/etc/clickhouse-server/config.d/logging.xml","content":"00"},{"location":"/docker-entrypoint-initdb.d/init.query","content":"CREATE DATABASE IF NOT EXISTS plausible;"},{"location":"/docker-entrypoint-initdb.d/init-db.sh","content":"clickhouse client --queries-file /docker-entrypoint-initdb.d/init.query"}]}},"variables":[{"id":"$$config_base_url","name":"BASE_URL","label":"Base URL","defaultValue":"$$generate_fqdn","description":"You must set this to the FQDN of the Plausible Analytics instance. This is used to generate the links to the Plausible Analytics instance."},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_clickhouse_database_url","name":"CLICKHOUSE_DATABASE_URL","label":"Database URL for Clickhouse","defaultValue":"http://$$id-clickhouse:8123/plausible","description":""},{"id":"$$config_admin_user_email","name":"ADMIN_USER_EMAIL","label":"Admin Email Address","defaultValue":"admin@example.com","description":"This is the admin email. Please change it."},{"id":"$$config_admin_user_name","name":"ADMIN_USER_NAME","label":"Admin User Name","defaultValue":"$$generate_username","description":"This is the admin username. Please change it."},{"id":"$$secret_admin_user_pwd","name":"ADMIN_USER_PWD","label":"Admin User Password","defaultValue":"$$generate_password","description":"This is the admin password. Please change it.","showOnConfiguration":true},{"id":"$$secret_secret_key_base","name":"SECRET_KEY_BASE","label":"Secret Key Base","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_disable_auth","name":"DISABLE_AUTH","label":"Authentication","defaultValue":"false","description":""},{"id":"$$config_disable_registration","name":"DISABLE_REGISTRATION","label":"Registration","defaultValue":"true","description":""},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL Username","defaultValue":"postgresql","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"plausible","description":""},{"id":"$$config_scriptName","name":"SCRIPT_NAME","label":"Custom Script Name","defaultValue":"plausible.js","description":"This is the default script name."}]},{"templateVersion":"1.0.0","defaultVersion":"1.18","documentation":"https://docs.gitea.io","type":"gitea","name":"Gitea","description":"Gitea is a community managed lightweight code hosting solution written in Go.","labels":["storage","git"],"services":{"$$id":{"name":"Gitea","documentation":"https://docs.gitea.io","image":"gitea/gitea:$$core_version","volumes":["$$id-data:/data","/etc/timezone:/etc/timezone:ro","/etc/localtime:/etc/localtime:ro"],"environment":["USER_UID=1000","USER_GID=1000","DOMAIN=$$config_domain","SSH_DOMAIN=$$config_ssh_domain","ROOT_URL=$$config_root_url","SECRET_KEY=$$secret_secret_key","INTERNAL_TOKEN=$$secret_internal_token","SSH_PORT=22","START_SSH_SERVER=$$config_start_ssh_server"],"ports":["3000","22"],"proxy":[{"port":"22","hostPort":"$$config_hostport_ssh"}]}},"variables":[{"id":"$$config_hostport_ssh","name":"SSH_PORT","label":"SSH Port","defaultValue":"8022","description":"","required":true},{"id":"$$config_domain","name":"DOMAIN","label":"Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$config_ssh_domain","name":"SSH_DOMAIN","label":"SSH Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$config_start_ssh_server","name":"START_SSH_SERVER","label":"Start SSH Server","defaultValue":"true","description":""},{"id":"$$config_root_url","name":"ROOT_URL","label":"Root URL of Gitea","defaultValue":"$$generate_fqdn_slash","description":""},{"id":"$$secret_secret_key","name":"SECRET_KEY","label":"Secret Key","defaultValue":"$$generate_hex(32)","description":""},{"id":"$$secret_internal_token","name":"INTERNAL_TOKEN","label":"Internal JWT Token","defaultValue":"$$generate_token","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"20.0","documentation":"https://www.keycloak.org/documentation","type":"keycloak","name":"Keycloak","description":"Keycloak provides user federation, strong authentication, user management, fine-grained authorization, and more.","labels":["authentication","authorization","oidconnect","saml2"],"services":{"$$id":{"name":"Keycloak","command":"start --db=postgres --features=token-exchange --import-realm","depends_on":["$$id-postgresql"],"image":"quay.io/keycloak/keycloak:$$core_version","volumes":["$$id-import:/opt/keycloak/data/import"],"environment":["KC_HEALTH_ENABLED=true","KC_PROXY=edge","KC_DB=postgres","KC_HOSTNAME=$$config_keycloak_domain","KEYCLOAK_ADMIN=$$config_admin_user","KEYCLOAK_ADMIN_PASSWORD=$$secret_keycloak_admin_password","KC_DB_PASSWORD=$$secret_postgres_password","KC_DB_USERNAME=$$config_postgres_user","KC_DB_URL=$$secret_keycloak_database_url"],"ports":["8080"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]}},"variables":[{"id":"$$config_keycloak_domain","name":"KEYCLOAK_DOMAIN","label":"Keycloak Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$secret_keycloak_database_url","name":"KEYCLOAK_DATABASE_URL","label":"Keycloak Database Url","defaultValue":"jdbc:postgresql://$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$config_admin_user","name":"KEYCLOAK_ADMIN","label":"Keycloak Admin User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_keycloak_admin_password","name":"KEYCLOAK_ADMIN_PASSWORD","label":"Keycloak Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"keycloak","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v3.7","documentation":"https://github.com/freyacodes/Lavalink","description":"Standalone audio sending node based on Lavaplayer.","type":"lavalink","name":"Lavalink","labels":["discord","discord bot","audio","lavalink","jda"],"services":{"$$id":{"name":"Lavalink","image":"fredboat/lavalink:$$core_version","environment":[],"volumes":["$$id-lavalink:/lavalink"],"ports":["$$config_port"],"files":[{"location":"/opt/Lavalink/application.yml","content":"server:\n port: 2333\n address: 0.0.0.0\nlavalink:\n server:\n password: \"$$secret_password\"\n sources:\n youtube: true\n bandcamp: true\n soundcloud: true\n twitch: true\n vimeo: true\n http: true\n local: false\n\nlogging:\n file:\n path: ./logs/\n\n level:\n root: INFO\n lavalink: INFO\n\n logback:\n rollingpolicy:\n max-file-size: 1GB\n max-history: 30"}]}},"variables":[{"id":"$$secret_password","name":"PASSWORD","label":"Password","defaultValue":"$$generate_password","required":true}]},{"templateVersion":"1.0.0","defaultVersion":"v1.9.3","documentation":"https://docs.appsmith.com/getting-started/setup/instance-configuration/","type":"appsmith","name":"Appsmith","description":"Fastest way to build internal apps over any database or API.","services":{"$$id":{"image":"appsmith/appsmith-ce:$$core_version","environment":["APPSMITH_MAIL_ENABLED=$$config_appsmith_mail_enabled","APPSMITH_DISABLE_TELEMETRY=$$config_appsmith_disable_telemetry","APPSMITH_DISABLE_INTERCOM=$$config_appsmith_disable_intercom"],"volumes":["$$id-stacks-data:/appsmith-stacks"],"ports":["80"]}},"variables":[{"id":"$$config_appsmith_mail_enabled","name":"APPSMITH_MAIL_ENABLED","label":"Enable Mail","defaultValue":"false","description":""},{"id":"$$config_appsmith_disable_telemetry","name":"APPSMITH_DISABLE_TELEMETRY","label":"Disable Telemetry","defaultValue":"true","description":""},{"id":"$$config_appsmith_disable_intercom","name":"APPSMITH_DISABLE_INTERCOM","label":"Disable Intercom","defaultValue":"true","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"0.58.4","documentation":"https://hub.docker.com/r/zadam/trilium","description":"A hierarchical note taking application with focus on building large personal knowledge bases.","labels":["personal","knowledge","notes","wiki"],"type":"trilium","name":"Trilium Notes","services":{"$$id":{"image":"zadam/trilium:$$core_version","environment":[],"volumes":["$$id-trilium:/home/node/trilium-data"],"ports":["8080"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"1.19.4","documentation":"https://hub.docker.com/r/louislam/uptime-kuma","description":"A free & fancy self-hosted monitoring tool.","labels":["uptime"],"type":"uptimekuma","name":"UptimeKuma","services":{"$$id":{"image":"louislam/uptime-kuma:$$core_version","environment":[],"volumes":["$$id-uptimekuma:/app/data"],"ports":["3001"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"6.0","documentation":"https://hub.docker.com/r/silviof/docker-languagetool","description":"A multilingual grammar, style and spell checker.","type":"languagetool","name":"LanguageTool","services":{"$$id":{"image":"silviof/docker-languagetool:$$core_version","environment":[],"volumes":["$$id-ngrams:/ngrams"],"ports":["8010"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"1.27.0","documentation":"https://hub.docker.com/r/vaultwarden/server","description":"Bitwarden compatible server written in Rust.","type":"vaultwarden","name":"VaultWarden","labels":["bitwarden","password manager"],"services":{"$$id":{"image":"vaultwarden/server:$$core_version","environment":[],"volumes":["$$id-data:/data"],"ports":["80"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"9.3.2","documentation":"https://hub.docker.com/r/grafana/grafana","type":"grafana","name":"Grafana","description":"Grafana allows you to query, visualize, alert on and understand your metrics.","labels":["monitoring","metrics","dashboard"],"services":{"$$id":{"image":"grafana/grafana:$$core_version","environment":[],"volumes":["$$id-config:/etc/grafana","$$id-grafana:/var/lib/grafana"],"ports":["3000"]}},"variables":[]},{"templateVersion":"1.0.0","defaultVersion":"1.2.0","documentation":"https://appwrite.io/docs","type":"appwrite","name":"Appwrite","description":"Secure Backend Server for Web, Mobile & Flutter Developers.","labels":["serverless","backend","storage","api"],"services":{"$$id":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_WORKER_PER_CORE=$$config__app_worker_per_core","_APP_LOCALE=$$config__app_locale","_APP_CONSOLE_WHITELIST_ROOT=$$config__app_console_whitelist_root","_APP_CONSOLE_WHITELIST_EMAILS=$$config__app_console_whitelist_emails","_APP_CONSOLE_WHITELIST_IPS=$$config__app_console_whitelist_ips","_APP_SYSTEM_EMAIL_NAME=$$config__app_system_email_name","_APP_SYSTEM_EMAIL_ADDRESS=$$config__app_system_email_address","_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=$$config__app_system_security_email_address","_APP_SYSTEM_RESPONSE_FORMAT=$$config__app_system_response_format","_APP_OPTIONS_ABUSE=$$config__app_options_abuse","_APP_OPTIONS_FORCE_HTTPS=$$config__app_options_force_https","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DOMAIN=$$config__app_domain","_APP_DOMAIN_TARGET=$$config__app_domain_target","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_SMTP_HOST=$$config__app_smtp_host","_APP_SMTP_PORT=$$config__app_smtp_port","_APP_SMTP_SECURE=$$config__app_smtp_secure","_APP_SMTP_USERNAME=$$config__app_smtp_username","_APP_SMTP_PASSWORD=$$secret__app_smtp_password","_APP_USAGE_STATS=$$config__app_usage_stats","_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","_APP_STORAGE_LIMIT=$$config__app_storage_limit","_APP_STORAGE_PREVIEW_LIMIT=$$config__app_storage_preview_limit","_APP_STORAGE_ANTIVIRUS=$$config__app_storage_antivirus_enabled","_APP_STORAGE_ANTIVIRUS_HOST=$$config__app_storage_antivirus_host","_APP_STORAGE_ANTIVIRUS_PORT=$$config__app_storage_antivirus_port","_APP_STORAGE_DEVICE=$$config__app_storage_device","_APP_STORAGE_S3_ACCESS_KEY=$$secret__app_storage_s3_access_key","_APP_STORAGE_S3_SECRET=$$secret__app_storage_s3_secret","_APP_STORAGE_S3_REGION=$$config__app_storage_s3_region","_APP_STORAGE_S3_BUCKET=$$config__app_storage_s3_bucket","_APP_STORAGE_DO_SPACES_ACCESS_KEY=$$secret__app_storage_do_spaces_access_key","_APP_STORAGE_DO_SPACES_SECRET=$$secret__app_storage_do_spaces_secret","_APP_STORAGE_DO_SPACES_REGION=$$config__app_storage_do_spaces_region","_APP_STORAGE_DO_SPACES_BUCKET=$$config__app_storage_do_spaces_bucket","_APP_STORAGE_BACKBLAZE_ACCESS_KEY=$$secret__app_storage_backblaze_access_key","_APP_STORAGE_BACKBLAZE_SECRET=$$secret__app_storage_backblaze_secret","_APP_STORAGE_BACKBLAZE_REGION=$$config__app_storage_backblaze_region","_APP_STORAGE_BACKBLAZE_BUCKET=$$config__app_storage_backblaze_bucket","_APP_STORAGE_LINODE_ACCESS_KEY=$$secret__app_storage_linode_access_key","_APP_STORAGE_LINODE_SECRET=$$secret__app_storage_linode_secret","_APP_STORAGE_LINODE_REGION=$$config__app_storage_linode_region","_APP_STORAGE_LINODE_BUCKET=$$config__app_storage_linode_bucket","_APP_STORAGE_WASABI_ACCESS_KEY=$$secret__app_storage_wasabi_access_key","_APP_STORAGE_WASABI_SECRET=$$secret__app_storage_wasabi_secret","_APP_STORAGE_WASABI_REGION=$$config__app_storage_wasabi_region","_APP_STORAGE_WASABI_BUCKET=$$config__app_storage_wasabi_bucket","_APP_FUNCTIONS_SIZE_LIMIT=$$config__app_functions_size_limit","_APP_FUNCTIONS_TIMEOUT=$$config__app_functions_timeout","_APP_FUNCTIONS_BUILD_TIMEOUT=$$config__app_functions_build_timeout","_APP_FUNCTIONS_CONTAINERS=$$config__app_functions_containers","_APP_FUNCTIONS_CPUS=$$config__app_functions_cpus","_APP_FUNCTIONS_MEMORY=$$config__app_functions_memory_allocated","_APP_FUNCTIONS_MEMORY_SWAP=$$config__app_functions_memory_swap","_APP_FUNCTIONS_RUNTIMES=$$config__app_functions_runtimes","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","_APP_STATSD_HOST=$$config__app_statsd_host","_APP_STATSD_PORT=$$config__app_statsd_port","_APP_MAINTENANCE_INTERVAL=$$config__app_maintenance_interval","_APP_MAINTENANCE_RETENTION_EXECUTION=$$config__app_maintenance_retention_execution","_APP_MAINTENANCE_RETENTION_CACHE=$$config__app_maintenance_retention_cache","_APP_MAINTENANCE_RETENTION_ABUSE=$$config__app_maintenance_retention_abuse","_APP_MAINTENANCE_RETENTION_AUDIT=$$config__app_maintenance_retention_audit","_APP_SMS_PROVIDER=$$config__app_sms_provider","_APP_SMS_FROM=$$config__app_sms_from","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-uploads:/storage/uploads","$$id-cache:/storage/cache","$$id-config:/storage/config","$$id-certificates:/storage/certificates","$$id-functions:/storage/functions"],"ports":["80"],"proxy":[{"port":"80"}]},"$$id-executor":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_FUNCTIONS_TIMEOUT=$$config__app_functions_timeout","_APP_FUNCTIONS_BUILD_TIMEOUT=$$config__app_functions_build_timeout","_APP_FUNCTIONS_CONTAINERS=$$config__app_functions_containers","_APP_FUNCTIONS_RUNTIMES=$$config__app_functions_runtimes","_APP_FUNCTIONS_CPUS=$$config__app_functions_cpus","_APP_FUNCTIONS_MEMORY=$$config__app_functions_memory_allocated","_APP_FUNCTIONS_MEMORY_SWAP=$$config__app_functions_memory_swap","_APP_FUNCTIONS_INACTIVE_THRESHOLD=$$config__app_functions_inactive_threshold","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","_APP_STORAGE_DEVICE=$$config__app_storage_device","_APP_STORAGE_S3_ACCESS_KEY=$$secret__app_storage_s3_access_key","_APP_STORAGE_S3_SECRET=$$secret__app_storage_s3_secret","_APP_STORAGE_S3_REGION=$$config__app_storage_s3_region","_APP_STORAGE_S3_BUCKET=$$config__app_storage_s3_bucket","_APP_STORAGE_DO_SPACES_ACCESS_KEY=$$secret__app_storage_do_spaces_access_key","_APP_STORAGE_DO_SPACES_SECRET=$$secret__app_storage_do_spaces_secret","_APP_STORAGE_DO_SPACES_REGION=$$config__app_storage_do_spaces_region","_APP_STORAGE_DO_SPACES_BUCKET=$$config__app_storage_do_spaces_bucket","_APP_STORAGE_BACKBLAZE_ACCESS_KEY=$$secret__app_storage_backblaze_access_key","_APP_STORAGE_BACKBLAZE_SECRET=$$secret__app_storage_backblaze_secret","_APP_STORAGE_BACKBLAZE_REGION=$$config__app_storage_backblaze_region","_APP_STORAGE_BACKBLAZE_BUCKET=$$config__app_storage_backblaze_bucket","_APP_STORAGE_LINODE_ACCESS_KEY=$$secret__app_storage_linode_access_key","_APP_STORAGE_LINODE_SECRET=$$secret__app_storage_linode_secret","_APP_STORAGE_LINODE_REGION=$$config__app_storage_linode_region","_APP_STORAGE_LINODE_BUCKET=$$config__app_storage_linode_bucket","_APP_STORAGE_WASABI_ACCESS_KEY=$$secret__app_storage_wasabi_access_key","_APP_STORAGE_WASABI_SECRET=$$secret__app_storage_wasabi_secret","_APP_STORAGE_WASABI_REGION=$$config__app_storage_wasabi_region","_APP_STORAGE_WASABI_BUCKET=$$config__app_storage_wasabi_bucket","DOCKERHUB_PULL_USERNAME=$$config_dockerhub_pull_username","DOCKERHUB_PULL_PASSWORD=$$secret_dockerhub_pull_password","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-functions:/storage/functions","$$id-builds:/storage/builds","/var/run/docker.sock:/var/run/docker.sock","/tmp:/tmp:rw"],"entrypoint":"executor"},"$$id-influxdb":{"image":"appwrite/influxdb:1.5.0","environment":[],"volumes":["$$id-influxdb:/var/lib/influxdb"]},"$$id-maintenance":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DOMAIN=$$config__app_domain","_APP_DOMAIN_TARGET=$$config__app_domain_target","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_MAINTENANCE_INTERVAL=$$config__app_maintenance_interval","_APP_MAINTENANCE_RETENTION_EXECUTION=$$config__app_maintenance_retention_execution","_APP_MAINTENANCE_RETENTION_CACHE=$$config__app_maintenance_retention_cache","_APP_MAINTENANCE_RETENTION_ABUSE=$$config__app_maintenance_retention_abuse","_APP_MAINTENANCE_RETENTION_AUDIT=$$config__app_maintenance_retention_audit","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"maintenance"},"$$id-mariadb":{"image":"mariadb:10.7","command":"--innodb-flush-method fsync","environment":["MARIADB_ROOT_PASSWORD=$$secret__app_db_root_pass","MARIADB_DATABASE=$$config__app_db_schema","MARIADB_USER=$$config__app_db_user","MARIADB_PASSWORD=$$secret__app_db_pass","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-mariadb:/var/lib/mysql"]},"$$id-realtime":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_WORKER_PER_CORE=$$config__app_worker_per_core","_APP_OPTIONS_ABUSE=$$config__app_options_abuse","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_USAGE_STATS=$$config__app_usage_stats","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"realtime","proxy":[{"port":"80","pathPrefix":"/v1/realtime"}]},"$$id-redis":{"image":"redis:7.0.4-alpine","command":"--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5","environment":[],"volumes":["$$id-redis:/data"]},"$$id-schedule":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"schedule"},"$$id-telegraf":{"image":"appwrite/telegraf:1.4.0","environment":["_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-influxdb:/var/lib/influxdb"]},"$$id-usage-database":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","_APP_USAGE_TIMESERIES_INTERVAL=$$config__app_usage_timeseries_interval","_APP_USAGE_DATABASE_INTERVAL=$$config__app_usage_database_interval","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"usage --type database"},"$$id-usage":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_INFLUXDB_HOST=$$config__app_influxdb_host","_APP_INFLUXDB_PORT=$$config__app_influxdb_port","_APP_USAGE_TIMESERIES_INTERVAL=$$config__app_usage_timeseries_interval","_APP_USAGE_DATABASE_INTERVAL=$$config__app_usage_database_interval","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"usage --type timeseries"},"$$id-worker-audits":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-audits"},"$$id-worker-builds":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-builds"},"$$id-worker-certificates":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_DOMAIN=$$config__app_domain","_APP_DOMAIN_TARGET=$$config__app_domain_target","_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=$$config__app_system_security_email_address","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-config:/storage/config","$$id-certificates:/storage/certificates"],"entrypoint":"worker-certificates"},"$$id-worker-databases":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-databases"},"$$id-worker-deletes":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_STORAGE_DEVICE=$$config__app_storage_device","_APP_STORAGE_S3_ACCESS_KEY=$$secret__app_storage_s3_access_key","_APP_STORAGE_S3_SECRET=$$secret__app_storage_s3_secret","_APP_STORAGE_S3_REGION=$$config__app_storage_s3_region","_APP_STORAGE_S3_BUCKET=$$config__app_storage_s3_bucket","_APP_STORAGE_DO_SPACES_ACCESS_KEY=$$secret__app_storage_do_spaces_access_key","_APP_STORAGE_DO_SPACES_SECRET=$$secret__app_storage_do_spaces_secret","_APP_STORAGE_DO_SPACES_REGION=$$config__app_storage_do_spaces_region","_APP_STORAGE_DO_SPACES_BUCKET=$$config__app_storage_do_spaces_bucket","_APP_STORAGE_BACKBLAZE_ACCESS_KEY=$$secret__app_storage_backblaze_access_key","_APP_STORAGE_BACKBLAZE_SECRET=$$secret__app_storage_backblaze_secret","_APP_STORAGE_BACKBLAZE_REGION=$$config__app_storage_backblaze_region","_APP_STORAGE_BACKBLAZE_BUCKET=$$config__app_storage_backblaze_bucket","_APP_STORAGE_LINODE_ACCESS_KEY=$$secret__app_storage_linode_access_key","_APP_STORAGE_LINODE_SECRET=$$secret__app_storage_linode_secret","_APP_STORAGE_LINODE_REGION=$$config__app_storage_linode_region","_APP_STORAGE_LINODE_BUCKET=$$config__app_storage_linode_bucket","_APP_STORAGE_WASABI_ACCESS_KEY=$$secret__app_storage_wasabi_access_key","_APP_STORAGE_WASABI_SECRET=$$secret__app_storage_wasabi_secret","_APP_STORAGE_WASABI_REGION=$$config__app_storage_wasabi_region","_APP_STORAGE_WASABI_BUCKET=$$config__app_storage_wasabi_bucket","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":["$$id-uploads:/storage/uploads","$$id-cache:/storage/cache","$$id-functions:/storage/functions","$$id-builds:/storage/builds","$$id-certificates:/storage/certificates"],"entrypoint":"worker-deletes"},"$$id-worker-functions":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_DB_HOST=$$config__app_db_host","_APP_DB_PORT=$$config__app_db_port","_APP_DB_SCHEMA=$$config__app_db_schema","_APP_DB_USER=$$config__app_db_user","_APP_DB_PASS=$$secret__app_db_pass","_APP_FUNCTIONS_TIMEOUT=$$config__app_functions_timeout","_APP_EXECUTOR_SECRET=$$secret__app_executor_secret","_APP_EXECUTOR_HOST=$$config__app_executor_host","_APP_USAGE_STATS=$$config__app_usage_stats","DOCKERHUB_PULL_USERNAME=$$config_dockerhub_pull_username","DOCKERHUB_PULL_PASSWORD=$$secret_dockerhub_pull_password","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-functions"},"$$id-worker-mails":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_SYSTEM_EMAIL_NAME=$$config__app_system_email_name","_APP_SYSTEM_EMAIL_ADDRESS=$$config__app_system_email_address","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_SMTP_HOST=$$config__app_smtp_host","_APP_SMTP_PORT=$$config__app_smtp_port","_APP_SMTP_SECURE=$$config__app_smtp_secure","_APP_SMTP_USERNAME=$$config__app_smtp_username","_APP_SMTP_PASSWORD=$$secret__app_smtp_password","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-mails"},"$$id-worker-messaging":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_SMS_PROVIDER=$$config__app_sms_provider","_APP_SMS_FROM=$$config__app_sms_from","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-messaging"},"$$id-worker-webhooks":{"image":"appwrite/appwrite:$$core_version","environment":["_APP_ENV=$$config__app_env","_APP_OPENSSL_KEY_V1=$$secret__app_openssl_key_v1","_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=$$config__app_system_security_email_address","_APP_REDIS_HOST=$$config__app_redis_host","_APP_REDIS_PORT=$$config__app_redis_port","_APP_REDIS_USER=$$config__app_redis_user","_APP_REDIS_PASS=$$secret__app_redis_pass","_APP_LOGGING_PROVIDER=$$config__app_logging_provider","_APP_LOGGING_CONFIG=$$config__app_logging_config","OPEN_RUNTIMES_NETWORK=$$config_open_runtimes_network"],"volumes":[],"entrypoint":"worker-webhooks"}},"variables":[{"id":"$$config__app_influxdb_host","name":"_APP_INFLUXDB_HOST","label":"InfluxDB | _APP_INFLUXDB_HOST","defaultValue":"$$id-influxdb","description":""},{"id":"$$config__app_influxdb_port","name":"_APP_INFLUXDB_PORT","label":"InfluxDB | _APP_INFLUXDB_PORT","defaultValue":"8086","description":"InfluxDB server TCP port."},{"id":"$$config__app_env","name":"_APP_ENV","label":"General | _APP_ENV","defaultValue":"production","description":"Set your server running environment."},{"id":"$$config__app_worker_per_core","name":"_APP_WORKER_PER_CORE","label":"General | _APP_WORKER_PER_CORE","defaultValue":"6","description":"Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance."},{"id":"$$config__app_locale","name":"_APP_LOCALE","label":"General | _APP_LOCALE","defaultValue":"en","description":"Set your Appwrite's locale. By default, the locale is set to 'en'."},{"id":"$$config__app_console_whitelist_root","name":"_APP_CONSOLE_WHITELIST_ROOT","label":"General | _APP_CONSOLE_WHITELIST_ROOT","defaultValue":"enabled","description":"This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled."},{"id":"$$config__app_console_whitelist_emails","name":"_APP_CONSOLE_WHITELIST_EMAILS","label":"General | _APP_CONSOLE_WHITELIST_EMAILS","defaultValue":"","description":"This option allows you to limit creation of new users on the Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma."},{"id":"$$config__app_console_whitelist_ips","name":"_APP_CONSOLE_WHITELIST_IPS","label":"General | _APP_CONSOLE_WHITELIST_IPS","defaultValue":"","description":"This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\\n\\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma."},{"id":"$$config__app_system_email_name","name":"_APP_SYSTEM_EMAIL_NAME","label":"General | _APP_SYSTEM_EMAIL_NAME","defaultValue":"Appwrite","description":"This is the sender name value that will appear on email messages sent to developers from the Appwrite console. You can use url encoded strings for spaces and special chars."},{"id":"$$config__app_system_email_address","name":"_APP_SYSTEM_EMAIL_ADDRESS","label":"General | _APP_SYSTEM_EMAIL_ADDRESS","defaultValue":"team@appwrite.io","description":"This is the sender email address that will appear on email messages sent to developers from the Appwrite console. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users' SPAM folders."},{"id":"$$config__app_system_security_email_address","name":"_APP_SYSTEM_SECURITY_EMAIL_ADDRESS","label":"General | _APP_SYSTEM_SECURITY_EMAIL_ADDRESS","defaultValue":"certs@appwrite.io","description":"This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload."},{"id":"$$config__app_system_response_format","name":"_APP_SYSTEM_RESPONSE_FORMAT","label":"General | _APP_SYSTEM_RESPONSE_FORMAT","defaultValue":"","description":"Use this environment variable to set the default Appwrite HTTP response format to support an older version of Appwrite. This option is useful to overcome breaking changes between versions. You can also use the X-Appwrite-Response-Format HTTP request header to overwrite the response for a specific request. This variable accepts any valid Appwrite version. To use the current version format, leave the value of the variable empty."},{"id":"$$config__app_options_abuse","name":"_APP_OPTIONS_ABUSE","label":"General | _APP_OPTIONS_ABUSE","defaultValue":"enabled","description":"Allows you to disable abuse checks and API rate limiting. By default, set to 'enabled'. To cancel the abuse checking, set to 'disabled'. It is not recommended to disable this check-in a production environment."},{"id":"$$config__app_options_force_https","name":"_APP_OPTIONS_FORCE_HTTPS","label":"General | _APP_OPTIONS_FORCE_HTTPS","defaultValue":"disabled","description":"Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the 'Strict-Transport-Security' header to all HTTP responses."},{"id":"$$secret__app_openssl_key_v1","name":"_APP_OPENSSL_KEY_V1","label":"General | _APP_OPENSSL_KEY_V1","defaultValue":"$$generate_hex(256)","description":"This is your server private secret key that is used to encrypt all sensitive data on your server. Appwrite server encrypts all secret data on your server like webhooks, HTTP passwords, user sessions, and storage files. Keep it a secret and have a backup for it."},{"id":"$$config__app_domain","name":"_APP_DOMAIN","label":"General | _APP_DOMAIN","defaultValue":"$$generate_domain","description":"Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is 'localhost'."},{"id":"$$config__app_domain_target","name":"_APP_DOMAIN_TARGET","label":"General | _APP_DOMAIN_TARGET","defaultValue":"$$generate_fqdn","description":"A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite '_APP_DOMAIN' variable. The default value is 'localhost'."},{"id":"$$config__app_redis_host","name":"_APP_REDIS_HOST","label":"Redis | _APP_REDIS_HOST","defaultValue":"$$id-redis","description":""},{"id":"$$config__app_redis_port","name":"_APP_REDIS_PORT","label":"Redis | _APP_REDIS_PORT","defaultValue":"6379","description":"Redis server TCP port."},{"id":"$$config__app_redis_user","name":"_APP_REDIS_USER","label":"Redis | _APP_REDIS_USER","defaultValue":"","description":"Redis server user. This is an optional variable. Default value is an empty string."},{"id":"$$secret__app_redis_pass","name":"_APP_REDIS_PASS","label":"Redis | _APP_REDIS_PASS","defaultValue":"","description":"Redis server password. This is an optional variable. Default value is an empty string."},{"id":"$$config__app_db_host","name":"_APP_DB_HOST","label":"MariaDB | _APP_DB_HOST","defaultValue":"$$id-mariadb","description":""},{"id":"$$config__app_db_port","name":"_APP_DB_PORT","label":"MariaDB | _APP_DB_PORT","defaultValue":"3306","description":"MariaDB server TCP port."},{"id":"$$config__app_db_schema","name":"_APP_DB_SCHEMA","label":"MariaDB | _APP_DB_SCHEMA","defaultValue":"appwrite","description":"MariaDB server database schema."},{"id":"$$config__app_db_user","name":"_APP_DB_USER","label":"MariaDB | _APP_DB_USER","defaultValue":"user","description":"MariaDB server user name."},{"id":"$$secret__app_db_root_pass","name":"MARIADB_ROOT_PASSWORD","label":"MariaDB | MARIADB_ROOT_PASSWORD","defaultValue":"$$generate_hex(16)","description":"MariaDB server root user password."},{"id":"$$secret__app_db_pass","name":"_APP_DB_PASS","label":"MariaDB | _APP_DB_PASS","defaultValue":"$$generate_hex(16)","description":"MariaDB server user password."},{"id":"$$config__app_smtp_host","name":"_APP_SMTP_HOST","label":"SMTP | _APP_SMTP_HOST","defaultValue":"","description":"SMTP server host name address. Use an empty string to disable all mail sending from the server. The default value for this variable is an empty string."},{"id":"$$config__app_smtp_port","name":"_APP_SMTP_PORT","label":"SMTP | _APP_SMTP_PORT","defaultValue":"","description":"SMTP server TCP port. Empty by default."},{"id":"$$config__app_smtp_secure","name":"_APP_SMTP_SECURE","label":"SMTP | _APP_SMTP_SECURE","defaultValue":"","description":"SMTP secure connection protocol. Empty by default, change to 'tls' if running on a secure connection."},{"id":"$$config__app_smtp_username","name":"_APP_SMTP_USERNAME","label":"SMTP | _APP_SMTP_USERNAME","defaultValue":"","description":"SMTP server user name. Empty by default."},{"id":"$$secret__app_smtp_password","name":"_APP_SMTP_PASSWORD","label":"SMTP | _APP_SMTP_PASSWORD","defaultValue":"","description":"SMTP server user password. Empty by default."},{"id":"$$config__app_usage_stats","name":"_APP_USAGE_STATS","label":"General | _APP_USAGE_STATS","defaultValue":"enabled","description":"This variable allows you to disable the collection and displaying of usage stats. This value is set to 'enabled' by default, to disable the usage stats set the value to 'disabled'. When disabled, it's recommended to turn off the Worker Usage, Influxdb and Telegraf containers for better resource usage."},{"id":"$$config__app_storage_limit","name":"_APP_STORAGE_LIMIT","label":"Storage | _APP_STORAGE_LIMIT","defaultValue":"30000000","description":"Maximum file size allowed for file upload. The default value is 30MB. You should pass your size limit value in bytes."},{"id":"$$config__app_storage_preview_limit","name":"_APP_STORAGE_PREVIEW_LIMIT","label":"Storage | _APP_STORAGE_PREVIEW_LIMIT","defaultValue":"20000000","description":"Maximum file size allowed for file image preview. The default value is 20MB. You should pass your size limit value in bytes."},{"id":"$$config__app_storage_antivirus_enabled","name":"_APP_STORAGE_ANTIVIRUS","label":"Storage | _APP_STORAGE_ANTIVIRUS","defaultValue":"disabled","description":"This variable allows you to disable the internal anti-virus scans. This value is set to 'disabled' by default, to enable the scans set the value to 'enabled'. Before enabling, you must add the ClamAV service and depend on it on main Appwrite service."},{"id":"$$config__app_storage_antivirus_host","name":"_APP_STORAGE_ANTIVIRUS_HOST","label":"Storage | _APP_STORAGE_ANTIVIRUS_HOST","defaultValue":"clamav","description":"ClamAV server host name address."},{"id":"$$config__app_storage_antivirus_port","name":"_APP_STORAGE_ANTIVIRUS_PORT","label":"Storage | _APP_STORAGE_ANTIVIRUS_PORT","defaultValue":"3310","description":"ClamAV server TCP port."},{"id":"$$config__app_storage_device","name":"_APP_STORAGE_DEVICE","label":"Storage | _APP_STORAGE_DEVICE","defaultValue":"Local","description":"Select default storage device. The default value is 'Local'. List of supported adapters are 'Local', 'S3', 'DOSpaces', 'Backblaze', 'Linode' and 'Wasabi'."},{"id":"$$secret__app_storage_s3_access_key","name":"_APP_STORAGE_S3_ACCESS_KEY","label":"Storage | _APP_STORAGE_S3_ACCESS_KEY","defaultValue":"","description":"AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console."},{"id":"$$secret__app_storage_s3_secret","name":"_APP_STORAGE_S3_SECRET","label":"Storage | _APP_STORAGE_S3_SECRET","defaultValue":"","description":"AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console."},{"id":"$$config__app_storage_s3_region","name":"_APP_STORAGE_S3_REGION","label":"Storage | _APP_STORAGE_S3_REGION","defaultValue":"us-east-1","description":"AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console."},{"id":"$$config__app_storage_s3_bucket","name":"_APP_STORAGE_S3_BUCKET","label":"Storage | _APP_STORAGE_S3_BUCKET","defaultValue":"","description":"AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console."},{"id":"$$secret__app_storage_do_spaces_access_key","name":"_APP_STORAGE_DO_SPACES_ACCESS_KEY","label":"Storage | _APP_STORAGE_DO_SPACES_ACCESS_KEY","defaultValue":"","description":"DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console."},{"id":"$$secret__app_storage_do_spaces_secret","name":"_APP_STORAGE_DO_SPACES_SECRET","label":"Storage | _APP_STORAGE_DO_SPACES_SECRET","defaultValue":"","description":"DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console."},{"id":"$$config__app_storage_do_spaces_region","name":"_APP_STORAGE_DO_SPACES_REGION","label":"Storage | _APP_STORAGE_DO_SPACES_REGION","defaultValue":"us-east-1","description":"DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console."},{"id":"$$config__app_storage_do_spaces_bucket","name":"_APP_STORAGE_DO_SPACES_BUCKET","label":"Storage | _APP_STORAGE_DO_SPACES_BUCKET","defaultValue":"","description":"DigitalOcean spaces bucket. Required when storage adapter is set to DOSpaces. You can create spaces in your DigitalOcean console."},{"id":"$$secret__app_storage_backblaze_access_key","name":"_APP_STORAGE_BACKBLAZE_ACCESS_KEY","label":"Storage | _APP_STORAGE_BACKBLAZE_ACCESS_KEY","defaultValue":"","description":"Backblaze access key. Required when the storage adapter is set to Backblaze. Your Backblaze keyID will be your access key. You can get your keyID from your Backblaze console."},{"id":"$$secret__app_storage_backblaze_secret","name":"_APP_STORAGE_BACKBLAZE_SECRET","label":"Storage | _APP_STORAGE_BACKBLAZE_SECRET","defaultValue":"","description":"Backblaze secret key. Required when the storage adapter is set to Backblaze. Your Backblaze applicationKey will be your secret key. You can get your applicationKey from your Backblaze console."},{"id":"$$config__app_storage_backblaze_region","name":"_APP_STORAGE_BACKBLAZE_REGION","label":"Storage | _APP_STORAGE_BACKBLAZE_REGION","defaultValue":"us-west-004","description":"Backblaze region. Required when storage adapter is set to Backblaze. You can find your region info from your Backblaze console."},{"id":"$$config__app_storage_backblaze_bucket","name":"_APP_STORAGE_BACKBLAZE_BUCKET","label":"Storage | _APP_STORAGE_BACKBLAZE_BUCKET","defaultValue":"","description":"Backblaze bucket. Required when storage adapter is set to Backblaze. You can create your bucket from your Backblaze console."},{"id":"$$secret__app_storage_linode_access_key","name":"_APP_STORAGE_LINODE_ACCESS_KEY","label":"Storage | _APP_STORAGE_LINODE_ACCESS_KEY","defaultValue":"","description":"Linode object storage access key. Required when the storage adapter is set to Linode. You can get your access key from your Linode console."},{"id":"$$secret__app_storage_linode_secret","name":"_APP_STORAGE_LINODE_SECRET","label":"Storage | _APP_STORAGE_LINODE_SECRET","defaultValue":"","description":"Linode object storage secret key. Required when the storage adapter is set to Linode. You can get your secret key from your Linode console."},{"id":"$$config__app_storage_linode_region","name":"_APP_STORAGE_LINODE_REGION","label":"Storage | _APP_STORAGE_LINODE_REGION","defaultValue":"eu-central-1","description":"Linode object storage region. Required when storage adapter is set to Linode. You can find your region info from your Linode console."},{"id":"$$config__app_storage_linode_bucket","name":"_APP_STORAGE_LINODE_BUCKET","label":"Storage | _APP_STORAGE_LINODE_BUCKET","defaultValue":"","description":"Linode object storage bucket. Required when storage adapter is set to Linode. You can create buckets in your Linode console."},{"id":"$$secret__app_storage_wasabi_access_key","name":"_APP_STORAGE_WASABI_ACCESS_KEY","label":"Storage | _APP_STORAGE_WASABI_ACCESS_KEY","defaultValue":"","description":"Wasabi access key. Required when the storage adapter is set to Wasabi. You can get your access key from your Wasabi console."},{"id":"$$secret__app_storage_wasabi_secret","name":"_APP_STORAGE_WASABI_SECRET","label":"Storage | _APP_STORAGE_WASABI_SECRET","defaultValue":"","description":"Wasabi secret key. Required when the storage adapter is set to Wasabi. You can get your secret key from your Wasabi console."},{"id":"$$config__app_storage_wasabi_region","name":"_APP_STORAGE_WASABI_REGION","label":"Storage | _APP_STORAGE_WASABI_REGION","defaultValue":"eu-central-1","description":"Wasabi region. Required when storage adapter is set to Wasabi. You can find your region info from your Wasabi console."},{"id":"$$config__app_storage_wasabi_bucket","name":"_APP_STORAGE_WASABI_BUCKET","label":"Storage | _APP_STORAGE_WASABI_BUCKET","defaultValue":"","description":"Wasabi bucket. Required when storage adapter is set to Wasabi. You can create buckets in your Wasabi console."},{"id":"$$config__app_functions_size_limit","name":"_APP_FUNCTIONS_SIZE_LIMIT","label":"Functions | _APP_FUNCTIONS_SIZE_LIMIT","defaultValue":"30000000","description":"The maximum size deployment in bytes. The default value is 30MB."},{"id":"$$config__app_functions_timeout","name":"_APP_FUNCTIONS_TIMEOUT","label":"Functions | _APP_FUNCTIONS_TIMEOUT","defaultValue":"900","description":"The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds."},{"id":"$$config__app_functions_build_timeout","name":"_APP_FUNCTIONS_BUILD_TIMEOUT","label":"Functions | _APP_FUNCTIONS_BUILD_TIMEOUT","defaultValue":"900","description":"The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds."},{"id":"$$config__app_functions_containers","name":"_APP_FUNCTIONS_CONTAINERS","label":"Functions | _APP_FUNCTIONS_CONTAINERS","defaultValue":"10","description":"The maximum number of containers Appwrite is allowed to keep alive in the background for function environments. Running containers allow faster execution time as there is no need to recreate each container every time a function gets executed. The default value is 10."},{"id":"$$config__app_functions_cpus","name":"_APP_FUNCTIONS_CPUS","label":"Functions | _APP_FUNCTIONS_CPUS","defaultValue":"","description":"The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it's empty, CPU limit will be disabled."},{"id":"$$config__app_functions_memory_allocated","name":"_APP_FUNCTIONS_MEMORY","label":"Functions | _APP_FUNCTIONS_MEMORY","defaultValue":"","description":"The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it's empty, memory limit will be disabled."},{"id":"$$config__app_functions_memory_swap","name":"_APP_FUNCTIONS_MEMORY_SWAP","label":"Functions | _APP_FUNCTIONS_MEMORY_SWAP","defaultValue":"","description":"The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is empty. When it's empty, swap memory limit will be disabled."},{"id":"$$config__app_functions_runtimes","name":"_APP_FUNCTIONS_RUNTIMES","label":"Functions | _APP_FUNCTIONS_RUNTIMES","defaultValue":"node-18.0","description":"This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\nCurrently, supported environments are: node-14.5, node-16.0, node-18.0, php-8.0, php-8.1, ruby-3.0, ruby-3.1, python-3.8, python-3.9, python-3.10, deno-1.21, deno-1.24, dart-2.15, dart-2.16, dart-2.17, dotnet-3.1, dotnet-6.0, java-8.0, java-11.0, java-17.0, java-18.0, swift-5.5, kotlin-1.6, cpp-17.0"},{"id":"$$secret__app_executor_secret","name":"_APP_EXECUTOR_SECRET","label":"Functions | _APP_EXECUTOR_SECRET","defaultValue":"$$generate_hex(16)","description":"The secret key used by Appwrite to communicate with the function executor."},{"id":"$$config__app_executor_host","name":"_APP_EXECUTOR_HOST","label":"","defaultValue":"http://$$id-executor/v1","description":""},{"id":"$$config__app_logging_provider","name":"_APP_LOGGING_PROVIDER","label":"General | _APP_LOGGING_PROVIDER","defaultValue":"","description":"This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of 'sentry', 'raygun', 'appsignal', 'logowl'"},{"id":"$$config__app_logging_config","name":"_APP_LOGGING_CONFIG","label":"General | _APP_LOGGING_CONFIG","defaultValue":"","description":"This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be 'SENTRY_API_KEY;SENTRY_APP_ID'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket."},{"id":"$$config__app_statsd_host","name":"_APP_STATSD_HOST","label":"","defaultValue":"$$id-telegraf","description":""},{"id":"$$config__app_statsd_port","name":"_APP_STATSD_PORT","label":"StatsD | _APP_STATSD_PORT","defaultValue":"8125","description":"StatsD server TCP port."},{"id":"$$config__app_maintenance_interval","name":"_APP_MAINTENANCE_INTERVAL","label":"Functions | _APP_MAINTENANCE_INTERVAL","defaultValue":"86400","description":"Interval value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 86400 seconds (1 day)."},{"id":"$$config__app_maintenance_retention_execution","name":"_APP_MAINTENANCE_RETENTION_EXECUTION","label":"Functions | _APP_MAINTENANCE_RETENTION_EXECUTION","defaultValue":"1209600","description":"The maximum duration (in seconds) upto which to retain execution logs. The default value is 1209600 seconds (14 days)."},{"id":"$$config__app_maintenance_retention_cache","name":"_APP_MAINTENANCE_RETENTION_CACHE","label":"Functions | _APP_MAINTENANCE_RETENTION_CACHE","defaultValue":"2592000","description":"The maximum duration (in seconds) upto which to retain cached files. The default value is 2592000 seconds (30 days)."},{"id":"$$config__app_maintenance_retention_abuse","name":"_APP_MAINTENANCE_RETENTION_ABUSE","label":"Functions | _APP_MAINTENANCE_RETENTION_ABUSE","defaultValue":"86400","description":"The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day)."},{"id":"$$config__app_maintenance_retention_audit","name":"_APP_MAINTENANCE_RETENTION_AUDIT","label":"Functions | _APP_MAINTENANCE_RETENTION_AUDIT","defaultValue":"1209600","description":"The maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days)."},{"id":"$$config__app_sms_provider","name":"_APP_SMS_PROVIDER","label":"Phone | _APP_SMS_PROVIDER","defaultValue":"","description":"Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. Available providers are twilio, text-magic, telesign, msg91, and vonage."},{"id":"$$config__app_sms_from","name":"_APP_SMS_FROM","label":"Phone | _APP_SMS_FROM","defaultValue":"","description":"Phone number used for sending out messages. Must start with a leading '+' and maximum of 15 digits without spaces (+123456789)."},{"id":"$$config__app_functions_inactive_threshold","name":"_APP_FUNCTIONS_INACTIVE_THRESHOLD","label":"Functions | _APP_FUNCTIONS_INACTIVE_THRESHOLD","defaultValue":"60","description":"The minimum time a function can be inactive before it's container is shutdown and put to sleep. The default value is 60 seconds"},{"id":"$$config_open_runtimes_network","name":"OPEN_RUNTIMES_NETWORK","label":"","defaultValue":"$$generate_network","description":""},{"id":"$$config_dockerhub_pull_username","name":"DOCKERHUB_PULL_USERNAME","label":"Functions | DOCKERHUB_PULL_USERNAME","defaultValue":"","description":"The username for hub.docker.com. This variable is used to pull images from hub.docker.com."},{"id":"$$secret_dockerhub_pull_password","name":"DOCKERHUB_PULL_PASSWORD","label":"Functions | DOCKERHUB_PULL_PASSWORD","defaultValue":"","description":"The password for hub.docker.com. This variable is used to pull images from hub.docker.com."},{"id":"$$config__app_usage_timeseries_interval","name":"_APP_USAGE_TIMESERIES_INTERVAL","label":"General | _APP_USAGE_TIMESERIES_INTERVAL","defaultValue":"30","description":"Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to mariadb from InfluxDB. The default value is 30 seconds."},{"id":"$$config__app_usage_database_interval","name":"_APP_USAGE_DATABASE_INTERVAL","label":"General | _APP_USAGE_DATABASE_INTERVAL","defaultValue":"900","description":"Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats from data in Appwrite Database. The default value is 15 minutes."}]},{"templateVersion":"1.0.0","defaultVersion":"latest","documentation":"https://docs.weblate.org/en/latest/admin/install/docker.html","description":"A copylefted libre software web-based continuous localization system.","type":"weblate","name":"Weblate","labels":["translate","localization"],"services":{"$$id":{"name":"Weblate","depends_on":["$$id-postgresql","$$id-redis"],"image":"weblate/weblate:$$core_version","volumes":["$$id-data:/app/data"],"environment":["WEBLATE_SITE_DOMAIN=$$config_weblate_site_domain","WEBLATE_ADMIN_PASSWORD=$$secret_weblate_admin_password","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_USER=$$config_postgres_user","POSTGRES_DATABASE=$$config_postgres_db","POSTGRES_HOST=$$id-postgresql","POSTGRES_PORT=5432","REDIS_HOST=$$id-redis"],"ports":["8080"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]},"$$id-redis":{"name":"Redis","depends_on":[],"image":"redis:7-alpine","volumes":["$$id-redis-data:/data"],"environment":[],"ports":[]}},"variables":[{"id":"$$config_weblate_site_domain","name":"WEBLATE_SITE_DOMAIN","label":"Weblate Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$secret_weblate_admin_password","name":"WEBLATE_ADMIN_PASSWORD","label":"Weblate Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"weblate","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"2023.01.15-52d41559","documentation":"https://docs.searxng.org/","type":"searxng","name":"SearXNG","description":"Free internet metasearch engine which aggregates results from more than 70 search services.","services":{"$$id":{"name":"SearXNG","depends_on":["$$id-redis"],"image":"searxng/searxng:$$core_version","volumes":["$$id-searxng:/etc/searxng"],"environment":["SEARXNG_BASE_URL=$$config_searxng_base_url"],"ports":["8080"],"cap_drop":["ALL"],"cap_add":["CHOWN","SETGID","SETUID","DAC_OVERRIDE"],"files":[{"location":"/etc/searxng/settings.yml","content":"\n # see https://docs.searxng.org/admin/engines/settings.html#use-default-settings\n use_default_settings: true\n server:\n secret_key: $$secret_secret_key\n limiter: true\n image_proxy: true\n ui:\n static_use_hash: true\n redis:\n url: redis://:$$secret_redis_password@$$id-redis:6379/0"}]},"$$id-redis":{"name":"Redis","command":"redis-server --requirepass $$secret_redis_password --save \"\" --appendonly \"no\"","depends_on":[],"image":"redis:7-alpine","volumes":["$$id-redis-data:/data"],"environment":["REDIS_PASSWORD=$$secret_redis_password"],"ports":[],"cap_drop":["ALL"],"cap_add":["SETGID","SETUID","DAC_OVERRIDE"]}},"variables":[{"id":"$$config_searxng_base_url","name":"SEARXNG_BASE_URL","label":"SearXNG Base URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_secret_key","name":"SECRET_KEY","label":"Secret Key","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$secret_redis_password","name":"REDIS_PASSWORD","label":"Redis Password","defaultValue":"$$generate_password","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v3.0.2","documentation":"https://glitchtip.com/documentation","type":"glitchtip","name":"GlitchTip","description":"Simple, open source error tracking.","labels":["sentry","bugsnag"],"services":{"$$id":{"name":"GlitchTip","depends_on":["$$id-postgresql","$$id-redis"],"image":"glitchtip/glitchtip:$$core_version","volumes":[],"environment":["PORT=$$config_port","GLITCHTIP_DOMAIN=$$config_glitchtip_domain","SECRET_KEY=$$secret_secret_key","DATABASE_URL=$$secret_database_url","REDIS_URL=$$secret_redis_url","DEFAULT_FROM_EMAIL=$$config_default_from_email","EMAIL_URL=$$secret_email_url","EMAIL_HOST=$$config_email_host","EMAIL_PORT=$$config_email_port","EMAIL_HOST_USER=$$config_email_host_user","EMAIL_HOST_PASSWORD=$$secret_email_host_password","EMAIL_USE_TLS=$$config_email_use_tls","EMAIL_USE_SSL=$$config_email_use_ssl","EMAIL_BACKEND=$$config_email_backend","MAILGUN_API_KEY=$$secret_mailgun_api_key","SENDGRID_API_KEY=$$secret_sendgrid_api_key","ENABLE_OPEN_USER_REGISTRATION=$$config_enable_open_user_registration","DJANGO_SUPERUSER_EMAIL=$$config_django_superuser_email","DJANGO_SUPERUSER_PASSWORD=$$secret_django_superuser_password","DJANGO_SUPERUSER_USERNAME=$$config_django_superuser_username","CELERY_WORKER_CONCURRENCY=$$config_celery_worker_concurrency"],"ports":["8000"]},"$$id-worker":{"name":"Celery Worker","command":"./bin/run-celery-with-beat.sh","depends_on":["$$id-postgresql","$$id-redis"],"image":"glitchtip/glitchtip:$$core_version","environment":["GLITCHTIP_DOMAIN=$$config_glitchtip_domain","SECRET_KEY=$$secret_secret_key","DATABASE_URL=$$secret_database_url","REDIS_URL=$$secret_redis_url","DEFAULT_FROM_EMAIL=$$config_default_from_email","EMAIL_URL=$$secret_email_url","CELERY_WORKER_CONCURRENCY=$$config_celery_worker_concurrency"],"ports":[]},"$$id-migrate":{"exclude":true,"name":"Migrate","command":"./manage.py migrate","depends_on":["$$id-postgresql","$$id-redis"],"image":"glitchtip/glitchtip:$$core_version","environment":["GLITCHTIP_DOMAIN=$$config_glitchtip_domain","SECRET_KEY=$$secret_secret_key","DATABASE_URL=$$secret_database_url","REDIS_URL=$$secret_redis_url","DEFAULT_FROM_EMAIL=$$config_default_from_email","EMAIL_URL=$$secret_email_url"],"ports":[]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:14-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]},"$$id-redis":{"name":"Redis","depends_on":[],"image":"redis:7-alpine","volumes":["$$id-postgresql-redis-data:/data"],"environment":[],"ports":[]}},"variables":[{"id":"$$config_django_superuser_username","name":"DJANGO_SUPERUSER_USERNAME","label":"Django Superuser Username","defaultValue":"$$generate_username","description":""},{"id":"$$secret_django_superuser_password","name":"DJANGO_SUPERUSER_PASSWORD","label":"Django Superuser Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_port","name":"PORT","label":"GlitchTip Port","defaultValue":"8000","description":""},{"id":"$$config_celery_worker_concurrency","main":"$$id-worker","name":"CELERY_WORKER_CONCURRENCY","label":"Celery Worker Concurrency","defaultValue":"2","description":""},{"id":"$$config_glitchtip_domain","name":"GLITCHTIP_DOMAIN","label":"GlitchTip Domain","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_email_url","name":"EMAIL_URL","label":"SMTP Email URL","defaultValue":"smtp://$$config_email_host_user:$$secret_email_host_password@$$config_email_host:$$config_email_port","description":""},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_redis_url","name":"REDIS_URL","label":"Redis URL","defaultValue":"redis://$$id-redis:6379/0","description":""},{"id":"$$config_default_from_email","name":"DEFAULT_FROM_EMAIL","label":"Default Email Address","defaultValue":"noreply@example.com","description":""},{"id":"$$config_email_host","name":"EMAIL_HOST","label":"Email SMTP Host","defaultValue":"","description":""},{"id":"$$config_email_port","name":"EMAIL_PORT","label":"Email SMTP Port","defaultValue":"25","description":""},{"id":"$$config_email_host_user","name":"EMAIL_HOST_USER","label":"Email SMTP User","defaultValue":"","description":""},{"id":"$$secret_email_host_password","name":"EMAIL_HOST_PASSWORD","label":"Email SMTP Password","defaultValue":"","description":""},{"id":"$$config_email_use_tls","name":"EMAIL_USE_TLS","label":"Email Use TLS","defaultValue":"false","description":""},{"id":"$$config_email_use_ssl","name":"EMAIL_USE_SSL","label":"Email Use SSL","defaultValue":"false","description":""},{"id":"$$secret_email_smtp_password","name":"EMAIL_SMTP_PASSWORD","label":"SMTP Password","defaultValue":"","description":""},{"id":"$$config_email_backend","name":"EMAIL_BACKEND","label":"Email Backend","defaultValue":"","description":""},{"id":"$$secret_mailgun_api_key","name":"MAILGUN_API_KEY","label":"Mailgun API Key","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$secret_sendgrid_api_key","name":"SENDGRID_API_KEY","label":"Sendgrid API Key","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$config_enable_open_user_registration","name":"ENABLE_OPEN_USER_REGISTRATION","label":"Enable Open User Registration","defaultValue":"true","description":""},{"id":"$$config_django_superuser_email","name":"DJANGO_SUPERUSER_EMAIL","label":"Django Superuser Email","defaultValue":"noreply@example.com","description":""},{"id":"$$config_postgres_user","main":"$$id-postgresql","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","main":"$$id-postgresql","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","main":"$$id-postgresql","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"glitchtip","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v2.16.1","documentation":"https://hasura.io/docs/latest/index/","type":"hasura","name":"Hasura","description":"Instant realtime GraphQL APIs on any Postgres application, existing or new.","labels":["graphql","database"],"services":{"$$id":{"name":"Hasura","depends_on":["$$id-postgresql"],"image":"hasura/graphql-engine:$$core_version","volumes":[],"environment":["HASURA_GRAPHQL_ENABLE_CONSOLE=$$config_hasura_graphql_enable_console","HASURA_GRAPHQL_METADATA_DATABASE_URL=$$secret_hasura_graphql_metadata_database_url","HASURA_GRAPHQL_ADMIN_SECRET=$$secret_hasura_graphql_admin_secret"],"ports":["8080"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[]}},"variables":[{"id":"$$config_hasura_graphql_enable_console","name":"HASURA_GRAPHQL_ENABLE_CONSOLE","label":"Enable Hasura Console","defaultValue":"true","description":""},{"id":"$$secret_hasura_graphql_metadata_database_url","name":"HASURA_GRAPHQL_METADATA_DATABASE_URL","label":"Hasura Metadata Database URL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_hasura_graphql_admin_secret","name":"HASURA_GRAPHQL_ADMIN_SECRET","label":"Hasura Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"hasura","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"postgresql-v1.39.5","documentation":"https://umami.is/docs/getting-started","type":"umami-postgresql","name":"Umami","subname":"(PostgreSQL)","description":"A simple, easy to use, self-hosted web analytics solution.","services":{"$$id":{"name":"Umami","depends_on":["$$id-postgresql"],"image":"ghcr.io/umami-software/umami:$$core_version","volumes":[],"environment":["ADMIN_PASSWORD=$$secret_admin_password","DATABASE_URL=$$secret_database_url","DATABASE_TYPE=$$config_database_type","HASH_SALT=$$secret_hash_salt"],"ports":["3000"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[],"files":[{"location":"/docker-entrypoint-initdb.d/schema.postgresql.sql","content":"\n -- CreateTable\n CREATE TABLE \"account\" (\n \"user_id\" SERIAL NOT NULL,\n \"username\" VARCHAR(255) NOT NULL,\n \"password\" VARCHAR(60) NOT NULL,\n \"is_admin\" BOOLEAN NOT NULL DEFAULT false,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"updated_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"user_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"event\" (\n \"event_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"event_type\" VARCHAR(50) NOT NULL,\n \"event_value\" VARCHAR(50) NOT NULL,\n\n PRIMARY KEY (\"event_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"pageview\" (\n \"view_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"referrer\" VARCHAR(500),\n\n PRIMARY KEY (\"view_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"session\" (\n \"session_id\" SERIAL NOT NULL,\n \"session_uuid\" UUID NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"hostname\" VARCHAR(100),\n \"browser\" VARCHAR(20),\n \"os\" VARCHAR(20),\n \"device\" VARCHAR(20),\n \"screen\" VARCHAR(11),\n \"language\" VARCHAR(35),\n \"country\" CHAR(2),\n\n PRIMARY KEY (\"session_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"website\" (\n \"website_id\" SERIAL NOT NULL,\n \"website_uuid\" UUID NOT NULL,\n \"user_id\" INTEGER NOT NULL,\n \"name\" VARCHAR(100) NOT NULL,\n \"domain\" VARCHAR(500),\n \"share_id\" VARCHAR(64),\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"website_id\")\n );\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"account.username_unique\" ON \"account\"(\"username\");\n\n -- CreateIndex\n CREATE INDEX \"event_created_at_idx\" ON \"event\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"event_session_id_idx\" ON \"event\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"event_website_id_idx\" ON \"event\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_created_at_idx\" ON \"pageview\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_session_id_idx\" ON \"pageview\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_idx\" ON \"pageview\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_session_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"session_id\", \"created_at\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"session.session_uuid_unique\" ON \"session\"(\"session_uuid\");\n\n -- CreateIndex\n CREATE INDEX \"session_created_at_idx\" ON \"session\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"session_website_id_idx\" ON \"session\"(\"website_id\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.website_uuid_unique\" ON \"website\"(\"website_uuid\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.share_id_unique\" ON \"website\"(\"share_id\");\n\n -- CreateIndex\n CREATE INDEX \"website_user_id_idx\" ON \"website\"(\"user_id\");\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"session\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"website\" ADD FOREIGN KEY (\"user_id\") REFERENCES \"account\"(\"user_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n insert into account (username, password, is_admin) values ('admin', '$$hashed$$secret_admin_password', true);"}]}},"variables":[{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_hash_salt","name":"HASH_SALT","label":"Hash Salt","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_database_type","name":"DATABASE_TYPE","label":"Database Type","defaultValue":"postgresql","description":""},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"umami","description":""},{"id":"$$secret_admin_password","name":"ADMIN_PASSWORD","label":"Initial Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","ignore":true,"defaultVersion":"postgresql-v1.39.5","documentation":"https://umami.is/docs/getting-started","type":"umami","name":"Umami","subname":"(PostgreSQL)","description":"A simple, easy to use, self-hosted web analytics solution.","services":{"$$id":{"name":"Umami","depends_on":["$$id-postgresql"],"image":"ghcr.io/umami-software/umami:$$core_version","volumes":[],"environment":["ADMIN_PASSWORD=$$secret_admin_password","DATABASE_URL=$$secret_database_url","DATABASE_TYPE=$$config_database_type","HASH_SALT=$$secret_hash_salt"],"ports":["3000"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"],"ports":[],"files":[{"location":"/docker-entrypoint-initdb.d/schema.postgresql.sql","content":"\n -- CreateTable\n CREATE TABLE \"account\" (\n \"user_id\" SERIAL NOT NULL,\n \"username\" VARCHAR(255) NOT NULL,\n \"password\" VARCHAR(60) NOT NULL,\n \"is_admin\" BOOLEAN NOT NULL DEFAULT false,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"updated_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"user_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"event\" (\n \"event_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"event_type\" VARCHAR(50) NOT NULL,\n \"event_value\" VARCHAR(50) NOT NULL,\n\n PRIMARY KEY (\"event_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"pageview\" (\n \"view_id\" SERIAL NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"session_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"url\" VARCHAR(500) NOT NULL,\n \"referrer\" VARCHAR(500),\n\n PRIMARY KEY (\"view_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"session\" (\n \"session_id\" SERIAL NOT NULL,\n \"session_uuid\" UUID NOT NULL,\n \"website_id\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n \"hostname\" VARCHAR(100),\n \"browser\" VARCHAR(20),\n \"os\" VARCHAR(20),\n \"device\" VARCHAR(20),\n \"screen\" VARCHAR(11),\n \"language\" VARCHAR(35),\n \"country\" CHAR(2),\n\n PRIMARY KEY (\"session_id\")\n );\n\n -- CreateTable\n CREATE TABLE \"website\" (\n \"website_id\" SERIAL NOT NULL,\n \"website_uuid\" UUID NOT NULL,\n \"user_id\" INTEGER NOT NULL,\n \"name\" VARCHAR(100) NOT NULL,\n \"domain\" VARCHAR(500),\n \"share_id\" VARCHAR(64),\n \"created_at\" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,\n\n PRIMARY KEY (\"website_id\")\n );\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"account.username_unique\" ON \"account\"(\"username\");\n\n -- CreateIndex\n CREATE INDEX \"event_created_at_idx\" ON \"event\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"event_session_id_idx\" ON \"event\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"event_website_id_idx\" ON \"event\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_created_at_idx\" ON \"pageview\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_session_id_idx\" ON \"pageview\"(\"session_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_idx\" ON \"pageview\"(\"website_id\");\n\n -- CreateIndex\n CREATE INDEX \"pageview_website_id_session_id_created_at_idx\" ON \"pageview\"(\"website_id\", \"session_id\", \"created_at\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"session.session_uuid_unique\" ON \"session\"(\"session_uuid\");\n\n -- CreateIndex\n CREATE INDEX \"session_created_at_idx\" ON \"session\"(\"created_at\");\n\n -- CreateIndex\n CREATE INDEX \"session_website_id_idx\" ON \"session\"(\"website_id\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.website_uuid_unique\" ON \"website\"(\"website_uuid\");\n\n -- CreateIndex\n CREATE UNIQUE INDEX \"website.share_id_unique\" ON \"website\"(\"share_id\");\n\n -- CreateIndex\n CREATE INDEX \"website_user_id_idx\" ON \"website\"(\"user_id\");\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"event\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"session_id\") REFERENCES \"session\"(\"session_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"pageview\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"session\" ADD FOREIGN KEY (\"website_id\") REFERENCES \"website\"(\"website_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n -- AddForeignKey\n ALTER TABLE \"website\" ADD FOREIGN KEY (\"user_id\") REFERENCES \"account\"(\"user_id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n insert into account (username, password, is_admin) values ('admin', '$$hashed$$secret_admin_password', true);"}]}},"variables":[{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db","description":""},{"id":"$$secret_hash_salt","name":"HASH_SALT","label":"Hash Salt","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_database_type","name":"DATABASE_TYPE","label":"Database Type","defaultValue":"postgresql","description":""},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"umami","description":""},{"id":"$$secret_admin_password","name":"ADMIN_PASSWORD","label":"Initial Admin Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"v0.30.5","documentation":"https://docs.meilisearch.com/learn/getting_started/quick_start.html","type":"meilisearch","name":"MeiliSearch","description":"A lightning Fast, Ultra Relevant, and Typo-Tolerant Search Engine.","services":{"$$id":{"name":"MeiliSearch","documentation":"https://docs.meilisearch.com/","depends_on":[],"image":"getmeili/meilisearch:$$core_version","volumes":["$$id-datams:/meili_data/data.ms","$$id-data:/meili_data","$$id-snapshot:/snapshot","$$id-dump:/dumps"],"environment":["MEILI_MASTER_KEY=$$secret_meili_master_key"],"ports":["7700"]}},"variables":[{"id":"$$secret_meili_master_key","name":"MEILI_MASTER_KEY","label":"Master Key","defaultValue":"$$generate_hex(64)","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","ignore":true,"defaultVersion":"5.30.0","documentation":"https://docs.ghost.org","arch":"amd64","type":"ghost-mariadb","name":"Ghost","subname":"(MariaDB)","description":"Free and open source blogging platform.","labels":["cms","blog"],"services":{"$$id":{"name":"Ghost","depends_on":["$$id-mariadb"],"image":"bitnami/ghost:$$core_version","volumes":["$$id-ghost:/bitnami/ghost"],"environment":["url=$$config_url","GHOST_HOST=$$config_ghost_host","GHOST_ENABLE_HTTPS=$$config_ghost_enable_https","GHOST_EMAIL=$$config_ghost_email","GHOST_PASSWORD=$$secret_ghost_password","GHOST_DATABASE_HOST=$$config_ghost_database_host","GHOST_DATABASE_USER=$$config_mariadb_user","GHOST_DATABASE_PASSWORD=$$secret_ghost_database_password","GHOST_DATABASE_NAME=$$config_mariadb_database","GHOST_DATABASE_PORT_NUMBER=3306"],"ports":["2368"]},"$$id-mariadb":{"name":"MariaDB","depends_on":[],"image":"bitnami/mariadb:latest","volumes":["$$id-mariadb:/bitnami/mariadb"],"environment":["MARIADB_USER=$$config_mariadb_user","MARIADB_PASSWORD=$$secret_mariadb_password","MARIADB_DATABASE=$$config_mariadb_database","MARIADB_ROOT_USER=$$config_mariadb_root_user","MARIADB_ROOT_PASSWORD=$$secret_mariadb_root_password"],"ports":[]}},"variables":[{"id":"$$config_url","name":"url","label":"URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_ghost_host","name":"GHOST_HOST","label":"Ghost Host","defaultValue":"$$generate_domain","description":""},{"id":"$$config_ghost_enable_https","name":"GHOST_ENABLE_HTTPS","label":"Ghost Enable HTTPS","defaultValue":"no","description":""},{"id":"$$config_ghost_email","name":"GHOST_EMAIL","label":"Ghost Default Email","defaultValue":"admin@example.com","description":""},{"id":"$$secret_ghost_password","name":"GHOST_PASSWORD","label":"Ghost Default Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_ghost_database_host","name":"GHOST_DATABASE_HOST","label":"Ghost Database Host","defaultValue":"$$id-mariadb","description":""},{"id":"$$config_ghost_database_user","name":"GHOST_DATABASE_USER","label":"MariaDB User","defaultValue":"$$config_mariadb_user","description":""},{"id":"$$secret_ghost_database_password","name":"GHOST_DATABASE_PASSWORD","label":"MariaDB Password","defaultValue":"$$secret_mariadb_password","description":""},{"id":"$$config_ghost_database_name","name":"GHOST_DATABASE_NAME","label":"MariaDB Database","defaultValue":"$$config_mariadb_database","description":""},{"id":"$$config_mariadb_user","name":"MARIADB_USER","label":"MariaDB User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_mariadb_password","name":"MARIADB_PASSWORD","label":"MariaDB Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_mariadb_database","name":"MARIADB_DATABASE","label":"MariaDB Database","defaultValue":"ghost","description":""},{"id":"$$config_mariadb_root_user","name":"MARIADB_ROOT_USER","label":"MariaDB Root User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_mariadb_root_password","name":"MARIADB_ROOT_PASSWORD","label":"MariaDB Root Password","defaultValue":"$$generate_password","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"5.30.0","documentation":"https://docs.ghost.org","type":"ghost-only","name":"Ghost","subname":"(without Database)","description":"Free and open source blogging platform.","services":{"$$id":{"name":"Ghost","image":"ghost:$$core_version","volumes":["$$id-ghost:/var/lib/ghost/content"],"environment":["url=$$config_url","database__client=$$config_database__client","database__connection__host=$$config_database__connection__host","database__connection__user=$$config_database__connection__user","database__connection__password=$$secret_database__connection__password","database__connection__database=$$config_database__connection__database"],"ports":["2368"]}},"variables":[{"id":"$$config_url","name":"url","label":"URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_database__client","name":"database__client","label":"Database Client","defaultValue":"mysql","description":"","required":true},{"id":"$$config_database__connection__host","name":"database__connection__host","label":"Database Host","defaultValue":"","description":"","required":true,"placeholder":"db.coolify.io"},{"id":"$$config_database__connection__user","name":"database__connection__user","label":"Database User","defaultValue":"","description":"","placeholder":"ghost","required":true},{"id":"$$secret_database__connection__password","name":"database__connection__password","label":"Database Password","defaultValue":"","description":"","placeholder":"superSecretP4ssword","showOnConfiguration":true,"required":true},{"id":"$$config_database__connection__database","name":"database__connection__database","label":"Database Name","defaultValue":"","description":"","placeholder":"ghost_db","required":true}]},{"templateVersion":"1.0.0","defaultVersion":"5.30.0","documentation":"https://docs.ghost.org","type":"ghost-mysql","name":"Ghost","subname":"(MySQL)","description":"Ghost is a free and open source blogging platform.","services":{"$$id":{"name":"Ghost","depends_on":["$$id-mysql"],"image":"ghost:$$core_version","volumes":["$$id-ghost:/var/lib/ghost/content"],"environment":["url=$$config_url","database__client=$$config_database__client","database__connection__host=$$config_database__connection__host","database__connection__user=$$config_mysql_user","database__connection__password=$$secret_mysql_password","database__connection__database=$$config_mysql_database"],"ports":["2368"]},"$$id-mysql":{"name":"MySQL","depends_on":[],"image":"mysql:8.0","volumes":["$$id-mysql:/var/lib/mysql"],"environment":["MYSQL_USER=$$config_mysql_user","MYSQL_PASSWORD=$$secret_mysql_password","MYSQL_DATABASE=$$config_mysql_database","MYSQL_ROOT_PASSWORD=$$secret_mysql_root_password"],"ports":[]}},"variables":[{"id":"$$config_url","name":"url","label":"URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_database__client","name":"database__client","label":"Database Client","defaultValue":"mysql","description":"","readOnly":true},{"id":"$$config_database__connection__host","name":"database__connection__host","label":"Database Host","defaultValue":"$$id-mysql","description":""},{"id":"$$config_mysql_user","main":"$$id-mysql","name":"MYSQL_USER","label":"MySQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_mysql_password","main":"$$id-mysql","name":"MYSQL_PASSWORD","label":"MySQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_mysql_database","main":"$$id-mysql","name":"MYSQL_DATABASE","label":"MySQL Database","defaultValue":"ghost","description":""},{"id":"$$secret_mysql_root_password","name":"MYSQL_ROOT_PASSWORD","label":"MySQL Root Password","defaultValue":"$$generate_password","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"php8.2","documentation":"https://wordpress.org/","type":"wordpress","name":"WordPress","subname":"(MySQL)","description":"A content management system based on PHP.","labels":["wordpress","php","cms"],"services":{"$$id":{"name":"WordPress","depends_on":["$$id-mysql"],"image":"wordpress:$$core_version","volumes":["$$id-wordpress-data:/var/www/html"],"environment":["WORDPRESS_DB_HOST=$$config_wordpress_db_host","WORDPRESS_DB_USER=$$config_mysql_user","WORDPRESS_DB_PASSWORD=$$secret_mysql_password","WORDPRESS_DB_NAME=$$config_mysql_database","WORDPRESS_CONFIG_EXTRA=$$config_wordpress_config_extra"],"ports":["80"]},"$$id-mysql":{"name":"MySQL","depends_on":[],"image":"bitnami/mysql:5.7","imageArm":"mysql:8.0","volumes":["$$id-mysql-data:/bitnami/mysql/data"],"volumesArm":["$$id-mysql-data:/var/lib/mysql"],"environment":["MYSQL_ROOT_PASSWORD=$$secret_mysql_root_password","MYSQL_ROOT_USER=$$config_mysql_root_user","MYSQL_DATABASE=$$config_mysql_database","MYSQL_USER=$$config_mysql_user","MYSQL_PASSWORD=$$secret_mysql_password"]}},"variables":[{"id":"$$config_wordpress_db_host","name":"WORDPRESS_DB_HOST","label":"Database Host","defaultValue":"$$id-mysql","description":"","readOnly":true},{"id":"$$config_wordpress_config_extra","name":"WORDPRESS_CONFIG_EXTRA","label":"WordPress Config Extra","defaultValue":"","description":"","type":"textarea","placeholder":"define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);\n@ini_set('display_errors', 0);\n"},{"id":"$$secret_mysql_root_password","name":"MYSQL_ROOT_PASSWORD","label":"MySQL Root Password","defaultValue":"$$generate_password","description":"","readOnly":true},{"id":"$$config_mysql_root_user","name":"MYSQL_ROOT_USER","label":"MySQL Root User","defaultValue":"$$generate_username","description":"","readOnly":true},{"id":"$$config_mysql_database","name":"MYSQL_DATABASE","label":"MySQL Database","defaultValue":"wordpress","description":"","readOnly":true},{"id":"$$config_mysql_user","name":"MYSQL_USER","label":"MySQL User","defaultValue":"$$generate_username","description":"","readOnly":true},{"id":"$$secret_mysql_password","name":"MYSQL_PASSWORD","label":"MySQL Password","defaultValue":"$$generate_password","description":"","readOnly":true}]},{"templateVersion":"1.0.0","defaultVersion":"php8.2","documentation":"https://wordpress.org/","type":"wordpress-only","name":"WordPress","subname":"(without DB)","description":"A content management system based on PHP.","labels":["wordpress","php","cms"],"services":{"$$id":{"name":"WordPress","image":"wordpress:$$core_version","volumes":["$$id-wordpress-data:/var/www/html"],"environment":["WORDPRESS_DB_HOST=$$config_wordpress_db_host","WORDPRESS_DB_PORT=$$config_wordpress_db_port","WORDPRESS_DB_USER=$$config_wordpress_db_user","WORDPRESS_DB_PASSWORD=$$secret_wordpress_db_password","WORDPRESS_DB_NAME=$$config_wordpress_db_name","WORDPRESS_CONFIG_EXTRA=$$config_wordpress_config_extra"],"ports":["80"]}},"variables":[{"id":"$$config_wordpress_db_host","name":"WORDPRESS_DB_HOST","label":"Database Host","defaultValue":"","description":"","placeholder":"db.coollabs.io","required":true},{"id":"$$config_wordpress_db_port","name":"WORDPRESS_DB_PORT","label":"Database Port","defaultValue":"","description":"","placeholder":"3306","required":true},{"id":"$$config_wordpress_db_user","name":"WORDPRESS_DB_USER","label":"Database User","defaultValue":"","description":"","placeholder":"wordpress","required":true},{"id":"$$secret_wordpress_db_password","name":"WORDPRESS_DB_PASSWORD","label":"Database Password","defaultValue":"","description":"","placeholder":"supers3cr3tpassw0rd!","required":true,"showOnConfiguration":true},{"id":"$$config_wordpress_db_name","name":"WORDPRESS_DB_NAME","label":"Database Name","defaultValue":"","description":"","placeholder":"wordpress","required":true},{"id":"$$config_wordpress_config_extra","name":"WORDPRESS_CONFIG_EXTRA","label":"Extra Config","defaultValue":"","description":"","type":"textarea","placeholder":"define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);\n@ini_set('display_errors', 0);\n"}]},{"templateVersion":"1.0.0","defaultVersion":"4.9.1","documentation":"https://coder.com/docs/coder-oss/latest","type":"vscodeserver","name":"VSCode Server","description":"Visual Studio Code on a remote server, accessible through the browser.","labels":["vscode","ide"],"services":{"$$id":{"name":"VSCode Server","depends_on":[],"image":"codercom/code-server:$$core_version","volumes":["$$id-vscodeserver-data:/home/coder","$$id-keys-directory:/root/.ssh","$$id-theme-and-plugin-directory:/root/.local/share/code-server"],"environment":["PASSWORD=$$secret_password"],"ports":["8080"]}},"variables":[{"id":"$$secret_password","name":"PASSWORD","label":"Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"RELEASE.2023-01-12T02-06-16Z","documentation":"https://min.io/docs/minio","type":"minio","name":"MinIO","description":"A cloud storage server compatible with Amazon S3.","labels":["storage","s3"],"services":{"$$id":{"name":"MinIO","command":"server /data --console-address :9001","depends_on":[],"image":"minio/minio:$$core_version","volumes":["$$id-minio-data:/data","$$id-data-write:/files"],"environment":["MINIO_SERVER_URL=$$config_coolify_fqdn_minio_console","MINIO_BROWSER_REDIRECT_URL=$$config_minio_browser_redirect_url","MINIO_DOMAIN=$$config_minio_domain","MINIO_ROOT_USER=$$config_minio_root_user","MINIO_ROOT_PASSWORD=$$secret_minio_root_password"],"ports":["9000","9001"],"proxy":[{"port":"9000","domain":"$$config_coolify_fqdn_minio_console"},{"port":"9001"}]}},"variables":[{"id":"$$config_coolify_fqdn_minio_console","name":"MINIO_SERVER_URL","label":"MinIO Server URL","defaultValue":"","description":"Specify the URL hostname the MinIO Console should use for connecting to the MinIO Server.","required":true},{"id":"$$config_minio_browser_redirect_url","name":"MINIO_BROWSER_REDIRECT_URL","label":"Browser Redirect URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$config_minio_domain","name":"MINIO_DOMAIN","label":"Domain","defaultValue":"$$generate_domain","description":""},{"id":"$$config_minio_root_user","name":"MINIO_ROOT_USER","label":"Root User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_minio_root_password","name":"MINIO_ROOT_PASSWORD","label":"Root User Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true}]},{"templateVersion":"1.0.0","defaultVersion":"stable","documentation":"https://fider.io/docs","type":"fider","name":"Fider","description":"A platform to collect and organize customer feedback.","labels":["suggestion","feedback"],"services":{"$$id":{"name":"Fider","image":"getfider/fider:$$core_version","depends_on":["$$id-postgresql"],"environment":["BASE_URL=$$config_base_url","DATABASE_URL=$$secret_database_url","JWT_SECRET=$$secret_jwt_secret","EMAIL_NOREPLY=$$config_email_noreply","EMAIL_MAILGUN_API=$$secret_email_mailgun_api","EMAIL_MAILGUN_REGION=$$config_email_mailgun_region","EMAIL_MAILGUN_DOMAIN=$$config_email_mailgun_domain","EMAIL_SMTP_HOST=$$config_email_smtp_host","EMAIL_SMTP_PORT=$$config_email_smtp_port","EMAIL_SMTP_USER=$$config_email_smtp_user","EMAIL_SMTP_PASSWORD=$$secret_email_smtp_password","EMAIL_SMTP_ENABLE_STARTTLS=$$config_email_smtp_enable_starttls"],"ports":["3000"]},"$$id-postgresql":{"name":"PostgreSQL","depends_on":[],"image":"postgres:12-alpine","volumes":["$$id-postgresql-data:/var/lib/postgresql/data"],"environment":["POSTGRES_USER=$$config_postgres_user","POSTGRES_PASSWORD=$$secret_postgres_password","POSTGRES_DB=$$config_postgres_db"]}},"variables":[{"id":"$$config_base_url","name":"BASE_URL","label":"Base URL","defaultValue":"$$generate_fqdn","description":""},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db?sslmode=disable","description":""},{"id":"$$secret_jwt_secret","name":"JWT_SECRET","label":"JWT Secret","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_email_noreply","name":"EMAIL_NOREPLY","label":"No Reply Email Address","defaultValue":"noreply@example.com","description":""},{"id":"$$secret_email_mailgun_api","name":"EMAIL_MAILGUN_API","label":"Mailgun API Key","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$config_email_mailgun_region","name":"EMAIL_MAILGUN_REGION","label":"Mailgun Region","defaultValue":"EU","description":""},{"id":"$$config_email_mailgun_domain","name":"EMAIL_MAILGUN_DOMAIN","label":"Mailgun Domain","defaultValue":"","description":""},{"id":"$$config_email_smtp_host","name":"EMAIL_SMTP_HOST","label":"SMTP Host","defaultValue":"","description":""},{"id":"$$config_email_smtp_port","name":"EMAIL_SMTP_PORT","label":"SMTP Port","defaultValue":"587","description":""},{"id":"$$config_email_smtp_user","name":"EMAIL_SMTP_USER","label":"SMTP User","defaultValue":"","description":""},{"id":"$$secret_email_smtp_password","name":"EMAIL_SMTP_PASSWORD","label":"SMTP Password","defaultValue":"","description":"","showOnConfiguration":true},{"id":"$$config_email_smtp_enable_starttls","name":"EMAIL_SMTP_ENABLE_STARTTLS","label":"SMTP Enable StartTLS","defaultValue":"false","description":""},{"id":"$$config_postgres_user","name":"POSTGRES_USER","label":"PostgreSQL User","defaultValue":"$$generate_username","description":""},{"id":"$$secret_postgres_password","name":"POSTGRES_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":""},{"id":"$$config_postgres_db","name":"POSTGRES_DB","label":"PostgreSQL Database","defaultValue":"$$generate_username","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"0.210.1","documentation":"https://docs.n8n.io","type":"n8n","name":"n8n.io","description":"A free and open node based Workflow Automation Tool.","labels":["workflow","automation","ifttt","zapier","nodered"],"services":{"$$id":{"name":"N8n","depends_on":[],"image":"n8nio/n8n:$$core_version","volumes":["$$id-data:/root/.n8n","$$id-data-write:/files","/var/run/docker.sock:/var/run/docker.sock"],"environment":["WEBHOOK_URL=$$config_webhook_url"],"ports":["5678"]}},"variables":[{"id":"$$config_webhook_url","name":"WEBHOOK_URL","label":"Webhook URL","defaultValue":"$$generate_fqdn","description":""}]},{"templateVersion":"1.0.0","defaultVersion":"v1.5.1","documentation":"https://plausible.io/doc/","arch":"amd64","type":"plausibleanalytics","name":"Plausible Analytics","description":"A lightweight and open-source website analytics tool.","labels":["analytics","statistics","plausible","gdpr","no-cookie","google analytics"],"services":{"$$id":{"name":"Plausible Analytics","command":"sh -c \"sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run\"","depends_on":["$$id-postgresql","$$id-clickhouse"],"image":"plausible/analytics:$$core_version","environment":["ADMIN_USER_EMAIL=$$config_admin_user_email","ADMIN_USER_NAME=$$config_admin_user_name","ADMIN_USER_PWD=$$secret_admin_user_pwd","BASE_URL=$$config_base_url","SECRET_KEY_BASE=$$secret_secret_key_base","DISABLE_AUTH=$$config_disable_auth","DISABLE_REGISTRATION=$$config_disable_registration","DATABASE_URL=$$secret_database_url","CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url"],"ports":["8000"]},"$$id-postgresql":{"name":"PostgreSQL","image":"bitnami/postgresql:13","volumes":["$$id-postgresql-data:/bitnami/postgresql"],"environment":["POSTGRESQL_PASSWORD=$$secret_postgresql_password","POSTGRESQL_USERNAME=$$config_postgresql_username","POSTGRESQL_DATABASE=$$config_postgresql_database"]},"$$id-clickhouse":{"name":"Clickhouse","volumes":["$$id-clickhouse-data:/var/lib/clickhouse"],"image":"clickhouse/clickhouse-server:22.6-alpine","ulimits":{"nofile":{"soft":262144,"hard":262144}},"files":[{"location":"/etc/clickhouse-server/users.d/logging.xml","content":"warningtrue"},{"location":"/etc/clickhouse-server/config.d/logging.xml","content":"00"},{"location":"/docker-entrypoint-initdb.d/init.query","content":"CREATE DATABASE IF NOT EXISTS plausible;"},{"location":"/docker-entrypoint-initdb.d/init-db.sh","content":"clickhouse client --queries-file /docker-entrypoint-initdb.d/init.query"}]}},"variables":[{"id":"$$config_base_url","name":"BASE_URL","label":"Base URL","defaultValue":"$$generate_fqdn","description":"You must set this to the FQDN of the Plausible Analytics instance. This is used to generate the links to the Plausible Analytics instance."},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL for PostgreSQL","defaultValue":"postgresql://$$config_postgresql_username:$$secret_postgresql_password@$$id-postgresql:5432/$$config_postgresql_database","description":""},{"id":"$$secret_clickhouse_database_url","name":"CLICKHOUSE_DATABASE_URL","label":"Database URL for Clickhouse","defaultValue":"http://$$id-clickhouse:8123/plausible","description":""},{"id":"$$config_admin_user_email","name":"ADMIN_USER_EMAIL","label":"Admin Email Address","defaultValue":"admin@example.com","description":"This is the admin email. Please change it."},{"id":"$$config_admin_user_name","name":"ADMIN_USER_NAME","label":"Admin User Name","defaultValue":"$$generate_username","description":"This is the admin username. Please change it."},{"id":"$$secret_admin_user_pwd","name":"ADMIN_USER_PWD","label":"Admin User Password","defaultValue":"$$generate_password","description":"This is the admin password. Please change it.","showOnConfiguration":true},{"id":"$$secret_secret_key_base","name":"SECRET_KEY_BASE","label":"Secret Key Base","defaultValue":"$$generate_hex(64)","description":""},{"id":"$$config_disable_auth","name":"DISABLE_AUTH","label":"Authentication","defaultValue":"false","description":""},{"id":"$$config_disable_registration","name":"DISABLE_REGISTRATION","label":"Registration","defaultValue":"true","description":""},{"id":"$$config_postgresql_username","main":"$$id-postgresql","name":"POSTGRESQL_USERNAME","label":"PostgreSQL Username","defaultValue":"postgresql","description":""},{"id":"$$secret_postgresql_password","main":"$$id-postgresql","name":"POSTGRESQL_PASSWORD","label":"PostgreSQL Password","defaultValue":"$$generate_password","description":"","showOnConfiguration":true},{"id":"$$config_postgresql_database","main":"$$id-postgresql","name":"POSTGRESQL_DATABASE","label":"PostgreSQL Database","defaultValue":"plausible","description":""},{"id":"$$config_scriptName","name":"SCRIPT_NAME","label":"Custom Script Name","defaultValue":"plausible.js","description":"This is the default script name."}]},{"templateVersion":"1.0.0","defaultVersion":"0.101.2","documentation":"https://docs.nocodb.com","type":"nocodb","name":"NocoDB","description":"Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart-spreadsheet.","labels":["database","airtable","spreadsheet"],"services":{"$$id":{"name":"NocoDB","image":"nocodb/nocodb:$$core_version","environment":["PORT=$$config_port","NC_DB=$$config_nc_db","DATABASE_URL=$$secret_database_url","NC_PUBLIC_URL=$$config_public_url","NC_AUTH_JWT_SECRET=$$secret_auth_jwt_secret","NC_SENTRY_DSN=$$secret_sentry_dsn","NC_CONNECT_TO_EXTERNAL_DB_DISABLED=$$config_connect_to_external_db_disabled","NC_DISABLE_TELE=$$config_disable_tele"],"volumes":["$$id-data:/usr/app/data"],"ports":["8080"]}},"variables":[{"id":"$$config_nc_db","name":"NC_DB","label":"Database","defaultValue":"","description":"MySQL, PostgreSQL and MSSQL connection urls supported. If absent: A local SQLite will be created in root folder."},{"id":"$$config_port","name":"PORT","label":"Port","defaultValue":"8080","description":""},{"id":"$$secret_database_url","name":"DATABASE_URL","label":"Database URL","defaultValue":"","description":"JDBC URL Format. Can be used instead of NC_DB. Used in 1-Click Heroku deployment."},{"id":"$$config_public_url","name":"NC_PUBLIC_URL","label":"Public URL","defaultValue":"","description":"Used for sending Email invitations. If absent: Best guess from http request params."},{"id":"$$secret_auth_jwt_secret","name":"NC_AUTH_JWT_SECRET","label":"Auth JWT Secret","defaultValue":"$$generate_hex(64)","description":"JWT secret used for auth and storing other secrets. If absent: A Random secret will be generated."},{"id":"$$secret_sentry_dsn","name":"NC_SENTRY_DSN","label":"Sentry DSN","defaultValue":"","description":"For Sentry monitoring."},{"id":"$$config_connect_to_external_db_disabled","name":"NC_CONNECT_TO_EXTERNAL_DB_DISABLED","label":"Disable External Database","defaultValue":"0","description":"Disable Project creation with external database. (Enter \"1\" to disable)."},{"id":"$$config_disable_tele","name":"NC_DISABLE_TELE","label":"NocoDB Disable Telemetry","defaultValue":"1","description":"Disable telemetry (Enter \"1\" to disable)."}]}] \ No newline at end of file diff --git a/apps/client/src/lib/common.ts b/apps/client/src/lib/common.ts index 8c2361a0c..6fa60b03b 100644 --- a/apps/client/src/lib/common.ts +++ b/apps/client/src/lib/common.ts @@ -3,6 +3,15 @@ import { addToast } from './store'; import Cookies from 'js-cookie'; export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay)); +export function dashify(str: string, options?: any): string { + if (typeof str !== 'string') return str; + return str + .trim() + .replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-')) + .replace(/^-+|-+$/g, '') + .replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m)) + .toLowerCase(); +} export function errorNotification(error: any | { message: string }): void { if (error instanceof Error) { console.error(error.message) diff --git a/apps/client/src/lib/components/DocLink.svelte b/apps/client/src/lib/components/DocLink.svelte new file mode 100644 index 000000000..803b48583 --- /dev/null +++ b/apps/client/src/lib/components/DocLink.svelte @@ -0,0 +1,44 @@ + + + + + + + {text} + {#if isExternal} + + {/if} + +{#if !text} + See details in the documentation +{/if} diff --git a/apps/client/src/lib/components/ExternalLink.svelte b/apps/client/src/lib/components/ExternalLink.svelte new file mode 100644 index 000000000..62f2e312a --- /dev/null +++ b/apps/client/src/lib/components/ExternalLink.svelte @@ -0,0 +1,10 @@ + + + diff --git a/apps/client/src/lib/components/SimpleExplainer.svelte b/apps/client/src/lib/components/SimpleExplainer.svelte new file mode 100644 index 000000000..6a3198c27 --- /dev/null +++ b/apps/client/src/lib/components/SimpleExplainer.svelte @@ -0,0 +1,6 @@ + + +
{@html text}
\ No newline at end of file diff --git a/apps/client/src/lib/components/icons/services/ServiceIcons.svelte b/apps/client/src/lib/components/icons/services/ServiceIcons.svelte index 7f146e3fb..3f832a3f5 100644 --- a/apps/client/src/lib/components/icons/services/ServiceIcons.svelte +++ b/apps/client/src/lib/components/icons/services/ServiceIcons.svelte @@ -5,6 +5,9 @@ const handleError = (ev: { target: { src: string } }) => (ev.target.src = fallback); let extension = 'png'; let svgs = [ + 'mattermost', + 'repman', + 'directus', 'pocketbase', 'gitea', 'languagetool', diff --git a/apps/client/src/lib/store.ts b/apps/client/src/lib/store.ts index 87afa0034..f25055de2 100644 --- a/apps/client/src/lib/store.ts +++ b/apps/client/src/lib/store.ts @@ -42,6 +42,7 @@ interface AppSession { gitlab: string | null; }; pendingInvitations: Array; + isARM: boolean } export const appSession: Writable = writable({ @@ -61,7 +62,8 @@ export const appSession: Writable = writable({ github: null, gitlab: null }, - pendingInvitations: [] + pendingInvitations: [], + isARM: false }); interface AddToast { @@ -171,3 +173,11 @@ export const setLocation = (resource: any, settings?: any) => { } }; export const selectedBuildId: any = writable(null) +export function checkIfDeploymentEnabledServices( service: any) { + return ( + service.fqdn && + service.destinationDocker && + service.version && + service.type + ); +} \ No newline at end of file diff --git a/apps/client/src/routes/applications/[id]/+page.svelte b/apps/client/src/routes/applications/[id]/+page.svelte index be04741aa..086c3ac57 100644 --- a/apps/client/src/routes/applications/[id]/+page.svelte +++ b/apps/client/src/routes/applications/[id]/+page.svelte @@ -351,13 +351,17 @@ } async function reloadCompose() { if (loading.reloadCompose) return; + if (!$appSession.tokens.github && !isPublicRepository) { + const { token } = await get(`/applications/${id}/configuration/githubToken`); + $appSession.tokens.github = token; + } loading.reloadCompose = true; try { if (application.gitSource.type === 'github') { const composeLocation = application.dockerComposeFileLocation.startsWith('/') - ? application.dockerComposeFileLocation - : `/${application.dockerComposeFileLocation}`; - + ? application.dockerComposeFileLocation + : `/${application.dockerComposeFileLocation}`; + const headers = isPublicRepository ? {} : { @@ -381,20 +385,20 @@ } } if (application.gitSource.type === 'gitlab') { - if (!$appSession.tokens.gitlab) { + if (!$appSession.tokens.gitlab && !isPublicRepository) { await getGitlabToken(); } - + const composeLocation = application.dockerComposeFileLocation.startsWith('/') - ? application.dockerComposeFileLocation.substring(1) // Remove the '/' from the start - : application.dockerComposeFileLocation; - + ? application.dockerComposeFileLocation.substring(1) // Remove the '/' from the start + : application.dockerComposeFileLocation; + // If the file is in a subdirectory, lastIndex will be > 0 // Otherwise it will be -1 and path will be an empty string - const lastIndex = composeLocation.lastIndexOf('/') + 1 - const path = composeLocation.substring(0, lastIndex) - const fileName = composeLocation.substring(lastIndex) - + const lastIndex = composeLocation.lastIndexOf('/') + 1; + const path = composeLocation.substring(0, lastIndex); + const fileName = composeLocation.substring(lastIndex); + const headers = isPublicRepository ? {} : { @@ -407,8 +411,7 @@ ...headers }); const dockerComposeFileYml = files.find( - (file: { name: string; type: string }) => - file.name === fileName && file.type === 'blob' + (file: { name: string; type: string }) => file.name === fileName && file.type === 'blob' ); const id = dockerComposeFileYml.id; diff --git a/apps/client/src/routes/applications/[id]/danger/+page.svelte b/apps/client/src/routes/applications/[id]/danger/+page.svelte new file mode 100644 index 000000000..4fede0195 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/danger/+page.svelte @@ -0,0 +1,60 @@ + + +
+
+
Danger Zone
+
+ + {#if forceDelete} + + {:else} + + {/if} +
diff --git a/apps/client/src/routes/applications/[id]/logs/+page.svelte b/apps/client/src/routes/applications/[id]/logs/+page.svelte index 310d65b32..2448ab296 100644 --- a/apps/client/src/routes/applications/[id]/logs/+page.svelte +++ b/apps/client/src/routes/applications/[id]/logs/+page.svelte @@ -21,7 +21,7 @@ onMount(async () => { const { data } = await trpc.applications.getApplicationById.query({ id }); application = data; - if (data.dockerComposeFile) { + if (application.dockerComposeFile && application.buildPack === 'compose') { services = normalizeDockerServices(JSON.parse(data.dockerComposeFile).services); } else { services = [ diff --git a/apps/client/src/routes/applications/[id]/previews/+page.svelte b/apps/client/src/routes/applications/[id]/previews/+page.svelte new file mode 100644 index 000000000..a4095f8b1 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/previews/+page.svelte @@ -0,0 +1,323 @@ + + +
+
+
+
Preview Deployments
+
+ +
+
+
+
+ +{#if loading.init} +
+
Loading...
+
+{:else if application.previewApplication.length > 0} +
+ {#each application.previewApplication as preview} +
+
+ {#await getStatus(preview)} + + {:then} + {#if status[preview.id] === 'running'} + + {:else} + + {/if} + {/await} +
+
+

+ PR #{preview.pullmergeRequestId} + {#if status[preview.id] === 'building'} + + BUILDING + + {/if} +

+
+

{preview.customDomain.replace('https://', '').replace('http://', '')}

+
+ +
+ {#if preview.customDomain} + + + + + + + + + {/if} + Open Preview + {#if loading.restart} + + {:else} + + {/if} + + Restart (useful to change secrets) + + Force redeploy (without cache) + + Delete Preview +
+
+
+
+
+ {/each} +
+{:else} + No previews found. +{/if} diff --git a/apps/client/src/routes/applications/[id]/revert/+page.svelte b/apps/client/src/routes/applications/[id]/revert/+page.svelte new file mode 100644 index 000000000..80013e0f0 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/revert/+page.svelte @@ -0,0 +1,151 @@ + + +
+
+
+
+ Revert +
+
+
+ If you do not want the next commit to overwrite the reverted application, temporary disable Automatic Deployment + feature here. +
+ {#if imagesAvailables.length > 0} +
Local Images
+
+ {#each imagesAvailables as image} +
+
+
+ {image.tag} +
+
+ + + {#if image.repository + ':' + image.tag !== runningImage} + + {:else} + + {/if} +
+
+
+ {/each} +
+ {:else} +
+
No Local images available
+
+ {/if} +
+ Remote Images (Docker Registry) +
+
+ + +
+
+
diff --git a/apps/client/src/routes/applications/[id]/revert/+page.ts b/apps/client/src/routes/applications/[id]/revert/+page.ts new file mode 100644 index 000000000..2fc882718 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/revert/+page.ts @@ -0,0 +1,16 @@ +import { error } from '@sveltejs/kit'; +import { trpc } from '$lib/store'; +import type { PageLoad } from './$types'; +export const ssr = false; + +export const load: PageLoad = async ({ params }) => { + try { + const { id } = params; + const { data } = await trpc.applications.getLocalImages.query({ id }); + return data; + } catch (err) { + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + }); + } +}; diff --git a/apps/client/src/routes/applications/[id]/usage/+page.svelte b/apps/client/src/routes/applications/[id]/usage/+page.svelte new file mode 100644 index 000000000..446f5abf5 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/usage/+page.svelte @@ -0,0 +1,116 @@ + + +
+
+
Monitoring
+
+
+
+ {#each services as service} + + {/each} +
+{#if selectedService} +
+ {#if usageLoading} +
+{/if} diff --git a/apps/client/src/routes/databases/[id]/+layout.svelte b/apps/client/src/routes/databases/[id]/+layout.svelte new file mode 100644 index 000000000..666317092 --- /dev/null +++ b/apps/client/src/routes/databases/[id]/+layout.svelte @@ -0,0 +1,305 @@ + + +{#if id !== 'new'} + +{/if} + diff --git a/apps/client/src/routes/databases/[id]/+layout.ts b/apps/client/src/routes/databases/[id]/+layout.ts new file mode 100644 index 000000000..5dff433c0 --- /dev/null +++ b/apps/client/src/routes/databases/[id]/+layout.ts @@ -0,0 +1,48 @@ +import { error } from '@sveltejs/kit'; +import { trpc } from '$lib/store'; +import type { LayoutLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +function checkConfiguration(database: any): string | null { + let configurationPhase = null; + if (!database.type) { + configurationPhase = 'type'; + } else if (!database.version) { + configurationPhase = 'version'; + } else if (!database.destinationDockerId) { + configurationPhase = 'destination'; + } + return configurationPhase; +} + +export const load: LayoutLoad = async ({ params, url }) => { + const { pathname } = new URL(url); + const { id } = params; + try { + const database = await trpc.databases.getDatabaseById.query({ id }); + if (!database) { + throw redirect(307, '/databases'); + } + const configurationPhase = checkConfiguration(database); + console.log({ configurationPhase }); + // if ( + // configurationPhase && + // pathname !== `/applications/${params.id}/configuration/${configurationPhase}` + // ) { + // throw redirect(302, `/applications/${params.id}/configuration/${configurationPhase}`); + // } + return { + database + }; + } catch (err) { + if (err instanceof Error) { + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + '

' + err.message + }); + } + + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + }); + } +}; diff --git a/apps/client/src/routes/databases/[id]/+page.svelte b/apps/client/src/routes/databases/[id]/+page.svelte new file mode 100644 index 000000000..1a8e76a21 --- /dev/null +++ b/apps/client/src/routes/databases/[id]/+page.svelte @@ -0,0 +1,62 @@ + + +
+
+
+
Used Memory / Memory Limit
+
{usage?.MemUsage}
+
+ +
+
Used CPU
+
{usage?.CPUPerc}
+
+ +
+
Network IO
+
{usage?.NetIO}
+
+
+
+ diff --git a/apps/client/src/routes/databases/[id]/components/DatabaseLinks.svelte b/apps/client/src/routes/databases/[id]/components/DatabaseLinks.svelte new file mode 100644 index 000000000..a3053a174 --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/DatabaseLinks.svelte @@ -0,0 +1,32 @@ + + + + {#if database.type === 'clickhouse'} + + {:else if database.type === 'couchdb'} + + {:else if database.type === 'mongodb'} + + {:else if database.type === 'mysql'} + + {:else if database.type === 'mariadb'} + + {:else if database.type === 'postgresql'} + + {:else if database.type === 'redis'} + + {:else if database.type === 'edgedb'} + + {/if} + diff --git a/apps/client/src/routes/databases/[id]/components/Databases/CouchDb.svelte b/apps/client/src/routes/databases/[id]/components/Databases/CouchDb.svelte new file mode 100644 index 000000000..e6f9acb28 --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/CouchDb.svelte @@ -0,0 +1,68 @@ + + +
+

CouchDB

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/Databases.svelte b/apps/client/src/routes/databases/[id]/components/Databases/Databases.svelte new file mode 100644 index 000000000..25ccd352b --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/Databases.svelte @@ -0,0 +1,281 @@ + + +
+
+
+

General

+ {#if $appSession.isAdmin} + + {/if} +
+
+ + + + {#if database.destinationDockerId} +
+ +
+ {/if} + + + + + + + +
+ {#if database.type === 'mysql'} + + {:else if database.type === 'postgresql'} + + {:else if database.type === 'mongodb'} + + {:else if database.type === 'mariadb'} + + {:else if database.type === 'redis'} + + {:else if database.type === 'couchdb'} + + {:else if database.type === 'edgedb'} + + {/if} +
+
+ + +
+
+ {#if publicUrl} + + {/if} +
+
+ +
+

Features

+
+
+ changeSettings('isPublic')} + title="Set it Public" + description="Your database will be reachable over the internet.
Take security seriously in this case!" + disabled={!$status.database.isRunning} + /> + {#if database.type === 'redis'} + changeSettings('appendOnly')} + title="Change append only mode" + description="Useful if you would like to restore redis data from a backup.
Database restart is required." + /> + {/if} +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/EdgeDB.svelte b/apps/client/src/routes/databases/[id]/components/Databases/EdgeDB.svelte new file mode 100644 index 000000000..ec04aa03d --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/EdgeDB.svelte @@ -0,0 +1,50 @@ + + +
+
EdgeDB
+
+
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/MariaDB.svelte b/apps/client/src/routes/databases/[id]/components/Databases/MariaDB.svelte new file mode 100644 index 000000000..1831a6401 --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/MariaDB.svelte @@ -0,0 +1,78 @@ + + +
+

MariaDB

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/MongoDB.svelte b/apps/client/src/routes/databases/[id]/components/Databases/MongoDB.svelte new file mode 100644 index 000000000..7f110703e --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/MongoDB.svelte @@ -0,0 +1,38 @@ + + +
+

MongoDB

+
+
+
+ + +
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/MySQL.svelte b/apps/client/src/routes/databases/[id]/components/Databases/MySQL.svelte new file mode 100644 index 000000000..db8df91cd --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/MySQL.svelte @@ -0,0 +1,76 @@ + + +
+

MySQL

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/PostgreSQL.svelte b/apps/client/src/routes/databases/[id]/components/Databases/PostgreSQL.svelte new file mode 100644 index 000000000..8b4589e0d --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/PostgreSQL.svelte @@ -0,0 +1,68 @@ + + +
+

PostgreSQL

+
+
+
+ + +
+ {#if !$appSession.isARM} +
+ + +
+ {/if} +
+ + +
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/components/Databases/Redis.svelte b/apps/client/src/routes/databases/[id]/components/Databases/Redis.svelte new file mode 100644 index 000000000..50049d52c --- /dev/null +++ b/apps/client/src/routes/databases/[id]/components/Databases/Redis.svelte @@ -0,0 +1,27 @@ + + +
+

Redis

+
+
+
+ + +
+
diff --git a/apps/client/src/routes/databases/[id]/utils.ts b/apps/client/src/routes/databases/[id]/utils.ts new file mode 100644 index 000000000..2ed88f33d --- /dev/null +++ b/apps/client/src/routes/databases/[id]/utils.ts @@ -0,0 +1,37 @@ +import { errorNotification } from '$lib/common'; +import { trpc } from '$lib/store'; + +type Props = { + isNew: boolean; + name: string; + value: string; + isBuildSecret?: boolean; + isPRMRSecret?: boolean; + isNewSecret?: boolean; + databaseId: string; +}; + +export async function saveSecret({ + isNew, + name, + value, + isNewSecret, + databaseId +}: Props): Promise { + if (!name) return errorNotification('Name is required'); + if (!value) return errorNotification('Value is required'); + try { + await trpc.databases.saveSecret.mutate({ + name, + value, + isNew: isNew || false + }); + + if (isNewSecret) { + name = ''; + value = ''; + } + } catch (error) { + throw error; + } +} diff --git a/apps/client/src/routes/destinations/[id]/+layout.svelte b/apps/client/src/routes/destinations/[id]/+layout.svelte new file mode 100644 index 000000000..3f6cfa737 --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/+layout.svelte @@ -0,0 +1,69 @@ + + +{#if $page.params.id !== 'new'} + +{/if} + diff --git a/apps/client/src/routes/destinations/[id]/+layout.ts b/apps/client/src/routes/destinations/[id]/+layout.ts new file mode 100644 index 000000000..e9cc53efe --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/+layout.ts @@ -0,0 +1,45 @@ +import { error } from '@sveltejs/kit'; +import { trpc } from '$lib/store'; +import type { LayoutLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +function checkConfiguration(destination: any): string | null { + let configurationPhase = null; + if (!destination?.remoteEngine) return configurationPhase; + if (!destination?.sshKey) { + configurationPhase = 'sshkey'; + } + return configurationPhase; +} + +export const load: LayoutLoad = async ({ params, url }) => { + const { pathname } = new URL(url); + const { id } = params; + try { + const destination = await trpc.destinations.getDestinationById.query({ id }); + if (!destination) { + throw redirect(307, '/destinations'); + } + const configurationPhase = checkConfiguration(destination); + console.log({ configurationPhase }); + // if ( + // configurationPhase && + // pathname !== `/applications/${params.id}/configuration/${configurationPhase}` + // ) { + // throw redirect(302, `/applications/${params.id}/configuration/${configurationPhase}`); + // } + return { + destination + }; + } catch (err) { + if (err instanceof Error) { + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + '

' + err.message + }); + } + + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + }); + } +}; diff --git a/apps/client/src/routes/destinations/[id]/+page.svelte b/apps/client/src/routes/destinations/[id]/+page.svelte new file mode 100644 index 000000000..6b6716f52 --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/+page.svelte @@ -0,0 +1,18 @@ + + +{#if id === 'new'} + +{:else} + +{/if} diff --git a/apps/client/src/routes/destinations/[id]/components/Destination.svelte b/apps/client/src/routes/destinations/[id]/components/Destination.svelte new file mode 100644 index 000000000..11135717d --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/components/Destination.svelte @@ -0,0 +1,14 @@ + + +
+ {#if destination.remoteEngine} + + {:else} + + {/if} +
diff --git a/apps/client/src/routes/destinations/[id]/components/LocalDocker.svelte b/apps/client/src/routes/destinations/[id]/components/LocalDocker.svelte new file mode 100644 index 000000000..237688577 --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/components/LocalDocker.svelte @@ -0,0 +1,212 @@ + + +
+
+ + +
+
+ + + + + + + {#if $appSession.teamId === '0'} + You cannot disable this proxy as FQDN is configured for Coolify.' + : '' + }`} + /> + {/if} +
+
diff --git a/apps/client/src/routes/destinations/[id]/components/New.svelte b/apps/client/src/routes/destinations/[id]/components/New.svelte new file mode 100644 index 000000000..321ee7c70 --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/components/New.svelte @@ -0,0 +1,53 @@ + + +
+
Add New Destination
+
+
+
Predefined destinations
+
+ + + +
+
+{#if selected === 'localDocker'} + +{:else if selected === 'remoteDocker'} + +{:else} +
Not implemented yet
+{/if} diff --git a/apps/client/src/routes/destinations/[id]/components/NewLocalDocker.svelte b/apps/client/src/routes/destinations/[id]/components/NewLocalDocker.svelte new file mode 100644 index 000000000..81eb9c0f8 --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/components/NewLocalDocker.svelte @@ -0,0 +1,72 @@ + + +
+
+
+
Configuration
+ +
+
+ + +
+ +
+ + +
+
+ + +
+ {#if $appSession.teamId === '0'} +
+ (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)} + title="Use Coolify Proxy?" + description={'This will install a proxy on the destination to allow you to access your applications and services without any manual configuration.'} + /> +
+ {/if} +
+
diff --git a/apps/client/src/routes/destinations/[id]/components/NewRemoteDocker.svelte b/apps/client/src/routes/destinations/[id]/components/NewRemoteDocker.svelte new file mode 100644 index 000000000..5fed699d5 --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/components/NewRemoteDocker.svelte @@ -0,0 +1,104 @@ + + +
+ +
+
+
+
+
Configuration
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)} + title="Use Coolify Proxy?" + description={'This will install a proxy on the destination to allow you to access your applications and services without any manual configuration.'} + /> +
+
+
diff --git a/apps/client/src/routes/destinations/[id]/components/RemoteDocker.svelte b/apps/client/src/routes/destinations/[id]/components/RemoteDocker.svelte new file mode 100644 index 000000000..f67fab14e --- /dev/null +++ b/apps/client/src/routes/destinations/[id]/components/RemoteDocker.svelte @@ -0,0 +1,279 @@ + + +
+
+ {#if $appSession.isAdmin} + + + {#if destination.remoteVerified} + + {/if} + {/if} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ You cannot disable this proxy as FQDN is configured for Coolify.' + : '' + }`} + /> +
+
diff --git a/apps/client/src/routes/services/[id]/+layout.svelte b/apps/client/src/routes/services/[id]/+layout.svelte new file mode 100644 index 000000000..f218a664c --- /dev/null +++ b/apps/client/src/routes/services/[id]/+layout.svelte @@ -0,0 +1,366 @@ + + +
+ +
+ {#if $status.service.initialLoading} + + {:else if $status.service.overallStatus === 'healthy'} + + + {:else if $status.service.overallStatus === 'degraded'} + + {:else if $status.service.overallStatus === 'stopped'} + {#if $status.service.overallStatus === 'degraded'} + + {:else if $status.service.overallStatus === 'stopped'} + + {/if} + {/if} +
+
+ +
+ {#if !$page.url.pathname.startsWith(`/services/${id}/configuration/`)} + + {/if} +
+ +
+
diff --git a/apps/client/src/routes/services/[id]/+layout.ts b/apps/client/src/routes/services/[id]/+layout.ts new file mode 100644 index 000000000..9027326c5 --- /dev/null +++ b/apps/client/src/routes/services/[id]/+layout.ts @@ -0,0 +1,46 @@ +import { error } from '@sveltejs/kit'; +import { trpc } from '$lib/store'; +import type { LayoutLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +function checkConfiguration(service: any): string | null { + let configurationPhase = null; + if (!service.type) { + configurationPhase = 'type'; + } else if (!service.destinationDockerId) { + configurationPhase = 'destination'; + } + return configurationPhase; +} + +export const load: LayoutLoad = async ({ params, url }) => { + const { pathname } = new URL(url); + const { id } = params; + try { + const service = await trpc.services.getServices.query({ id }); + if (!service) { + throw redirect(307, '/services'); + } + const configurationPhase = checkConfiguration(service); + console.log({ configurationPhase }); + // if ( + // configurationPhase && + // pathname !== `/applications/${params.id}/configuration/${configurationPhase}` + // ) { + // throw redirect(302, `/applications/${params.id}/configuration/${configurationPhase}`); + // } + return { + ...service.data + }; + } catch (err) { + if (err instanceof Error) { + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + '

' + err.message + }); + } + + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + }); + } +}; diff --git a/apps/client/src/routes/services/[id]/+page.svelte b/apps/client/src/routes/services/[id]/+page.svelte new file mode 100644 index 000000000..2dd38eadb --- /dev/null +++ b/apps/client/src/routes/services/[id]/+page.svelte @@ -0,0 +1,562 @@ + + +
+
+
+
+
General
+ {#if $appSession.isAdmin} + + {/if} + {#if service.type === 'plausibleanalytics' && $status.service.overallStatus === 'healthy'} + + + {/if} + {#if service.type === 'appwrite' && $status.service.overallStatus === 'healthy'} + +
+ +
+ {/if} +
+
+ +
+
+ + +
+
+ + {#if tags.tags?.length > 0} +
+ + {/if} +
+ +
+ +
+ {#if service.destinationDockerId} +
+ +
+ {/if} +
+
+ +
+ + +
+ {#each Object.keys(template) as oneService} + {#each template[oneService].fqdns as fqdn} +
+ + +
+ {/each} + {/each} +
+ {#if forceSave} +
+ {#if isNonWWWDomainOK} + + {:else} + + {/if} + {#if dualCerts} + {#if isWWWDomainOK} + + {:else} + + {/if} + {/if} +
+ {/if} + +
+
+ You need to have both DNS entries set in advance.

Service needs to be restarted."} + on:click={() => !$status.service.isRunning && changeSettings('dualCerts')} + /> +
+ {#if hostPorts.length === 0} +
+ + +
+ {/if} +
+
+ {#each Object.keys(template) as oneService} +
0 && + template[oneService].environment.find((env) => env.main === oneService)} + class:border-b={template[oneService].environment.length > 0 && + template[oneService].environment.find((env) => env.main === oneService)} + class:border-coolgray-500={template[oneService].environment.length > 0 && + template[oneService].environment.find((env) => env.main === oneService)} + > +
+ {template[oneService].name || + oneService.replace(`${id}-`, '').replace(id, service.type)} +
+ +
+
+ {#if template[oneService].environment.length > 0} + {#each template[oneService].environment as variable} + {#if variable.main === oneService} +
+ + {#if variable.defaultValue === '$$generate_fqdn'} + + {:else if variable.defaultValue === '$$generate_fqdn_slash'} + + {:else if variable.defaultValue === '$$generate_domain'} + + {:else if variable.defaultValue === '$$generate_network'} + + {:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'} + {#if variable.value === 'true' || variable.value === 'false' || variable.value === 'invite_only'} + + {:else} + + {/if} + {:else if variable.defaultValue === '$$generate_password'} + + {:else if variable.type === 'textarea'} +