Merge branch 'feat/python' into main
This commit is contained in:
		
							
								
								
									
										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 @@ This is a little list of what you can do to help the project:
 | 
			
		||||
 | 
			
		||||
## 👋 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 @@ Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I re
 | 
			
		||||
 | 
			
		||||
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 @@ https://demo.coolify.io/
 | 
			
		||||
(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 @@ These are the predefined build packs, but with the Docker build pack, you can ho
 | 
			
		||||
- 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
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										106
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -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 @@ textarea {
 | 
			
		||||
	@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 @@ textarea {
 | 
			
		||||
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']
 | 
			
		||||
			},
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user