Merge branch 'feat/python' into main
This commit is contained in:
		
						commit
						cd9b642c5e
					
				
							
								
								
									
										16
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile | ||||
| 
 | ||||
| # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster | ||||
| ARG VARIANT="16-bullseye" | ||||
| FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} | ||||
| 
 | ||||
| # [Optional] Uncomment this section to install additional OS packages. | ||||
| # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ | ||||
| #     && apt-get -y install --no-install-recommends <your-package-list-here> | ||||
| 
 | ||||
| # [Optional] Uncomment if you want to install an additional version of node using nvm | ||||
| # ARG EXTRA_NODE_VERSION=10 | ||||
| # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" | ||||
| 
 | ||||
| # [Optional] Uncomment if you want to install more global node modules | ||||
| RUN su node -c "npm install -g pnpm" | ||||
							
								
								
									
										28
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: | ||||
| // https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node | ||||
| { | ||||
| 	"name": "Node.js", | ||||
| 	"build": { | ||||
| 		"dockerfile": "Dockerfile", | ||||
| 		// Update 'VARIANT' to pick a Node version: 18, 16, 14. | ||||
| 		// Append -bullseye or -buster to pin to an OS version. | ||||
| 		// Use -bullseye variants on local arm64/Apple Silicon. | ||||
| 		"args": { | ||||
| 			"VARIANT": "16-bullseye" | ||||
| 		} | ||||
| 	}, | ||||
| 	// Set *default* container specific settings.json values on container create. | ||||
| 	"settings": {}, | ||||
| 	// Add the IDs of extensions you want installed when the container is created. | ||||
| 	"extensions": ["dbaeumer.vscode-eslint", "svelte.svelte-vscode"], | ||||
| 	// Use 'forwardPorts' to make a list of ports inside the container available locally. | ||||
| 	"forwardPorts": [3000], | ||||
| 	// Use 'postCreateCommand' to run commands after the container is created. | ||||
| 	"postCreateCommand": "cp .env.template .env && pnpm install && pnpm db:push && pnpm db:seed", | ||||
| 	// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. | ||||
| 	"remoteUser": "node", | ||||
| 	"features": { | ||||
| 		"docker-in-docker": "20.10", | ||||
| 		"github-cli": "latest" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										3
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,3 +3,6 @@ contact_links: | ||||
|   - name: 🤔 Questions and Help | ||||
|     url: https://discord.com/invite/6rDM4fkymF | ||||
|     about: Reach out to us on discord or our github discussions page. | ||||
|   - name: 🙋♂️ service request | ||||
|     url: https://feedback.coolify.io/ | ||||
|     about: want to request a new service? for e.g wordpress, hasura, appwrite etc... | ||||
|  | ||||
| @ -15,7 +15,13 @@ 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'] | ||||
| 			}, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user