Merge branch 'feat/python' into main
This commit is contained in:
commit
cd9b642c5e
16
.devcontainer/Dockerfile
Normal file
16
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT="16-bullseye"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node modules
|
||||
RUN su node -c "npm install -g pnpm"
|
28
.devcontainer/devcontainer.json
Normal file
28
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,28 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node
|
||||
{
|
||||
"name": "Node.js",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use -bullseye variants on local arm64/Apple Silicon.
|
||||
"args": {
|
||||
"VARIANT": "16-bullseye"
|
||||
}
|
||||
},
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": ["dbaeumer.vscode-eslint", "svelte.svelte-vscode"],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [3000],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp .env.template .env && pnpm install && pnpm db:push && pnpm db:seed",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"features": {
|
||||
"docker-in-docker": "20.10",
|
||||
"github-cli": "latest"
|
||||
}
|
||||
}
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -3,3 +3,6 @@ contact_links:
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://discord.com/invite/6rDM4fkymF
|
||||
about: Reach out to us on discord or our github discussions page.
|
||||
- name: 🙋♂️ service request
|
||||
url: https://feedback.coolify.io/
|
||||
about: want to request a new service? for e.g wordpress, hasura, appwrite etc...
|
||||
|
@ -15,7 +15,13 @@ ## 🙋 Want to help?
|
||||
|
||||
## 👋 Introduction
|
||||
|
||||
🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS.
|
||||
### Setup with github codespaces
|
||||
|
||||
If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already.
|
||||
|
||||
### Setup locally in your machine
|
||||
|
||||
> 🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. 💡 Although windows users can use github codespaces for development
|
||||
|
||||
#### Recommended Pull Request Guideline
|
||||
|
||||
@ -35,20 +41,16 @@ # How to start after you set up your local fork?
|
||||
|
||||
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
||||
|
||||
#### Setup a local development environment
|
||||
#### Steps for local setup
|
||||
|
||||
- Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
- Install dependencies with `pnpm install`.
|
||||
- Need to create a local SQlite database with `pnpm db:push`.
|
||||
- This will apply all migrations at `db/dev.db`.
|
||||
- Seed the database with base entities with `pnpm db:seed`
|
||||
- You can start coding after starting `pnpm dev`.
|
||||
1. Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
2. Install dependencies with `pnpm install`.
|
||||
3. Need to create a local SQlite database with `pnpm db:push`.
|
||||
|
||||
#### How to start after you set up your local fork?
|
||||
This will apply all migrations at `db/dev.db`.
|
||||
|
||||
This repository works better with [pnpm](https://pnpm.io) due to the lock file. I recommend you to give it a try and use `pnpm` as well because it is cool and efficient!
|
||||
|
||||
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
||||
4. Seed the database with base entities with `pnpm db:seed`
|
||||
5. You can start coding after starting `pnpm dev`.
|
||||
|
||||
## 🧑💻 Developer contribution
|
||||
|
||||
|
@ -9,6 +9,7 @@ ## Live Demo
|
||||
(If it is unresponsive, that means someone overloaded the server. 🙃)
|
||||
|
||||
## Feedback
|
||||
|
||||
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
|
||||
## How to install
|
||||
@ -62,6 +63,7 @@ ### Applications
|
||||
- Rust
|
||||
- Docker
|
||||
- Python
|
||||
- Deno
|
||||
|
||||
### Databases
|
||||
|
||||
|
23
data/traefik/docker-compose-tcp.yaml
Normal file
23
data/traefik/docker-compose-tcp.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
${ID}:
|
||||
container_name: proxy-for-${PORT}
|
||||
image: traefik:v2.6
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --entrypoints.web.address=:${PORT}
|
||||
- --providers.docker=false
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.http.endpoint=http://host.docker.internal:3000/traefik.json?id=${ID}
|
||||
- --providers.http.pollTimeout=5s
|
||||
- --log.level=error
|
||||
ports:
|
||||
- '${PORT}:${PORT}'
|
||||
networks:
|
||||
- ${NETWORK}
|
||||
|
||||
networks:
|
||||
net:
|
||||
external: false
|
||||
name: ${NETWORK}
|
29
docker-compose-traefik.yaml
Normal file
29
docker-compose-traefik.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: traefik:v2.6
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --providers.docker=false
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.http.endpoint=http://host.docker.internal:3000/traefik.json
|
||||
- --providers.http.pollTimeout=5s
|
||||
- --log.level=error
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
networks:
|
||||
- coolify-infra
|
||||
|
||||
networks:
|
||||
coolify-infra:
|
||||
attachable: true
|
||||
name: coolify-infra
|
@ -39,3 +39,5 @@ volumes:
|
||||
name: coolify-ssl-certs
|
||||
coolify-letsencrypt:
|
||||
name: coolify-letsencrypt
|
||||
coolify-traefik-letsencrypt:
|
||||
name: coolify-traefik-letsencrypt
|
||||
|
13
package.json
13
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.8.2",
|
||||
"version": "2.9.8",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0",
|
||||
@ -30,10 +30,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "1.0.0-next.73",
|
||||
"@sveltejs/kit": "1.0.0-next.326",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.31",
|
||||
"@sveltejs/kit": "1.0.0-next.334",
|
||||
"@types/js-cookie": "3.0.2",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/node": "17.0.31",
|
||||
"@types/node": "17.0.34",
|
||||
"@types/node-forge": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||
"@typescript-eslint/parser": "4.31.1",
|
||||
@ -49,10 +50,10 @@
|
||||
"postcss": "8.4.13",
|
||||
"prettier": "2.6.2",
|
||||
"prettier-plugin-svelte": "2.7.0",
|
||||
"prettier-plugin-tailwindcss": "0.1.10",
|
||||
"prettier-plugin-tailwindcss": "0.1.11",
|
||||
"prisma": "3.11.1",
|
||||
"svelte": "3.48.0",
|
||||
"svelte-check": "2.7.0",
|
||||
"svelte-check": "2.7.1",
|
||||
"svelte-preprocess": "4.10.6",
|
||||
"svelte-select": "4.4.7",
|
||||
"sveltekit-i18n": "2.2.1",
|
||||
@ -67,7 +68,7 @@
|
||||
"@prisma/client": "3.11.1",
|
||||
"@sentry/node": "6.19.7",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bullmq": "1.81.4",
|
||||
"bullmq": "1.82.2",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.5.0",
|
||||
"cuid": "2.1.8",
|
||||
|
106
pnpm-lock.yaml
106
pnpm-lock.yaml
@ -5,17 +5,18 @@ specifiers:
|
||||
'@prisma/client': 3.11.1
|
||||
'@sentry/node': 6.19.7
|
||||
'@sveltejs/adapter-node': 1.0.0-next.73
|
||||
'@sveltejs/kit': 1.0.0-next.326
|
||||
'@sveltejs/adapter-static': 1.0.0-next.31
|
||||
'@sveltejs/kit': 1.0.0-next.334
|
||||
'@types/js-cookie': 3.0.2
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
'@types/node-forge': 1.0.2
|
||||
'@typescript-eslint/eslint-plugin': 4.31.1
|
||||
'@typescript-eslint/parser': 4.31.1
|
||||
'@zerodevx/svelte-toast': 0.7.1
|
||||
autoprefixer: 10.4.7
|
||||
bcryptjs: 2.4.3
|
||||
bullmq: 1.81.4
|
||||
bullmq: 1.82.2
|
||||
compare-versions: 4.1.3
|
||||
cookie: 0.5.0
|
||||
cross-env: 7.0.3
|
||||
@ -43,10 +44,10 @@ specifiers:
|
||||
postcss: 8.4.13
|
||||
prettier: 2.6.2
|
||||
prettier-plugin-svelte: 2.7.0
|
||||
prettier-plugin-tailwindcss: 0.1.10
|
||||
prettier-plugin-tailwindcss: 0.1.11
|
||||
prisma: 3.11.1
|
||||
svelte: 3.48.0
|
||||
svelte-check: 2.7.0
|
||||
svelte-check: 2.7.1
|
||||
svelte-kit-cookie-session: 2.1.4
|
||||
svelte-preprocess: 4.10.6
|
||||
svelte-select: 4.4.7
|
||||
@ -63,7 +64,7 @@ dependencies:
|
||||
'@prisma/client': 3.11.1_prisma@3.11.1
|
||||
'@sentry/node': 6.19.7
|
||||
bcryptjs: 2.4.3
|
||||
bullmq: 1.81.4
|
||||
bullmq: 1.82.2
|
||||
compare-versions: 4.1.3
|
||||
cookie: 0.5.0
|
||||
cuid: 2.1.8
|
||||
@ -87,10 +88,11 @@ dependencies:
|
||||
|
||||
devDependencies:
|
||||
'@sveltejs/adapter-node': 1.0.0-next.73
|
||||
'@sveltejs/kit': 1.0.0-next.326_svelte@3.48.0
|
||||
'@sveltejs/adapter-static': 1.0.0-next.31
|
||||
'@sveltejs/kit': 1.0.0-next.334_svelte@3.48.0
|
||||
'@types/js-cookie': 3.0.2
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
'@types/node-forge': 1.0.2
|
||||
'@typescript-eslint/eslint-plugin': 4.31.1_lii63oz3usekbu5ehvrcuwn5jy
|
||||
'@typescript-eslint/parser': 4.31.1_e4zyhrvfnqudwdx5bevnvkluy4
|
||||
@ -106,15 +108,15 @@ devDependencies:
|
||||
postcss: 8.4.13
|
||||
prettier: 2.6.2
|
||||
prettier-plugin-svelte: 2.7.0_kkjbqzpydplecjtkxrgomroeru
|
||||
prettier-plugin-tailwindcss: 0.1.10_prettier@2.6.2
|
||||
prettier-plugin-tailwindcss: 0.1.11_prettier@2.6.2
|
||||
prisma: 3.11.1
|
||||
svelte: 3.48.0
|
||||
svelte-check: 2.7.0_f2ke6qjyzu5axsjd6yk3u4tn7a
|
||||
svelte-check: 2.7.1_f2ke6qjyzu5axsjd6yk3u4tn7a
|
||||
svelte-preprocess: 4.10.6_nq4dx2skq5drra53vttuo4lltu
|
||||
svelte-select: 4.4.7
|
||||
sveltekit-i18n: 2.2.1_svelte@3.48.0
|
||||
tailwindcss: 3.0.24_ts-node@10.7.0
|
||||
ts-node: 10.7.0_l47be6km5p57gglrggidw5gsgm
|
||||
ts-node: 10.7.0_3smuweqyuzdazdnyhhezld6mfa
|
||||
tslib: 2.4.0
|
||||
typescript: 4.6.4
|
||||
|
||||
@ -214,6 +216,31 @@ packages:
|
||||
}
|
||||
dev: false
|
||||
|
||||
/@jridgewell/resolve-uri/3.0.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==
|
||||
}
|
||||
engines: { node: '>=6.0.0' }
|
||||
dev: true
|
||||
|
||||
/@jridgewell/sourcemap-codec/1.4.13:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping/0.3.13:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==
|
||||
}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.0.7
|
||||
'@jridgewell/sourcemap-codec': 1.4.13
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution:
|
||||
{
|
||||
@ -380,12 +407,21 @@ packages:
|
||||
tiny-glob: 0.2.9
|
||||
dev: true
|
||||
|
||||
/@sveltejs/kit/1.0.0-next.326_svelte@3.48.0:
|
||||
/@sveltejs/adapter-static/1.0.0-next.31:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-prJqmXZ2H1wmFfnMw7wDujfbkcA8vuubuqUkpVVmXhfh2+SEzQscPTNwxoE5EJxb5sywtLWEvYx3hv1gPS4Lvg==
|
||||
integrity: sha512-d9RNA/de5ljb+gN8mKA3YfmfJoTbYFdH96NYDD8u4Lu9O/ZnseUxOAcAmD4/LKbLXOY/oYhRpt029xT2owyI3Q==
|
||||
}
|
||||
engines: { node: '>=14.13' }
|
||||
dependencies:
|
||||
tiny-glob: 0.2.9
|
||||
dev: true
|
||||
|
||||
/@sveltejs/kit/1.0.0-next.334_svelte@3.48.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-HPMF1oYBfyOG6wfU0Y6F4SID8jphue9yF+PXJqVTDBL5Z2WBG2ogum6MavE8aWhq+g2H6w5y0jNT8+8DO2KTCA==
|
||||
}
|
||||
engines: { node: '>=16' }
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
svelte: ^3.44.0
|
||||
@ -495,7 +531,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/http-cache-semantics': 4.0.1
|
||||
'@types/keyv': 3.1.3
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
'@types/responselike': 1.0.0
|
||||
dev: false
|
||||
|
||||
@ -533,7 +569,7 @@ packages:
|
||||
integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==
|
||||
}
|
||||
dependencies:
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
dev: false
|
||||
|
||||
/@types/node-forge/1.0.2:
|
||||
@ -542,13 +578,13 @@ packages:
|
||||
integrity: sha512-J1OkeZGaW0y9Y7xD49Ja8O82B9l5nZDeoYuGWqIOYPAf9LR+xF23k9ILdzv8dH+2H033fx3D5oiA0GlmicI+sg==
|
||||
}
|
||||
dependencies:
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
dev: true
|
||||
|
||||
/@types/node/17.0.31:
|
||||
/@types/node/17.0.34:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==
|
||||
integrity: sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
|
||||
}
|
||||
|
||||
/@types/pug/2.0.5:
|
||||
@ -564,7 +600,7 @@ packages:
|
||||
integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
|
||||
}
|
||||
dependencies:
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
dev: false
|
||||
|
||||
/@types/sass/1.16.1:
|
||||
@ -573,7 +609,7 @@ packages:
|
||||
integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==
|
||||
}
|
||||
dependencies:
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/eslint-plugin/4.31.1_lii63oz3usekbu5ehvrcuwn5jy:
|
||||
@ -1689,10 +1725,10 @@ packages:
|
||||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/bullmq/1.81.4:
|
||||
/bullmq/1.82.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-sUEWOMKZnWlh1/XNqYAoSwXW6P8nZN7uJiHKZ8XlZCiIxWlEGjFtlugkkiCZ0lsTI2nNRHdxfpn78x9K3L1utQ==
|
||||
integrity: sha512-pDmMl6HmL/7B41ldBK4lnmGUcobkI/n/a0T3d/volMWC0ULxsaZ6R6fDePk23LwH9Fxu4o9Ny+zurCL3vG7lbg==
|
||||
}
|
||||
dependencies:
|
||||
cron-parser: 4.2.1
|
||||
@ -4150,7 +4186,7 @@ packages:
|
||||
dependencies:
|
||||
lilconfig: 2.0.5
|
||||
postcss: 8.4.13
|
||||
ts-node: 10.7.0_l47be6km5p57gglrggidw5gsgm
|
||||
ts-node: 10.7.0_3smuweqyuzdazdnyhhezld6mfa
|
||||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
@ -4218,10 +4254,10 @@ packages:
|
||||
svelte: 3.48.0
|
||||
dev: true
|
||||
|
||||
/prettier-plugin-tailwindcss/0.1.10_prettier@2.6.2:
|
||||
/prettier-plugin-tailwindcss/0.1.11_prettier@2.6.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ooDGNuXUjgCXfShliVYQ6+0iXqUFXn+zdNInPe0WZN9qINt9srbLGFGY5jeVL4MXtY20/4S8JaBcd8l6N6NfCQ==
|
||||
integrity: sha512-a28+1jvpIZQdZ/W97wOXb6VqI762MKE/TxMMuibMEHhyYsSxQA8Ek30KObd5kJI2HF1ldtSYprFayXJXi3pz8Q==
|
||||
}
|
||||
engines: { node: '>=12.17.0' }
|
||||
peerDependencies:
|
||||
@ -4708,14 +4744,6 @@ packages:
|
||||
engines: { node: '>=0.10.0' }
|
||||
dev: true
|
||||
|
||||
/source-map/0.7.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
}
|
||||
engines: { node: '>= 8' }
|
||||
dev: true
|
||||
|
||||
/sourcemap-codec/1.4.8:
|
||||
resolution:
|
||||
{
|
||||
@ -4888,21 +4916,21 @@ packages:
|
||||
engines: { node: '>= 0.4' }
|
||||
dev: true
|
||||
|
||||
/svelte-check/2.7.0_f2ke6qjyzu5axsjd6yk3u4tn7a:
|
||||
/svelte-check/2.7.1_f2ke6qjyzu5axsjd6yk3u4tn7a:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-GrvG24j0+i8AOm0k0KyJ6Dqc+TAR2yzB7rtS4nljHStunVxCTr/1KYlv4EsOeoqtHLzeWMOd5D2O6nDdP/yw4A==
|
||||
integrity: sha512-vHVu2+SQ6ibt77iTQaq2oiOjBgGL48qqcg0ZdEOsP5pPOjgeyR9QbnaEdzdBs9nsVYBc/42haKtzb2uFqS8GVw==
|
||||
}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
svelte: ^3.24.0
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.13
|
||||
chokidar: 3.5.3
|
||||
fast-glob: 3.2.11
|
||||
import-fresh: 3.3.0
|
||||
picocolors: 1.0.0
|
||||
sade: 1.7.4
|
||||
source-map: 0.7.3
|
||||
svelte: 3.48.0
|
||||
svelte-preprocess: 4.10.6_nq4dx2skq5drra53vttuo4lltu
|
||||
typescript: 4.6.4
|
||||
@ -5143,7 +5171,7 @@ packages:
|
||||
engines: { node: '>=0.10.0' }
|
||||
dev: true
|
||||
|
||||
/ts-node/10.7.0_l47be6km5p57gglrggidw5gsgm:
|
||||
/ts-node/10.7.0_3smuweqyuzdazdnyhhezld6mfa:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
|
||||
@ -5165,7 +5193,7 @@ packages:
|
||||
'@tsconfig/node12': 1.0.9
|
||||
'@tsconfig/node14': 1.0.1
|
||||
'@tsconfig/node16': 1.0.2
|
||||
'@types/node': 17.0.31
|
||||
'@types/node': 17.0.34
|
||||
acorn: 8.5.0
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
|
24
prisma/migrations/20220517081328_traefik/migration.sql
Normal file
24
prisma/migrations/20220517081328_traefik/migration.sql
Normal file
@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"proxyPassword" TEXT NOT NULL,
|
||||
"proxyUser" TEXT NOT NULL,
|
||||
"proxyHash" TEXT,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Minio" ADD COLUMN "apiFqdn" TEXT;
|
@ -20,6 +20,7 @@ model Setting {
|
||||
proxyHash String?
|
||||
isAutoUpdateEnabled Boolean @default(false)
|
||||
isDNSCheckEnabled Boolean @default(true)
|
||||
isTraefikUsed Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
@ -334,6 +335,7 @@ model Minio {
|
||||
rootUser String
|
||||
rootUserPassword String
|
||||
publicPort Int?
|
||||
apiFqdn String?
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
@ -5,6 +5,12 @@ const prisma = new PrismaClient();
|
||||
const crypto = require('crypto');
|
||||
const generator = require('generate-password');
|
||||
const cuid = require('cuid');
|
||||
const compare = require('compare-versions');
|
||||
const { version } = require('../package.json');
|
||||
const child = require('child_process');
|
||||
const util = require('util');
|
||||
|
||||
const algorithm = 'aes-256-ctr';
|
||||
|
||||
function generatePassword(length = 24) {
|
||||
return generator.generate({
|
||||
@ -13,7 +19,7 @@ function generatePassword(length = 24) {
|
||||
strict: true
|
||||
});
|
||||
}
|
||||
const algorithm = 'aes-256-ctr';
|
||||
const asyncExecShell = util.promisify(child.exec);
|
||||
|
||||
async function main() {
|
||||
// Enable registration for the first user
|
||||
@ -64,6 +70,56 @@ async function main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (settings.isTraefikUsed) {
|
||||
// Force stop Coolify Proxy, as it had a bug in < 2.9.2. TrustProxy + api.insecure
|
||||
try {
|
||||
const { stdout } = await asyncExecShell(
|
||||
`docker inspect coolify-proxy --format '{{json .Config.Cmd}}'`
|
||||
);
|
||||
if (
|
||||
!stdout
|
||||
.replaceAll('[', '')
|
||||
.replaceAll(']', '')
|
||||
.replaceAll('"', '')
|
||||
.replace('\n', '')
|
||||
.split(',')
|
||||
.includes('--entrypoints.web.forwardedHeaders.insecure=true')
|
||||
) {
|
||||
console.log('Reconfiguring Coolify Proxy (Traefik)...');
|
||||
await asyncExecShell(`docker stop -t 0 coolify-proxy && docker rm coolify-proxy`);
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
await asyncExecShell(
|
||||
`docker run --restart always \
|
||||
--add-host 'host.docker.internal:host-gateway' \
|
||||
--add-host 'host.docker.internal:${ip}' \
|
||||
-v coolify-traefik-letsencrypt:/etc/traefik/acme \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--network coolify-infra \
|
||||
-p "80:80" \
|
||||
-p "443:443" \
|
||||
--name coolify-proxy \
|
||||
-d traefik:v2.6 \
|
||||
--entrypoints.web.address=:80 \
|
||||
--entrypoints.web.forwardedHeaders.insecure=true \
|
||||
--entrypoints.websecure.address=:443 \
|
||||
--entrypoints.websecure.forwardedHeaders.insecure=true \
|
||||
--providers.docker=true \
|
||||
--providers.docker.exposedbydefault=false \
|
||||
--providers.http.endpoint=http://coolify:3000/webhooks/traefik/main.json \
|
||||
--providers.http.pollTimeout=5s \
|
||||
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
|
||||
--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
|
||||
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
|
||||
--log.level=error`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
main()
|
||||
.catch((e) => {
|
||||
|
@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Coolify</title>
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
|
@ -10,8 +10,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`);
|
||||
|
||||
|
@ -35,8 +35,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
|
@ -36,8 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
|
@ -35,8 +35,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
|
@ -96,12 +96,16 @@ export const getUserDetails = async (
|
||||
const userId = event?.locals?.session?.data?.userId || null;
|
||||
let permission = 'read';
|
||||
if (teamId && userId) {
|
||||
const data = await db.prisma.permission.findFirst({
|
||||
where: { teamId, userId },
|
||||
select: { permission: true },
|
||||
rejectOnNotFound: true
|
||||
});
|
||||
if (data.permission) permission = data.permission;
|
||||
try {
|
||||
const data = await db.prisma.permission.findFirst({
|
||||
where: { teamId, userId },
|
||||
select: { permission: true },
|
||||
rejectOnNotFound: true
|
||||
});
|
||||
if (data.permission) permission = data.permission;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
|
35
src/lib/components/PageLoader.svelte
Normal file
35
src/lib/components/PageLoader.svelte
Normal file
@ -0,0 +1,35 @@
|
||||
<script>
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { tweened } from 'svelte/motion';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
|
||||
let timeout;
|
||||
const progress = tweened(0, {
|
||||
duration: 2000,
|
||||
easing: cubicOut
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
timeout = setTimeout(() => {
|
||||
progress.set(0.7);
|
||||
}, 500);
|
||||
});
|
||||
onDestroy(() => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="progress-sliver" style={`--width: ${$progress * 100}%`} />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.progress-bar {
|
||||
height: 0.2rem;
|
||||
@apply fixed top-0 left-0 right-0;
|
||||
}
|
||||
.progress-sliver {
|
||||
width: var(--width);
|
||||
@apply h-full bg-coollabs;
|
||||
}
|
||||
</style>
|
@ -62,7 +62,7 @@ export const supportedDatabaseTypesAndVersions = [
|
||||
name: 'postgresql',
|
||||
fancyName: 'PostgreSQL',
|
||||
baseImage: 'bitnami/postgresql',
|
||||
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
|
||||
versions: ['14.2.0', '13.6.0', '12.10.0', '11.15.0', '10.20.0']
|
||||
},
|
||||
{
|
||||
name: 'redis',
|
||||
@ -219,6 +219,18 @@ export const supportedServiceTypesAndVersions = [
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
// },
|
||||
// {
|
||||
// name: 'appwrite',
|
||||
// fancyName: 'AppWrite',
|
||||
// baseImage: 'appwrite/appwrite',
|
||||
// images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'],
|
||||
// versions: ['latest', '0.13.0'],
|
||||
// recommendedVersion: '0.13.0',
|
||||
// ports: {
|
||||
// main: 3000
|
||||
// }
|
||||
// }
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -51,10 +51,12 @@ export async function isSecretExists({
|
||||
|
||||
export async function isDomainConfigured({
|
||||
id,
|
||||
fqdn
|
||||
fqdn,
|
||||
checkOwn = false
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
checkOwn?: boolean;
|
||||
}): Promise<boolean> {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace('www.', '');
|
||||
@ -72,12 +74,15 @@ export async function isDomainConfigured({
|
||||
where: {
|
||||
OR: [
|
||||
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } },
|
||||
{ minio: { apiFqdn: { endsWith: `//${nakedDomain}` } } },
|
||||
{ minio: { apiFqdn: { endsWith: `//www.${nakedDomain}` } } }
|
||||
],
|
||||
id: { not: id }
|
||||
id: { not: checkOwn ? undefined : id }
|
||||
},
|
||||
select: { fqdn: true }
|
||||
});
|
||||
|
||||
const coolifyFqdn = await prisma.setting.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
|
@ -305,6 +305,12 @@ export async function getFreePort() {
|
||||
select: { mysqlPublicPort: true }
|
||||
})
|
||||
).map((a) => a.mysqlPublicPort);
|
||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
|
||||
const minioUsed = await (
|
||||
await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
select: { publicPort: true }
|
||||
})
|
||||
).map((a) => a.publicPort);
|
||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
||||
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import { startCoolifyProxy } from '$lib/haproxy';
|
||||
import { startCoolifyProxy, startTraefikProxy } from '$lib/haproxy';
|
||||
import { getDatabaseImage } from '.';
|
||||
import { prisma } from './common';
|
||||
import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client';
|
||||
@ -125,7 +125,14 @@ export async function newLocalDestination({
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
||||
if (isCoolifyProxyUsed) {
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (settings?.isTraefikUsed) {
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
}
|
||||
return destination.id;
|
||||
}
|
||||
export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> {
|
||||
@ -133,12 +140,14 @@ export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>):
|
||||
if (destination.isCoolifyProxyUsed) {
|
||||
const host = getEngine(destination.engine);
|
||||
const { network } = destination;
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy';
|
||||
const { stdout: found } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'`
|
||||
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'`
|
||||
);
|
||||
if (found) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy`
|
||||
`DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}`
|
||||
);
|
||||
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
|
||||
}
|
||||
|
@ -67,16 +67,10 @@ export async function getSource({
|
||||
return body;
|
||||
}
|
||||
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization }) {
|
||||
await prisma.gitSource.update({
|
||||
return await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { type, name, htmlUrl, apiUrl, organization }
|
||||
});
|
||||
return await prisma.githubApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
gitSource: { connect: { id } }
|
||||
}
|
||||
});
|
||||
}
|
||||
export async function addGitLabSource({
|
||||
id,
|
||||
|
@ -18,7 +18,7 @@ const include: Prisma.ServiceInclude = {
|
||||
hasura: true,
|
||||
fider: true
|
||||
};
|
||||
export async function listServicesWithIncludes() {
|
||||
export async function listServicesWithIncludes(): Promise<Service[]> {
|
||||
return await prisma.service.findMany({
|
||||
include,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
@ -360,7 +360,24 @@ export async function updateService({
|
||||
}): Promise<Service> {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
|
||||
}
|
||||
|
||||
export async function updateMinioService({
|
||||
id,
|
||||
fqdn,
|
||||
apiFqdn,
|
||||
exposePort,
|
||||
name
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
apiFqdn: string;
|
||||
exposePort?: number;
|
||||
name: string;
|
||||
}): Promise<Service> {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, exposePort, minio: { update: { apiFqdn } } }
|
||||
});
|
||||
}
|
||||
export async function updateFiderService({
|
||||
id,
|
||||
fqdn,
|
||||
@ -418,7 +435,6 @@ export async function updateWordpress({
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
ownMysql,
|
||||
mysqlDatabase,
|
||||
extraConfig,
|
||||
mysqlHost,
|
||||
@ -459,7 +475,7 @@ export async function updateWordpress({
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateMinioService({
|
||||
export async function updateMinioServicePort({
|
||||
id,
|
||||
publicPort
|
||||
}: {
|
||||
|
@ -66,8 +66,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
|
||||
Dockerfile.push('RUN pnpm add -g pnpm');
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
if (installCommand) {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''}/package.json ./`);
|
||||
|
@ -3,12 +3,22 @@ import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import got, { type Got, type Response } from 'got';
|
||||
import * as db from '$lib/database';
|
||||
import type { DestinationDocker } from '@prisma/client';
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||
|
||||
export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
|
||||
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
||||
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
|
||||
export const defaultTraefikImage = `traefik:v2.6`;
|
||||
|
||||
const mainTraefikEndpoint = dev
|
||||
? 'http://host.docker.internal:3000/webhooks/traefik/main.json'
|
||||
: 'http://coolify:3000/webhooks/traefik/main.json';
|
||||
|
||||
const otherTraefikEndpoint = dev
|
||||
? 'http://host.docker.internal:3000/webhooks/traefik/other.json'
|
||||
: 'http://coolify:3000/webhooks/traefik/other.json';
|
||||
|
||||
export async function haproxyInstance(): Promise<Got> {
|
||||
const { proxyPassword } = await db.listSettings();
|
||||
@ -98,13 +108,21 @@ export async function checkHAProxy(haproxy?: Got): Promise<void> {
|
||||
}
|
||||
|
||||
export async function stopTcpHttpProxy(
|
||||
id: string,
|
||||
destinationDocker: DestinationDocker,
|
||||
publicPort: number
|
||||
publicPort: number,
|
||||
forceName: string = null
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
const containerName = `haproxy-for-${publicPort}`;
|
||||
const settings = await db.listSettings();
|
||||
let containerName = `${id}-${publicPort}`;
|
||||
if (!settings.isTraefikUsed) {
|
||||
containerName = `haproxy-for-${publicPort}`;
|
||||
}
|
||||
if (forceName) containerName = forceName;
|
||||
const found = await checkContainer(engine, containerName);
|
||||
|
||||
try {
|
||||
if (found) {
|
||||
return await asyncExecShell(
|
||||
@ -115,12 +133,77 @@ export async function stopTcpHttpProxy(
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function startTcpProxy(
|
||||
export async function startTraefikTCPProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
publicPort: number,
|
||||
privatePort: number,
|
||||
volume?: string
|
||||
type?: string
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
const containerName = `${id}-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName, true);
|
||||
let dependentId = id;
|
||||
if (type === 'wordpressftp') dependentId = `${id}-ftp`;
|
||||
const foundDependentContainer = await checkContainer(engine, dependentId, true);
|
||||
try {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
const tcpProxy = {
|
||||
version: '3.5',
|
||||
services: {
|
||||
[`${id}-${publicPort}`]: {
|
||||
container_name: containerName,
|
||||
image: 'traefik:v2.6',
|
||||
command: [
|
||||
`--entrypoints.tcp.address=:${publicPort}`,
|
||||
`--entryPoints.tcp.forwardedHeaders.insecure=true`,
|
||||
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp&address=${dependentId}`,
|
||||
'--providers.http.pollTimeout=2s',
|
||||
'--log.level=error'
|
||||
],
|
||||
ports: [`${publicPort}:${publicPort}`],
|
||||
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`],
|
||||
volumes: ['/var/run/docker.sock:/var/run/docker.sock'],
|
||||
networks: ['coolify-infra', network]
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: false,
|
||||
name: network
|
||||
},
|
||||
'coolify-infra': {
|
||||
external: false,
|
||||
name: 'coolify-infra'
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d`
|
||||
);
|
||||
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function startTcpProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
publicPort: number,
|
||||
privatePort: number
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
@ -128,7 +211,6 @@ export async function startTcpProxy(
|
||||
const containerName = `haproxy-for-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName, true);
|
||||
const foundDependentContainer = await checkContainer(engine, id, true);
|
||||
|
||||
try {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
@ -136,9 +218,7 @@ export async function startTcpProxy(
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} ${
|
||||
volume ? `-v ${volume}` : ''
|
||||
} -d coollabsio/${defaultProxyImageTcp}`
|
||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
|
||||
);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
@ -151,6 +231,76 @@ export async function startTcpProxy(
|
||||
}
|
||||
}
|
||||
|
||||
export async function startTraefikHTTPProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
publicPort: number,
|
||||
privatePort: number
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
|
||||
const containerName = `${id}-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName, true);
|
||||
const foundDependentContainer = await checkContainer(engine, id, true);
|
||||
|
||||
try {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
const tcpProxy = {
|
||||
version: '3.5',
|
||||
services: {
|
||||
[`${id}-${publicPort}`]: {
|
||||
container_name: containerName,
|
||||
image: 'traefik:v2.6',
|
||||
command: [
|
||||
`--entrypoints.http.address=:${publicPort}`,
|
||||
`--entryPoints.http.forwardedHeaders.insecure=true`,
|
||||
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`,
|
||||
'--providers.http.pollTimeout=2s',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge=true',
|
||||
'--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http',
|
||||
'--log.level=error'
|
||||
],
|
||||
ports: [`${publicPort}:${publicPort}`],
|
||||
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`],
|
||||
networks: ['coolify-infra', network],
|
||||
volumes: ['coolify-traefik-letsencrypt:/etc/traefik/acme']
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: false,
|
||||
name: network
|
||||
},
|
||||
'coolify-infra': {
|
||||
external: false,
|
||||
name: 'coolify-infra'
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
'coolify-traefik-letsencrypt': {}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d`
|
||||
);
|
||||
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function startHttpProxy(
|
||||
destinationDocker: DestinationDocker,
|
||||
id: string,
|
||||
@ -197,10 +347,50 @@ export async function startCoolifyProxy(engine: string): Promise<void> {
|
||||
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
|
||||
);
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
|
||||
}
|
||||
await configureNetworkCoolifyProxy(engine);
|
||||
}
|
||||
|
||||
export async function startTraefikProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(engine);
|
||||
const found = await checkContainer(engine, 'coolify-proxy', true);
|
||||
const { id, proxyPassword, proxyUser } = await db.listSettings();
|
||||
if (!found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker run --restart always \
|
||||
--add-host 'host.docker.internal:host-gateway' \
|
||||
--add-host 'host.docker.internal:${ip}' \
|
||||
-v coolify-traefik-letsencrypt:/etc/traefik/acme \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--network coolify-infra \
|
||||
-p "80:80" \
|
||||
-p "443:443" \
|
||||
--name coolify-proxy \
|
||||
-d ${defaultTraefikImage} \
|
||||
--entrypoints.web.address=:80 \
|
||||
--entrypoints.web.forwardedHeaders.insecure=true \
|
||||
--entrypoints.websecure.address=:443 \
|
||||
--entrypoints.websecure.forwardedHeaders.insecure=true \
|
||||
--providers.docker=true \
|
||||
--providers.docker.exposedbydefault=false \
|
||||
--providers.http.endpoint=${mainTraefikEndpoint} \
|
||||
--providers.http.pollTimeout=5s \
|
||||
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
|
||||
--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
|
||||
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
|
||||
--log.level=error`
|
||||
);
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
|
||||
}
|
||||
await configureNetworkTraefikProxy(engine);
|
||||
}
|
||||
|
||||
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
|
||||
let isExited = false;
|
||||
const host = getEngine(engine);
|
||||
@ -245,6 +435,21 @@ export async function checkContainer(
|
||||
return containerFound;
|
||||
}
|
||||
|
||||
export async function getContainerUsage(engine: string, container: string): Promise<any> {
|
||||
const host = getEngine(engine);
|
||||
try {
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker container stats ${container} --no-stream --no-trunc --format "{{json .}}"`
|
||||
);
|
||||
return JSON.parse(stdout);
|
||||
} catch (err) {
|
||||
return {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
export async function stopCoolifyProxy(
|
||||
engine: string
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
@ -263,6 +468,24 @@ export async function stopCoolifyProxy(
|
||||
return error;
|
||||
}
|
||||
}
|
||||
export async function stopTraefikProxy(
|
||||
engine: string
|
||||
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||
const host = getEngine(engine);
|
||||
const found = await checkContainer(engine, 'coolify-proxy');
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
|
||||
const { id } = await db.prisma.setting.findFirst({});
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
|
||||
try {
|
||||
if (found) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker stop -t 0 coolify-proxy && docker rm coolify-proxy`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(engine);
|
||||
@ -279,3 +502,19 @@ export async function configureNetworkCoolifyProxy(engine: string): Promise<void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureNetworkTraefikProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(engine);
|
||||
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const { stdout: networks } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'`
|
||||
);
|
||||
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
|
||||
for (const destination of destinations) {
|
||||
if (!configuredNetworks.includes(destination.network)) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-proxy`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler, prisma } from '$lib/database';
|
||||
import { configureHAProxy } from '$lib/haproxy/configuration';
|
||||
|
||||
export default async function (): Promise<void | {
|
||||
@ -6,7 +6,10 @@ export default async function (): Promise<void | {
|
||||
body: { message: string; error: string };
|
||||
}> {
|
||||
try {
|
||||
return await configureHAProxy();
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (!settings.isTraefikUsed) {
|
||||
return await configureHAProxy();
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error.response?.body || error);
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
|
||||
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy';
|
||||
import {
|
||||
checkContainer,
|
||||
startCoolifyProxy,
|
||||
startHttpProxy,
|
||||
startTcpProxy,
|
||||
startTraefikHTTPProxy,
|
||||
startTraefikProxy,
|
||||
startTraefikTCPProxy,
|
||||
stopCoolifyProxy,
|
||||
stopTcpHttpProxy,
|
||||
stopTraefikProxy
|
||||
} from '$lib/haproxy';
|
||||
|
||||
export default async function (): Promise<void | {
|
||||
status: number;
|
||||
@ -7,12 +18,23 @@ export default async function (): Promise<void | {
|
||||
}> {
|
||||
try {
|
||||
// Coolify Proxy
|
||||
const engine = '/var/run/docker.sock';
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine: '/var/run/docker.sock' }
|
||||
where: { engine, network: 'coolify' }
|
||||
});
|
||||
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||
await startCoolifyProxy('/var/run/docker.sock');
|
||||
if (settings.isTraefikUsed) {
|
||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||
if (found) await stopCoolifyProxy(engine);
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
const found = await checkContainer(engine, 'coolify-proxy');
|
||||
if (found) await stopTraefikProxy(engine);
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
}
|
||||
|
||||
// TCP Proxies
|
||||
const databasesWithPublicPort = await prisma.database.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
@ -21,8 +43,16 @@ export default async function (): Promise<void | {
|
||||
for (const database of databasesWithPublicPort) {
|
||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||
if (destinationDockerId) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
if (destinationDocker.isCoolifyProxyUsed) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
if (settings.isTraefikUsed) {
|
||||
await stopTcpHttpProxy(id, destinationDocker, publicPort, `haproxy-for-${publicPort}`);
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
} else {
|
||||
await stopTcpHttpProxy(id, destinationDocker, publicPort, `${id}-${publicPort}`);
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||
@ -33,20 +63,38 @@ export default async function (): Promise<void | {
|
||||
const { service, ftpPublicPort } = ftp;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||
if (destinationDocker.isCoolifyProxyUsed) {
|
||||
if (settings.isTraefikUsed) {
|
||||
await stopTcpHttpProxy(
|
||||
id,
|
||||
destinationDocker,
|
||||
ftpPublicPort,
|
||||
`haproxy-for-${ftpPublicPort}`
|
||||
);
|
||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||
} else {
|
||||
await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort, `${id}-${ftpPublicPort}`);
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Proxies
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||
if (!settings.isTraefikUsed) {
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
if (destinationDocker.isCoolifyProxyUsed) {
|
||||
await stopTcpHttpProxy(id, destinationDocker, publicPort, `${id}-${publicPort}`);
|
||||
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { generateSSLCerts } from '$lib/letsencrypt';
|
||||
import { prisma } from '$lib/database';
|
||||
|
||||
export default async function (): Promise<void> {
|
||||
try {
|
||||
return await generateSSLCerts();
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (!settings.isTraefikUsed) {
|
||||
return await generateSSLCerts();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { renewSSLCerts } from '$lib/letsencrypt';
|
||||
import { prisma } from '$lib/database';
|
||||
|
||||
export default async function (): Promise<void> {
|
||||
try {
|
||||
return await renewSSLCerts();
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (!settings.isTraefikUsed) {
|
||||
return await renewSSLCerts();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
|
3
src/lib/realtime.ts
Normal file
3
src/lib/realtime.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// import ioClient from 'socket.io-client';
|
||||
// const socket = ioClient('http://localhost:3000');
|
||||
// export const io = socket;
|
@ -12,3 +12,14 @@ export const features: Readable<{ latestVersion: string; beta: boolean }> = read
|
||||
beta: browser && window.localStorage.getItem('beta') === 'true',
|
||||
latestVersion: browser && window.localStorage.getItem('latestVersion')
|
||||
});
|
||||
|
||||
export const isTraefikUsed: Writable<boolean> = writable(false);
|
||||
|
||||
export const status: Writable<any> = writable({
|
||||
application: {
|
||||
isRunning: false,
|
||||
isExited: false,
|
||||
loading: false,
|
||||
initialLoading: true
|
||||
}
|
||||
});
|
||||
|
@ -34,23 +34,30 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let settings;
|
||||
import '../tailwind.css';
|
||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast';
|
||||
import { page, session } from '$app/stores';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { onMount } from 'svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { asyncSleep } from '$lib/components/common';
|
||||
import { del, get, post } from '$lib/api';
|
||||
import { dev } from '$app/env';
|
||||
import { features } from '$lib/store';
|
||||
let isUpdateAvailable = false;
|
||||
import { features, isTraefikUsed } from '$lib/store';
|
||||
import { navigating } from '$app/stores';
|
||||
import PageLoader from '$lib/components/PageLoader.svelte';
|
||||
|
||||
$isTraefikUsed = settings?.isTraefikUsed || false;
|
||||
|
||||
let isUpdateAvailable = false;
|
||||
let updateStatus = {
|
||||
found: false,
|
||||
loading: false,
|
||||
success: null
|
||||
};
|
||||
let latestVersion = 'latest';
|
||||
|
||||
onMount(async () => {
|
||||
if ($session.userId) {
|
||||
const overrideVersion = $features.latestVersion;
|
||||
@ -129,9 +136,16 @@
|
||||
<title>Coolify</title>
|
||||
{#if !$session.whiteLabeled}
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
{:else if $session.whiteLabelDetails.icon}
|
||||
<link rel="icon" href={$session.whiteLabelDetails.icon} />
|
||||
{/if}
|
||||
</svelte:head>
|
||||
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
||||
{#if $navigating}
|
||||
<div out:fade={{ delay: 100 }}>
|
||||
<PageLoader />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $session.userId}
|
||||
<nav class="nav-main">
|
||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||
|
@ -17,7 +17,7 @@
|
||||
const endpoint = `/applications/${params.id}.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
let { application, isRunning, isExited, appId, githubToken, gitlabToken } = await res.json();
|
||||
let { application, appId, githubToken, gitlabToken } = await res.json();
|
||||
if (!application || Object.entries(application).length === 0) {
|
||||
return {
|
||||
status: 302,
|
||||
@ -45,13 +45,10 @@
|
||||
return {
|
||||
props: {
|
||||
application,
|
||||
isRunning,
|
||||
isExited,
|
||||
githubToken,
|
||||
gitlabToken
|
||||
},
|
||||
stuff: {
|
||||
isRunning,
|
||||
application,
|
||||
appId
|
||||
}
|
||||
@ -67,8 +64,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let isRunning;
|
||||
export let isExited;
|
||||
export let githubToken;
|
||||
export let gitlabToken;
|
||||
import { page, session } from '$app/stores';
|
||||
@ -77,7 +72,7 @@
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { del, get, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { gitTokens, status } from '$lib/store';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { disabledButton } from '$lib/store';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
@ -135,17 +130,31 @@
|
||||
}
|
||||
}
|
||||
async function getStatus() {
|
||||
statusInterval = setInterval(async () => {
|
||||
const data = await get(`/applications/${id}.json`);
|
||||
isRunning = data.isRunning;
|
||||
isExited = data.isExited;
|
||||
}, 1000);
|
||||
if ($status.application.loading) return;
|
||||
$status.application.loading = true;
|
||||
const data = await get(`/applications/${id}/status.json`);
|
||||
$status.application.isRunning = data.isRunning;
|
||||
$status.application.isExited = data.isExited;
|
||||
$status.application.loading = false;
|
||||
$status.application.initialLoading = false;
|
||||
}
|
||||
onDestroy(() => {
|
||||
$status.application.initialLoading = true;
|
||||
clearInterval(statusInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
await getStatus();
|
||||
if (!application.gitSourceId || !application.destinationDockerId || !application.fqdn) {
|
||||
$status.application.initialLoading = false;
|
||||
$status.application.isRunning = false;
|
||||
$status.application.isExited = false;
|
||||
$status.application.loading = false;
|
||||
return;
|
||||
} else {
|
||||
await getStatus();
|
||||
statusInterval = setInterval(async () => {
|
||||
await getStatus();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -153,16 +162,16 @@
|
||||
{#if loading}
|
||||
<Loading fullscreen cover />
|
||||
{:else}
|
||||
{#if isExited}
|
||||
{#if $status.application.isExited}
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
|
||||
class=" icons tooltip-bottom tooltip-red-500 flex items-center bg-transparent text-sm text-red-500"
|
||||
data-tooltip="Application exited with an error!"
|
||||
sveltekit:prefetch
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentcolor"
|
||||
@ -179,20 +188,43 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if isRunning}
|
||||
{#if $status.application.initialLoading}
|
||||
<button
|
||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
|
||||
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
|
||||
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
|
||||
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
|
||||
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
|
||||
<line x1="11" y1="19.94" x2="11" y2="19.95" />
|
||||
</svg>
|
||||
</button>
|
||||
{:else if $status.application.isRunning}
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
title="Stop application"
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
||||
class="icons tooltip-bottom flex items-center space-x-2 bg-transparent text-sm text-red-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? $t('application.stop_application')
|
||||
: $t('application.permission_denied_stop_application')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -210,14 +242,14 @@
|
||||
title="Rebuild application"
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:text-green-500"
|
||||
class="icons tooltip-bottom flex items-center space-x-2 bg-transparent text-sm hover:text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Rebuild application'
|
||||
: 'You do not have permission to rebuild application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -239,14 +271,14 @@
|
||||
title="Build and start application"
|
||||
type="submit"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
||||
class="icons tooltip-bottom flex items-center space-x-2 bg-transparent text-sm text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Build and start application'
|
||||
: 'You do not have permission to Build and start application.'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -261,18 +293,18 @@
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<div class="h-8 border border-coolgray-500" />
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-yellow-500 rounded"
|
||||
class="rounded hover:text-yellow-500"
|
||||
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
||||
>
|
||||
<button
|
||||
title="Configurations"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Configurations"
|
||||
>
|
||||
<svg
|
||||
@ -301,19 +333,19 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/secrets` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class="rounded hover:text-pink-500"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||
>
|
||||
<button
|
||||
title="Secret"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Secret"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -333,19 +365,19 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/storage` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class="rounded hover:text-pink-500"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
>
|
||||
<button
|
||||
title="Persistent Storage"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Persistent Storage"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -363,19 +395,19 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-orange-500 rounded"
|
||||
class="rounded hover:text-orange-500"
|
||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||
>
|
||||
<button
|
||||
title="Previews"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Previews"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@ -392,18 +424,18 @@
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<div class="h-8 border border-coolgray-500" />
|
||||
<a
|
||||
href={!$disabledButton && isRunning ? `/applications/${id}/logs` : null}
|
||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-sky-500 rounded"
|
||||
class="rounded hover:text-sky-500"
|
||||
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||
>
|
||||
<button
|
||||
title={$t('application.logs')}
|
||||
disabled={$disabledButton || !isRunning}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
disabled={$disabledButton || !$status.application.isRunning}
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip={$t('application.logs')}
|
||||
>
|
||||
<svg
|
||||
@ -428,14 +460,14 @@
|
||||
<a
|
||||
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-red-500 rounded"
|
||||
class="rounded hover:text-red-500"
|
||||
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||
>
|
||||
<button
|
||||
title="Build Logs"
|
||||
disabled={$disabledButton}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip="Build Logs"
|
||||
>
|
||||
<svg
|
||||
@ -460,7 +492,7 @@
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<div class="border border-coolgray-500 h-8" />
|
||||
<div class="h-8 border border-coolgray-500" />
|
||||
|
||||
<button
|
||||
on:click={() => deleteApplication(application.name)}
|
||||
@ -468,7 +500,7 @@
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class:hover:text-red-500={$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm"
|
||||
class="icons tooltip-bottom bg-transparent text-sm"
|
||||
data-tooltip={$session.isAdmin
|
||||
? $t('application.delete_application')
|
||||
: $t('application.permission_denied_delete_application')}
|
||||
|
@ -52,7 +52,7 @@ export const post: RequestHandler = async (event) => {
|
||||
exposePort = Number(exposePort);
|
||||
|
||||
if (exposePort < 1024 || exposePort > 65535) {
|
||||
throw { message: `Expose Port needs to be between 1024 and 65535.` };
|
||||
throw { message: `Exposed Port needs to be between 1024 and 65535.` };
|
||||
}
|
||||
|
||||
const publicPort = await getPort({ port: exposePort });
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let appId;
|
||||
import Select from 'svelte-select';
|
||||
import { page, session } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
@ -33,6 +34,10 @@
|
||||
let showSave = false;
|
||||
let autodeploy = application.settings.autodeploy || true;
|
||||
|
||||
let search = {
|
||||
project: '',
|
||||
branch: ''
|
||||
};
|
||||
let selected = {
|
||||
group: undefined,
|
||||
project: undefined,
|
||||
@ -84,16 +89,49 @@
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function selectGroup(event) {
|
||||
selected.group = event.detail;
|
||||
selected.project = null;
|
||||
selected.branch = null;
|
||||
showSave = false;
|
||||
loadProjects();
|
||||
}
|
||||
|
||||
async function searchProjects(searchText) {
|
||||
if (!selected.group) {
|
||||
return;
|
||||
}
|
||||
|
||||
search.project = searchText;
|
||||
await loadProjects();
|
||||
return projects;
|
||||
}
|
||||
|
||||
function selectProject(event) {
|
||||
selected.project = event.detail;
|
||||
selected.branch = null;
|
||||
showSave = false;
|
||||
loadBranches();
|
||||
}
|
||||
|
||||
async function loadProjects() {
|
||||
const params = new URLSearchParams({
|
||||
page: 1,
|
||||
per_page: 25,
|
||||
archived: false
|
||||
});
|
||||
|
||||
if (search.project) {
|
||||
params.append('search', search.project);
|
||||
}
|
||||
|
||||
loading.projects = true;
|
||||
if (username === selected.group.name) {
|
||||
try {
|
||||
projects = await get(
|
||||
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
params.append('min_access_level', 40);
|
||||
projects = await get(`${apiUrl}/v4/users/${selected.group.name}/projects?${params}`, {
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
@ -102,12 +140,9 @@
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
projects = await get(
|
||||
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
projects = await get(`${apiUrl}/v4/groups/${selected.group.id}/projects?${params}`, {
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
throw new Error(error);
|
||||
@ -117,11 +152,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function searchBranches(searchText) {
|
||||
if (!selected.project) {
|
||||
return;
|
||||
}
|
||||
|
||||
search.branch = searchText;
|
||||
await loadBranches();
|
||||
return branches;
|
||||
}
|
||||
|
||||
function selectBranch(event) {
|
||||
selected.branch = event.detail;
|
||||
isBranchAlreadyUsed();
|
||||
}
|
||||
|
||||
async function loadBranches() {
|
||||
const params = new URLSearchParams({
|
||||
page: 1,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
if (search.branch) {
|
||||
params.append('search', search.branch);
|
||||
}
|
||||
|
||||
loading.branches = true;
|
||||
try {
|
||||
branches = await get(
|
||||
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`,
|
||||
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?${params}`,
|
||||
{
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
@ -267,70 +326,79 @@
|
||||
|
||||
<form on:submit={handleSubmit}>
|
||||
<div class="flex flex-col space-y-2 px-4 xl:flex-row xl:space-y-0 xl:space-x-2 ">
|
||||
{#if loading.base}
|
||||
<select name="group" disabled class="w-96">
|
||||
<option selected value="">{$t('application.configuration.loading_groups')}</option>
|
||||
</select>
|
||||
{:else}
|
||||
<select name="group" class="w-96" bind:value={selected.group} on:change={loadProjects}>
|
||||
<option value="" disabled selected>{$t('application.configuration.select_a_group')}</option>
|
||||
{#each groups as group}
|
||||
<option value={group}>{group.full_name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
{#if loading.projects}
|
||||
<select name="project" disabled class="w-96">
|
||||
<option selected value="">{$t('application.configuration.loading_projects')}</option>
|
||||
</select>
|
||||
{:else if !loading.projects && projects.length > 0}
|
||||
<select
|
||||
name="project"
|
||||
class="w-96"
|
||||
bind:value={selected.project}
|
||||
on:change={loadBranches}
|
||||
disabled={!selected.group}
|
||||
>
|
||||
<option value="" disabled selected
|
||||
>{$t('application.configuration.select_a_project')}</option
|
||||
>
|
||||
{#each projects as project}
|
||||
<option value={project}>{project.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<select name="project" disabled class="w-96">
|
||||
<option disabled selected value=""
|
||||
>{$t('application.configuration.no_projects_found')}</option
|
||||
>
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
{#if loading.branches}
|
||||
<select name="branch" disabled class="w-96">
|
||||
<option selected value="">{$t('application.configuration.loading_branches')}</option>
|
||||
</select>
|
||||
{:else if !loading.branches && branches.length > 0}
|
||||
<select
|
||||
name="branch"
|
||||
class="w-96"
|
||||
bind:value={selected.branch}
|
||||
on:change={isBranchAlreadyUsed}
|
||||
disabled={!selected.project}
|
||||
>
|
||||
<option value="" disabled selected>{$t('application.configuration.select_a_branch')}</option
|
||||
>
|
||||
{#each branches as branch}
|
||||
<option value={branch}>{branch.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<select name="project" disabled class="w-96">
|
||||
<option disabled selected value=""
|
||||
>{$t('application.configuration.no_branches_found')}</option
|
||||
>
|
||||
</select>
|
||||
{/if}
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.base
|
||||
? $t('application.configuration.loading_groups')
|
||||
: $t('application.configuration.select_a_group')}
|
||||
id="group"
|
||||
showIndicator={!loading.base}
|
||||
isWaiting={loading.base}
|
||||
on:select={selectGroup}
|
||||
on:clear={() => {
|
||||
showSave = false;
|
||||
projects = [];
|
||||
branches = [];
|
||||
selected.group = null;
|
||||
selected.project = null;
|
||||
selected.branch = null;
|
||||
}}
|
||||
value={selected.group}
|
||||
isDisabled={loading.base}
|
||||
isClearable={false}
|
||||
items={groups}
|
||||
labelIdentifier="full_name"
|
||||
optionIdentifier="id"
|
||||
/>
|
||||
</div>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.projects
|
||||
? $t('application.configuration.loading_projects')
|
||||
: $t('application.configuration.select_a_project')}
|
||||
noOptionsMessage={$t('application.configuration.no_projects_found')}
|
||||
id="project"
|
||||
showIndicator={!loading.projects}
|
||||
isWaiting={loading.projects}
|
||||
isDisabled={loading.projects || !selected.group}
|
||||
on:select={selectProject}
|
||||
on:clear={() => {
|
||||
showSave = false;
|
||||
branches = [];
|
||||
selected.project = null;
|
||||
selected.branch = null;
|
||||
}}
|
||||
value={selected.project}
|
||||
isClearable={false}
|
||||
items={projects}
|
||||
loadOptions={searchProjects}
|
||||
labelIdentifier="name"
|
||||
optionIdentifier="id"
|
||||
/>
|
||||
</div>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
placeholder={loading.branches
|
||||
? $t('application.configuration.loading_branches')
|
||||
: $t('application.configuration.select_a_branch')}
|
||||
noOptionsMessage={$t('application.configuration.no_branches_found')}
|
||||
id="branch"
|
||||
showIndicator={!loading.branches}
|
||||
isWaiting={loading.branches}
|
||||
isDisabled={loading.branches || !selected.project}
|
||||
on:select={selectBranch}
|
||||
on:clear={() => {
|
||||
showSave = false;
|
||||
selected.branch = null;
|
||||
}}
|
||||
value={selected.branch}
|
||||
isClearable={false}
|
||||
items={branches}
|
||||
loadOptions={searchBranches}
|
||||
labelIdentifier="name"
|
||||
optionIdentifier="web_url"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center space-y-4 pt-5">
|
||||
<button
|
||||
|
@ -19,10 +19,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/translations';
|
||||
import { gitTokens } from '$lib/store';
|
||||
|
||||
export let application;
|
||||
export let appId;
|
||||
|
||||
$gitTokens.githubToken = null;
|
||||
|
||||
import GithubRepositories from './_GithubRepositories.svelte';
|
||||
import GitlabRepositories from './_GitlabRepositories.svelte';
|
||||
</script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { asyncExecShell, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer, isContainerExited } from '$lib/haproxy';
|
||||
import { checkContainer, getContainerUsage, isContainerExited } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { setDefaultConfiguration } from '$lib/buildPacks/common';
|
||||
|
||||
@ -12,21 +12,14 @@ export const get: RequestHandler = async (event) => {
|
||||
const { id } = event.params;
|
||||
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let githubToken = event.locals.cookies?.githubToken || null;
|
||||
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
||||
isExited = await isContainerExited(application.destinationDocker.engine, id);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
isRunning,
|
||||
isExited,
|
||||
application,
|
||||
appId,
|
||||
githubToken,
|
||||
|
@ -4,8 +4,7 @@
|
||||
if (stuff?.application?.id) {
|
||||
return {
|
||||
props: {
|
||||
application: stuff.application,
|
||||
isRunning: stuff.isRunning
|
||||
application: stuff.application
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -34,10 +33,9 @@
|
||||
baseImages: Array<{ value: string; label: string }>;
|
||||
baseBuildImages: Array<{ value: string; label: string }>;
|
||||
};
|
||||
export let isRunning;
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
@ -47,11 +45,20 @@
|
||||
import { get, post } from '$lib/api';
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
import { disabledButton } from '$lib/store';
|
||||
import { disabledButton, status } from '$lib/store';
|
||||
import { t } from '$lib/translations';
|
||||
const { id } = $page.params;
|
||||
let domainEl: HTMLInputElement;
|
||||
let loading = false;
|
||||
|
||||
let usageLoading = false;
|
||||
let usage = {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
let usageInterval;
|
||||
|
||||
let forceSave = false;
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
@ -60,6 +67,9 @@
|
||||
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
let isNonWWWDomainOK = false;
|
||||
let isWWWDomainOK = false;
|
||||
|
||||
$: isDisabled = !$session.isAdmin || $status.application.isRunning;
|
||||
|
||||
let wsgis = [
|
||||
{
|
||||
value: 'None',
|
||||
@ -75,15 +85,29 @@
|
||||
}
|
||||
];
|
||||
function containerClass() {
|
||||
if (!$session.isAdmin || isRunning) {
|
||||
return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0';
|
||||
return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0';
|
||||
}
|
||||
|
||||
async function getUsage() {
|
||||
if (usageLoading) return;
|
||||
usageLoading = true;
|
||||
const data = await get(`/applications/${id}/usage.json`);
|
||||
usage = data.usage;
|
||||
usageLoading = false;
|
||||
}
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
await handleSubmit();
|
||||
}
|
||||
}
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
onMount(() => {
|
||||
domainEl.focus();
|
||||
await getUsage();
|
||||
usageInterval = setInterval(async () => {
|
||||
await getUsage();
|
||||
}, 1000);
|
||||
});
|
||||
async function changeSettings(name) {
|
||||
if (name === 'debug') {
|
||||
@ -125,6 +149,7 @@
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
try {
|
||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||
@ -254,6 +279,34 @@
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||
<div class="text-2xl font-bold">Application Usage</div>
|
||||
<div class="mx-auto">
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white">
|
||||
{usage?.MemUsage}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.CPUPerc}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.NetIO}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||
@ -347,7 +400,7 @@
|
||||
value={application.destinationDocker.name}
|
||||
id="destination"
|
||||
disabled
|
||||
class="bg-transparent "
|
||||
class="bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -358,10 +411,10 @@
|
||||
>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
isDisabled={!$session.isAdmin || isRunning}
|
||||
containerClasses={containerClass()}
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="baseImages"
|
||||
showIndicator={!isRunning}
|
||||
showIndicator={!$status.application.isRunning}
|
||||
items={application.baseImages}
|
||||
on:select={selectBaseImage}
|
||||
value={application.baseImage}
|
||||
@ -378,10 +431,10 @@
|
||||
>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
isDisabled={!$session.isAdmin || isRunning}
|
||||
containerClasses={containerClass()}
|
||||
{isDisabled}
|
||||
containerClasses={isDisabled && containerClass()}
|
||||
id="baseBuildImages"
|
||||
showIndicator={!isRunning}
|
||||
showIndicator={!$status.application.isRunning}
|
||||
items={application.baseBuildImages}
|
||||
on:select={selectBaseBuildImage}
|
||||
value={application.baseBuildImage}
|
||||
@ -414,8 +467,8 @@
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
readonly={!$session.isAdmin || isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
readonly={isDisabled}
|
||||
disabled={isDisabled}
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
@ -462,12 +515,12 @@
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<Setting
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
disabled={isRunning}
|
||||
disabled={$status.application.isRunning}
|
||||
isCenter={false}
|
||||
bind:setting={dualCerts}
|
||||
title={$t('application.ssl_www_and_non_www')}
|
||||
description={$t('application.ssl_explainer')}
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
{#if application.buildPack === 'python'}
|
||||
@ -531,8 +584,8 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
readonly={!$session.isAdmin && !$status.application.isRunning}
|
||||
disabled={isDisabled}
|
||||
name="exposePort"
|
||||
id="exposePort"
|
||||
bind:value={application.exposePort}
|
||||
|
@ -17,7 +17,7 @@ export const get: RequestHandler = async (event) => {
|
||||
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
const listContainers = await docker.engine.listContainers({
|
||||
filters: { network: [destinationDocker.network] }
|
||||
filters: { network: [destinationDocker.network], name: [id] }
|
||||
});
|
||||
const containers = listContainers.filter((container) => {
|
||||
return (
|
||||
@ -30,11 +30,7 @@ export const get: RequestHandler = async (event) => {
|
||||
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
|
||||
)
|
||||
.filter((container) => {
|
||||
return (
|
||||
container.type !== 'manual' &&
|
||||
container.type !== 'webhook_commit' &&
|
||||
container.applicationId === id
|
||||
);
|
||||
return container.pullmergeRequestId && container.applicationId === id;
|
||||
});
|
||||
return {
|
||||
body: {
|
||||
|
@ -31,6 +31,7 @@
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { t } from '$lib/translations';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshSecrets() {
|
||||
@ -39,11 +40,18 @@
|
||||
}
|
||||
async function redeploy(container) {
|
||||
try {
|
||||
await post(`/applications/${id}/deploy.json`, {
|
||||
const { buildId } = await post(`/applications/${id}/deploy.json`, {
|
||||
pullmergeRequestId: container.pullmergeRequestId,
|
||||
branch: container.branch
|
||||
});
|
||||
toast.push('Application redeployed queued.');
|
||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
} else {
|
||||
return await goto(`/applications/${id}/logs/build?buildId=${buildId}`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
|
36
src/routes/applications/[id]/status.json.ts
Normal file
36
src/routes/applications/[id]/status.json.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { asyncExecShell, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer, getContainerUsage, isContainerExited } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { setDefaultConfiguration } from '$lib/buildPacks/common';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
[isRunning, isExited] = await Promise.all([
|
||||
checkContainer(application.destinationDocker.engine, id),
|
||||
isContainerExited(application.destinationDocker.engine, id)
|
||||
]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
isRunning,
|
||||
isExited
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
30
src/routes/applications/[id]/usage.json.ts
Normal file
30
src/routes/applications/[id]/usage.json.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { getContainerUsage } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let usage = {};
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
if (application.destinationDockerId) {
|
||||
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
usage
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -49,6 +49,7 @@ export const get: RequestHandler = async (event) => {
|
||||
where: { userId },
|
||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||
});
|
||||
const settings = await db.prisma.setting.findFirst();
|
||||
return {
|
||||
body: {
|
||||
teams,
|
||||
@ -57,7 +58,8 @@ export const get: RequestHandler = async (event) => {
|
||||
destinationsCount,
|
||||
teamsCount,
|
||||
databasesCount,
|
||||
servicesCount
|
||||
servicesCount,
|
||||
settings
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
@ -12,7 +12,7 @@ export const del: RequestHandler = async (event) => {
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabase(database);
|
||||
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
|
||||
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
}
|
||||
await db.removeDatabase({ id });
|
||||
return { status: 200 };
|
||||
|
@ -33,10 +33,40 @@
|
||||
|
||||
<script lang="ts">
|
||||
import DatabaseLinks from '$lib/components/DatabaseLinks.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
export let database;
|
||||
export let settings;
|
||||
export let privatePort;
|
||||
export let isRunning;
|
||||
|
||||
const { id } = $page.params;
|
||||
let usageLoading = false;
|
||||
let usage = {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
let usageInterval;
|
||||
|
||||
async function getUsage() {
|
||||
if (usageLoading) return;
|
||||
usageLoading = true;
|
||||
const data = await get(`/databases/${id}/usage.json`);
|
||||
usage = data.usage;
|
||||
usageLoading = false;
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
await getUsage();
|
||||
usageInterval = setInterval(async () => {
|
||||
await getUsage();
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
|
||||
@ -49,4 +79,31 @@
|
||||
<DatabaseLinks {database} />
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||
<div class="text-2xl font-bold">Database Usage</div>
|
||||
<div class="mx-auto">
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white">
|
||||
{usage?.MemUsage}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.CPUPerc}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.NetIO}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<Databases bind:database {privatePort} {settings} {isRunning} />
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database';
|
||||
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||
import { startTcpProxy, startTraefikTCPProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -13,6 +13,7 @@ export const post: RequestHandler = async (event) => {
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
try {
|
||||
const settings = await db.listSettings();
|
||||
await db.setDatabase({ id, isPublic, appendOnly });
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
|
||||
@ -21,10 +22,14 @@ export const post: RequestHandler = async (event) => {
|
||||
if (destinationDockerId) {
|
||||
if (isPublic) {
|
||||
await db.prisma.database.update({ where: { id }, data: { publicPort } });
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
if (settings.isTraefikUsed) {
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
} else {
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
}
|
||||
} else {
|
||||
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
|
||||
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
|
||||
await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
|
||||
try {
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
const everStarted = await stopDatabase(database);
|
||||
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
|
||||
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
await db.setDatabase({ id, isPublic: false });
|
||||
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
|
||||
|
||||
@ -21,6 +21,7 @@ export const post: RequestHandler = async (event) => {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
30
src/routes/databases/[id]/usage.json.ts
Normal file
30
src/routes/databases/[id]/usage.json.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { getContainerUsage } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let usage = {};
|
||||
try {
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
if (database.destinationDockerId) {
|
||||
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.engine, id)]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
usage
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -26,8 +26,12 @@ export const get: RequestHandler = async (event) => {
|
||||
// // await saveSshKey(destination);
|
||||
// payload.state = await checkContainer(engine, 'coolify-haproxy');
|
||||
} else {
|
||||
let containerName = 'coolify-proxy';
|
||||
if (!settings.isTraefikUsed) {
|
||||
containerName = 'coolify-haproxy';
|
||||
}
|
||||
payload.state =
|
||||
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy'));
|
||||
destination?.engine && (await checkContainer(destination.engine, containerName));
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import * as db from '$lib/database';
|
||||
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
|
||||
import {
|
||||
startCoolifyProxy,
|
||||
startTraefikProxy,
|
||||
stopCoolifyProxy,
|
||||
stopTraefikProxy
|
||||
} from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -11,9 +16,16 @@ export const post: RequestHandler = async (event) => {
|
||||
const { engine } = await event.request.json();
|
||||
|
||||
try {
|
||||
await stopCoolifyProxy(engine);
|
||||
await startCoolifyProxy(engine);
|
||||
const settings = await db.prisma.setting.findFirst({});
|
||||
if (settings?.isTraefikUsed) {
|
||||
await stopTraefikProxy(engine);
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await stopCoolifyProxy(engine);
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import {
|
||||
startCoolifyProxy,
|
||||
startTraefikProxy,
|
||||
stopCoolifyProxy,
|
||||
stopTraefikProxy
|
||||
} from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -8,14 +14,24 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { engine } = await event.request.json();
|
||||
const settings = await db.prisma.setting.findFirst({});
|
||||
|
||||
try {
|
||||
await startCoolifyProxy(engine);
|
||||
if (settings?.isTraefikUsed) {
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
await stopCoolifyProxy(engine);
|
||||
if (settings?.isTraefikUsed) {
|
||||
await stopTraefikProxy(engine);
|
||||
} else {
|
||||
await stopCoolifyProxy(engine);
|
||||
}
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { stopCoolifyProxy } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { stopCoolifyProxy, stopTraefikProxy } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@ -9,7 +10,13 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
const { engine } = await event.request.json();
|
||||
try {
|
||||
await stopCoolifyProxy(engine);
|
||||
const settings = await db.prisma.setting.findFirst({});
|
||||
if (settings?.isTraefikUsed) {
|
||||
await stopTraefikProxy(engine);
|
||||
} else {
|
||||
await stopCoolifyProxy(engine);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
@ -23,7 +23,6 @@
|
||||
import { t } from '$lib/translations';
|
||||
import { get } from '$lib/api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Loading from './applications/[id]/logs/_Loading.svelte';
|
||||
import Trend from './_Trend.svelte';
|
||||
import { session } from '$app/stores';
|
||||
|
||||
|
@ -30,14 +30,16 @@
|
||||
value={service.minio.rootUserPassword}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="publicPort">{$t('forms.api_port')}</label>
|
||||
<input
|
||||
name="publicPort"
|
||||
id="publicPort"
|
||||
value={service.minio.publicPort}
|
||||
disabled
|
||||
readonly
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
/>
|
||||
</div>
|
||||
{#if !service.minio.apiFqdn}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="publicPort">{$t('forms.api_port')}</label>
|
||||
<input
|
||||
name="publicPort"
|
||||
id="publicPort"
|
||||
value={service.minio.publicPort}
|
||||
disabled
|
||||
readonly
|
||||
placeholder={$t('forms.generated_automatically_after_start')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export let service;
|
||||
export let isRunning;
|
||||
export let readOnly;
|
||||
@ -12,6 +14,8 @@
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { t } from '$lib/translations';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import cuid from 'cuid';
|
||||
import { onMount } from 'svelte';
|
||||
import Fider from './_Fider.svelte';
|
||||
import Ghost from './_Ghost.svelte';
|
||||
import Hasura from './_Hasura.svelte';
|
||||
@ -29,9 +33,14 @@
|
||||
let dualCerts = service.dualCerts;
|
||||
|
||||
async function handleSubmit() {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
try {
|
||||
await post(`/services/${id}/check.json`, { fqdn: service.fqdn });
|
||||
await post(`/services/${id}/check.json`, {
|
||||
fqdn: service.fqdn,
|
||||
otherFqdns: service.minio?.apiFqdn ? [service.minio?.apiFqdn] : [],
|
||||
exposePort: service.exposePort
|
||||
});
|
||||
await post(`/services/${id}/${service.type}.json`, { ...service });
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
@ -62,6 +71,28 @@
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
|
||||
service.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
if (service.type === 'wordpress') {
|
||||
service.wordpress.mysqlDatabase = 'db';
|
||||
}
|
||||
if (service.type === 'plausibleanalytics') {
|
||||
service.plausibleAnalytics.email = 'noreply@demo.com';
|
||||
service.plausibleAnalytics.username = 'admin';
|
||||
}
|
||||
if (service.type === 'minio') {
|
||||
service.minio.apiFqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
if (service.type === 'ghost') {
|
||||
service.ghost.mariadbDatabase = 'db';
|
||||
}
|
||||
if (service.type === 'fider') {
|
||||
service.fider.emailNoreply = 'noreply@demo.com';
|
||||
}
|
||||
await handleSubmit();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6 pb-12">
|
||||
@ -86,6 +117,14 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
{#if service.type === 'minio' && !service.minio.apiFqdn && isRunning}
|
||||
<div class="text-center">
|
||||
<span class="font-bold text-red-500">IMPORTANT!</span> There was a small modification with
|
||||
Minio in the latest version of Coolify. Now you can separate the Console URL from the API URL,
|
||||
so you could use both through SSL. But this proccess cannot be done automatically, so you have
|
||||
to stop your Minio instance, configure the new domain and start it back. Sorry for any inconvenience.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-2 grid grid-cols-2 items-center px-10">
|
||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||
<div>
|
||||
@ -131,25 +170,62 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||
>{$t('application.url_fqdn')}</label
|
||||
>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
{#if service.type === 'minio'}
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Console URL</label>
|
||||
</div>
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://console.min.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="apiFqdn" class="pt-2 text-base font-bold text-stone-100">API URL</label>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://min.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="apiFqdn"
|
||||
id="apiFqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.minio.apiFqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||
>{$t('application.url_fqdn')}</label
|
||||
>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://analytics.coollabs.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://analytics.coollabs.io"
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
disabled={isRunning}
|
||||
@ -160,7 +236,7 @@
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin && !isRunning}
|
||||
|
21
src/routes/services/[id]/appwrite-wip/index.json.ts
Normal file
21
src/routes/services/[id]/appwrite-wip/index.json.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn, exposePort } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
try {
|
||||
await db.updateService({ id, fqdn, name, exposePort });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
519
src/routes/services/[id]/appwrite-wip/start.json.ts
Normal file
519
src/routes/services/[id]/appwrite-wip/start.json.ts
Normal file
@ -0,0 +1,519 @@
|
||||
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import { getServiceMainPort } from '$lib/components/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||
service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
const port = getServiceMainPort('n8n');
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
variables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
|
||||
const variables = {
|
||||
_APP_ENV: 'production',
|
||||
_APP_VERSION: '',
|
||||
_APP_LOCALE: '',
|
||||
_APP_OPTIONS_ABUSE: '',
|
||||
_APP_OPTIONS_FORCE_HTTPS: '',
|
||||
_APP_OPENSSL_KEY_V1: '',
|
||||
_APP_DOMAIN: '',
|
||||
_APP_DOMAIN_TARGET: '',
|
||||
_APP_CONSOLE_WHITELIST_ROOT: '',
|
||||
_APP_CONSOLE_WHITELIST_EMAILS: '',
|
||||
_APP_CONSOLE_WHITELIST_IPS: '',
|
||||
_APP_SYSTEM_EMAIL_NAME: '',
|
||||
_APP_SYSTEM_EMAIL_ADDRESS: '',
|
||||
_APP_SYSTEM_RESPONSE_FORMAT: '',
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: '',
|
||||
_APP_USAGE_STATS: '',
|
||||
_APP_LOGGING_PROVIDER: '',
|
||||
_APP_LOGGING_CONFIG: '',
|
||||
_APP_USAGE_AGGREGATION_INTERVAL: '',
|
||||
_APP_WORKER_PER_CORE: '',
|
||||
_APP_REDIS_HOST: '',
|
||||
_APP_REDIS_PORT: '',
|
||||
_APP_REDIS_USER: '',
|
||||
_APP_REDIS_PASS: '',
|
||||
_APP_DB_HOST: '',
|
||||
_APP_DB_PORT: '',
|
||||
_APP_DB_SCHEMA: '',
|
||||
_APP_DB_USER: '',
|
||||
_APP_DB_PASS: '',
|
||||
_APP_DB_ROOT_PASS: '',
|
||||
_APP_INFLUXDB_HOST: '',
|
||||
_APP_INFLUXDB_PORT: '',
|
||||
_APP_STATSD_HOST: '',
|
||||
_APP_STATSD_PORT: '',
|
||||
_APP_SMTP_HOST: '',
|
||||
_APP_SMTP_PORT: '',
|
||||
_APP_SMTP_SECURE: '',
|
||||
_APP_SMTP_USERNAME: '',
|
||||
_APP_SMTP_PASSWORD: '',
|
||||
_APP_STORAGE_LIMIT: '',
|
||||
_APP_STORAGE_ANTIVIRUS: '',
|
||||
_APP_STORAGE_ANTIVIRUS_HOST: '',
|
||||
_APP_STORAGE_ANTIVIRUS_PORT: '',
|
||||
_APP_STORAGE_DEVICE: '',
|
||||
_APP_STORAGE_S3_ACCESS_KEY: '',
|
||||
_APP_STORAGE_S3_SECRET: '',
|
||||
_APP_STORAGE_S3_REGION: '',
|
||||
_APP_STORAGE_S3_BUCKET: '',
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: '',
|
||||
_APP_STORAGE_DO_SPACES_SECRET: '',
|
||||
_APP_STORAGE_DO_SPACES_REGION: '',
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: '',
|
||||
_APP_FUNCTIONS_SIZE_LIMIT: '',
|
||||
_APP_FUNCTIONS_TIMEOUT: '',
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT: '',
|
||||
_APP_FUNCTIONS_CONTAINERS: '',
|
||||
_APP_FUNCTIONS_CPUS: '',
|
||||
_APP_FUNCTIONS_MEMORY: '',
|
||||
_APP_FUNCTIONS_MEMORY_SWAP: '',
|
||||
_APP_FUNCTIONS_RUNTIMES: '',
|
||||
_APP_EXECUTOR_SECRET: '',
|
||||
_APP_EXECUTOR_RUNTIME_NETWORK: '',
|
||||
_APP_FUNCTIONS_ENVS: '',
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD: '',
|
||||
DOCKERHUB_PULL_USERNAME: '',
|
||||
DOCKERHUB_PULL_PASSWORD: '',
|
||||
DOCKERHUB_PULL_EMAIL: '',
|
||||
_APP_MAINTENANCE_INTERVAL: '',
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION: '',
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE: '',
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT: ''
|
||||
};
|
||||
const config = {
|
||||
appwrite: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-uploads:/storage/uploads`,
|
||||
`${id}-appwrite-cache:/storage/cache`,
|
||||
`${id}-appwrite-config:/storage/config`,
|
||||
`${id}-appwrite-certificates:/storage/certificates`,
|
||||
`${id}-appwrite-functions:/storage/functions`
|
||||
],
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_WORKER_PER_CORE: variables._APP_WORKER_PER_CORE,
|
||||
_APP_LOCALE: variables._APP_LOCALE,
|
||||
_APP_CONSOLE_WHITELIST_ROOT: variables._APP_CONSOLE_WHITELIST_ROOT,
|
||||
_APP_CONSOLE_WHITELIST_EMAILS: variables._APP_CONSOLE_WHITELIST_EMAILS,
|
||||
_APP_CONSOLE_WHITELIST_IPS: variables._APP_CONSOLE_WHITELIST_IPS,
|
||||
_APP_SYSTEM_EMAIL_NAME: variables._APP_SYSTEM_EMAIL_NAME,
|
||||
_APP_SYSTEM_EMAIL_ADDRESS: variables._APP_SYSTEM_EMAIL_ADDRESS,
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS,
|
||||
_APP_SYSTEM_RESPONSE_FORMAT: variables._APP_SYSTEM_RESPONSE_FORMAT,
|
||||
_APP_OPTIONS_ABUSE: variables._APP_OPTIONS_ABUSE,
|
||||
_APP_OPTIONS_FORCE_HTTPS: variables._APP_OPTIONS_FORCE_HTTPS,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_DOMAIN: variables._APP_DOMAIN,
|
||||
_APP_DOMAIN_TARGET: variables._APP_DOMAIN_TARGET,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_SMTP_HOST: variables._APP_SMTP_HOST,
|
||||
_APP_SMTP_PORT: variables._APP_SMTP_PORT,
|
||||
_APP_SMTP_SECURE: variables._APP_SMTP_SECURE,
|
||||
_APP_SMTP_USERNAME: variables._APP_SMTP_USERNAME,
|
||||
_APP_SMTP_PASSWORD: variables._APP_SMTP_PASSWORD,
|
||||
_APP_USAGE_STATS: variables._APP_USAGE_STATS,
|
||||
_APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST,
|
||||
_APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT,
|
||||
_APP_STORAGE_LIMIT: variables._APP_STORAGE_LIMIT,
|
||||
_APP_STORAGE_ANTIVIRUS: variables._APP_STORAGE_ANTIVIRUS,
|
||||
_APP_STORAGE_ANTIVIRUS_HOST: variables._APP_STORAGE_ANTIVIRUS_HOST,
|
||||
_APP_STORAGE_ANTIVIRUS_PORT: variables._APP_STORAGE_ANTIVIRUS_PORT,
|
||||
_APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE,
|
||||
_APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY,
|
||||
_APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET,
|
||||
_APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION,
|
||||
_APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET,
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY,
|
||||
_APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET,
|
||||
_APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION,
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET,
|
||||
_APP_FUNCTIONS_SIZE_LIMIT: variables._APP_FUNCTIONS_SIZE_LIMIT,
|
||||
_APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT,
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT: variables._APP_FUNCTIONS_BUILD_TIMEOUT,
|
||||
_APP_FUNCTIONS_CONTAINERS: variables._APP_FUNCTIONS_CONTAINERS,
|
||||
_APP_FUNCTIONS_CPUS: variables._APP_FUNCTIONS_CPUS,
|
||||
_APP_FUNCTIONS_MEMORY: variables._APP_FUNCTIONS_MEMORY,
|
||||
_APP_FUNCTIONS_MEMORY_SWAP: variables._APP_FUNCTIONS_MEMORY_SWAP,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_FUNCTIONS_RUNTIMES: variables._APP_FUNCTIONS_RUNTIMES,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG,
|
||||
_APP_STATSD_HOST: variables._APP_STATSD_HOST,
|
||||
_APP_STATSD_PORT: variables._APP_STATSD_PORT,
|
||||
_APP_MAINTENANCE_INTERVAL: variables._APP_MAINTENANCE_INTERVAL,
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION: variables._APP_MAINTENANCE_RETENTION_EXECUTION,
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE: variables._APP_MAINTENANCE_RETENTION_ABUSE,
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT: variables._APP_MAINTENANCE_RETENTION_AUDIT
|
||||
}
|
||||
},
|
||||
appwriteRealtime: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_WORKER_PER_CORE: variables._APP_WORKER_PER_CORE,
|
||||
_APP_OPTIONS_ABUSE: variables._APP_OPTIONS_ABUSE,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_USAGE_STATS: variables._APP_USAGE_STATS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteExecutor: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-functions:/storage/functions`,
|
||||
`/tmp:/tmp`,
|
||||
'/var/run/docker.sock:/var/run/docker.sock'
|
||||
],
|
||||
environmentVariables: {
|
||||
DOCKERHUB_PULL_USERNAME: variables.DOCKERHUB_PULL_USERNAME,
|
||||
DOCKERHUB_PULL_PASSWORD: variables.DOCKERHUB_PULL_PASSWORD,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG,
|
||||
_APP_VERSION: variables._APP_VERSION,
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE,
|
||||
_APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY,
|
||||
_APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET,
|
||||
_APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION,
|
||||
_APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET,
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY,
|
||||
_APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET,
|
||||
_APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION,
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET,
|
||||
_APP_FUNCTIONS_CPUS: variables._APP_FUNCTIONS_CPUS,
|
||||
_APP_FUNCTIONS_MEMORY: variables._APP_FUNCTIONS_MEMORY,
|
||||
_APP_FUNCTIONS_MEMORY_SWAP: variables._APP_FUNCTIONS_MEMORY_SWAP,
|
||||
_APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_FUNCTIONS_RUNTIMES: variables._APP_FUNCTIONS_RUNTIMES,
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD: variables._APP_FUNCTIONS_INACTIVE_THRESHOLD,
|
||||
_APP_EXECUTOR_RUNTIME_NETWORK: variables._APP_EXECUTOR_RUNTIME_NETWORK
|
||||
}
|
||||
},
|
||||
appwriteWorkerDatabase: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerBuilds: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerAudits: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerWebhooks: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerDeletes: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-uploads:/storage/uploads`,
|
||||
`${id}-appwrite-cache:/storage/cache`,
|
||||
`${id}-appwrite-certificates:/storage/certificates`
|
||||
],
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE,
|
||||
_APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY,
|
||||
_APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET,
|
||||
_APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION,
|
||||
_APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET,
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY,
|
||||
_APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET,
|
||||
_APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION,
|
||||
_APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerCertificates: {
|
||||
image: `${image}:${version}`,
|
||||
volumes: [
|
||||
`${id}-appwrite-config:/storage/config`,
|
||||
`${id}-appwrite-certificates:/storage/certificates`
|
||||
],
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DOMAIN_TARGET: variables._APP_DOMAIN_TARGET,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteWorkerFunctions: {
|
||||
image: `${image}:${version}`,
|
||||
envvironmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT,
|
||||
_APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET,
|
||||
_APP_USAGE_STATS: variables._APP_USAGE_STATS,
|
||||
DOCKERHUB_PULL_USERNAME: variables.DOCKERHUB_PULL_USERNAME,
|
||||
DOCKERHUB_PULL_PASSWORD: variables.DOCKERHUB_PULL_PASSWORD
|
||||
}
|
||||
},
|
||||
appwriteWorkerMails: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_SYSTEM_EMAIL_NAME: variables._APP_SYSTEM_EMAIL_NAME,
|
||||
_APP_SYSTEM_EMAIL_ADDRESS: variables._APP_SYSTEM_EMAIL_ADDRESS,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_SMTP_HOST: variables._APP_SMTP_HOST,
|
||||
_APP_SMTP_PORT: variables._APP_SMTP_PORT,
|
||||
_APP_SMTP_SECURE: variables._APP_SMTP_SECURE,
|
||||
_APP_SMTP_USERNAME: variables._APP_SMTP_USERNAME,
|
||||
_APP_SMTP_PASSWORD: variables._APP_SMTP_PASSWORD,
|
||||
_APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER,
|
||||
_APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG
|
||||
}
|
||||
},
|
||||
appwriteMaintenance: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS,
|
||||
_APP_MAINTENANCE_INTERVAL: variables._APP_MAINTENANCE_INTERVAL,
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION: variables._APP_MAINTENANCE_RETENTION_EXECUTION,
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE: variables._APP_MAINTENANCE_RETENTION_ABUSE,
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT: variables._APP_MAINTENANCE_RETENTION_AUDIT
|
||||
}
|
||||
},
|
||||
appwriteUsage: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1,
|
||||
_APP_DB_HOST: variables._APP_DB_HOST,
|
||||
_APP_DB_PORT: variables._APP_DB_PORT,
|
||||
_APP_DB_SCHEMA: variables._APP_DB_SCHEMA,
|
||||
_APP_DB_USER: variables._APP_DB_USER,
|
||||
_APP_DB_PASS: variables._APP_DB_PASS,
|
||||
_APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST,
|
||||
_APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT,
|
||||
_APP_USAGE_AGGREGATION_INTERVAL: variables._APP_USAGE_AGGREGATION_INTERVAL,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS
|
||||
}
|
||||
},
|
||||
appwriteSchedule: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
_APP_ENV: variables._APP_ENV,
|
||||
_APP_REDIS_HOST: variables._APP_REDIS_HOST,
|
||||
_APP_REDIS_PORT: variables._APP_REDIS_PORT,
|
||||
_APP_REDIS_USER: variables._APP_REDIS_USER,
|
||||
_APP_REDIS_PASS: variables._APP_REDIS_PASS
|
||||
}
|
||||
},
|
||||
mariadb: {
|
||||
image: 'mariadb:10.7',
|
||||
volumes: [`${id}-appwrite-mariadb:/var/lib/mysql`],
|
||||
environmentVariables: {
|
||||
MYSQL_ROOT_PASSWORD: variables._APP_DB_ROOT_PASS,
|
||||
MYSQL_DATABASE: variables._APP_DB_SCHEMA,
|
||||
MYSQL_USER: variables._APP_DB_USER,
|
||||
MYSQL_PASSWORD: variables._APP_DB_PASS
|
||||
}
|
||||
},
|
||||
redis: {
|
||||
image: 'redis:6.0-alpine3.12',
|
||||
volumes: [`${id}-appwrite-redis:/data`]
|
||||
},
|
||||
influxdb: {
|
||||
image: 'appwrite/influxdb:1.0.0',
|
||||
volumes: [`${id}-appwrite-influxdb:/var/lib/influxdb`]
|
||||
},
|
||||
telegraf: {
|
||||
image: 'appwrite/telegraf:1.0.0',
|
||||
environmentVariables: {
|
||||
_APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST,
|
||||
_APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const composeFile: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
volumes: [...config.appwrite.volumes],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('appwrite'),
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
35
src/routes/services/[id]/appwrite-wip/stop.json.ts
Normal file
35
src/routes/services/[id]/appwrite-wip/stop.json.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -1,19 +1,56 @@
|
||||
import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { t } from '$lib/translations';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import getPort from 'get-port';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { fqdn } = await event.request.json();
|
||||
let { fqdn, exposePort, otherFqdns } = await event.request.json();
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
try {
|
||||
const found = await db.isDomainConfigured({ id, fqdn });
|
||||
let found = await db.isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw {
|
||||
message: t.get('application.domain_already_in_use', {
|
||||
domain: getDomain(fqdn).replace('www.', '')
|
||||
})
|
||||
};
|
||||
}
|
||||
if (otherFqdns && otherFqdns.length > 0) {
|
||||
for (const ofqdn of otherFqdns) {
|
||||
const domain = getDomain(ofqdn);
|
||||
const nakedDomain = domain.replace('www.', '');
|
||||
found = await db.isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true });
|
||||
if (found) {
|
||||
throw {
|
||||
message: t.get('application.domain_already_in_use', {
|
||||
domain: nakedDomain
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exposePort) {
|
||||
exposePort = Number(exposePort);
|
||||
|
||||
if (exposePort < 1024 || exposePort > 65535) {
|
||||
throw { message: `Exposed Port needs to be between 1024 and 65535.` };
|
||||
}
|
||||
|
||||
const publicPort = await getPort({ port: exposePort });
|
||||
if (publicPort !== exposePort) {
|
||||
throw { message: `Port ${exposePort} is already in use.` };
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: found ? 500 : 200,
|
||||
body: {
|
||||
|
@ -59,7 +59,7 @@ export const post: RequestHandler = async (event) => {
|
||||
fider: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
HOST_DOMAIN: domain,
|
||||
BASE_URL: domain,
|
||||
DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}?sslmode=disable`,
|
||||
JWT_SECRET: `${jwtSecret.replace(/\$/g, '$$$')}`,
|
||||
EMAIL_NOREPLY: emailNoreply,
|
||||
|
@ -30,19 +30,43 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
import ServiceLinks from '$lib/components/ServiceLinks.svelte';
|
||||
import Services from './_Services/_Services.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
export let service;
|
||||
export let isRunning;
|
||||
export let readOnly;
|
||||
export let settings;
|
||||
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
|
||||
service.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
const { id } = $page.params;
|
||||
let usageLoading = false;
|
||||
let usage = {
|
||||
MemUsage: 0,
|
||||
CPUPerc: 0,
|
||||
NetIO: 0
|
||||
};
|
||||
let usageInterval;
|
||||
|
||||
async function getUsage() {
|
||||
if (usageLoading) return;
|
||||
usageLoading = true;
|
||||
const data = await get(`/services/${id}/usage.json`);
|
||||
usage = data.usage;
|
||||
usageLoading = false;
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
await getUsage();
|
||||
usageInterval = setInterval(async () => {
|
||||
await getUsage();
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
|
||||
@ -52,6 +76,7 @@
|
||||
</div>
|
||||
<span class="text-xs">{service.name}</span>
|
||||
</div>
|
||||
|
||||
{#if service.fqdn}
|
||||
<a
|
||||
href={service.fqdn}
|
||||
@ -77,5 +102,31 @@
|
||||
|
||||
<ServiceLinks {service} />
|
||||
</div>
|
||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||
<div class="text-2xl font-bold">Service Usage</div>
|
||||
<div class="mx-auto">
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white">
|
||||
{usage?.MemUsage}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.CPUPerc}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
|
||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
||||
{usage?.NetIO}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<Services bind:service {isRunning} {readOnly} {settings} />
|
||||
|
@ -9,12 +9,17 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn, exposePort } = await event.request.json();
|
||||
let {
|
||||
name,
|
||||
fqdn,
|
||||
exposePort,
|
||||
minio: { apiFqdn }
|
||||
} = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
if (apiFqdn) apiFqdn = apiFqdn.toLowerCase();
|
||||
try {
|
||||
await db.updateService({ id, fqdn, name, exposePort });
|
||||
await db.updateMinioService({ id, fqdn, apiFqdn, name, exposePort });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
@ -3,7 +3,6 @@ import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { startHttpProxy } from '$lib/haproxy';
|
||||
import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
@ -35,7 +34,6 @@ export const post: RequestHandler = async (event) => {
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
const consolePort = 9001;
|
||||
const apiPort = 9000;
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
@ -94,8 +92,7 @@ export const post: RequestHandler = async (event) => {
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
await db.updateMinioService({ id, publicPort });
|
||||
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
||||
await db.updateMinioServicePort({ id, publicPort });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
@ -12,12 +12,7 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const {
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
fqdn,
|
||||
minio: { publicPort }
|
||||
} = service;
|
||||
const { destinationDockerId, destinationDocker } = service;
|
||||
await db.updateMinioService({ id, publicPort: null });
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
@ -30,11 +25,6 @@ export const post: RequestHandler = async (event) => {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
try {
|
||||
await stopTcpHttpProxy(destinationDocker, publicPort);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -27,6 +27,7 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
const config = {
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-nc:/usr/app/data`,
|
||||
environmentVariables: {}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
@ -41,6 +42,7 @@ export const post: RequestHandler = async (event) => {
|
||||
container_name: id,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
volumes: [config.volume],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
@ -59,6 +61,11 @@ export const post: RequestHandler = async (event) => {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
|
@ -195,7 +195,6 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
console.log(JSON.stringify(composeFile, null, 2));
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(
|
||||
|
30
src/routes/services/[id]/usage.json.ts
Normal file
30
src/routes/services/[id]/usage.json.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { getContainerUsage } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let usage = {};
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
if (service.destinationDockerId) {
|
||||
[usage] = await Promise.all([getContainerUsage(service.destinationDocker.engine, id)]);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
usage
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -3,7 +3,12 @@ import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
|
||||
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||
import {
|
||||
checkContainer,
|
||||
startTcpProxy,
|
||||
startTraefikTCPProxy,
|
||||
stopTcpHttpProxy
|
||||
} from '$lib/haproxy';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import cuid from 'cuid';
|
||||
@ -31,49 +36,51 @@ export const post: RequestHandler = async (event) => {
|
||||
});
|
||||
const {
|
||||
service: { destinationDockerId, destinationDocker },
|
||||
ftpPublicPort: oldPublicPort,
|
||||
ftpPublicPort,
|
||||
ftpUser: user,
|
||||
ftpPassword: savedPassword,
|
||||
ftpHostKey,
|
||||
ftpHostKeyPrivate
|
||||
} = data;
|
||||
if (user) ftpUser = user;
|
||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||
const { network, engine } = destinationDocker;
|
||||
const settings = await db.prisma.setting.findFirst();
|
||||
const host = getEngine(engine);
|
||||
if (ftpEnabled) {
|
||||
if (user) ftpUser = user;
|
||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||
|
||||
const { stdout: password } = await asyncExecShell(
|
||||
`echo ${ftpPassword} | openssl passwd -1 -stdin`
|
||||
);
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
await fs.stat(hostkeyDir);
|
||||
} catch (error) {
|
||||
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
|
||||
}
|
||||
if (!ftpHostKey) {
|
||||
await asyncExecShell(
|
||||
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
||||
);
|
||||
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKey: encrypt(ftpHostKey) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
|
||||
}
|
||||
if (!ftpHostKeyPrivate) {
|
||||
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
|
||||
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
|
||||
}
|
||||
|
||||
const { stdout: password } = await asyncExecShell(
|
||||
`echo ${ftpPassword} | openssl passwd -1 -stdin`
|
||||
);
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
await fs.stat(hostkeyDir);
|
||||
} catch (error) {
|
||||
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
|
||||
}
|
||||
if (!ftpHostKey) {
|
||||
await asyncExecShell(
|
||||
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
||||
);
|
||||
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKey: encrypt(ftpHostKey) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
|
||||
}
|
||||
if (!ftpHostKeyPrivate) {
|
||||
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
|
||||
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
|
||||
}
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
if (ftpEnabled) {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: {
|
||||
@ -142,24 +149,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
||||
);
|
||||
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
|
||||
} else {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpPublicPort: null }
|
||||
});
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
|
||||
}
|
||||
}
|
||||
if (ftpEnabled) {
|
||||
return {
|
||||
status: 201,
|
||||
body: {
|
||||
@ -169,6 +159,18 @@ export const post: RequestHandler = async (event) => {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpPublicPort: null }
|
||||
});
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort);
|
||||
return {
|
||||
status: 200,
|
||||
body: {}
|
||||
|
@ -72,8 +72,7 @@ export const post: RequestHandler = async (event) => {
|
||||
minPort,
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
forceSave
|
||||
isDNSCheckEnabled
|
||||
} = await event.request.json();
|
||||
try {
|
||||
const { id } = await db.listSettings();
|
||||
|
@ -37,12 +37,13 @@
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { t } from '$lib/translations';
|
||||
import { features } from '$lib/store';
|
||||
import { features, isTraefikUsed } from '$lib/store';
|
||||
|
||||
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
||||
let dualCerts = settings.dualCerts;
|
||||
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
||||
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
|
||||
$isTraefikUsed = settings.isTraefikUsed;
|
||||
|
||||
let minPort = settings.minPort;
|
||||
let maxPort = settings.maxPort;
|
||||
@ -55,7 +56,8 @@
|
||||
let isFqdnSet = !!settings.fqdn;
|
||||
let loading = {
|
||||
save: false,
|
||||
remove: false
|
||||
remove: false,
|
||||
proxyMigration: false
|
||||
};
|
||||
|
||||
async function removeFqdn() {
|
||||
@ -86,6 +88,7 @@
|
||||
if (name === 'isDNSCheckEnabled') {
|
||||
isDNSCheckEnabled = !isDNSCheckEnabled;
|
||||
}
|
||||
|
||||
await post(`/settings.json`, {
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
@ -156,6 +159,20 @@
|
||||
function resetView() {
|
||||
forceSave = false;
|
||||
}
|
||||
async function migrateProxy(to) {
|
||||
if (loading.proxyMigration) return;
|
||||
try {
|
||||
loading.proxyMigration = true;
|
||||
await post(`/update.json`, { type: to });
|
||||
const data = await get(`/settings.json`);
|
||||
$isTraefikUsed = data.settings.isTraefikUsed;
|
||||
return toast.push('Proxy migration started, it takes a few seconds.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading.proxyMigration = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
@ -192,6 +209,26 @@
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<!-- <Language /> -->
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="flex items-center py-2 pr-8">
|
||||
<div class="flex w-96 flex-col">
|
||||
<div class="text-xs font-bold text-stone-100 md:text-base">New Proxy Available!</div>
|
||||
<Explainer
|
||||
text="We are changing to <span class='text-sky-500 font-bold'>Traefik</span> as <span class='text-red-500 font-bold'>HAProxy</span> had several problems and uses a LOT of unnecessary memory (<span class='text-sky-500 font-bold'>~20MB</span> vs <span class='text-red-500 font-bold'>~200MB</span>).<br><br>You can switch back to HAProxy if something is not working and <span class='text-yellow-500 font-bold'>please let us know</span>!"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
disabled={loading.proxyMigration}
|
||||
class="bg-green-600 text-white hover:bg-green-500"
|
||||
on:click={() => migrateProxy($isTraefikUsed ? 'haproxy' : 'traefik')}
|
||||
>{loading.proxyMigration
|
||||
? 'Migrating...'
|
||||
: $isTraefikUsed
|
||||
? 'Switch back to HAProxy'
|
||||
: 'Migrate to Traefik'}</button
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-start">
|
||||
<div class="flex-col">
|
||||
<div class="pt-2 text-base font-bold text-stone-100">
|
||||
|
@ -84,6 +84,9 @@
|
||||
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
||||
<div class="flex space-x-1 pb-5 font-bold">
|
||||
<div class="title">General</div>
|
||||
{#if source.apiUrl && source.htmlUrl && source.name}
|
||||
<button class=" bg-orange-600" type="submit">Create new GitHub App</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
@ -117,11 +120,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if source.apiUrl && source.htmlUrl && source.name}
|
||||
<div class="text-center">
|
||||
<button class=" mt-8 bg-orange-600" type="submit">Create new GitHub App</button>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
{:else if source.githubApp?.installationId}
|
||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||
@ -173,7 +171,7 @@
|
||||
</form>
|
||||
{:else}
|
||||
<div class="text-center">
|
||||
<button class=" bg-orange-600 mt-8" on:click={() => installRepositories(source)}
|
||||
<button class=" mt-8 bg-orange-600" on:click={() => installRepositories(source)}
|
||||
>Install Repositories</button
|
||||
>
|
||||
</div>
|
||||
|
@ -34,14 +34,14 @@ export const get: RequestHandler = async (request) => {
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { type, latestVersion } = await event.request.json();
|
||||
const settings = await db.prisma.setting.findFirst();
|
||||
if (type === 'update') {
|
||||
try {
|
||||
if (!dev) {
|
||||
const { isAutoUpdateEnabled } = await db.prisma.setting.findFirst();
|
||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
||||
await asyncExecShell(
|
||||
`sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
`sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${settings.isAutoUpdateEnabled}' .env`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`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-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"`
|
||||
@ -61,6 +61,44 @@ export const post: RequestHandler = async (event) => {
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} else if (type === 'traefik') {
|
||||
try {
|
||||
// const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
|
||||
// if (found) {
|
||||
// await asyncExecShell(`docker stop -t 0 coolify-haproxy`);
|
||||
// await asyncExecShell(`docker rm coolify-haproxy`);
|
||||
// }
|
||||
// await startTraefikProxy('/var/run/docker.sock');
|
||||
await db.prisma.setting.update({
|
||||
where: { id: settings.id },
|
||||
data: { isTraefikUsed: true }
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} else if (type === 'haproxy') {
|
||||
try {
|
||||
// const found = await checkContainer('/var/run/docker.sock', 'coolify-proxy');
|
||||
// if (found) {
|
||||
// await asyncExecShell(`docker stop -t 0 coolify-proxy`);
|
||||
// await asyncExecShell(`docker rm coolify-proxy`);
|
||||
// }
|
||||
// await startCoolifyProxy('/var/run/docker.sock');
|
||||
await db.prisma.setting.update({
|
||||
where: { id: settings.id },
|
||||
data: { isTraefikUsed: false }
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
body: {}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 500
|
||||
|
364
src/routes/webhooks/traefik/main.json.ts
Normal file
364
src/routes/webhooks/traefik/main.json.ts
Normal file
@ -0,0 +1,364 @@
|
||||
import { dev } from '$app/env';
|
||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||
import * as db from '$lib/database';
|
||||
import { listServicesWithIncludes } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
function configureMiddleware(
|
||||
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
|
||||
traefik
|
||||
) {
|
||||
if (isHttps) {
|
||||
traefik.http.routers[id] = {
|
||||
entrypoints: ['web'],
|
||||
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
middlewares: ['redirect-to-https']
|
||||
};
|
||||
|
||||
traefik.http.services[id] = {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `http://${container}:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isDualCerts) {
|
||||
traefik.http.routers[`${id}-secure`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
} else {
|
||||
if (isWWW) {
|
||||
traefik.http.routers[`${id}-secure-www`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`www.${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
traefik.http.routers[`${id}-secure`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
tls: {
|
||||
domains: {
|
||||
main: `${domain}`
|
||||
}
|
||||
},
|
||||
middlewares: ['redirect-to-www']
|
||||
};
|
||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
|
||||
} else {
|
||||
traefik.http.routers[`${id}-secure-www`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`www.${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
tls: {
|
||||
domains: {
|
||||
main: `${domain}`
|
||||
}
|
||||
},
|
||||
middlewares: ['redirect-to-non-www']
|
||||
};
|
||||
traefik.http.routers[`${id}-secure`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`${domain}\`)`,
|
||||
service: `${id}`,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
traefik.http.routers[id] = {
|
||||
entrypoints: ['web'],
|
||||
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
middlewares: []
|
||||
};
|
||||
|
||||
traefik.http.routers[`${id}-secure`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||
service: `${id}`,
|
||||
tls: {
|
||||
domains: {
|
||||
main: `${nakedDomain}`
|
||||
}
|
||||
},
|
||||
middlewares: ['redirect-to-http']
|
||||
};
|
||||
|
||||
traefik.http.services[id] = {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `http://${container}:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (!isDualCerts) {
|
||||
if (isWWW) {
|
||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
|
||||
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www');
|
||||
} else {
|
||||
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
|
||||
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'plausibleanalytics' && scriptName && scriptName !== 'plausible.js') {
|
||||
if (!traefik.http.routers[`${id}`].middlewares.includes(`${id}-redir`)) {
|
||||
traefik.http.routers[`${id}`].middlewares.push(`${id}-redir`);
|
||||
}
|
||||
if (!traefik.http.routers[`${id}-secure`].middlewares.includes(`${id}-redir`)) {
|
||||
traefik.http.routers[`${id}-secure`].middlewares.push(`${id}-redir`);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const traefik = {
|
||||
http: {
|
||||
routers: {},
|
||||
services: {},
|
||||
middlewares: {
|
||||
'redirect-to-https': {
|
||||
redirectscheme: {
|
||||
scheme: 'https'
|
||||
}
|
||||
},
|
||||
'redirect-to-http': {
|
||||
redirectscheme: {
|
||||
scheme: 'http'
|
||||
}
|
||||
},
|
||||
'redirect-to-non-www': {
|
||||
redirectregex: {
|
||||
regex: '^https?://www\\.(.+)',
|
||||
replacement: 'http://${1}'
|
||||
}
|
||||
},
|
||||
'redirect-to-www': {
|
||||
redirectregex: {
|
||||
regex: '^https?://(?:www\\.)?(.+)',
|
||||
replacement: 'http://www.${1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const applications = await db.prisma.application.findMany({
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
const data = {
|
||||
applications: [],
|
||||
services: [],
|
||||
coolify: []
|
||||
};
|
||||
for (const application of applications) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
port,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings: { previews, dualCerts }
|
||||
} = application;
|
||||
if (destinationDockerId) {
|
||||
const { engine, network } = destinationDocker;
|
||||
const isRunning = true;
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
if (isRunning) {
|
||||
data.applications.push({
|
||||
id,
|
||||
container: id,
|
||||
port: port || 3000,
|
||||
domain,
|
||||
nakedDomain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts
|
||||
});
|
||||
}
|
||||
if (previews) {
|
||||
const host = getEngine(engine);
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||
);
|
||||
const containers = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((a) => a)
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||
data.applications.push({
|
||||
id: container,
|
||||
container,
|
||||
port: port || 3000,
|
||||
domain: previewDomain,
|
||||
isRunning,
|
||||
nakedDomain,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const services = await listServicesWithIncludes();
|
||||
|
||||
for (const service of services) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
dualCerts,
|
||||
plausibleAnalytics
|
||||
} = service;
|
||||
if (destinationDockerId) {
|
||||
const { engine } = destinationDocker;
|
||||
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||
if (found) {
|
||||
const port = found.ports.main;
|
||||
const publicPort = service[type]?.publicPort;
|
||||
const isRunning = true;
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
if (isRunning) {
|
||||
// Plausible Analytics custom script
|
||||
let scriptName = false;
|
||||
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
|
||||
scriptName = plausibleAnalytics.scriptName;
|
||||
}
|
||||
|
||||
let container = id;
|
||||
let otherDomain = null;
|
||||
let otherNakedDomain = null;
|
||||
let otherIsHttps = null;
|
||||
let otherIsWWW = null;
|
||||
|
||||
if (type === 'minio' && service.minio.apiFqdn) {
|
||||
otherDomain = getDomain(service.minio.apiFqdn);
|
||||
otherNakedDomain = otherDomain.replace(/^www\./, '');
|
||||
otherIsHttps = service.minio.apiFqdn.startsWith('https://');
|
||||
otherIsWWW = service.minio.apiFqdn.includes('www.');
|
||||
}
|
||||
data.services.push({
|
||||
id,
|
||||
container,
|
||||
type,
|
||||
otherDomain,
|
||||
otherNakedDomain,
|
||||
otherIsHttps,
|
||||
otherIsWWW,
|
||||
port,
|
||||
publicPort,
|
||||
domain,
|
||||
nakedDomain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
scriptName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { fqdn, dualCerts } = await db.prisma.setting.findFirst();
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
data.coolify.push({
|
||||
id: dev ? 'host.docker.internal' : 'coolify',
|
||||
container: dev ? 'host.docker.internal' : 'coolify',
|
||||
port: 3000,
|
||||
domain,
|
||||
nakedDomain,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts
|
||||
});
|
||||
}
|
||||
for (const application of data.applications) {
|
||||
configureMiddleware(application, traefik);
|
||||
}
|
||||
for (const service of data.services) {
|
||||
const { id, scriptName } = service;
|
||||
|
||||
configureMiddleware(service, traefik);
|
||||
if (service.type === 'minio') {
|
||||
service.id = id + '-minio';
|
||||
service.container = id;
|
||||
service.domain = service.otherDomain;
|
||||
service.nakedDomain = service.otherNakedDomain;
|
||||
service.isHttps = service.otherIsHttps;
|
||||
service.isWWW = service.otherIsWWW;
|
||||
service.port = 9000;
|
||||
configureMiddleware(service, traefik);
|
||||
}
|
||||
|
||||
if (scriptName) {
|
||||
traefik.http.middlewares[`${id}-redir`] = {
|
||||
replacepathregex: {
|
||||
regex: `/js/${scriptName}`,
|
||||
replacement: '/js/plausible.js'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
for (const coolify of data.coolify) {
|
||||
configureMiddleware(coolify, traefik);
|
||||
}
|
||||
if (Object.keys(traefik.http.routers).length === 0) {
|
||||
traefik.http.routers = null;
|
||||
}
|
||||
if (Object.keys(traefik.http.services).length === 0) {
|
||||
traefik.http.services = null;
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
...traefik
|
||||
}
|
||||
};
|
||||
};
|
137
src/routes/webhooks/traefik/other.json.ts
Normal file
137
src/routes/webhooks/traefik/other.json.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import { dev } from '$app/env';
|
||||
import { getDomain } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const id = event.url.searchParams.get('id');
|
||||
if (id) {
|
||||
const privatePort = event.url.searchParams.get('privatePort');
|
||||
const publicPort = event.url.searchParams.get('publicPort');
|
||||
const type = event.url.searchParams.get('type');
|
||||
const address = event.url.searchParams.get('address') || id;
|
||||
let traefik = {};
|
||||
if (publicPort && type && privatePort) {
|
||||
if (type === 'tcp') {
|
||||
traefik = {
|
||||
[type]: {
|
||||
routers: {
|
||||
[id]: {
|
||||
entrypoints: [type],
|
||||
rule: `HostSNI(\`*\`)`,
|
||||
service: id
|
||||
}
|
||||
},
|
||||
services: {
|
||||
[id]: {
|
||||
loadbalancer: {
|
||||
servers: [{ address: `${address}:${privatePort}` }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (type === 'http') {
|
||||
const service = await db.prisma.service.findFirst({
|
||||
where: { id },
|
||||
include: { minio: true }
|
||||
});
|
||||
if (service) {
|
||||
if (service.type === 'minio') {
|
||||
if (service?.minio?.apiFqdn) {
|
||||
const {
|
||||
minio: { apiFqdn }
|
||||
} = service;
|
||||
const domain = getDomain(apiFqdn);
|
||||
const isHttps = apiFqdn.startsWith('https://');
|
||||
traefik = {
|
||||
[type]: {
|
||||
routers: {
|
||||
[id]: {
|
||||
entrypoints: [type],
|
||||
rule: `Host(\`${domain}\`)`,
|
||||
service: id
|
||||
}
|
||||
},
|
||||
services: {
|
||||
[id]: {
|
||||
loadbalancer: {
|
||||
servers: [{ url: `http://${id}:${privatePort}` }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isHttps) {
|
||||
if (dev) {
|
||||
traefik[type].routers[id].tls = {
|
||||
domains: {
|
||||
main: `${domain}`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
traefik[type].routers[id].tls = {
|
||||
certresolver: 'letsencrypt'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (service?.fqdn) {
|
||||
const domain = getDomain(service.fqdn);
|
||||
const isHttps = service.fqdn.startsWith('https://');
|
||||
traefik = {
|
||||
[type]: {
|
||||
routers: {
|
||||
[id]: {
|
||||
entrypoints: [type],
|
||||
rule: `Host(\`${domain}:${privatePort}\`)`,
|
||||
service: id
|
||||
}
|
||||
},
|
||||
services: {
|
||||
[id]: {
|
||||
loadbalancer: {
|
||||
servers: [{ url: `http://${id}:${privatePort}` }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isHttps) {
|
||||
if (dev) {
|
||||
traefik[type].routers[id].tls = {
|
||||
domains: {
|
||||
main: `${domain}`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
traefik[type].routers[id].tls = {
|
||||
certresolver: 'letsencrypt'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
status: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
status: 500
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
...traefik
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 500
|
||||
};
|
||||
};
|
@ -49,6 +49,9 @@ #svelte .custom-select-wrapper .selectContainer {
|
||||
@apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .spinner {
|
||||
@apply text-coollabs-100;
|
||||
}
|
||||
#svelte .listContainer {
|
||||
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
|
||||
}
|
||||
@ -66,26 +69,6 @@ #svelte .item.active {
|
||||
select {
|
||||
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
|
||||
}
|
||||
.svelte-select {
|
||||
--background: rgb(32 32 32);
|
||||
--inputColor: white;
|
||||
--multiItemPadding: 0;
|
||||
--multiSelectPadding: 0 0.5rem 0 0.5rem;
|
||||
--border: none;
|
||||
--placeholderColor: rgb(87 83 78);
|
||||
--listBackground: rgb(32 32 32);
|
||||
--itemColor: white;
|
||||
--itemHoverBG: rgb(107 22 237);
|
||||
--multiItemBG: rgb(32 32 32);
|
||||
--multiClearHoverBG: transparent;
|
||||
--multiClearHoverFill: rgb(239 68 68);
|
||||
--multiItemActiveBG: transparent;
|
||||
--multiClearBG: transparent;
|
||||
--clearSelectFocusColor: white;
|
||||
--clearSelectHoverColor: rgb(239 68 68);
|
||||
--multiItemBorderRadius: 0.25rem;
|
||||
--listShadow: none;
|
||||
}
|
||||
|
||||
label {
|
||||
@apply inline-block w-64 text-xs tracking-tight md:text-sm;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import preprocess from 'svelte-preprocess';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
|
||||
const config = {
|
||||
preprocess: preprocess(),
|
||||
kit: {
|
||||
@ -10,6 +9,12 @@ const config = {
|
||||
},
|
||||
floc: true,
|
||||
vite: {
|
||||
proxy: {
|
||||
'/api/v2': {
|
||||
target: 'http://localhost:3001/api/v2',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['svelte-kit-cookie-session']
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user