fixes
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 1.0.3
|
defaultVersion: 1.0.3
|
||||||
|
type: appwrite
|
||||||
name: Appwrite
|
name: Appwrite
|
||||||
documentation: https://appwrite.io/docs
|
documentation: https://appwrite.io/docs
|
||||||
description: Secure Backend Server for Web, Mobile & Flutter Developers.
|
description: Secure Backend Server for Web, Mobile & Flutter Developers.
|
||||||
@@ -1035,6 +1036,7 @@
|
|||||||
The default value is 15 minutes.
|
The default value is 15 minutes.
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: latest
|
defaultVersion: latest
|
||||||
|
type: weblate
|
||||||
name: Weblate
|
name: Weblate
|
||||||
description: ''
|
description: ''
|
||||||
services:
|
services:
|
||||||
@@ -1087,7 +1089,7 @@
|
|||||||
label: Weblate Admin Password
|
label: Weblate Admin Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- id: $$config_postgres_user
|
- id: $$config_postgres_user
|
||||||
main: $$id-postgresql
|
main: $$id-postgresql
|
||||||
name: POSTGRES_USER
|
name: POSTGRES_USER
|
||||||
@@ -1100,7 +1102,7 @@
|
|||||||
label: PostgreSQL Password
|
label: PostgreSQL Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- id: $$config_postgres_db
|
- id: $$config_postgres_db
|
||||||
main: $$id-postgresql
|
main: $$id-postgresql
|
||||||
name: POSTGRES_DB
|
name: POSTGRES_DB
|
||||||
@@ -1109,6 +1111,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 2022.10.14-1a5b0965
|
defaultVersion: 2022.10.14-1a5b0965
|
||||||
|
type: searxng
|
||||||
name: SearXNG
|
name: SearXNG
|
||||||
description: ''
|
description: ''
|
||||||
services:
|
services:
|
||||||
@@ -1181,6 +1184,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v2.0.6
|
defaultVersion: v2.0.6
|
||||||
|
type: glitchtip
|
||||||
name: GlitchTip
|
name: GlitchTip
|
||||||
description: ''
|
description: ''
|
||||||
services:
|
services:
|
||||||
@@ -1344,6 +1348,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v2.13.0
|
defaultVersion: v2.13.0
|
||||||
|
type: hasura
|
||||||
name: Hasura
|
name: Hasura
|
||||||
description: 'Instant realtime GraphQL APIs on any Postgres application, existing or new.'
|
description: 'Instant realtime GraphQL APIs on any Postgres application, existing or new.'
|
||||||
services:
|
services:
|
||||||
@@ -1388,7 +1393,7 @@
|
|||||||
label: Hasura Admin Password
|
label: Hasura Admin Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- id: $$config_postgres_user
|
- id: $$config_postgres_user
|
||||||
name: POSTGRES_USER
|
name: POSTGRES_USER
|
||||||
label: PostgreSQL User
|
label: PostgreSQL User
|
||||||
@@ -1406,6 +1411,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: postgresql-v1.38.0
|
defaultVersion: postgresql-v1.38.0
|
||||||
|
type: umami
|
||||||
name: Umami
|
name: Umami
|
||||||
description: >-
|
description: >-
|
||||||
Umami is a simple, easy to use, self-hosted web analytics solution. The goal
|
Umami is a simple, easy to use, self-hosted web analytics solution. The goal
|
||||||
@@ -1606,9 +1612,10 @@
|
|||||||
label: Initial Admin Password
|
label: Initial Admin Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v0.29.1
|
defaultVersion: v0.29.1
|
||||||
|
type: meilisearch
|
||||||
name: MeiliSearch
|
name: MeiliSearch
|
||||||
description: >-
|
description: >-
|
||||||
MeiliSearch is a lightning Fast, Ultra Relevant, and Typo-Tolerant Search
|
MeiliSearch is a lightning Fast, Ultra Relevant, and Typo-Tolerant Search
|
||||||
@@ -1634,9 +1641,10 @@
|
|||||||
label: Master Key
|
label: Master Key
|
||||||
defaultValue: $$generate_hex(64)
|
defaultValue: $$generate_hex(64)
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: latest
|
defaultVersion: latest
|
||||||
|
type: ghost
|
||||||
name: Ghost
|
name: Ghost
|
||||||
description: >-
|
description: >-
|
||||||
Ghost is a free and open source blogging platform written in JavaScript and
|
Ghost is a free and open source blogging platform written in JavaScript and
|
||||||
@@ -1749,8 +1757,9 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: php8.1
|
defaultVersion: php8.1
|
||||||
name: WordPress
|
type: wordpress
|
||||||
description: WordPress is a content management system based on PHP.
|
name: WordPress (with MySQL)
|
||||||
|
description: WordPress is a content management system based on PHP with new MySQL database.
|
||||||
services:
|
services:
|
||||||
$$id:
|
$$id:
|
||||||
name: WordPress
|
name: WordPress
|
||||||
@@ -1789,53 +1798,138 @@
|
|||||||
label: WordPress DB Host
|
label: WordPress DB Host
|
||||||
defaultValue: $$id-mysql
|
defaultValue: $$id-mysql
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$config_wordpress_db_user
|
- id: $$config_wordpress_db_user
|
||||||
name: WORDPRESS_DB_USER
|
name: WORDPRESS_DB_USER
|
||||||
label: WordPress DB User
|
label: WordPress DB User
|
||||||
defaultValue: $$config_mysql_user
|
defaultValue: $$config_mysql_user
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$secret_wordpress_db_password
|
- id: $$secret_wordpress_db_password
|
||||||
name: WORDPRESS_DB_PASSWORD
|
name: WORDPRESS_DB_PASSWORD
|
||||||
label: WordPress DB Password
|
label: WordPress DB Password
|
||||||
defaultValue: $$secret_mysql_password
|
defaultValue: $$secret_mysql_password
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$config_wordpress_db_name
|
- id: $$config_wordpress_db_name
|
||||||
name: WORDPRESS_DB_NAME
|
name: WORDPRESS_DB_NAME
|
||||||
label: WordPress DB Name
|
label: WordPress DB Name
|
||||||
defaultValue: $$config_mysql_database
|
defaultValue: $$config_mysql_database
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$config_wordpress_config_extra
|
- id: $$config_wordpress_config_extra
|
||||||
name: WORDPRESS_CONFIG_EXTRA
|
name: WORDPRESS_CONFIG_EXTRA
|
||||||
label: WordPress Config Extra
|
label: WordPress Config Extra
|
||||||
defaultValue: ''
|
defaultValue: ''
|
||||||
description: ''
|
description: ''
|
||||||
|
type: 'textarea'
|
||||||
|
placeholder: |
|
||||||
|
define('WP_DEBUG', true);
|
||||||
|
define('WP_DEBUG_LOG', true);
|
||||||
|
define('WP_DEBUG_DISPLAY', false);
|
||||||
|
@ini_set('display_errors', 0);
|
||||||
- id: $$secret_mysql_root_password
|
- id: $$secret_mysql_root_password
|
||||||
name: MYSQL_ROOT_PASSWORD
|
name: MYSQL_ROOT_PASSWORD
|
||||||
label: MySQL Root Password
|
label: MySQL Root Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$config_mysql_root_user
|
- id: $$config_mysql_root_user
|
||||||
name: MYSQL_ROOT_USER
|
name: MYSQL_ROOT_USER
|
||||||
label: MySQL Root User
|
label: MySQL Root User
|
||||||
defaultValue: $$generate_username
|
defaultValue: $$generate_username
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$config_mysql_database
|
- id: $$config_mysql_database
|
||||||
name: MYSQL_DATABASE
|
name: MYSQL_DATABASE
|
||||||
label: MySQL Database
|
label: MySQL Database
|
||||||
defaultValue: wordpress
|
defaultValue: wordpress
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$config_mysql_user
|
- id: $$config_mysql_user
|
||||||
name: MYSQL_USER
|
name: MYSQL_USER
|
||||||
label: MySQL User
|
label: MySQL User
|
||||||
defaultValue: $$generate_username
|
defaultValue: $$generate_username
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
- id: $$secret_mysql_password
|
- id: $$secret_mysql_password
|
||||||
name: MYSQL_PASSWORD
|
name: MYSQL_PASSWORD
|
||||||
label: MySQL Password
|
label: MySQL Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
|
readonly: true
|
||||||
|
- templateVersion: 1.0.0
|
||||||
|
defaultVersion: php8.1
|
||||||
|
type: wordpress-only
|
||||||
|
name: WordPress (without MySQL)
|
||||||
|
description: WordPress is a content management system based on PHP without MySQL.
|
||||||
|
services:
|
||||||
|
$$id:
|
||||||
|
name: WordPress
|
||||||
|
documentation: 'Taken from https://docs.docker.com/compose/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);
|
||||||
|
define('WP_DEBUG_LOG', true);
|
||||||
|
define('WP_DEBUG_DISPLAY', false);
|
||||||
|
@ini_set('display_errors', 0);
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 4.7.1
|
defaultVersion: 4.7.1
|
||||||
|
type: vscodeserver
|
||||||
name: VSCode Server
|
name: VSCode Server
|
||||||
description: >-
|
description: >-
|
||||||
vscode-server by Coder is VS Code running on a remote server, accessible
|
vscode-server by Coder is VS Code running on a remote server, accessible
|
||||||
@@ -1861,9 +1955,10 @@
|
|||||||
label: Password
|
label: Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: RELEASE.2022-10-15T19-57-03Z
|
defaultVersion: RELEASE.2022-10-15T19-57-03Z
|
||||||
|
type: minio
|
||||||
name: MinIO
|
name: MinIO
|
||||||
description: ' MinIO is a cloud storage server compatible with Amazon S3'
|
description: ' MinIO is a cloud storage server compatible with Amazon S3'
|
||||||
services:
|
services:
|
||||||
@@ -1915,9 +2010,10 @@
|
|||||||
label: Root User Password
|
label: Root User Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.21.1
|
defaultVersion: 0.21.1
|
||||||
|
type: fider
|
||||||
name: Fider
|
name: Fider
|
||||||
description: Fider is a platform to collect and organize customer feedback.
|
description: Fider is a platform to collect and organize customer feedback.
|
||||||
services:
|
services:
|
||||||
@@ -2032,6 +2128,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.198.1
|
defaultVersion: 0.198.1
|
||||||
|
type: n8nio
|
||||||
name: n8n.io
|
name: n8n.io
|
||||||
description: n8n is a free and open node based Workflow Automation Tool.
|
description: n8n is a free and open node based Workflow Automation Tool.
|
||||||
services:
|
services:
|
||||||
@@ -2056,6 +2153,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: stable
|
defaultVersion: stable
|
||||||
|
type: plausibleanalytics
|
||||||
name: Plausible Analytics
|
name: Plausible Analytics
|
||||||
description: Plausible is a lightweight and open-source website analytics tool.
|
description: Plausible is a lightweight and open-source website analytics tool.
|
||||||
services:
|
services:
|
||||||
@@ -2157,7 +2255,7 @@
|
|||||||
label: Admin User Password
|
label: Admin User Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: This is the admin password. Please change it.
|
description: This is the admin password. Please change it.
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- id: $$secret_secret_key_base
|
- id: $$secret_secret_key_base
|
||||||
name: SECRET_KEY_BASE
|
name: SECRET_KEY_BASE
|
||||||
label: Secret Key Base
|
label: Secret Key Base
|
||||||
@@ -2185,7 +2283,7 @@
|
|||||||
label: PostgreSQL Password
|
label: PostgreSQL Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ''
|
description: ''
|
||||||
showOnUI: true
|
showOnConfiguration: true
|
||||||
- id: $$config_postgresql_database
|
- id: $$config_postgresql_database
|
||||||
main: $$id-postgresql
|
main: $$id-postgresql
|
||||||
name: POSTGRESQL_DATABASE
|
name: POSTGRESQL_DATABASE
|
||||||
@@ -2199,6 +2297,7 @@
|
|||||||
description: This is the default script name.
|
description: This is the default script name.
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.98.1
|
defaultVersion: 0.98.1
|
||||||
|
type: nocodb
|
||||||
name: NocoDB
|
name: NocoDB
|
||||||
description: >-
|
description: >-
|
||||||
The Open Source Airtable Alternative - Turns any MySQL, PostgreSQL, SQL
|
The Open Source Airtable Alternative - Turns any MySQL, PostgreSQL, SQL
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Wordpress" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"extraConfig" TEXT,
|
||||||
|
"tablePrefix" TEXT,
|
||||||
|
"ownMysql" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"mysqlHost" TEXT,
|
||||||
|
"mysqlPort" INTEGER,
|
||||||
|
"mysqlUser" TEXT,
|
||||||
|
"mysqlPassword" TEXT,
|
||||||
|
"mysqlRootUser" TEXT,
|
||||||
|
"mysqlRootUserPassword" TEXT,
|
||||||
|
"mysqlDatabase" TEXT,
|
||||||
|
"mysqlPublicPort" INTEGER,
|
||||||
|
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"ftpUser" TEXT,
|
||||||
|
"ftpPassword" TEXT,
|
||||||
|
"ftpPublicPort" INTEGER,
|
||||||
|
"ftpHostKey" TEXT,
|
||||||
|
"ftpHostKeyPrivate" TEXT,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlHost", "mysqlPassword", "mysqlPort", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "ownMysql", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlHost", "mysqlPassword", "mysqlPort", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "ownMysql", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
|
||||||
|
DROP TABLE "Wordpress";
|
||||||
|
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
|
||||||
|
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
@@ -480,10 +480,10 @@ model Wordpress {
|
|||||||
ownMysql Boolean @default(false)
|
ownMysql Boolean @default(false)
|
||||||
mysqlHost String?
|
mysqlHost String?
|
||||||
mysqlPort Int?
|
mysqlPort Int?
|
||||||
mysqlUser String
|
mysqlUser String?
|
||||||
mysqlPassword String
|
mysqlPassword String?
|
||||||
mysqlRootUser String
|
mysqlRootUser String?
|
||||||
mysqlRootUserPassword String
|
mysqlRootUserPassword String?
|
||||||
mysqlDatabase String?
|
mysqlDatabase String?
|
||||||
mysqlPublicPort Int?
|
mysqlPublicPort Int?
|
||||||
ftpEnabled Boolean @default(false)
|
ftpEnabled Boolean @default(false)
|
||||||
|
@@ -8,6 +8,7 @@ const template = yaml.load(templateYml)
|
|||||||
const newTemplate = {
|
const newTemplate = {
|
||||||
"templateVersion": "1.0.0",
|
"templateVersion": "1.0.0",
|
||||||
"defaultVersion": "latest",
|
"defaultVersion": "latest",
|
||||||
|
"type": "",
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"services": {
|
"services": {
|
||||||
@@ -18,6 +19,7 @@ const newTemplate = {
|
|||||||
const version = template.caproverOneClickApp.variables.find(v => v.id === '$$cap_APP_VERSION' || v.id === '$$cap_version').defaultValue || 'latest'
|
const version = template.caproverOneClickApp.variables.find(v => v.id === '$$cap_APP_VERSION' || v.id === '$$cap_version').defaultValue || 'latest'
|
||||||
|
|
||||||
newTemplate.name = template.caproverOneClickApp.displayName
|
newTemplate.name = template.caproverOneClickApp.displayName
|
||||||
|
newTemplate.type = template.caproverOneClickApp.displayName.replaceAll(' ', '').toLowerCase()
|
||||||
newTemplate.documentation = template.caproverOneClickApp.documentation
|
newTemplate.documentation = template.caproverOneClickApp.documentation
|
||||||
newTemplate.description = template.caproverOneClickApp.description
|
newTemplate.description = template.caproverOneClickApp.description
|
||||||
newTemplate.defaultVersion = version
|
newTemplate.defaultVersion = version
|
||||||
|
@@ -7,19 +7,32 @@ const templates = await fs.readFile('../devTemplates.yaml', 'utf8');
|
|||||||
const devTemplates = yaml.load(templates);
|
const devTemplates = yaml.load(templates);
|
||||||
for (const template of devTemplates) {
|
for (const template of devTemplates) {
|
||||||
let image = template.services['$$id'].image.replaceAll(':$$core_version', '');
|
let image = template.services['$$id'].image.replaceAll(':$$core_version', '');
|
||||||
const name = template.name
|
|
||||||
if (!image.includes('/')) {
|
if (!image.includes('/')) {
|
||||||
image = `library/${image}`;
|
image = `library/${image}`;
|
||||||
}
|
}
|
||||||
repositories.push({ image, name: name.toLowerCase().replaceAll(' ', '') });
|
repositories.push({ image, name: template.type });
|
||||||
}
|
}
|
||||||
const services = []
|
const services = []
|
||||||
|
|
||||||
const numberOfTags = 30;
|
const numberOfTags = 30;
|
||||||
// const semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g)
|
// const semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g)
|
||||||
const semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/g)
|
|
||||||
for (const repository of repositories) {
|
for (const repository of repositories) {
|
||||||
console.log('Querying', repository.name, 'at', repository.image);
|
console.log('Querying', repository.name, 'at', repository.image);
|
||||||
|
let semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/g)
|
||||||
|
if (repository.name.startsWith('wordpress')) {
|
||||||
|
semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-php(0|[1-9]\d*)$/g)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('minio')) {
|
||||||
|
semverRegex = new RegExp(/^RELEASE.*$/g)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('fider')) {
|
||||||
|
semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-([0-9]+)$/g)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('searxng')) {
|
||||||
|
semverRegex = new RegExp(/^\d{4}[\.\-](0?[1-9]|[12][0-9]|3[01])[\.\-](0?[1-9]|1[012]).*$/)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('umami')) {
|
||||||
|
semverRegex = new RegExp(/^postgresql-v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-([0-9]+)$/g)
|
||||||
|
}
|
||||||
if (repository.image.includes('ghcr.io')) {
|
if (repository.image.includes('ghcr.io')) {
|
||||||
const { execaCommand } = await import('execa');
|
const { execaCommand } = await import('execa');
|
||||||
const { stdout } = await execaCommand(`docker run --rm quay.io/skopeo/stable list-tags docker://${repository.image}`);
|
const { stdout } = await execaCommand(`docker run --rm quay.io/skopeo/stable list-tags docker://${repository.image}`);
|
||||||
|
@@ -317,7 +317,9 @@ async function wordpress(service: any, template: any) {
|
|||||||
]
|
]
|
||||||
await migrateSettings(settings, service, template);
|
await migrateSettings(settings, service, template);
|
||||||
await migrateSecrets(secrets, service);
|
await migrateSecrets(secrets, service);
|
||||||
|
if (ownMysql) {
|
||||||
|
await prisma.service.update({ where: { id: service.id }, data: { type: "wordpress-only" } })
|
||||||
|
}
|
||||||
// Remove old service data
|
// Remove old service data
|
||||||
// await prisma.service.update({ where: { id: service.id }, data: { wordpress: { delete: true } } })
|
// await prisma.service.update({ where: { id: service.id }, data: { wordpress: { delete: true } } })
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ export function getAPIUrl() {
|
|||||||
if (process.env.CODESANDBOX_HOST) {
|
if (process.env.CODESANDBOX_HOST) {
|
||||||
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
|
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
|
||||||
}
|
}
|
||||||
return isDev ? 'http://localhost:3001' : 'http://localhost:3000';
|
return isDev ? 'http://host.docker.internal:3001' : 'http://localhost:3000';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUIUrl() {
|
export function getUIUrl() {
|
||||||
@@ -1447,12 +1447,13 @@ export async function getServiceFromDB({
|
|||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
serviceSecret: true,
|
serviceSecret: true,
|
||||||
serviceSetting: true,
|
serviceSetting: true,
|
||||||
|
wordpress: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
body.type = fixType(body.type);
|
// body.type = fixType(body.type);
|
||||||
|
|
||||||
if (body?.serviceSecret.length > 0) {
|
if (body?.serviceSecret.length > 0) {
|
||||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||||
@@ -1460,6 +1461,9 @@ export async function getServiceFromDB({
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (body.wordpress) {
|
||||||
|
body.wordpress.ftpPassword = decrypt(body.wordpress.ftpPassword);
|
||||||
|
}
|
||||||
|
|
||||||
return { ...body, settings };
|
return { ...body, settings };
|
||||||
}
|
}
|
||||||
|
@@ -42,11 +42,16 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const template: any = await parseAndFindServiceTemplates(service, workdir, true)
|
const template: any = await parseAndFindServiceTemplates(service, workdir, true)
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const config = {};
|
const config = {};
|
||||||
for (const service in template.services) {
|
const isWordpress = type === 'wordpress';
|
||||||
|
for (const s in template.services) {
|
||||||
|
if (isWordpress && service.wordpress.ownMysql && s.name === 'MySQL') {
|
||||||
|
console.log('skipping predefined mysql')
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let newEnvironments = []
|
let newEnvironments = []
|
||||||
if (arm) {
|
if (arm) {
|
||||||
if (template.services[service]?.environmentArm?.length > 0) {
|
if (template.services[s]?.environmentArm?.length > 0) {
|
||||||
for (const environment of template.services[service].environmentArm) {
|
for (const environment of template.services[s].environmentArm) {
|
||||||
const [env, value] = environment.split("=");
|
const [env, value] = environment.split("=");
|
||||||
if (!value.startsWith('$$secret') && value !== '') {
|
if (!value.startsWith('$$secret') && value !== '') {
|
||||||
newEnvironments.push(`${env}=${value}`)
|
newEnvironments.push(`${env}=${value}`)
|
||||||
@@ -54,8 +59,8 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (template.services[service]?.environment?.length > 0) {
|
if (template.services[s]?.environment?.length > 0) {
|
||||||
for (const environment of template.services[service].environment) {
|
for (const environment of template.services[s].environment) {
|
||||||
const [env, value] = environment.split("=");
|
const [env, value] = environment.split("=");
|
||||||
if (!value.startsWith('$$secret') && value !== '') {
|
if (!value.startsWith('$$secret') && value !== '') {
|
||||||
newEnvironments.push(`${env}=${value}`)
|
newEnvironments.push(`${env}=${value}`)
|
||||||
@@ -68,7 +73,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
for (const secret of secrets) {
|
for (const secret of secrets) {
|
||||||
const { name, value } = secret
|
const { name, value } = secret
|
||||||
if (value) {
|
if (value) {
|
||||||
const foundEnv = !!template.services[service].environment?.find(env => env.startsWith(`${name}=`))
|
const foundEnv = !!template.services[s].environment?.find(env => env.startsWith(`${name}=`))
|
||||||
const foundNewEnv = !!newEnvironments?.find(env => env.startsWith(`${name}=`))
|
const foundNewEnv = !!newEnvironments?.find(env => env.startsWith(`${name}=`))
|
||||||
if (foundEnv && !foundNewEnv) {
|
if (foundEnv && !foundNewEnv) {
|
||||||
newEnvironments.push(`${name}=${decrypt(value)}`)
|
newEnvironments.push(`${name}=${decrypt(value)}`)
|
||||||
@@ -76,7 +81,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } })
|
const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } })
|
||||||
let volumes = arm ? template.services[service].volumesArm : template.services[service].volumes
|
let volumes = arm ? template.services[s].volumesArm : template.services[s].volumes
|
||||||
if (customVolumes.length > 0) {
|
if (customVolumes.length > 0) {
|
||||||
for (const customVolume of customVolumes) {
|
for (const customVolume of customVolumes) {
|
||||||
const { volumeName, path, containerId } = customVolume
|
const { volumeName, path, containerId } = customVolume
|
||||||
@@ -85,41 +90,58 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config[service] = {
|
if (isWordpress) {
|
||||||
container_name: service,
|
const { wordpress: { mysqlHost, mysqlPort, mysqlUser, mysqlPassword, mysqlDatabase, ownMysql } } = service
|
||||||
build: template.services[service].build || undefined,
|
console.log({ mysqlHost, mysqlPort, mysqlUser, mysqlPassword, mysqlDatabase, ownMysql })
|
||||||
command: template.services[service].command,
|
if (ownMysql) {
|
||||||
entrypoint: template.services[service]?.entrypoint,
|
let envsToRemove = ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME']
|
||||||
image: arm ? template.services[service].imageArm : template.services[service].image,
|
for (const env of newEnvironments) {
|
||||||
expose: template.services[service].ports,
|
if (envsToRemove.includes(env.split('=')[0])) {
|
||||||
|
console.log('removing', env)
|
||||||
|
newEnvironments = newEnvironments.filter(e => e !== env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newEnvironments.push(`WORDPRESS_DB_HOST=${mysqlHost}:${mysqlPort}`)
|
||||||
|
newEnvironments.push(`WORDPRESS_DB_USER=${mysqlUser}`)
|
||||||
|
newEnvironments.push(`WORDPRESS_DB_PASSWORD=${mysqlPassword}`)
|
||||||
|
newEnvironments.push(`WORDPRESS_DB_NAME=${mysqlDatabase}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config[s] = {
|
||||||
|
container_name: s,
|
||||||
|
build: template.services[s].build || undefined,
|
||||||
|
command: template.services[s].command,
|
||||||
|
entrypoint: template.services[s]?.entrypoint,
|
||||||
|
image: arm ? template.services[s].imageArm : template.services[s].image,
|
||||||
|
expose: template.services[s].ports,
|
||||||
...(exposePort ? { ports: [`${exposePort}:${exposePort}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${exposePort}`] } : {}),
|
||||||
volumes,
|
volumes,
|
||||||
environment: newEnvironments,
|
environment: newEnvironments,
|
||||||
depends_on: template.services[service]?.depends_on,
|
depends_on: template.services[s]?.depends_on,
|
||||||
ulimits: template.services[service]?.ulimits,
|
ulimits: template.services[s]?.ulimits,
|
||||||
cap_drop: template.services[service]?.cap_drop,
|
cap_drop: template.services[s]?.cap_drop,
|
||||||
cap_add: template.services[service]?.cap_add,
|
cap_add: template.services[s]?.cap_add,
|
||||||
labels: makeLabelForServices(type),
|
labels: makeLabelForServices(type),
|
||||||
...defaultComposeConfiguration(network),
|
...defaultComposeConfiguration(network),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate files for builds
|
// Generate files for builds
|
||||||
if (template.services[service]?.files?.length > 0) {
|
if (template.services[s]?.files?.length > 0) {
|
||||||
if (!template.services[service].build) {
|
if (!template.services[s].build) {
|
||||||
template.services[service].build = {
|
template.services[s].build = {
|
||||||
context: workdir,
|
context: workdir,
|
||||||
dockerfile: `Dockerfile.${service}`
|
dockerfile: `Dockerfile.${s}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let Dockerfile = `
|
let Dockerfile = `
|
||||||
FROM ${template.services[service].image}`
|
FROM ${template.services[s].image}`
|
||||||
for (const file of template.services[service].files) {
|
for (const file of template.services[s].files) {
|
||||||
const { source, destination, content } = file;
|
const { source, destination, content } = file;
|
||||||
await fs.writeFile(source, content);
|
await fs.writeFile(source, content);
|
||||||
Dockerfile += `
|
Dockerfile += `
|
||||||
COPY ./${path.basename(source)} ${destination}`
|
COPY ./${path.basename(source)} ${destination}`
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/Dockerfile.${service}`, Dockerfile);
|
await fs.writeFile(`${workdir}/Dockerfile.${servsice}`, Dockerfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
|
@@ -113,7 +113,7 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
}
|
}
|
||||||
export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) {
|
export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) {
|
||||||
const templates = await getTemplates()
|
const templates = await getTemplates()
|
||||||
const foundTemplate = templates.find(t => fixType(t.name) === service.type)
|
const foundTemplate = templates.find(t => fixType(t.type) === service.type)
|
||||||
let parsedTemplate = {}
|
let parsedTemplate = {}
|
||||||
if (foundTemplate) {
|
if (foundTemplate) {
|
||||||
if (!isDeploy) {
|
if (!isDeploy) {
|
||||||
@@ -130,16 +130,21 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
|
|||||||
for (const env of value.environment) {
|
for (const env of value.environment) {
|
||||||
const [envKey, envValue] = env.split('=')
|
const [envKey, envValue] = env.split('=')
|
||||||
const variable = foundTemplate.variables.find(v => v.name === envKey) || foundTemplate.variables.find(v => v.id === envValue)
|
const variable = foundTemplate.variables.find(v => v.name === envKey) || foundTemplate.variables.find(v => v.id === envValue)
|
||||||
|
const id = variable.id.replaceAll('$$', '')
|
||||||
const label = variable?.label
|
const label = variable?.label
|
||||||
const description = variable?.description
|
const description = variable?.description
|
||||||
const defaultValue = variable?.defaultValue
|
const defaultValue = variable?.defaultValue
|
||||||
const main = variable?.main || '$$id'
|
const main = variable?.main || '$$id'
|
||||||
if (envValue.startsWith('$$config') || variable?.showOnUI) {
|
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')) {
|
if (envValue.startsWith('$$config_coolify')) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
parsedTemplate[realKey].environment.push(
|
parsedTemplate[realKey].environment.push(
|
||||||
{ name: envKey, value: envValue, main, label, description, defaultValue }
|
{ id, name: envKey, value: envValue, main, label, description, defaultValue, type, placeholder, required, readonly }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,6 +208,9 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
|
|||||||
if (value) {
|
if (value) {
|
||||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value, 10) + "\"")
|
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value, 10) + "\"")
|
||||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, value + "\"")
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, value + "\"")
|
||||||
|
} else {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, "\"")
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, "\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +257,7 @@ export async function saveServiceType(request: FastifyRequest<SaveServiceType>,
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { type } = request.body;
|
const { type } = request.body;
|
||||||
const templates = await getTemplates()
|
const templates = await getTemplates()
|
||||||
let foundTemplate = templates.find(t => fixType(t.name) === fixType(type))
|
let foundTemplate = templates.find(t => fixType(t.type) === fixType(type))
|
||||||
if (foundTemplate) {
|
if (foundTemplate) {
|
||||||
foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
|
foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
|
||||||
if (foundTemplate.variables.length > 0) {
|
if (foundTemplate.variables.length > 0) {
|
||||||
@@ -307,6 +315,10 @@ export async function saveServiceType(request: FastifyRequest<SaveServiceType>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await prisma.service.update({ where: { id }, data: { type, version: foundTemplate.defaultVersion, templateVersion: foundTemplate.templateVersion } })
|
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()
|
return reply.code(201).send()
|
||||||
} else {
|
} else {
|
||||||
throw { status: 404, message: 'Service type not found.' }
|
throw { status: 404, message: 'Service type not found.' }
|
||||||
@@ -480,7 +492,7 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
|||||||
}
|
}
|
||||||
const templates = await getTemplates()
|
const templates = await getTemplates()
|
||||||
const service = await prisma.service.findUnique({ where: { id } })
|
const service = await prisma.service.findUnique({ where: { id } })
|
||||||
const foundTemplate = templates.find(t => fixType(t.name) === fixType(service.type))
|
const foundTemplate = templates.find(t => fixType(t.type) === fixType(service.type))
|
||||||
for (const setting of serviceSetting) {
|
for (const setting of serviceSetting) {
|
||||||
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting
|
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting
|
||||||
if (changed) {
|
if (changed) {
|
||||||
@@ -506,11 +518,19 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
|||||||
export async function getServiceSecrets(request: FastifyRequest<OnlyId>) {
|
export async function getServiceSecrets(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
let secrets = await prisma.serviceSecret.findMany({
|
let secrets = await prisma.serviceSecret.findMany({
|
||||||
where: { serviceId: id },
|
where: { serviceId: id },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
|
const templates = await getTemplates()
|
||||||
|
const foundTemplate = templates.find(t => fixType(t.type) === service.type)
|
||||||
secrets = secrets.map((secret) => {
|
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);
|
secret.value = decrypt(secret.value);
|
||||||
return secret;
|
return secret;
|
||||||
});
|
});
|
||||||
|
@@ -381,7 +381,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
} = service;
|
} = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const templates = await getTemplates();
|
const templates = await getTemplates();
|
||||||
let found = templates.find((a) => fixType(a.name) === fixType(type));
|
let found = templates.find((a) => a.type === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
||||||
for (const oneService of Object.keys(found.services)) {
|
for (const oneService of Object.keys(found.services)) {
|
||||||
@@ -509,7 +509,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
|
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
|
||||||
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}-secure`, nakedDomain, pathPrefix, 'https-to-http')
|
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix, 'https-to-http')
|
||||||
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
|
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
|
||||||
|
|
||||||
if (!dualCerts) {
|
if (!dualCerts) {
|
||||||
@@ -873,7 +873,7 @@ export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>
|
|||||||
} = service;
|
} = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const templates = await getTemplates();
|
const templates = await getTemplates();
|
||||||
let found = templates.find((a) => fixType(a.name) === fixType(type));
|
let found = templates.find((a) => a.type === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
const port = found.ports.main;
|
const port = found.ports.main;
|
||||||
const publicPort = service[type]?.publicPort;
|
const publicPort = service[type]?.publicPort;
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,7 +2,12 @@
|
|||||||
export let type: string;
|
export let type: string;
|
||||||
export let isAbsolute = true;
|
export let isAbsolute = true;
|
||||||
import * as Icons from '$lib/components/svg/services';
|
import * as Icons from '$lib/components/svg/services';
|
||||||
const name: any = type && type[0].toUpperCase() + type.substring(1).toLowerCase();
|
const name: any =
|
||||||
|
type &&
|
||||||
|
(type[0].toUpperCase() + type.substring(1).toLowerCase())
|
||||||
|
.replaceAll('.', '')
|
||||||
|
.replaceAll(' ', '')
|
||||||
|
.split('-')[0];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:component this={Icons[name.replace('.','').replaceAll(' ','')]} {isAbsolute} />
|
<svelte:component this={Icons[name]} {isAbsolute} />
|
||||||
|
@@ -41,7 +41,7 @@
|
|||||||
"saving": "Saving...",
|
"saving": "Saving...",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"action": "Action",
|
"action": "Actions",
|
||||||
"is_required": "is required.",
|
"is_required": "is required.",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"set": "Set",
|
"set": "Set",
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let name = '';
|
export let name = '';
|
||||||
export let value = '';
|
export let value = '';
|
||||||
|
export let readonly = false;
|
||||||
export let isNewSecret = false;
|
export let isNewSecret = false;
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
@@ -55,19 +56,20 @@
|
|||||||
bind:value={name}
|
bind:value={name}
|
||||||
required
|
required
|
||||||
placeholder="EXAMPLE_VARIABLE"
|
placeholder="EXAMPLE_VARIABLE"
|
||||||
readonly={!isNewSecret}
|
readonly={!isNewSecret || readonly}
|
||||||
class="w-full"
|
class="w-full"
|
||||||
class:bg-coolblack={!isNewSecret}
|
class:bg-coolblack={!isNewSecret}
|
||||||
class:border={!isNewSecret}
|
class:border={!isNewSecret}
|
||||||
class:border-dashed={!isNewSecret}
|
class:border-dashed={!isNewSecret}
|
||||||
class:border-coolgray-300={!isNewSecret}
|
class:border-coolgray-300={!isNewSecret}
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||||
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||||
|
disabled={readonly}
|
||||||
|
{readonly}
|
||||||
isPasswordField={true}
|
isPasswordField={true}
|
||||||
bind:value
|
bind:value
|
||||||
placeholder="J$#@UIO%HO#$U%H"
|
placeholder="J$#@UIO%HO#$U%H"
|
||||||
@@ -80,7 +82,7 @@
|
|||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="btn btn-sm btn-primary" on:click={() => saveSecret(true)}>Add</button>
|
<button class="btn btn-sm btn-primary" on:click={() => saveSecret(true)}>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else if !readonly}
|
||||||
<div class="flex flex-row justify-center space-x-2">
|
<div class="flex flex-row justify-center space-x-2">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="btn btn-sm btn-primary" on:click={() => saveSecret(false)}>Set</button>
|
<button class="btn btn-sm btn-primary" on:click={() => saveSecret(false)}>Set</button>
|
||||||
|
86
apps/ui/src/routes/services/[id]/_Services/wordpress.svelte
Normal file
86
apps/ui/src/routes/services/[id]/_Services/wordpress.svelte
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { post } from '$lib/api';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { status } from '$lib/store';
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import { browser } from '$app/env';
|
||||||
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
|
|
||||||
|
export let service: any;
|
||||||
|
const { id } = $page.params;
|
||||||
|
const settings = service.settings;
|
||||||
|
const { ipv4, ipv6 } = settings;
|
||||||
|
|
||||||
|
let ftpUrl = generateUrl(service.wordpress?.ftpPublicPort) || '';
|
||||||
|
let ftpUser = service.wordpress?.ftpUser;
|
||||||
|
let ftpPassword = service.wordpress?.ftpPassword;
|
||||||
|
let ftpLoading = false;
|
||||||
|
let ftpEnabled = service.wordpress?.ftpEnabled || false;
|
||||||
|
|
||||||
|
function generateUrl(publicPort: any) {
|
||||||
|
return browser
|
||||||
|
? `sftp://${settings?.fqdn ? getDomain(settings.fqdn) : ipv4 || ipv6}:${publicPort}`
|
||||||
|
: 'Loading...';
|
||||||
|
}
|
||||||
|
async function changeSettings(name: any) {
|
||||||
|
if (ftpLoading) return;
|
||||||
|
if ($status.service.overallStatus === 'healthy') {
|
||||||
|
ftpLoading = true;
|
||||||
|
if (name === 'ftpEnabled') {
|
||||||
|
ftpEnabled = !ftpEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
publicPort,
|
||||||
|
ftpUser: user,
|
||||||
|
ftpPassword: password
|
||||||
|
} = await post(`/services/${id}/wordpress/ftp`, {
|
||||||
|
ftpEnabled
|
||||||
|
});
|
||||||
|
ftpUrl = generateUrl(publicPort);
|
||||||
|
ftpUser = user;
|
||||||
|
ftpPassword = password;
|
||||||
|
service.wordpress.ftpEnabled = ftpEnabled;
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
ftpLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
id="ftpEnabled"
|
||||||
|
bind:setting={ftpEnabled}
|
||||||
|
loading={ftpLoading}
|
||||||
|
disabled={$status.service.overallStatus !== 'healthy'}
|
||||||
|
on:click={() => changeSettings('ftpEnabled')}
|
||||||
|
title="Enable sFTP connection to WordPress data"
|
||||||
|
description="Enables an on-demand sFTP connection to the WordPress data directory. This is useful if you want to use sFTP to upload files."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if service.wordpress?.ftpEnabled}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="ftpUrl">sFTP Connection URI</label>
|
||||||
|
<CopyPasswordField id="ftpUrl" readonly disabled name="ftpUrl" value={ftpUrl} />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="ftpUser">User</label>
|
||||||
|
<CopyPasswordField id="ftpUser" readonly disabled name="ftpUser" value={ftpUser} />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="ftpPassword">Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="ftpPassword"
|
||||||
|
isPasswordField
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="ftpPassword"
|
||||||
|
value={ftpPassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
async function handleSubmit(service: any) {
|
async function handleSubmit(service: any) {
|
||||||
try {
|
try {
|
||||||
await post(`/services/${id}/configuration/type`, { type: service.name });
|
await post(`/services/${id}/configuration/type`, { type: service.type });
|
||||||
return await goto(from || `/services/${id}`);
|
return await goto(from || `/services/${id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -62,6 +62,7 @@
|
|||||||
|
|
||||||
<div class="container lg:mx-auto lg:p-0 px-8 pt-5">
|
<div class="container lg:mx-auto lg:p-0 px-8 pt-5">
|
||||||
<div class="input-group flex w-full">
|
<div class="input-group flex w-full">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="btn btn-square cursor-default no-animation hover:bg-error" on:click={cleanupSearch}>
|
<div class="btn btn-square cursor-default no-animation hover:bg-error" on:click={cleanupSearch}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<form on:submit|preventDefault={() => handleSubmit(service)}>
|
<form on:submit|preventDefault={() => handleSubmit(service)}>
|
||||||
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-primary">
|
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-primary">
|
||||||
<ServiceIcons type={service.name} />
|
<ServiceIcons type={service.type} />
|
||||||
{service.name}
|
{service.name}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -35,6 +35,7 @@
|
|||||||
import ServiceStatus from '$lib/components/ServiceStatus.svelte';
|
import ServiceStatus from '$lib/components/ServiceStatus.svelte';
|
||||||
import { saveForm } from './utils';
|
import { saveForm } from './utils';
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
|
import Wordpress from './_Services/wordpress.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
exposePort: service.exposePort
|
exposePort: service.exposePort
|
||||||
});
|
});
|
||||||
for (const setting of service.serviceSetting) {
|
for (const setting of service.serviceSetting) {
|
||||||
if (setting.variableName.startsWith('$$coolify_fqdn') && setting.value) {
|
if (setting.variableName?.startsWith('$$coolify_fqdn') && setting.value) {
|
||||||
for (let field of formData) {
|
for (let field of formData) {
|
||||||
const [key, value] = field;
|
const [key, value] = field;
|
||||||
if (setting.name === key) {
|
if (setting.name === key) {
|
||||||
@@ -413,7 +414,6 @@
|
|||||||
{template[oneService].name ||
|
{template[oneService].name ||
|
||||||
oneService.replace(`${id}-`, '').replace(id, service.type)}
|
oneService.replace(`${id}-`, '').replace(id, service.type)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ServiceStatus id={oneService} />
|
<ServiceStatus id={oneService} />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-4">
|
<div class="grid grid-flow-row gap-2 px-4">
|
||||||
@@ -434,6 +434,8 @@
|
|||||||
name={variable.name}
|
name={variable.name}
|
||||||
id={variable.name}
|
id={variable.name}
|
||||||
value={service.fqdn}
|
value={service.fqdn}
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
/>
|
/>
|
||||||
{:else if variable.defaultValue === '$$generate_domain'}
|
{:else if variable.defaultValue === '$$generate_domain'}
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@@ -442,6 +444,8 @@
|
|||||||
name={variable.name}
|
name={variable.name}
|
||||||
id={variable.name}
|
id={variable.name}
|
||||||
value={getDomain(service.fqdn) || ''}
|
value={getDomain(service.fqdn) || ''}
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
/>
|
/>
|
||||||
{:else if variable.defaultValue === '$$generate_network'}
|
{:else if variable.defaultValue === '$$generate_network'}
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@@ -450,6 +454,8 @@
|
|||||||
name={variable.name}
|
name={variable.name}
|
||||||
id={variable.name}
|
id={variable.name}
|
||||||
value={service.destinationDocker.network}
|
value={service.destinationDocker.network}
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
/>
|
/>
|
||||||
{:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'}
|
{:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'}
|
||||||
{#if variable.value === 'true' || variable.value === 'false'}
|
{#if variable.value === 'true' || variable.value === 'false'}
|
||||||
@@ -461,6 +467,8 @@
|
|||||||
name={variable.name}
|
name={variable.name}
|
||||||
bind:value={variable.value}
|
bind:value={variable.value}
|
||||||
form="saveForm"
|
form="saveForm"
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
>
|
>
|
||||||
<option value="true">enabled</option>
|
<option value="true">enabled</option>
|
||||||
<option value="false">disabled</option>
|
<option value="false">disabled</option>
|
||||||
@@ -474,6 +482,8 @@
|
|||||||
name={variable.name}
|
name={variable.name}
|
||||||
bind:value={variable.defaultValue}
|
bind:value={variable.defaultValue}
|
||||||
form="saveForm"
|
form="saveForm"
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
>
|
>
|
||||||
<option value="true">true</option>
|
<option value="true">true</option>
|
||||||
<option value="false"> false</option>
|
<option value="false"> false</option>
|
||||||
@@ -487,21 +497,40 @@
|
|||||||
name={variable.name}
|
name={variable.name}
|
||||||
id={variable.name}
|
id={variable.name}
|
||||||
value={variable.value}
|
value={variable.value}
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
|
/>
|
||||||
|
{:else if variable.type === 'textarea'}
|
||||||
|
<textarea
|
||||||
|
class="w-full"
|
||||||
|
value={variable.value}
|
||||||
|
readonly={isDisabled}
|
||||||
|
disabled={isDisabled}
|
||||||
|
class:resize-none={$status.service.overallStatus === 'healthy'}
|
||||||
|
rows="5"
|
||||||
|
name={variable.name}
|
||||||
|
id={variable.name}
|
||||||
|
placeholder={variable.placeholder}
|
||||||
|
required={variable?.required}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
placeholder={variable.defaultValue || 'optional'}
|
isPasswordField={variable.id.startsWith('secret')}
|
||||||
required={variable?.required}
|
required={variable?.required}
|
||||||
readonly={isDisabled}
|
readonly={variable.readonly || isDisabled}
|
||||||
disabled={isDisabled}
|
disabled={variable.readonly || isDisabled}
|
||||||
name={variable.name}
|
name={variable.name}
|
||||||
id={variable.name}
|
id={variable.name}
|
||||||
value={variable.value}
|
value={variable.value}
|
||||||
|
placeholder={variable.placeholder}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if template[oneService].name.toLowerCase() === 'wordpress' && service.type.startsWith('wordpress')}
|
||||||
|
<Wordpress {service} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let secrets: any;
|
export let secrets: any;
|
||||||
export let service: any;
|
|
||||||
import Secret from './_Secret.svelte';
|
import Secret from './_Secret.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
@@ -84,7 +83,7 @@
|
|||||||
{#each secrets as secret}
|
{#each secrets as secret}
|
||||||
{#key secret.id}
|
{#key secret.id}
|
||||||
<tr>
|
<tr>
|
||||||
<Secret name={secret.name} value={secret.value} on:refresh={refreshSecrets} />
|
<Secret name={secret.name} value={secret.value} readonly={secret.readonly} on:refresh={refreshSecrets} />
|
||||||
</tr>
|
</tr>
|
||||||
{/key}
|
{/key}
|
||||||
{/each}
|
{/each}
|
||||||
|
@@ -43,9 +43,16 @@ export async function saveSecret({
|
|||||||
|
|
||||||
export async function saveForm(formData: any, service: any) {
|
export async function saveForm(formData: any, service: any) {
|
||||||
const settings = service.serviceSetting.map((setting: { name: string }) => setting.name);
|
const settings = service.serviceSetting.map((setting: { name: string }) => setting.name);
|
||||||
|
const secrets = service.serviceSecret.map((secret: { name: string }) => secret.name);
|
||||||
const baseCoolifySetting = ['name', 'fqdn', 'exposePort', 'version'];
|
const baseCoolifySetting = ['name', 'fqdn', 'exposePort', 'version'];
|
||||||
for (let field of formData) {
|
for (let field of formData) {
|
||||||
const [key, value] = field;
|
const [key, value] = field;
|
||||||
|
if (secrets.includes(key)) {
|
||||||
|
await post(`/services/${service.id}/secrets`, {
|
||||||
|
name: key,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
service.serviceSetting = service.serviceSetting.map((setting: any) => {
|
service.serviceSetting = service.serviceSetting.map((setting: any) => {
|
||||||
if (setting.name === key) {
|
if (setting.name === key) {
|
||||||
setting.changed = true;
|
setting.changed = true;
|
||||||
@@ -65,6 +72,8 @@ export async function saveForm(formData: any, service: any) {
|
|||||||
service[key] = value;
|
service[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
await post(`/services/${service.id}`, { ...service });
|
await post(`/services/${service.id}`, { ...service });
|
||||||
const { service: reloadedService } = await get(`/services/${service.id}`);
|
const { service: reloadedService } = await get(`/services/${service.id}`);
|
||||||
return reloadedService;
|
return reloadedService;
|
||||||
|
Reference in New Issue
Block a user