mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
290 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b0d162226 | ||
|
|
2c5f09a8bb | ||
|
|
ef073e586b | ||
|
|
82bfdb87e3 | ||
|
|
767e7b80cb | ||
|
|
8d26ea9063 | ||
|
|
1a7c4310d0 | ||
|
|
4e8fe79e2b | ||
|
|
a8c5551292 | ||
|
|
2bf73109b2 | ||
|
|
f0ab3750bd | ||
|
|
58a11e37fe | ||
|
|
927bf46304 | ||
|
|
6b89857697 | ||
|
|
b72e5ccef6 | ||
|
|
6617b7811b | ||
|
|
e1c1988db4 | ||
|
|
af99ea4678 | ||
|
|
a6d5316090 | ||
|
|
f5e7a84fa6 | ||
|
|
c013764b61 | ||
|
|
2320ab0dfc | ||
|
|
1281a0f7e4 | ||
|
|
d8350cd4ee | ||
|
|
e3b7c23ed9 | ||
|
|
eae1ea21d6 | ||
|
|
541aa76b64 | ||
|
|
7b8555d524 | ||
|
|
fdf998c181 | ||
|
|
3d6b343adc | ||
|
|
e338cecc14 | ||
|
|
e5537a33fb | ||
|
|
35384deb68 | ||
|
|
547ca60c2a | ||
|
|
376f6f7455 | ||
|
|
abe92dedff | ||
|
|
4b521ceedc | ||
|
|
6dfcb9e52b | ||
|
|
335e3216e2 | ||
|
|
5b22bb4818 | ||
|
|
0097004882 | ||
|
|
1bc9e4c2d3 | ||
|
|
36c7e1a3c3 | ||
|
|
c6b4d04e26 | ||
|
|
fa6cf068c7 | ||
|
|
7c273a3a48 | ||
|
|
3de2ea1523 | ||
|
|
c5c9f84503 | ||
|
|
16ea9a3e07 | ||
|
|
48f952c798 | ||
|
|
f78ea5de07 | ||
|
|
5adbd5e784 | ||
|
|
5b2afa79d7 | ||
|
|
dc4e6d02b7 | ||
|
|
8ae61c8f78 | ||
|
|
684b8e0914 | ||
|
|
7c3314abae | ||
|
|
ab9f8ff356 | ||
|
|
892d8cd5c1 | ||
|
|
8b8b45778d | ||
|
|
6655fb182c | ||
|
|
0926d40247 | ||
|
|
ddc4d36688 | ||
|
|
53e1f22eb1 | ||
|
|
3d2a34737b | ||
|
|
ebde77008c | ||
|
|
3d27fd04ba | ||
|
|
d9fcaf3473 | ||
|
|
d266f761aa | ||
|
|
1d01405412 | ||
|
|
7c62eb5bd6 | ||
|
|
4dcc76d366 | ||
|
|
d2fad19a11 | ||
|
|
7c92c4c964 | ||
|
|
5a71d33236 | ||
|
|
1b4db4f793 | ||
|
|
c084b22815 | ||
|
|
acacef95cd | ||
|
|
5d722183d3 | ||
|
|
ac19ea5407 | ||
|
|
d19b05b970 | ||
|
|
a0795136ac | ||
|
|
d2566e345a | ||
|
|
66cd7cf90e | ||
|
|
9a599981ef | ||
|
|
f51f7bc82a | ||
|
|
dbcbac0137 | ||
|
|
e722f8a87c | ||
|
|
61679749eb | ||
|
|
23e12c9c44 | ||
|
|
6da78cd3e5 | ||
|
|
78ce8100a3 | ||
|
|
76ba338b45 | ||
|
|
823fe2deb2 | ||
|
|
cb90f692f2 | ||
|
|
0325343ede | ||
|
|
69d1556a1d | ||
|
|
2daa043840 | ||
|
|
f340ca9d05 | ||
|
|
02abd038fa | ||
|
|
b9da68ec28 | ||
|
|
88b3910d80 | ||
|
|
160412f6e4 | ||
|
|
59a86b25fc | ||
|
|
49e58b39f5 | ||
|
|
58e0757bbd | ||
|
|
5ff4197572 | ||
|
|
b56e28d27a | ||
|
|
c3d39e1dd4 | ||
|
|
716aa36bfd | ||
|
|
f01460170e | ||
|
|
a414ce282d | ||
|
|
6c32f3b130 | ||
|
|
4cf907c572 | ||
|
|
b28baaa5aa | ||
|
|
980dea64e0 | ||
|
|
c340f6436f | ||
|
|
54376fd105 | ||
|
|
ef006578b2 | ||
|
|
b0b1ee0c60 | ||
|
|
4e2026aa2d | ||
|
|
e0e50b4bd5 | ||
|
|
c9b52f1310 | ||
|
|
0195213dfb | ||
|
|
d6225cbde3 | ||
|
|
7b4c194b97 | ||
|
|
a5ecff24a3 | ||
|
|
c9c003dc9b | ||
|
|
fd95936219 | ||
|
|
15a3fd4456 | ||
|
|
df896542e4 | ||
|
|
8927e81274 | ||
|
|
340f061827 | ||
|
|
15cbac97c2 | ||
|
|
bb32d0f7d1 | ||
|
|
c370fba9ba | ||
|
|
6e32421172 | ||
|
|
6643687c0a | ||
|
|
ed01e78d77 | ||
|
|
93aed52f88 | ||
|
|
bb6d1fd6a3 | ||
|
|
6e33179fc2 | ||
|
|
277fd167cf | ||
|
|
98e8d5170b | ||
|
|
11ee1651ae | ||
|
|
0dfcf9b1e6 | ||
|
|
08f57ac5bc | ||
|
|
7095e781e9 | ||
|
|
df18b93809 | ||
|
|
0c2e028b38 | ||
|
|
80cb1bc129 | ||
|
|
74c1cb51f6 | ||
|
|
2e864bddf9 | ||
|
|
e60ae91b5d | ||
|
|
d606cd86a0 | ||
|
|
bc463c37f4 | ||
|
|
76c1480903 | ||
|
|
6f312caf8b | ||
|
|
980d8d374f | ||
|
|
c49b34942f | ||
|
|
fcfa8717a5 | ||
|
|
954a265965 | ||
|
|
69845a020a | ||
|
|
22200fd8a7 | ||
|
|
add441675d | ||
|
|
d3d9754277 | ||
|
|
aa5e2edbc5 | ||
|
|
310b099ecf | ||
|
|
1cfaef911c | ||
|
|
b931c5f638 | ||
|
|
7c683668eb | ||
|
|
cab7ac7d58 | ||
|
|
15e69c538a | ||
|
|
31ee938b66 | ||
|
|
e51a8d43d9 | ||
|
|
64cd5b6e4b | ||
|
|
6c9ef34905 | ||
|
|
aa89019236 | ||
|
|
df58fcee16 | ||
|
|
ea3ffc429f | ||
|
|
2efca7a2b5 | ||
|
|
9db448a5e2 | ||
|
|
feee90beef | ||
|
|
906a63b6b5 | ||
|
|
2ce64ac213 | ||
|
|
4d8bf57135 | ||
|
|
c5348ce4b3 | ||
|
|
7f87c03f97 | ||
|
|
9469f148ff | ||
|
|
ffb7dc4ec2 | ||
|
|
242b8fa746 | ||
|
|
50cae5ac3b | ||
|
|
6a71233eb2 | ||
|
|
1aff8933c9 | ||
|
|
0ed87a5dfc | ||
|
|
24a6bcbd1e | ||
|
|
ca7f3da19d | ||
|
|
bf047e2a3c | ||
|
|
4454287be9 | ||
|
|
3bd2183655 | ||
|
|
1f7080e8f8 | ||
|
|
8b20761e8b | ||
|
|
655d0b5d5f | ||
|
|
91849cdd3a | ||
|
|
df25a694c3 | ||
|
|
eabaca145e | ||
|
|
2f0e458765 | ||
|
|
ff8037f231 | ||
|
|
a116028e1b | ||
|
|
e606a02b29 | ||
|
|
531c712ea5 | ||
|
|
3ae7624361 | ||
|
|
fed83462fa | ||
|
|
58c9f937c5 | ||
|
|
5d14b9209d | ||
|
|
305a95fa74 | ||
|
|
b29c1e702a | ||
|
|
b04d75ab08 | ||
|
|
25abfaadb9 | ||
|
|
1df81b8698 | ||
|
|
4487846fd7 | ||
|
|
86918f5160 | ||
|
|
bc723b3f15 | ||
|
|
1881e646d4 | ||
|
|
aa98808a1a | ||
|
|
f9a2232703 | ||
|
|
19d6be8663 | ||
|
|
0eb7c890ad | ||
|
|
7bfa68aa58 | ||
|
|
857a38050e | ||
|
|
c5b7f92caf | ||
|
|
df31ffd7fb | ||
|
|
0df0322d36 | ||
|
|
260552322d | ||
|
|
88ef6496a2 | ||
|
|
bdf123bf7b | ||
|
|
8fc3760eef | ||
|
|
5656f6f709 | ||
|
|
53e7e8b77e | ||
|
|
b990915b7a | ||
|
|
15b7822ffd | ||
|
|
cfa28419cb | ||
|
|
30ef0d2a3a | ||
|
|
755f99200a | ||
|
|
7af79ed3a2 | ||
|
|
2971e14269 | ||
|
|
01954aaf30 | ||
|
|
da018a8f2a | ||
|
|
77400bbbb0 | ||
|
|
3c3333d3df | ||
|
|
4963bd4144 | ||
|
|
b4a418dded | ||
|
|
a724b0daee | ||
|
|
88aa620cb4 | ||
|
|
70d3448110 | ||
|
|
09a1a406a6 | ||
|
|
40939d0b7f | ||
|
|
aec1d184c8 | ||
|
|
69d3cb5dd8 | ||
|
|
3deff162bb | ||
|
|
4ed54568d3 | ||
|
|
004724da55 | ||
|
|
97e9b5ffe3 | ||
|
|
45f920f802 | ||
|
|
2b31532d19 | ||
|
|
e7a6ecf95b | ||
|
|
545c98cee0 | ||
|
|
d29ccbfe37 | ||
|
|
d0807862e6 | ||
|
|
b92616dc14 | ||
|
|
a1a436300d | ||
|
|
16a5aeb1ba | ||
|
|
872095ff7a | ||
|
|
d88f2ea4c3 | ||
|
|
02e0385ab8 | ||
|
|
c9751d4cd9 | ||
|
|
162b637992 | ||
|
|
a10ddd4063 | ||
|
|
f46ccc63a7 | ||
|
|
fc04a45744 | ||
|
|
90c2b59a51 | ||
|
|
d6bee99c1b | ||
|
|
0871d47568 | ||
|
|
5c646c1898 | ||
|
|
8974de165f | ||
|
|
e622294b87 | ||
|
|
cf9d32b556 | ||
|
|
e2d6b5bf64 | ||
|
|
dec58fd6d1 | ||
|
|
dbb2241213 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
/yarn.lock
|
||||
|
||||
.env
|
||||
.env.prod
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM node:16.14.0-alpine
|
||||
RUN apk add --no-cache g++ cmake make python3
|
||||
WORKDIR /app
|
||||
COPY package*.json .
|
||||
RUN yarn install
|
||||
@@ -8,6 +9,8 @@ RUN yarn build
|
||||
FROM node:16.14.0-alpine
|
||||
WORKDIR /app
|
||||
|
||||
LABEL coolify.managed true
|
||||
|
||||
RUN apk add --no-cache git openssh-client curl jq cmake sqlite
|
||||
|
||||
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
||||
|
||||
68
README.md
68
README.md
@@ -2,6 +2,72 @@
|
||||
|
||||
An open-source & self-hostable Heroku / Netlify alternative.
|
||||
|
||||
## Demo instance
|
||||
|
||||
https://demo.coolify.io/
|
||||
|
||||
(If it is unresponsible, that means someone overloaded the server. 🙃)
|
||||
|
||||
## Installation
|
||||
|
||||
Installation is automated with the following command:
|
||||
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
|
||||
```
|
||||
|
||||
## Migration from v1
|
||||
|
||||
A fresh installation is necessary. v2 is not compatible with v1.
|
||||
|
||||
## Features
|
||||
### Git Sources
|
||||
You can use the official ones or your self hosted version!
|
||||
|
||||
- Github
|
||||
- GitLab
|
||||
- Bitbucket (WIP)
|
||||
|
||||
### Destinations
|
||||
|
||||
- Local Docker Engine
|
||||
- Remote Docker Engine (WIP)
|
||||
- Kubernetes (WIP)
|
||||
|
||||
|
||||
### Applications
|
||||
|
||||
- Static sites
|
||||
- NodeJS
|
||||
- VueJS
|
||||
- NuxtJS
|
||||
- NextJS
|
||||
- React/Preact
|
||||
- NextJS
|
||||
- Gatsby
|
||||
- Svelte
|
||||
- PHP
|
||||
- Rust
|
||||
- Dockerfile (you can provide it)
|
||||
|
||||
### Databases
|
||||
|
||||
- MongoDB
|
||||
- MySQL
|
||||
- PostgreSQL
|
||||
- CouchDB
|
||||
- Redis
|
||||
|
||||
### One-click services
|
||||
|
||||
- [WordPress](https://wordpress.org)
|
||||
- [Plausible Analytics](https://plausible.io)
|
||||
- [NocoDB](https://nocodb.com)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||
@@ -11,7 +77,7 @@ An open-source & self-hostable Heroku / Netlify alternative.
|
||||
|
||||
## Roadmap
|
||||
|
||||
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
|
||||
[See the Roadmap here](https://github.com/orgs/coollabsio/projects/3/views/8)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
coolify:
|
||||
image: coollabsio/coolify:latest
|
||||
image: coollabsio/coolify:${TAG:-latest}
|
||||
restart: always
|
||||
container_name: coolify
|
||||
ports:
|
||||
|
||||
69
package.json
69
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.0.3",
|
||||
"version": "2.1.1",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev",
|
||||
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||
"studio": "npx prisma studio",
|
||||
@@ -16,66 +16,71 @@
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"prerelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
||||
"release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
||||
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
|
||||
"release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
||||
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
||||
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
|
||||
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
|
||||
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "1.0.0-next.67",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.27",
|
||||
"@sveltejs/kit": "1.0.0-next.259",
|
||||
"@sveltejs/adapter-node": "1.0.0-next.73",
|
||||
"@sveltejs/kit": "1.0.0-next.303",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/js-cookie": "3.0.1",
|
||||
"@types/node": "17.0.16",
|
||||
"@types/node-forge": "1.0.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node-forge": "1.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||
"@typescript-eslint/parser": "4.31.1",
|
||||
"@zerodevx/svelte-toast": "0.6.3",
|
||||
"autoprefixer": "10.4.2",
|
||||
"@zerodevx/svelte-toast": "0.7.1",
|
||||
"autoprefixer": "10.4.4",
|
||||
"cross-var": "1.1.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-svelte3": "3.2.1",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-svelte3": "3.4.1",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "12.3.3",
|
||||
"postcss": "8.4.6",
|
||||
"prettier": "2.5.1",
|
||||
"lint-staged": "12.3.7",
|
||||
"postcss": "8.4.12",
|
||||
"prettier": "2.6.1",
|
||||
"prettier-plugin-svelte": "2.6.0",
|
||||
"prettier-plugin-tailwindcss": "0.1.7",
|
||||
"prisma": "3.9.1",
|
||||
"prettier-plugin-tailwindcss": "0.1.8",
|
||||
"prisma": "3.11.1",
|
||||
"svelte": "3.46.4",
|
||||
"svelte-check": "2.4.3",
|
||||
"svelte-preprocess": "4.10.3",
|
||||
"tailwindcss": "3.0.19",
|
||||
"ts-node": "10.5.0",
|
||||
"svelte-check": "2.4.6",
|
||||
"svelte-preprocess": "4.10.4",
|
||||
"svelte-select": "^4.4.7",
|
||||
"tailwindcss": "3.0.23",
|
||||
"ts-node": "10.7.0",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "4.6.3"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@prisma/client": "3.9.1",
|
||||
"@sentry/node": "6.17.6",
|
||||
"@prisma/client": "3.11.1",
|
||||
"@sentry/node": "6.19.2",
|
||||
"bcrypt": "5.0.1",
|
||||
"bullmq": "1.69.0",
|
||||
"bullmq": "1.78.1",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.4.2",
|
||||
"cooltipz-css": "^2.1.0",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.10.7",
|
||||
"dayjs": "1.11.0",
|
||||
"dockerode": "3.3.1",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"generate-password": "1.7.0",
|
||||
"get-port": "6.0.0",
|
||||
"got": "12.0.1",
|
||||
"get-port": "6.1.2",
|
||||
"got": "12.0.2",
|
||||
"js-cookie": "3.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"node-forge": "1.2.1",
|
||||
"svelte-kit-cookie-session": "2.0.3",
|
||||
"unique-names-generator": "4.6.0"
|
||||
"mustache": "^4.2.0",
|
||||
"node-forge": "1.3.0",
|
||||
"svelte-kit-cookie-session": "2.1.2",
|
||||
"tailwindcss-scrollbar": "^0.1.0",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed.cjs"
|
||||
|
||||
609
pnpm-lock.yaml
generated
609
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[name,applicationId]` on the table `Secret` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "Secret_name_key";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Secret_name_applicationId_key" ON "Secret"("name", "applicationId");
|
||||
47
prisma/migrations/20220217211304_dualcerts/migration.sql
Normal file
47
prisma/migrations/20220217211304_dualcerts/migration.sql
Normal file
@@ -0,0 +1,47 @@
|
||||
-- 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,
|
||||
"proxyPassword" TEXT NOT NULL,
|
||||
"proxyUser" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("createdAt", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "id", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
CREATE TABLE "new_Service" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"fqdn" TEXT,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"type" TEXT,
|
||||
"version" TEXT,
|
||||
"destinationDockerId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Service_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Service" ("createdAt", "destinationDockerId", "fqdn", "id", "name", "type", "updatedAt", "version") SELECT "createdAt", "destinationDockerId", "fqdn", "id", "name", "type", "updatedAt", "version" FROM "Service";
|
||||
DROP TABLE "Service";
|
||||
ALTER TABLE "new_Service" RENAME TO "Service";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
19
prisma/migrations/20220219231255_prmr_secrets/migration.sql
Normal file
19
prisma/migrations/20220219231255_prmr_secrets/migration.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Secret" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"isPRMRSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isBuildSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
CONSTRAINT "Secret_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Secret" ("applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value") SELECT "applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value" FROM "Secret";
|
||||
DROP TABLE "Secret";
|
||||
ALTER TABLE "new_Secret" RENAME TO "Secret";
|
||||
CREATE UNIQUE INDEX "Secret_name_applicationId_isPRMRSecret_key" ON "Secret"("name", "applicationId", "isPRMRSecret");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 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,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "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;
|
||||
2
prisma/migrations/20220301101928_proxyhash/migration.sql
Normal file
2
prisma/migrations/20220301101928_proxyhash/migration.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Setting" ADD COLUMN "proxyHash" TEXT;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceSecret" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
CONSTRAINT "ServiceSecret_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ServiceSecret_name_serviceId_key" ON "ServiceSecret"("name", "serviceId");
|
||||
19
prisma/migrations/20220311213422_autodeploy/migration.sql
Normal file
19
prisma/migrations/20220311213422_autodeploy/migration.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "phpModules" TEXT;
|
||||
@@ -0,0 +1,18 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ApplicationPersistentStorage" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationPersistentStorage_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_applicationId_key" ON "ApplicationPersistentStorage"("applicationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_path_key" ON "ApplicationPersistentStorage"("path");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_applicationId_path_key" ON "ApplicationPersistentStorage"("applicationId", "path");
|
||||
@@ -11,8 +11,12 @@ model Setting {
|
||||
id String @id @default(cuid())
|
||||
fqdn String? @unique
|
||||
isRegistrationEnabled Boolean @default(false)
|
||||
dualCerts Boolean @default(false)
|
||||
minPort Int @default(9000)
|
||||
maxPort Int @default(9100)
|
||||
proxyPassword String
|
||||
proxyUser String
|
||||
proxyHash String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
@@ -51,9 +55,9 @@ model Team {
|
||||
permissions Permission[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
database Database[] @relation(fields: [databaseId], references: [id])
|
||||
database Database[] @relation(references: [id])
|
||||
databaseId String?
|
||||
service Service[] @relation(fields: [serviceId], references: [id])
|
||||
service Service[] @relation(references: [id])
|
||||
serviceId String?
|
||||
}
|
||||
|
||||
@@ -68,9 +72,9 @@ model TeamInvitation {
|
||||
}
|
||||
|
||||
model Application {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String? @unique
|
||||
fqdn String? @unique
|
||||
repository String?
|
||||
configHash String?
|
||||
branch String?
|
||||
@@ -82,36 +86,66 @@ model Application {
|
||||
startCommand String?
|
||||
baseDirectory String?
|
||||
publishDirectory String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
phpModules String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
settings ApplicationSettings?
|
||||
teams Team[]
|
||||
destinationDockerId String?
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
gitSourceId String?
|
||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||
secrets Secret[]
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
}
|
||||
|
||||
model ApplicationSettings {
|
||||
id String @id @default(cuid())
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String @unique
|
||||
dualCerts Boolean @default(false)
|
||||
debug Boolean @default(false)
|
||||
previews Boolean @default(false)
|
||||
autodeploy Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model ApplicationPersistentStorage {
|
||||
id String @id @default(cuid())
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String @unique
|
||||
path String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([applicationId, path])
|
||||
}
|
||||
|
||||
model Secret {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
name String
|
||||
value String
|
||||
isPRMRSecret Boolean @default(false)
|
||||
isBuildSecret Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String
|
||||
|
||||
@@unique([name, applicationId, isPRMRSecret])
|
||||
}
|
||||
|
||||
model ServiceSecret {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
value String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
serviceId String
|
||||
|
||||
@@unique([name, serviceId])
|
||||
}
|
||||
|
||||
model BuildLog {
|
||||
@@ -232,6 +266,7 @@ model Service {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String?
|
||||
dualCerts Boolean @default(false)
|
||||
type String?
|
||||
version String?
|
||||
teams Team[]
|
||||
@@ -243,6 +278,7 @@ model Service {
|
||||
minio Minio?
|
||||
vscodeserver Vscodeserver?
|
||||
wordpress Wordpress?
|
||||
serviceSecret ServiceSecret[]
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
|
||||
@@ -27,6 +27,15 @@ async function main() {
|
||||
proxyUser: cuid()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await prisma.setting.update({
|
||||
where: {
|
||||
id: settingsFound.id
|
||||
},
|
||||
data: {
|
||||
proxyHash: null
|
||||
}
|
||||
});
|
||||
}
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine: '/var/run/docker.sock' }
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
clear
|
||||
ARG1=$1
|
||||
WHO=$(whoami)
|
||||
APP_ID=$(cat /proc/sys/kernel/random/uuid)
|
||||
RANDOM_SECRET=$(echo $(($(date +%s%N) / 1000000)) | sha256sum | base64 | head -c 32)
|
||||
SENTRY_DSN="https://9e7a74326f29422584d2d0bebdc8b7d3@o1082494.ingest.sentry.io/6091062"
|
||||
DOCKER_MAJOR=20
|
||||
DOCKER_MINOR=10
|
||||
DOCKER_VERSION_OK="nok"
|
||||
|
||||
set -eou pipefail
|
||||
|
||||
if [ $ARG1 ] && [ $ARG1 == "-d" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
function errorchecker() {
|
||||
exitCode=$?
|
||||
if [ $exitCode -ne "0" ]; then
|
||||
echo "$0 exited unexpectedly with status: $exitCode"
|
||||
exit $exitCode
|
||||
fi
|
||||
}
|
||||
trap 'errorchecker' EXIT
|
||||
|
||||
echo -e "Welcome to Coolify installer! \n"
|
||||
echo "This script will install all the required packages and services to run Coolify."
|
||||
echo -e "If you want to install Coolify on a different OS, please open an issue on Github to get supported version.\n\n"
|
||||
|
||||
echo -e "To see what I'm doing, please check:"
|
||||
echo -e "https://github.com/coollabsio/get.coollabs.io/blob/main/static/coolify/install_v2.sh\n\n"
|
||||
|
||||
if [ $WHO != 'root' ]; then
|
||||
echo 'Run as root please: sudo sh -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$(command -v docker)" ]; then
|
||||
while true; do
|
||||
read -p "Docker Engine not found, should I install it automatically? [Yy/Nn] " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
sh -c "$(curl -fsSL https://get.docker.com)"
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
echo "Please install docker manually and update it to the latest, but at least to $DOCKER_MAJOR.$DOCKER_MINOR"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Please answer Y or N." ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
SERVER_VERSION=$(docker version -f "{{.Server.Version}}")
|
||||
SERVER_VERSION_MAJOR=$(echo "$SERVER_VERSION" | cut -d'.' -f 1)
|
||||
SERVER_VERSION_MINOR=$(echo "$SERVER_VERSION" | cut -d'.' -f 2)
|
||||
|
||||
if [ "$SERVER_VERSION_MAJOR" -ge "$DOCKER_MAJOR" ] &&
|
||||
[ "$SERVER_VERSION_MINOR" -ge "$DOCKER_MINOR" ]; then
|
||||
DOCKER_VERSION_OK="ok"
|
||||
fi
|
||||
|
||||
if [ $DOCKER_VERSION_OK == 'nok' ]; then
|
||||
echo "Docker version less than $DOCKER_MAJOR.$DOCKER_MINOR, please update it to at least to $DOCKER_MAJOR.$DOCKER_MINOR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Adding docker daemon configuration
|
||||
cat <<EOF >/etc/docker/daemon.json
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "100m",
|
||||
"max-file": "5"
|
||||
},
|
||||
"features": {
|
||||
"buildkit": true
|
||||
},
|
||||
"live-restore": true
|
||||
}
|
||||
EOF
|
||||
|
||||
# Restarting docker daemon
|
||||
sh -c "systemctl daemon-reload && systemctl restart docker"
|
||||
|
||||
# Downloading docker compose cli plugin
|
||||
mkdir -p ~/.docker/cli-plugins/
|
||||
curl -SL https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
|
||||
chmod +x ~/.docker/cli-plugins/docker-compose
|
||||
|
||||
# Making base directory for coolify
|
||||
if [ ! -d coolify ]; then
|
||||
mkdir coolify
|
||||
fi
|
||||
|
||||
if [ -f coolify/.env ]; then
|
||||
echo -e "Coolify is already installed, using some of the existing settings."
|
||||
else
|
||||
echo "COOLIFY_APP_ID=$APP_ID
|
||||
COOLIFY_SECRET_KEY=$RANDOM_SECRET
|
||||
COOLIFY_DATABASE_URL=file:../db/prod.db
|
||||
COOLIFY_SENTRY_DSN=$SENTRY_DSN
|
||||
COOLIFY_HOSTED_ON=docker" > coolify/.env
|
||||
fi
|
||||
|
||||
cd coolify && docker run -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db-sqlite coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker compose up -d --force-recreate"
|
||||
|
||||
echo -e "Congratulations! Your coolify is ready to use.\n"
|
||||
echo "Please visit http://<Your Public IP Address>:3000/ to get started."
|
||||
echo "It will take a few minutes to start up, don't worry."
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
clear
|
||||
ARG1=$1
|
||||
WHO=$(whoami)
|
||||
APP_ID=$(cat /proc/sys/kernel/random/uuid)
|
||||
RANDOM_SECRET=$(echo $(($(date +%s%N) / 1000000)) | sha256sum | base64 | head -c 32)
|
||||
SENTRY_DSN="https://9e7a74326f29422584d2d0bebdc8b7d3@o1082494.ingest.sentry.io/6091062"
|
||||
|
||||
UBUNTU_MAJOR_MIN=20
|
||||
UBUNTU_MINOR_MIN=04
|
||||
OS_OK="nok"
|
||||
|
||||
set -eou pipefail
|
||||
|
||||
if [ $ARG1 ] && [ $ARG1 == "-d" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
function errorchecker() {
|
||||
exitCode=$?
|
||||
if [ $exitCode -ne "0" ]; then
|
||||
echo "$0 exited unexpectedly with status: $exitCode"
|
||||
exit $exitCode
|
||||
fi
|
||||
}
|
||||
trap 'errorchecker' EXIT
|
||||
|
||||
if [ $WHO != 'root' ]; then
|
||||
echo 'Run as root please: sudo sh -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
. /etc/lsb-release
|
||||
if [ $DISTRIB_ID != 'Ubuntu' ]; then
|
||||
echo 'Not supported OS, please open an issue on Github to get supported version.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DISTRIB_RELEASE_MAJOR=$(echo "$DISTRIB_RELEASE" | cut -d'.' -f 1)
|
||||
DISTRIB_RELEASE_MINOR=$(echo "$DISTRIB_RELEASE" | cut -d'.' -f 2)
|
||||
|
||||
if [ "$DISTRIB_RELEASE_MAJOR" -ge "$UBUNTU_MAJOR_MIN" ] &&
|
||||
[ "$DISTRIB_RELEASE_MINOR" -ge "$UBUNTU_MINOR_MIN" ]; then
|
||||
OS_OK="ok"
|
||||
fi
|
||||
|
||||
if [ $OS_OK == 'nok' ]; then
|
||||
echo "Ubuntu version less than $UBUNTU_MAJOR_MIN.$UBUNTU_MINOR_MIN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function installPodman() {
|
||||
apt-get update -y
|
||||
apt-get install curl wget gnupg2 -y
|
||||
if [ "$DISTRIB_RELEASE_MAJOR" -eq "20" ] && [ "$DISTRIB_RELEASE_MINOR" -eq "04" ]; then
|
||||
echo 'Installing on 20.04'
|
||||
source /etc/os-release
|
||||
sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- | apt-key add -
|
||||
apt-get update -y
|
||||
apt-get -y install podman
|
||||
return 0
|
||||
elif [ "$DISTRIB_RELEASE_MAJOR" -eq "20" ] && [ "$DISTRIB_RELEASE_MINOR" -eq "10" ]; then
|
||||
apt-get -y install podman
|
||||
return 0
|
||||
elif [ "$DISTRIB_RELEASE_MAJOR" -gt "20" ]; then
|
||||
apt-get -y install podman
|
||||
return 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
if [ ! -x "$(command -v podman)" ]; then
|
||||
while true; do
|
||||
read -p "Podman not found, should I install it automatically? [Yy/Nn] " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
installPodman
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
echo "Please install docker manually and update it to the latest, but at least to $DOCKER_MAJOR.$DOCKER_MINOR"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Please answer Yy or Nn." ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Making base directory for coolify
|
||||
if [ ! -d coolify ]; then
|
||||
mkdir coolify
|
||||
fi
|
||||
|
||||
echo "COOLIFY_APP_ID=$APP_ID
|
||||
COOLIFY_SECRET_KEY=$RANDOM_SECRET
|
||||
COOLIFY_DATABASE_URL=file:../db/prod.db
|
||||
COOLIFY_SENTRY_DSN=$SENTRY_DSN
|
||||
COOLIFY_HOSTED_ON=docker" >coolify/.env
|
||||
|
||||
systemctl start podman.socket
|
||||
systemctl enable podman.socket
|
||||
|
||||
podman volume create coolify-db
|
||||
podman volume create coolify-ssl-certs
|
||||
podman volume create coolify-letsencrypt
|
||||
|
||||
|
||||
cd coolify && podman run --privileged -tid --env-file .env -v /var/run/podman/podman.sock:/var/run/podman/podman.sock -v coolify-db-sqlite:/app/db docker.io/coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker-compose up -d --force-recreate"
|
||||
echo "Done"
|
||||
exit 0
|
||||
109
src/global.d.ts → src/app.d.ts
vendored
109
src/global.d.ts → src/app.d.ts
vendored
@@ -1,74 +1,30 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
interface Cookies {
|
||||
teamId?: string;
|
||||
gitlabToken?: string;
|
||||
'kit.session'?: string;
|
||||
}
|
||||
interface Locals {
|
||||
gitlabToken?: string;
|
||||
user: {
|
||||
teamId: string;
|
||||
permission: string;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
session: {
|
||||
data: {
|
||||
uid?: string;
|
||||
teams?: string[];
|
||||
expires?: string;
|
||||
};
|
||||
};
|
||||
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
session: import('svelte-kit-cookie-session').Session<SessionData>;
|
||||
cookies: Record<string, string>;
|
||||
}
|
||||
interface Platform {}
|
||||
interface Session extends SessionData {}
|
||||
interface Stuff {
|
||||
application: any;
|
||||
isRunning: boolean;
|
||||
appId: string;
|
||||
}
|
||||
}
|
||||
|
||||
type Applications = {
|
||||
name: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
interface Hash {
|
||||
iv: string;
|
||||
content: string;
|
||||
interface SessionData {
|
||||
version?: string;
|
||||
userId?: string | null;
|
||||
teamId?: string | null;
|
||||
permission?: string;
|
||||
isAdmin?: boolean;
|
||||
expires?: string | null;
|
||||
gitlabToken?: string | null;
|
||||
ghToken?: string | null;
|
||||
}
|
||||
|
||||
interface BuildPack {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// TODO: Not used, not working what?!
|
||||
enum GitSource {
|
||||
Github = 'github',
|
||||
Gitlab = 'gitlab',
|
||||
Bitbucket = 'bitbucket'
|
||||
}
|
||||
|
||||
type RawHaproxyConfiguration = {
|
||||
_version: number;
|
||||
data: string;
|
||||
};
|
||||
|
||||
type NewTransaction = {
|
||||
_version: number;
|
||||
id: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
type HttpRequestRuleForceSSL = {
|
||||
return_hdrs: null;
|
||||
cond: string;
|
||||
cond_test: string;
|
||||
index: number;
|
||||
redir_code: number;
|
||||
redir_type: string;
|
||||
redir_value: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
// TODO: No any please
|
||||
type HttpRequestRule = {
|
||||
_version: number;
|
||||
data: Array<any>;
|
||||
};
|
||||
|
||||
type DateTimeFormatOptions = {
|
||||
localeMatcher?: 'lookup' | 'best fit';
|
||||
weekday?: 'long' | 'short' | 'narrow';
|
||||
@@ -84,3 +40,24 @@ type DateTimeFormatOptions = {
|
||||
hour12?: boolean;
|
||||
timeZone?: string;
|
||||
};
|
||||
|
||||
interface Hash {
|
||||
iv: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
type RawHaproxyConfiguration = {
|
||||
_version: number;
|
||||
data: string;
|
||||
};
|
||||
|
||||
type NewTransaction = {
|
||||
_version: number;
|
||||
id: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
type Application = {
|
||||
name: string;
|
||||
domain: string;
|
||||
};
|
||||
@@ -4,9 +4,6 @@
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
|
||||
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
|
||||
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/poppins/poppins.css" />
|
||||
<title>Coolify</title>
|
||||
%svelte.head%
|
||||
</head>
|
||||
|
||||
43
src/hooks.ts
43
src/hooks.ts
@@ -2,7 +2,7 @@ import dotEnvExtended from 'dotenv-extended';
|
||||
dotEnvExtended.load();
|
||||
import type { GetSession } from '@sveltejs/kit';
|
||||
import { handleSession } from 'svelte-kit-cookie-session';
|
||||
import { getUserDetails, isTeamIdTokenAvailable, sentry } from '$lib/common';
|
||||
import { getUserDetails, sentry } from '$lib/common';
|
||||
import { version } from '$lib/common';
|
||||
import cookie from 'cookie';
|
||||
import { dev } from '$app/env';
|
||||
@@ -10,27 +10,34 @@ import { dev } from '$app/env';
|
||||
export const handle = handleSession(
|
||||
{
|
||||
secret: process.env['COOLIFY_SECRET_KEY'],
|
||||
expires: 30
|
||||
expires: 30,
|
||||
cookie: { secure: false }
|
||||
},
|
||||
async function ({ event, resolve }) {
|
||||
let response;
|
||||
try {
|
||||
const cookies: Cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
||||
if (cookies['kit.session']) {
|
||||
const { permission, teamId } = await getUserDetails(event, false);
|
||||
event.locals.user = {
|
||||
teamId,
|
||||
permission,
|
||||
isAdmin: permission === 'admin' || permission === 'owner'
|
||||
};
|
||||
}
|
||||
if (cookies.gitlabToken) {
|
||||
event.locals.gitlabToken = cookies.gitlabToken;
|
||||
if (event.locals.cookies) {
|
||||
if (event.locals.cookies['kit.session']) {
|
||||
const { permission, teamId, userId } = await getUserDetails(event, false);
|
||||
const newSession = {
|
||||
userId,
|
||||
teamId,
|
||||
permission,
|
||||
isAdmin: permission === 'admin' || permission === 'owner',
|
||||
expires: event.locals.session.data.expires
|
||||
};
|
||||
|
||||
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {
|
||||
event.locals.session.data = { ...newSession };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response = await resolve(event, {
|
||||
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response = await resolve(event, {
|
||||
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
||||
});
|
||||
@@ -61,17 +68,13 @@ export const handle = handleSession(
|
||||
}
|
||||
);
|
||||
|
||||
export const getSession: GetSession = function (request) {
|
||||
export const getSession: GetSession = function ({ locals }) {
|
||||
return {
|
||||
version,
|
||||
gitlabToken: request.locals?.gitlabToken || null,
|
||||
uid: request.locals.session.data?.uid || null,
|
||||
teamId: request.locals.user?.teamId || null,
|
||||
permission: request.locals.user?.permission,
|
||||
isAdmin: request.locals.user?.isAdmin || false
|
||||
...locals.session.data
|
||||
};
|
||||
};
|
||||
|
||||
export async function handleError({ error, event }) {
|
||||
if (!dev) sentry.captureException(error, { event });
|
||||
if (!dev) sentry.captureException(error, event);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ export function makeLabelForStandaloneApplication({
|
||||
fqdn = `${protocol}://${pullmergeRequestId}.${domain}`;
|
||||
}
|
||||
return [
|
||||
'--label coolify.managed=true',
|
||||
`--label coolify.version=${version}`,
|
||||
`--label coolify.type=standalone-application`,
|
||||
`--label coolify.configuration=${base64Encode(
|
||||
'coolify.managed=true',
|
||||
`coolify.version=${version}`,
|
||||
`coolify.type=standalone-application`,
|
||||
`coolify.configuration=${base64Encode(
|
||||
JSON.stringify({
|
||||
applicationId,
|
||||
fqdn,
|
||||
@@ -74,31 +74,25 @@ export async function makeLabelForStandaloneDatabase({ id, image, volume }) {
|
||||
];
|
||||
}
|
||||
|
||||
export async function makeLabelForPlausibleAnalytics({ id, images, volume }) {
|
||||
const service = await db.prisma.service.findFirst({
|
||||
where: { id },
|
||||
include: { plausibleAnalytics: true }
|
||||
});
|
||||
delete service.destinationDockerId;
|
||||
delete service.createdAt;
|
||||
delete service.updatedAt;
|
||||
export function makeLabelForServices(type) {
|
||||
return [
|
||||
'coolify.managed=true',
|
||||
`coolify.version=${version}`,
|
||||
`coolify.type=service-plausibleanalytics`,
|
||||
`coolify.configuration=${base64Encode(
|
||||
JSON.stringify({
|
||||
version,
|
||||
images,
|
||||
volume,
|
||||
...service
|
||||
})
|
||||
)}`
|
||||
`coolify.type=service`,
|
||||
`coolify.service.type=${type}`
|
||||
];
|
||||
}
|
||||
|
||||
export const setDefaultConfiguration = async (data) => {
|
||||
let { buildPack, port, installCommand, startCommand, buildCommand, publishDirectory } = data;
|
||||
let {
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
} = data;
|
||||
const template = scanningTemplates[buildPack];
|
||||
if (!port) {
|
||||
port = template?.port || 3000;
|
||||
@@ -111,6 +105,10 @@ export const setDefaultConfiguration = async (data) => {
|
||||
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
||||
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||
if (baseDirectory) {
|
||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
if (!baseDirectory.endsWith('/')) baseDirectory = `${baseDirectory}/`;
|
||||
}
|
||||
|
||||
return {
|
||||
buildPack,
|
||||
@@ -118,7 +116,8 @@ export const setDefaultConfiguration = async (data) => {
|
||||
installCommand,
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
};
|
||||
};
|
||||
|
||||
@@ -136,6 +135,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
RewriteRule ^(.+)$ index.php [QSA,L]
|
||||
`
|
||||
);
|
||||
await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`);
|
||||
saveBuildLog({ line: 'Copied default configuration file for PHP.', buildId, applicationId });
|
||||
} else if (staticDeployments.includes(buildPack)) {
|
||||
await fs.writeFile(
|
||||
@@ -143,27 +143,35 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
`user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
error_log /docker.stdout;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
access_log off;
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
keepalive_timeout 65;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /docker.stdout main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/index.html $uri/ /index.html =404;
|
||||
}
|
||||
@@ -174,7 +182,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
root /app;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -189,3 +197,11 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function checkPnpm(installCommand = null, buildCommand = null, startCommand = null) {
|
||||
return (
|
||||
installCommand?.includes('pnpm') ||
|
||||
buildCommand?.includes('pnpm') ||
|
||||
startCommand?.includes('pnpm')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ export default async function ({
|
||||
docker,
|
||||
buildId,
|
||||
baseDirectory,
|
||||
secrets
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
}) {
|
||||
try {
|
||||
let file = `${workdir}/Dockerfile`;
|
||||
if (baseDirectory) {
|
||||
file = `${workdir}/${baseDirectory}/Dockerfile`;
|
||||
workdir = `${workdir}/${baseDirectory}`;
|
||||
}
|
||||
|
||||
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
||||
@@ -24,7 +26,15 @@ export default async function ({
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,16 +6,17 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${imageforBuild}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
|
||||
@@ -10,6 +10,8 @@ import nuxtjs from './nuxtjs';
|
||||
import vuejs from './vuejs';
|
||||
import php from './php';
|
||||
import rust from './rust';
|
||||
import astro from './static';
|
||||
import eleventy from './static';
|
||||
|
||||
export {
|
||||
node,
|
||||
@@ -23,5 +25,7 @@ export {
|
||||
nuxtjs,
|
||||
vuejs,
|
||||
php,
|
||||
rust
|
||||
rust,
|
||||
astro,
|
||||
eleventy
|
||||
};
|
||||
|
||||
@@ -4,12 +4,17 @@ import { promises as fs } from 'fs';
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = startCommand.includes('pnpm');
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push(
|
||||
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./`
|
||||
);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
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(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`);
|
||||
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
||||
data;
|
||||
const {
|
||||
workdir,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
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(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
||||
data;
|
||||
const {
|
||||
workdir,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
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(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
||||
data;
|
||||
const {
|
||||
workdir,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
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(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
|
||||
@@ -4,19 +4,19 @@ import { promises as fs } from 'fs';
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, baseDirectory } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('RUN a2enmod rewrite');
|
||||
Dockerfile.push('WORKDIR /var/www/html');
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`);
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} /app`);
|
||||
Dockerfile.push(`COPY /.htaccess .`);
|
||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["apache2-foreground"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'php:apache';
|
||||
const image = 'webdevops/php-nginx';
|
||||
await createDockerfile(data, image);
|
||||
await buildImage(data);
|
||||
} catch (error) {
|
||||
|
||||
@@ -6,16 +6,17 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
await createDockerfile(data, image);
|
||||
|
||||
@@ -7,22 +7,21 @@ const createDockerfile = async (data, image, name): Promise<void> => {
|
||||
const { workdir, port, applicationId, tag } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target target`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target target`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`);
|
||||
Dockerfile.push(`COPY . .`);
|
||||
Dockerfile.push(`RUN cargo build --release --bin ${name}`);
|
||||
Dockerfile.push('FROM debian:buster-slim');
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(
|
||||
`RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*`
|
||||
);
|
||||
Dockerfile.push(`RUN update-ca-certificates`);
|
||||
Dockerfile.push(
|
||||
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target/release/${name} ${name}`
|
||||
);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ["/usr/src/app/${name}"]`);
|
||||
Dockerfile.push(`CMD ["/app/${name}"]`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
||||
@@ -2,34 +2,49 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { applicationId, tag, workdir, buildCommand, baseDirectory, publishDirectory, secrets } =
|
||||
data;
|
||||
const {
|
||||
applicationId,
|
||||
tag,
|
||||
workdir,
|
||||
buildCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(
|
||||
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`
|
||||
);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
} else {
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
}
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
if (data.buildCommand) await buildCacheImageWithNode(data, imageForBuild);
|
||||
await createDockerfile(data, image);
|
||||
|
||||
@@ -6,16 +6,17 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
|
||||
@@ -6,16 +6,17 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /usr/share/nginx/html');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'nginx:stable-alpine';
|
||||
const image = 'webdevops/nginx:alpine';
|
||||
const imageForBuild = 'node:lts';
|
||||
await buildCacheImageWithNode(data, imageForBuild);
|
||||
await createDockerfile(data, image);
|
||||
|
||||
@@ -8,16 +8,27 @@ import * as db from '$lib/database';
|
||||
import { buildLogQueue } from './queues';
|
||||
|
||||
import { version as currentVersion } from '../../package.json';
|
||||
import { dockerInstance } from './docker';
|
||||
import dayjs from 'dayjs';
|
||||
import Cookie from 'cookie';
|
||||
import os from 'os';
|
||||
import cuid from 'cuid';
|
||||
|
||||
try {
|
||||
if (!dev) {
|
||||
Sentry.init({
|
||||
dsn: process.env['COOLIFY_SENTRY_DSN'],
|
||||
tracesSampleRate: 0,
|
||||
environment: 'production'
|
||||
environment: 'production',
|
||||
debug: true,
|
||||
release: currentVersion,
|
||||
initialScope: {
|
||||
tags: {
|
||||
appId: process.env['COOLIFY_APP_ID'],
|
||||
'os.arch': os.arch(),
|
||||
'os.platform': os.platform(),
|
||||
'os.release': os.release()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -57,19 +68,18 @@ export const isTeamIdTokenAvailable = (request) => {
|
||||
};
|
||||
|
||||
export const getTeam = (event) => {
|
||||
const cookies: Cookies = Cookie.parse(event.request.headers.get('cookie'));
|
||||
if (cookies.teamId) {
|
||||
const cookies = Cookie.parse(event.request.headers.get('cookie'));
|
||||
if (cookies?.teamId) {
|
||||
return cookies.teamId;
|
||||
} else if (event.locals.session.data.teamId) {
|
||||
} else if (event.locals.session.data?.teamId) {
|
||||
return event.locals.session.data.teamId;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||
// try {
|
||||
const teamId = getTeam(event);
|
||||
const userId = event.locals.session.data.uid || null;
|
||||
const userId = event.locals.session.data.userId || null;
|
||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
||||
where: { teamId, userId },
|
||||
select: { permission: true },
|
||||
@@ -91,24 +101,17 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||
}
|
||||
|
||||
return payload;
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// return {
|
||||
// teamId: null,
|
||||
// userId: null,
|
||||
// permission: 'read',
|
||||
// status: 401,
|
||||
// body: {
|
||||
// message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
};
|
||||
|
||||
export function getEngine(engine) {
|
||||
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : `tcp://${engine}:2375`;
|
||||
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
|
||||
}
|
||||
|
||||
// export async function saveSshKey(destination) {
|
||||
// return await asyncExecShell(
|
||||
// `echo '${destination.sshPrivateKey}' > /tmp/id_rsa_${destination.id} && chmod 600 /tmp/id_rsa_${destination.id}`
|
||||
// );
|
||||
// }
|
||||
export async function removeContainer(id, engine) {
|
||||
const host = getEngine(engine);
|
||||
try {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script>
|
||||
import { browser } from '$app/env';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
export let value;
|
||||
let showPassword = false;
|
||||
|
||||
export let value;
|
||||
export let disabled = false;
|
||||
export let isPasswordField = false;
|
||||
export let readonly = false;
|
||||
@@ -14,34 +14,25 @@
|
||||
export let name;
|
||||
export let placeholder = '';
|
||||
|
||||
let disabledClass = 'bg-coolback disabled:bg-coolblack select-all';
|
||||
let actionsShow = false;
|
||||
let disabledClass = 'bg-coolback disabled:bg-coolblack';
|
||||
let isHttps = browser && window.location.protocol === 'https:';
|
||||
|
||||
function showActions(value) {
|
||||
actionsShow = value;
|
||||
// if (value === false) {
|
||||
// showPassword = false;
|
||||
// }
|
||||
}
|
||||
function copyToClipboard() {
|
||||
if (isHttps && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(value);
|
||||
toast.push('Copied to clipboard');
|
||||
toast.push('Copied to clipboard.');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="relative"
|
||||
on:mouseenter={() => showActions(true)}
|
||||
on:mouseleave={() => showActions(false)}
|
||||
>
|
||||
<div class="relative">
|
||||
{#if !isPasswordField || showPassword}
|
||||
{#if textarea}
|
||||
<textarea
|
||||
rows="3"
|
||||
rows="5"
|
||||
class={disabledClass}
|
||||
class:pr-10={true}
|
||||
class:pr-20={value && isHttps}
|
||||
{placeholder}
|
||||
type="text"
|
||||
{id}
|
||||
@@ -55,6 +46,8 @@
|
||||
<input
|
||||
class={disabledClass}
|
||||
type="text"
|
||||
class:pr-10={true}
|
||||
class:pr-20={value && isHttps}
|
||||
{id}
|
||||
{name}
|
||||
{required}
|
||||
@@ -68,6 +61,8 @@
|
||||
{:else}
|
||||
<input
|
||||
class={disabledClass}
|
||||
class:pr-10={true}
|
||||
class:pr-20={value && isHttps}
|
||||
type="password"
|
||||
{id}
|
||||
{name}
|
||||
@@ -80,69 +75,67 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if actionsShow}
|
||||
<div class="absolute top-0 right-0 mx-2 cursor-pointer text-warmGray-600 hover:text-white">
|
||||
<div class="flex space-x-2">
|
||||
{#if isPasswordField}
|
||||
<div on:click={() => (showPassword = !showPassword)}>
|
||||
{#if showPassword}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if value && isHttps}
|
||||
<div on:click={copyToClipboard}>
|
||||
<div class="absolute top-0 right-0 m-3 cursor-pointer text-stone-600 hover:text-white">
|
||||
<div class="flex space-x-2">
|
||||
{#if isPasswordField}
|
||||
<div on:click={() => (showPassword = !showPassword)}>
|
||||
{#if showPassword}
|
||||
<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"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<rect x="8" y="8" width="12" height="12" rx="2" />
|
||||
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if value && isHttps}
|
||||
<div on:click={copyToClipboard}>
|
||||
<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" />
|
||||
<rect x="8" y="8" width="12" height="12" rx="2" />
|
||||
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
export let text;
|
||||
export let maxWidthClass = 'max-w-[24rem]';
|
||||
export let customClass = 'max-w-[24rem]';
|
||||
</script>
|
||||
|
||||
<div class="py-1 text-xs text-stone-400 {maxWidthClass}">{@html text}</div>
|
||||
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<span class="loader" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class=" main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
|
||||
<div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
|
||||
<span class="loader" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -4,21 +4,23 @@
|
||||
export let setting;
|
||||
export let title;
|
||||
export let description;
|
||||
export let isPadding = true;
|
||||
export let isCenter = true;
|
||||
export let disabled = false;
|
||||
// export let disabledReason = '';
|
||||
export let dataTooltip = null;
|
||||
</script>
|
||||
|
||||
<li class="flex items-center py-4">
|
||||
<div class="flex w-96 flex-col" class:px-4={isPadding} class:pr-32={!isPadding}>
|
||||
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
|
||||
<div class="flex items-center py-4 pr-8">
|
||||
<div class="flex w-96 flex-col">
|
||||
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
|
||||
<Explainer text={description} />
|
||||
</div>
|
||||
<!-- {#if disabled}
|
||||
<div class="flex" class:px-4={isPadding} class:pr-32={!isPadding}>
|
||||
<p class="text-xs font-bold text-stone-400">{disabledReason}</p>
|
||||
</div>
|
||||
{:else} -->
|
||||
</div>
|
||||
<div
|
||||
class:tooltip={dataTooltip}
|
||||
class:text-center={isCenter}
|
||||
data-tooltip={dataTooltip}
|
||||
class="flex justify-center"
|
||||
>
|
||||
<div
|
||||
type="button"
|
||||
on:click
|
||||
@@ -64,5 +66,4 @@
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- {/if} -->
|
||||
</li>
|
||||
</div>
|
||||
|
||||
@@ -9,25 +9,21 @@ export const dateOptions: DateTimeFormatOptions = {
|
||||
hour12: false
|
||||
};
|
||||
|
||||
export async function getGithubToken({ apiUrl, application, githubToken }): Promise<void> {
|
||||
const response = await fetch(
|
||||
`${apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${githubToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Git Source not configured.');
|
||||
}
|
||||
const data = await response.json();
|
||||
return data.token;
|
||||
}
|
||||
export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php'];
|
||||
export const staticDeployments = [
|
||||
'react',
|
||||
'vuejs',
|
||||
'static',
|
||||
'svelte',
|
||||
'gatsby',
|
||||
'php',
|
||||
'astro',
|
||||
'eleventy'
|
||||
];
|
||||
export const notNodeDeployments = ['php', 'docker', 'rust'];
|
||||
|
||||
export function getDomain(domain) {
|
||||
return domain?.replace('https://', '').replace('http://', '');
|
||||
}
|
||||
export function generateRemoteEngine(destination) {
|
||||
return `ssh://${destination.user}@${destination.ipAddress}:${destination.port}`;
|
||||
}
|
||||
|
||||
21
src/lib/components/svg/applications/Astro.svelte
Normal file
21
src/lib/components/svg/applications/Astro.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-6 h-14 w-14"
|
||||
viewBox="0 0 256 256"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
id="a"
|
||||
fill="#302649"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
|
||||
/>
|
||||
<path
|
||||
id="flame"
|
||||
fill="#EF661E"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
6
src/lib/components/svg/applications/Eleventy.svelte
Normal file
6
src/lib/components/svg/applications/Eleventy.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
|
||||
<path fill="transparent" d="M18 0h92v128H18z" /><path
|
||||
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
27
src/lib/components/svg/services/LanguageTool.svelte
Normal file
27
src/lib/components/svg/services/LanguageTool.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||
fill="none"
|
||||
viewBox="0 0 140 140"
|
||||
data-lt-extension-installed="true"
|
||||
><g clip-path="url(#clip0)"
|
||||
><path
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
d="M140 43.602c0-1.662.001-3.324-.01-4.987-.008-1.4-.024-2.8-.062-4.2-.082-3.05-.262-6.126-.805-9.142-.55-3.06-1.448-5.907-2.864-8.688A29.227 29.227 0 0 0 123.476 3.81c-2.783-1.416-5.634-2.314-8.697-2.864-3.016-.542-6.094-.722-9.144-.804-1.4-.038-2.801-.054-4.202-.063C99.77.068 98.107.07 96.444.07L77.135 0H62.694L43.726.07c-1.666 0-3.332-.002-4.998.008-1.404.01-2.807.025-4.21.063-3.058.082-6.142.262-9.166.805-3.067.55-5.922 1.447-8.709 2.862a29.293 29.293 0 0 0-7.419 5.377 29.223 29.223 0 0 0-5.389 7.4c-1.42 2.78-2.32 5.63-2.871 8.691-.543 3.016-.723 6.091-.806 9.14-.038 1.4-.054 2.8-.062 4.2C.086 40.277 0 42.342 0 44.004v33.3l.086 19.102c0 1.665 0 3.33.01 4.994a200.6 200.6 0 0 0 .062 4.205c.083 3.054.263 6.135.807 9.155.551 3.064 1.451 5.916 2.87 8.7a29.294 29.294 0 0 0 12.807 12.794c2.788 1.418 5.645 2.317 8.714 2.868 3.022.542 6.105.722 9.162.804 1.403.038 2.806.054 4.21.063 1.666.01 3.332.009 4.998.009l19.14.001h14.477l19.101-.001c1.663 0 3.326.001 4.989-.009a202.92 202.92 0 0 0 4.202-.063c3.052-.082 6.13-.262 9.148-.805 3.061-.551 5.911-1.45 8.692-2.867a29.215 29.215 0 0 0 7.405-5.384 29.22 29.22 0 0 0 5.378-7.409c1.417-2.785 2.315-5.639 2.866-8.704.542-3.02.722-6.099.804-9.152.038-1.402.054-2.804.062-4.205.011-1.665.01-3.33.01-4.993l-.001-19.103V62.694L140 43.602"
|
||||
clip-rule="evenodd"
|
||||
/><path
|
||||
fill="#000"
|
||||
fill-rule="evenodd"
|
||||
d="M39.375 40.188h8.313a6.25 6.25 0 0 1 6.25 6.25v24.25h16.25v8.75h-18.75a6.25 6.25 0 0 1-6.25-6.25v-24.25h-5.813v-8.75zm63.563 6.25v6.5h-8.75v-4h-6.876v30.5h-8.75v-30.5h-6.874v4h-8.75v-6.5a6.25 6.25 0 0 1 6.25-6.25h27.5a6.25 6.25 0 0 1 6.25 6.25z"
|
||||
clip-rule="evenodd"
|
||||
/><path
|
||||
fill="#239AFF"
|
||||
d="M35.319 102.906l-8.138-5.812c2.39-3.347 4.857-5.936 7.452-7.753 2.884-2.018 5.948-3.091 9.117-3.091 2.942 0 5.491.714 7.768 2.08a17.622 17.622 0 0 1 2.615 1.94c.589.518 1.009.926 1.903 1.82 1.355 1.354 1.917 1.851 2.591 2.255.731.439 1.503.655 2.623.655 1.121 0 1.896-.217 2.631-.657.677-.405 1.245-.905 2.6-2.257l.012-.012c.89-.888 1.314-1.299 1.902-1.817a17.643 17.643 0 0 1 2.61-1.933c2.273-1.362 4.814-2.074 7.745-2.074s5.472.712 7.745 2.074c.916.55 1.758 1.183 2.61 1.933.589.518 1.013.929 1.902 1.817l.013.012c1.354 1.352 1.922 1.852 2.599 2.257.735.44 1.51.657 2.631.657.998 0 2.1-.386 3.383-1.284 1.572-1.1 3.272-2.886 5.048-5.372l8.138 5.812c-2.391 3.347-4.857 5.936-7.452 7.753-2.884 2.018-5.948 3.091-9.117 3.091-2.941 0-5.49-.713-7.769-2.078a17.627 17.627 0 0 1-2.619-1.938c-.59-.519-1.015-.93-1.906-1.82l-.013-.013c-1.351-1.348-1.917-1.846-2.59-2.25-.728-.436-1.494-.651-2.603-.651-1.109 0-1.875.215-2.603.651-.673.404-1.239.902-2.59 2.25l-.012.013c-.892.89-1.317 1.301-1.907 1.82-.855.752-1.7 1.388-2.62 1.938C66.74 104.287 64.192 105 61.25 105c-2.942 0-5.49-.714-7.768-2.08a17.654 17.654 0 0 1-2.615-1.939c-.588-.519-1.009-.927-1.902-1.82-1.355-1.355-1.918-1.852-2.592-2.256-.731-.439-1.503-.655-2.623-.655-.998 0-2.1.386-3.383 1.284-1.572 1.1-3.272 2.886-5.048 5.372z"
|
||||
/></g
|
||||
><defs><clipPath id="clip0"><path fill="#fff" d="M0 0h140v140H0z" /></clipPath></defs></svg
|
||||
>
|
||||
@@ -5,5 +5,5 @@
|
||||
<img
|
||||
alt="minio logo"
|
||||
class={isAbsolute ? 'w-7 absolute top-0 left-0 -m-3 -mt-5' : 'w-4 mx-auto'}
|
||||
src="https://cdn.coollabs.io/assets/coolify/services/minio/MINIO_Bird.png"
|
||||
src="/minio.png"
|
||||
/>
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
<img
|
||||
alt="nocodb logo"
|
||||
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||
src="https://cdn.coollabs.io/assets/coolify/services/nocodb/nocodb.png"
|
||||
src="/nocodb.png"
|
||||
/>
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
<img
|
||||
alt="plausible logo"
|
||||
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
|
||||
src="https://cdn.coollabs.io/assets/coolify/services/plausible/logo_sm.png"
|
||||
src="/plausible.png"
|
||||
/>
|
||||
|
||||
35
src/lib/components/svg/services/VaultWarden.svelte
Normal file
35
src/lib/components/svg/services/VaultWarden.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="Icon"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 1024 1024"
|
||||
style="enable-background:new 0 0 1024 1024;"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #175ddc;
|
||||
}
|
||||
.st1 {
|
||||
fill: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
id="Background"
|
||||
class="st0"
|
||||
d="M1024,864c0,88.4-71.6,160-160,160H160C71.6,1024,0,952.4,0,864V160C0,71.6,71.6,0,160,0h704 c88.4,0,160,71.6,160,160V864z"
|
||||
/>
|
||||
<path
|
||||
id="Identity"
|
||||
class="st1"
|
||||
d="M829.8,128.6c-6.5-6.5-14.2-9.7-23-9.7H217.2c-8.9,0-16.5,3.2-23,9.7c-6.5,6.5-9.7,14.2-9.7,23 v393.1c0,29.3,5.7,58.4,17.1,87.3c11.4,28.8,25.6,54.4,42.5,76.8c16.9,22.3,37,44.1,60.4,65.3c23.4,21.2,45,38.7,64.7,52.7 c19.8,14,40.4,27.2,61.9,39.7c21.5,12.5,36.8,20.9,45.8,25.3c9,4.4,16.3,7.9,21.7,10.2c4.1,2,8.5,3.1,13.3,3.1c4.8,0,9.2-1,13.3-3.1 c5.5-2.4,12.7-5.8,21.8-10.2c9-4.4,24.3-12.9,45.8-25.3c21.5-12.5,42.1-25.7,61.9-39.7c19.8-14,41.4-31.6,64.8-52.7 c23.4-21.2,43.5-42.9,60.4-65.3c16.9-22.4,31-47.9,42.5-76.8c11.4-28.8,17.1-57.9,17.1-87.3V151.7 C839.6,142.8,836.3,135.1,829.8,128.6z M753.8,548.4c0,142.3-241.8,264.9-241.8,264.9V203.1h241.8 C753.8,203.1,753.8,406.1,753.8,548.4z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -1,127 +1,263 @@
|
||||
const defaultBuildAndDeploy = {
|
||||
installCommand: 'yarn install',
|
||||
buildCommand: 'yarn build',
|
||||
startCommand: 'yarn start'
|
||||
};
|
||||
export const buildPacks = [
|
||||
{
|
||||
function defaultBuildAndDeploy(packageManager) {
|
||||
return {
|
||||
installCommand:
|
||||
packageManager === 'npm' ? `${packageManager} install` : `${packageManager} install`,
|
||||
buildCommand:
|
||||
packageManager === 'npm' ? `${packageManager} run build` : `${packageManager} build`,
|
||||
startCommand:
|
||||
packageManager === 'npm' ? `${packageManager} run start` : `${packageManager} start`
|
||||
};
|
||||
}
|
||||
export function findBuildPack(pack, packageManager = 'npm') {
|
||||
const metaData = buildPacks.find((b) => b.name === pack);
|
||||
if (pack === 'node') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: null
|
||||
};
|
||||
}
|
||||
if (pack === 'static') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'docker') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: null
|
||||
};
|
||||
}
|
||||
if (pack === 'svelte') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: 'public',
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'nestjs') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
startCommand:
|
||||
packageManager === 'npm' ? 'npm run start:prod' : `${packageManager} run start:prod`,
|
||||
publishDirectory: null,
|
||||
port: 3000
|
||||
};
|
||||
}
|
||||
if (pack === 'react') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: 'build',
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'nextjs') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: null,
|
||||
port: 3000
|
||||
};
|
||||
}
|
||||
if (pack === 'gatsby') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: 'public',
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'vuejs') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: 'dist',
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'nuxtjs') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: null,
|
||||
port: 3000
|
||||
};
|
||||
}
|
||||
if (pack === 'preact') {
|
||||
return {
|
||||
...metaData,
|
||||
...defaultBuildAndDeploy(packageManager),
|
||||
publishDirectory: 'build',
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'php') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'rust') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: 3000
|
||||
};
|
||||
}
|
||||
if (pack === 'astro') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: `yarn install`,
|
||||
buildCommand: `yarn build`,
|
||||
startCommand: null,
|
||||
publishDirectory: `dist`,
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'eleventy') {
|
||||
return {
|
||||
...metaData,
|
||||
installCommand: `yarn install`,
|
||||
buildCommand: `yarn build`,
|
||||
startCommand: null,
|
||||
publishDirectory: `_site`,
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: 'node',
|
||||
fancyName: 'Node.js',
|
||||
hoverColor: 'hover:bg-green-700',
|
||||
color: 'bg-green-700',
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: null,
|
||||
port: null
|
||||
};
|
||||
}
|
||||
export const buildPacks = [
|
||||
{
|
||||
name: 'node',
|
||||
fancyName: 'Node.js',
|
||||
hoverColor: 'hover:bg-green-700',
|
||||
color: 'bg-green-700'
|
||||
},
|
||||
|
||||
{
|
||||
name: 'static',
|
||||
...defaultBuildAndDeploy,
|
||||
publishDirectory: 'dist',
|
||||
port: 80,
|
||||
fancyName: 'Static',
|
||||
hoverColor: 'hover:bg-orange-700',
|
||||
color: 'bg-orange-700'
|
||||
},
|
||||
{
|
||||
name: 'docker',
|
||||
installCommand: null,
|
||||
buildCommand: null,
|
||||
startCommand: null,
|
||||
publishDirectory: null,
|
||||
port: null,
|
||||
fancyName: 'Docker',
|
||||
hoverColor: 'hover:bg-sky-700',
|
||||
color: 'bg-sky-700'
|
||||
},
|
||||
{
|
||||
name: 'php',
|
||||
fancyName: 'PHP',
|
||||
hoverColor: 'hover:bg-indigo-700',
|
||||
color: 'bg-indigo-700'
|
||||
},
|
||||
{
|
||||
name: 'svelte',
|
||||
...defaultBuildAndDeploy,
|
||||
publishDirectory: 'public',
|
||||
port: 80,
|
||||
fancyName: 'Svelte',
|
||||
hoverColor: 'hover:bg-orange-700',
|
||||
color: 'bg-orange-700'
|
||||
},
|
||||
{
|
||||
name: 'nestjs',
|
||||
...defaultBuildAndDeploy,
|
||||
startCommand: 'yarn start:prod',
|
||||
port: 3000,
|
||||
fancyName: 'NestJS',
|
||||
hoverColor: 'hover:bg-red-700',
|
||||
color: 'bg-red-700'
|
||||
},
|
||||
{
|
||||
name: 'react',
|
||||
...defaultBuildAndDeploy,
|
||||
publishDirectory: 'build',
|
||||
port: 80,
|
||||
fancyName: 'React',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'nextjs',
|
||||
...defaultBuildAndDeploy,
|
||||
port: 3000,
|
||||
fancyName: 'NextJS',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'gatsby',
|
||||
...defaultBuildAndDeploy,
|
||||
publishDirectory: 'public',
|
||||
port: 80,
|
||||
fancyName: 'Gatsby',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'vuejs',
|
||||
...defaultBuildAndDeploy,
|
||||
publishDirectory: 'dist',
|
||||
port: 80,
|
||||
fancyName: 'VueJS',
|
||||
hoverColor: 'hover:bg-green-700',
|
||||
color: 'bg-green-700'
|
||||
},
|
||||
{
|
||||
name: 'nuxtjs',
|
||||
...defaultBuildAndDeploy,
|
||||
port: 3000,
|
||||
fancyName: 'NuxtJS',
|
||||
hoverColor: 'hover:bg-green-700',
|
||||
color: 'bg-green-700'
|
||||
},
|
||||
{
|
||||
name: 'gatsby',
|
||||
fancyName: 'Gatsby',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'astro',
|
||||
fancyName: 'Astro',
|
||||
hoverColor: 'hover:bg-pink-700',
|
||||
color: 'bg-pink-700'
|
||||
},
|
||||
{
|
||||
name: 'eleventy',
|
||||
fancyName: 'Eleventy',
|
||||
hoverColor: 'hover:bg-red-700',
|
||||
color: 'bg-red-700'
|
||||
},
|
||||
|
||||
{
|
||||
name: 'react',
|
||||
fancyName: 'React',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'preact',
|
||||
...defaultBuildAndDeploy,
|
||||
publishDirectory: 'build',
|
||||
port: 80,
|
||||
fancyName: 'Preact',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'php',
|
||||
port: 80,
|
||||
fancyName: 'PHP',
|
||||
hoverColor: 'hover:bg-indigo-700',
|
||||
color: 'bg-indigo-700'
|
||||
name: 'nextjs',
|
||||
fancyName: 'NextJS',
|
||||
hoverColor: 'hover:bg-blue-700',
|
||||
color: 'bg-blue-700'
|
||||
},
|
||||
{
|
||||
name: 'nestjs',
|
||||
fancyName: 'NestJS',
|
||||
hoverColor: 'hover:bg-red-700',
|
||||
color: 'bg-red-700'
|
||||
},
|
||||
{
|
||||
name: 'rust',
|
||||
port: 3000,
|
||||
fancyName: 'Rust',
|
||||
hoverColor: 'hover:bg-pink-700',
|
||||
color: 'bg-pink-700'
|
||||
}
|
||||
];
|
||||
export const scanningTemplates = {
|
||||
astro: {
|
||||
buildPack: 'astro'
|
||||
},
|
||||
'@11ty/eleventy': {
|
||||
buildPack: 'eleventy'
|
||||
},
|
||||
svelte: {
|
||||
buildPack: 'svelte'
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { removeProxyConfiguration } from '$lib/haproxy';
|
||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
|
||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
||||
import { prisma } from './common';
|
||||
@@ -59,25 +58,22 @@ export async function removeApplication({ id, teamId }) {
|
||||
const id = containerObj.ID;
|
||||
const preview = containerObj.Image.split('-')[1];
|
||||
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
||||
if (preview) {
|
||||
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
|
||||
} else {
|
||||
await removeProxyConfiguration({ domain });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
|
||||
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.build.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function getApplicationWebhook({ projectId, branch }) {
|
||||
try {
|
||||
let body = await prisma.application.findFirst({
|
||||
where: { projectId, branch },
|
||||
let application = await prisma.application.findFirst({
|
||||
where: { projectId, branch, settings: { autodeploy: true } },
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
@@ -85,34 +81,53 @@ export async function getApplicationWebhook({ projectId, branch }) {
|
||||
secrets: true
|
||||
}
|
||||
});
|
||||
|
||||
if (body.gitSource?.githubApp?.clientSecret) {
|
||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||
if (!application) {
|
||||
return null;
|
||||
}
|
||||
if (body.gitSource?.githubApp?.webhookSecret) {
|
||||
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
|
||||
if (application?.gitSource?.githubApp?.clientSecret) {
|
||||
application.gitSource.githubApp.clientSecret = decrypt(
|
||||
application.gitSource.githubApp.clientSecret
|
||||
);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.privateKey) {
|
||||
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
|
||||
if (application?.gitSource?.githubApp?.webhookSecret) {
|
||||
application.gitSource.githubApp.webhookSecret = decrypt(
|
||||
application.gitSource.githubApp.webhookSecret
|
||||
);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.appSecret) {
|
||||
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
|
||||
if (application?.gitSource?.githubApp?.privateKey) {
|
||||
application.gitSource.githubApp.privateKey = decrypt(
|
||||
application.gitSource.githubApp.privateKey
|
||||
);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.webhookToken) {
|
||||
body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken);
|
||||
if (application?.gitSource?.gitlabApp?.appSecret) {
|
||||
application.gitSource.gitlabApp.appSecret = decrypt(
|
||||
application.gitSource.gitlabApp.appSecret
|
||||
);
|
||||
}
|
||||
if (body?.secrets.length > 0) {
|
||||
body.secrets = body.secrets.map((s) => {
|
||||
if (application?.gitSource?.gitlabApp?.webhookToken) {
|
||||
application.gitSource.gitlabApp.webhookToken = decrypt(
|
||||
application.gitSource.gitlabApp.webhookToken
|
||||
);
|
||||
}
|
||||
if (application?.secrets.length > 0) {
|
||||
application.secrets = application.secrets.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
return { ...body };
|
||||
return { ...application };
|
||||
} catch (e) {
|
||||
throw { status: 404, body: { message: e.message } };
|
||||
}
|
||||
}
|
||||
export async function getApplicationById({ id }) {
|
||||
const body = await prisma.application.findFirst({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
|
||||
return { ...body };
|
||||
}
|
||||
export async function getApplication({ id, teamId }) {
|
||||
let body = await prisma.application.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } },
|
||||
@@ -120,17 +135,18 @@ export async function getApplication({ id, teamId }) {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
secrets: true
|
||||
secrets: true,
|
||||
persistentStorage: true
|
||||
}
|
||||
});
|
||||
|
||||
if (body.gitSource?.githubApp?.clientSecret) {
|
||||
if (body?.gitSource?.githubApp?.clientSecret) {
|
||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.webhookSecret) {
|
||||
if (body?.gitSource?.githubApp?.webhookSecret) {
|
||||
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.privateKey) {
|
||||
if (body?.gitSource?.githubApp?.privateKey) {
|
||||
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.appSecret) {
|
||||
@@ -146,24 +162,41 @@ export async function getApplication({ id, teamId }) {
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) {
|
||||
export async function configureGitRepository({
|
||||
id,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
webhookToken,
|
||||
autodeploy
|
||||
}) {
|
||||
if (webhookToken) {
|
||||
const encryptedWebhookToken = encrypt(webhookToken);
|
||||
return await prisma.application.update({
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } }
|
||||
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } },
|
||||
settings: { update: { autodeploy } }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return await prisma.application.update({
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { repository, branch, projectId }
|
||||
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
|
||||
});
|
||||
}
|
||||
if (!autodeploy) {
|
||||
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
||||
for (const application of applications) {
|
||||
await prisma.applicationSettings.updateMany({
|
||||
where: { applicationId: application.id },
|
||||
data: { autodeploy: false }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureBuildPack({ id, buildPack }) {
|
||||
@@ -198,10 +231,14 @@ export async function configureApplication({
|
||||
});
|
||||
}
|
||||
|
||||
export async function setApplicationSettings({ id, debug, previews }) {
|
||||
export async function checkDoubleBranch(branch, projectId) {
|
||||
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
||||
return applications.length > 1;
|
||||
}
|
||||
export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: { settings: { update: { debug, previews } } },
|
||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
}
|
||||
@@ -228,3 +265,7 @@ export async function createBuild({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPersistentStorage(id) {
|
||||
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getDomain } from '$lib/common';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function isBranchAlreadyUsed({ repository, branch, id }) {
|
||||
const application = await prisma.application.findUnique({
|
||||
@@ -15,22 +15,44 @@ export async function isDockerNetworkExists({ network }) {
|
||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
}
|
||||
|
||||
export async function isSecretExists({ id, name }) {
|
||||
return await prisma.secret.findFirst({ where: { name, applicationId: id } });
|
||||
export async function isServiceSecretExists({ id, name }) {
|
||||
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
||||
}
|
||||
export async function isSecretExists({ id, name, isPRMRSecret }) {
|
||||
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
||||
}
|
||||
|
||||
export async function isDomainConfigured({ id, fqdn }) {
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace('www.', '');
|
||||
const foundApp = await prisma.application.findFirst({
|
||||
where: { fqdn: { endsWith: domain }, id: { not: id } },
|
||||
where: {
|
||||
OR: [
|
||||
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||
],
|
||||
id: { not: id }
|
||||
},
|
||||
select: { fqdn: true }
|
||||
});
|
||||
const foundService = await prisma.service.findFirst({
|
||||
where: { fqdn: { endsWith: domain }, id: { not: id } },
|
||||
where: {
|
||||
OR: [
|
||||
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||
],
|
||||
id: { not: id }
|
||||
},
|
||||
select: { fqdn: true }
|
||||
});
|
||||
const coolifyFqdn = await prisma.setting.findFirst({
|
||||
where: { fqdn: { endsWith: domain }, id: { not: id } },
|
||||
where: {
|
||||
OR: [
|
||||
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||
],
|
||||
id: { not: id }
|
||||
},
|
||||
select: { fqdn: true }
|
||||
});
|
||||
if (foundApp || foundService || coolifyFqdn) return true;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { dev } from '$app/env';
|
||||
import { sentry } from '$lib/common';
|
||||
import * as Prisma from '@prisma/client';
|
||||
import { default as ProdPrisma } from '@prisma/client';
|
||||
import type { PrismaClientOptions } from '@prisma/client/runtime';
|
||||
import generator from 'generate-password';
|
||||
import forge from 'node-forge';
|
||||
|
||||
@@ -19,40 +20,45 @@ if (!dev) {
|
||||
PrismaClient = ProdPrisma.PrismaClient;
|
||||
P = ProdPrisma.Prisma;
|
||||
}
|
||||
let prismaOptions = {
|
||||
rejectOnNotFound: false
|
||||
};
|
||||
if (dev) {
|
||||
prismaOptions = {
|
||||
errorFormat: 'pretty',
|
||||
rejectOnNotFound: false,
|
||||
log: [
|
||||
{
|
||||
emit: 'event',
|
||||
level: 'query'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
export const prisma = new PrismaClient(prismaOptions);
|
||||
|
||||
export function PrismaErrorHandler(e) {
|
||||
export const prisma = new PrismaClient({
|
||||
errorFormat: 'pretty',
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
|
||||
export function ErrorHandler(e) {
|
||||
if (e! instanceof Error) {
|
||||
e = new Error(e.toString());
|
||||
}
|
||||
sentry.captureException(e);
|
||||
let truncatedError = e;
|
||||
if (e.stdout) {
|
||||
truncatedError = e.stdout;
|
||||
}
|
||||
if (e.message?.includes('docker run')) {
|
||||
let truncatedArray = [];
|
||||
truncatedArray = truncatedError.message.split('-').filter((line) => {
|
||||
if (!line.startsWith('e ')) {
|
||||
return line;
|
||||
}
|
||||
});
|
||||
truncatedError.message = truncatedArray.join('-');
|
||||
}
|
||||
if (e.message?.includes('git clone')) {
|
||||
truncatedError.message = 'git clone failed';
|
||||
}
|
||||
sentry.captureException(truncatedError);
|
||||
const payload = {
|
||||
status: e.status || 500,
|
||||
status: truncatedError.status || 500,
|
||||
body: {
|
||||
message: 'Ooops, something is not okay, are you okay?',
|
||||
error: e.error || e.message
|
||||
error: truncatedError.error || truncatedError.message
|
||||
}
|
||||
};
|
||||
if (e.name === 'NotFoundError') {
|
||||
if (truncatedError?.name === 'NotFoundError') {
|
||||
payload.status = 404;
|
||||
}
|
||||
if (e instanceof P.PrismaClientKnownRequestError) {
|
||||
if (e.code === 'P2002') {
|
||||
if (truncatedError instanceof P.PrismaClientKnownRequestError) {
|
||||
if (truncatedError?.code === 'P2002') {
|
||||
payload.body.message = 'Already exists. Choose another name.';
|
||||
}
|
||||
}
|
||||
@@ -101,21 +107,64 @@ export const supportedServiceTypesAndVersions = [
|
||||
name: 'plausibleanalytics',
|
||||
fancyName: 'Plausible Analytics',
|
||||
baseImage: 'plausible/analytics',
|
||||
versions: ['latest']
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 8000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'nocodb',
|
||||
fancyName: 'NocoDB',
|
||||
baseImage: 'nocodb/nocodb',
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'minio',
|
||||
fancyName: 'MinIO',
|
||||
baseImage: 'minio/minio',
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 9001
|
||||
}
|
||||
},
|
||||
{ name: 'nocodb', fancyName: 'NocoDB', baseImage: 'nocodb/nocodb', versions: ['latest'] },
|
||||
{ name: 'minio', fancyName: 'MinIO', baseImage: 'minio/minio', versions: ['latest'] },
|
||||
{
|
||||
name: 'vscodeserver',
|
||||
fancyName: 'VSCode Server',
|
||||
baseImage: 'codercom/code-server',
|
||||
versions: ['latest']
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'wordpress',
|
||||
fancyName: 'Wordpress',
|
||||
baseImage: 'wordpress',
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3']
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'vaultwarden',
|
||||
fancyName: 'Vaultwarden',
|
||||
baseImage: 'vaultwarden/server',
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'languagetool',
|
||||
fancyName: 'LanguageTool',
|
||||
baseImage: 'silviof/docker-languagetool',
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 8010
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import * as db from '$lib/database';
|
||||
import cuid from 'cuid';
|
||||
import { generatePassword } from '.';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import getPort from 'get-port';
|
||||
import { prisma, ErrorHandler } from './common';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||
|
||||
export async function listDatabases(teamId) {
|
||||
@@ -16,24 +16,9 @@ export async function newDatabase({ name, teamId }) {
|
||||
const rootUserPassword = encrypt(generatePassword());
|
||||
const defaultDatabase = cuid();
|
||||
|
||||
let publicPort = await getPort();
|
||||
let i = 0;
|
||||
|
||||
do {
|
||||
const usedPorts = await prisma.database.findMany({ where: { publicPort } });
|
||||
if (usedPorts.length === 0) break;
|
||||
publicPort = await getPort();
|
||||
i++;
|
||||
} while (i < 10);
|
||||
if (i === 9) {
|
||||
throw {
|
||||
error: 'No free port found!? Is it possible?'
|
||||
};
|
||||
}
|
||||
return await prisma.database.create({
|
||||
data: {
|
||||
name,
|
||||
publicPort,
|
||||
defaultDatabase,
|
||||
dbUser,
|
||||
dbUserPassword,
|
||||
@@ -114,15 +99,6 @@ export async function updateDatabase({
|
||||
});
|
||||
}
|
||||
|
||||
// export async function setDatabaseSettings({ id, isPublic }) {
|
||||
// try {
|
||||
// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } })
|
||||
// return { status: 201 }
|
||||
// } catch (e) {
|
||||
// throw PrismaErrorHandler(e)
|
||||
// }
|
||||
// }
|
||||
|
||||
export async function stopDatabase(database) {
|
||||
let everStarted = false;
|
||||
const {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy';
|
||||
import { startCoolifyProxy } from '$lib/haproxy';
|
||||
import { getDatabaseImage } from '.';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listDestinations(teamId) {
|
||||
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
|
||||
@@ -37,11 +38,9 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
|
||||
const host = getEngine(engine);
|
||||
if (type && version) {
|
||||
const baseImage = getDatabaseImage(type);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`);
|
||||
asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.image="true" -t "${baseImage}:${version}" -`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +48,36 @@ export async function updateDestination({ id, name, engine, network }) {
|
||||
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||
}
|
||||
|
||||
export async function newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) {
|
||||
export async function newRemoteDestination({
|
||||
name,
|
||||
teamId,
|
||||
engine,
|
||||
network,
|
||||
isCoolifyProxyUsed,
|
||||
remoteEngine,
|
||||
ipAddress,
|
||||
user,
|
||||
port,
|
||||
sshPrivateKey
|
||||
}) {
|
||||
const encryptedPrivateKey = encrypt(sshPrivateKey);
|
||||
const destination = await prisma.destinationDocker.create({
|
||||
data: {
|
||||
name,
|
||||
teams: { connect: { id: teamId } },
|
||||
engine,
|
||||
network,
|
||||
isCoolifyProxyUsed,
|
||||
remoteEngine,
|
||||
ipAddress,
|
||||
user,
|
||||
port,
|
||||
sshPrivateKey: encryptedPrivateKey
|
||||
}
|
||||
});
|
||||
return destination.id;
|
||||
}
|
||||
export async function newLocalDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) {
|
||||
const host = getEngine(engine);
|
||||
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
||||
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
||||
@@ -96,9 +124,13 @@ export async function removeDestination({ id }) {
|
||||
}
|
||||
|
||||
export async function getDestination({ id, teamId }) {
|
||||
return await prisma.destinationDocker.findFirst({
|
||||
let destination = await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } }
|
||||
});
|
||||
if (destination.remoteEngine) {
|
||||
destination.sshPrivateKey = decrypt(destination.sshPrivateKey);
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
export async function getDestinationByApplicationId({ id, teamId }) {
|
||||
return await prisma.destinationDocker.findFirst({
|
||||
@@ -111,22 +143,4 @@ export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
|
||||
// if (isCoolifyProxyUsed) {
|
||||
// await installCoolifyProxy(engine)
|
||||
// await configureNetworkCoolifyProxy(engine)
|
||||
// } else {
|
||||
// // TODO: must check if other destination is using the proxy??? or not?
|
||||
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
|
||||
// if (!domain) {
|
||||
// await uninstallCoolifyProxy(engine)
|
||||
// } else {
|
||||
// return {
|
||||
// stastus: 500,
|
||||
// body: {
|
||||
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listSources(teamId) {
|
||||
return await prisma.gitSource.findMany({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function addInstallation({ gitSourceId, installation_id }) {
|
||||
const source = await prisma.gitSource.findUnique({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { encrypt } from '$lib/crypto';
|
||||
import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common';
|
||||
import { generateSshKeyPair, prisma } from './common';
|
||||
|
||||
export async function updateDeployKey({ id, deployKeyId }) {
|
||||
const application = await prisma.application.findUnique({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma, ErrorHandler } from './common';
|
||||
|
||||
export async function listLogs({ buildId, last = 0 }) {
|
||||
try {
|
||||
@@ -7,7 +7,7 @@ export async function listLogs({ buildId, last = 0 }) {
|
||||
orderBy: { time: 'asc' }
|
||||
});
|
||||
return [...body];
|
||||
} catch (e) {
|
||||
throw PrismaErrorHandler(e);
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,78 @@
|
||||
import { encrypt } from '$lib/crypto';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { encrypt, decrypt } from '$lib/crypto';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listSecrets({ applicationId }) {
|
||||
return await prisma.secret.findMany({
|
||||
export async function listServiceSecrets(serviceId: string) {
|
||||
let secrets = await prisma.serviceSecret.findMany({
|
||||
where: { serviceId },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
secrets = secrets.map((secret) => {
|
||||
secret.value = decrypt(secret.value);
|
||||
return secret;
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
export async function listSecrets(applicationId: string) {
|
||||
let secrets = await prisma.secret.findMany({
|
||||
where: { applicationId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: { id: true, createdAt: true, name: true, isBuildSecret: true }
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
secrets = secrets.map((secret) => {
|
||||
secret.value = decrypt(secret.value);
|
||||
return secret;
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
export async function createServiceSecret({ id, name, value }) {
|
||||
value = encrypt(value);
|
||||
return await prisma.serviceSecret.create({
|
||||
data: { name, value, service: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||
value = encrypt(value);
|
||||
return await prisma.secret.create({
|
||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSecret({ id, name, value, isBuildSecret }) {
|
||||
export async function updateServiceSecret({ id, name, value }) {
|
||||
value = encrypt(value);
|
||||
return await prisma.secret.create({
|
||||
data: { name, value, isBuildSecret, application: { connect: { id } } }
|
||||
});
|
||||
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
||||
|
||||
if (found) {
|
||||
return await prisma.serviceSecret.updateMany({
|
||||
where: { serviceId: id, name },
|
||||
data: { value }
|
||||
});
|
||||
} else {
|
||||
return await prisma.serviceSecret.create({
|
||||
data: { name, value, service: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||
value = encrypt(value);
|
||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||
|
||||
if (found) {
|
||||
return await prisma.secret.updateMany({
|
||||
where: { applicationId: id, name, isPRMRSecret },
|
||||
data: { value, isBuildSecret, isPRMRSecret }
|
||||
});
|
||||
} else {
|
||||
return await prisma.secret.create({
|
||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeServiceSecret({ id, name }) {
|
||||
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
|
||||
}
|
||||
|
||||
export async function removeSecret({ id, name }) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import cuid from 'cuid';
|
||||
import { generatePassword } from '.';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listServices(teamId) {
|
||||
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
|
||||
@@ -20,7 +19,8 @@ export async function getService({ id, teamId }) {
|
||||
plausibleAnalytics: true,
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
wordpress: true,
|
||||
serviceSecret: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -43,6 +43,12 @@ export async function getService({ id, teamId }) {
|
||||
if (body.wordpress?.mysqlRootUserPassword)
|
||||
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
|
||||
|
||||
if (body?.serviceSecret.length > 0) {
|
||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
@@ -99,15 +105,36 @@ export async function configureServiceType({ id, type }) {
|
||||
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
|
||||
}
|
||||
});
|
||||
} else if (type === 'vaultwarden') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'languagetool') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function setService({ id, version }) {
|
||||
export async function setServiceVersion({ id, version }) {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { version }
|
||||
});
|
||||
}
|
||||
|
||||
export async function setServiceSettings({ id, dualCerts }) {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { dualCerts }
|
||||
});
|
||||
}
|
||||
|
||||
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
|
||||
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
||||
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
||||
@@ -115,6 +142,12 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
|
||||
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateLanguageToolService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateVaultWardenService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateVsCodeServer({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
@@ -133,5 +166,7 @@ export async function removeService({ id }) {
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listTeams() {
|
||||
return await prisma.team.findMany();
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
import cuid from 'cuid';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { asyncExecShell, removeContainer, uniqueName } from '$lib/common';
|
||||
import { prisma } from './common';
|
||||
import { asyncExecShell, uniqueName } from '$lib/common';
|
||||
|
||||
import * as db from '$lib/database';
|
||||
import { startCoolifyProxy } from '$lib/haproxy';
|
||||
|
||||
export async function login({ email, password }) {
|
||||
export async function hashPassword(password: string) {
|
||||
const saltRounds = 15;
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
}
|
||||
export async function login({ email, password, isLogin }) {
|
||||
const users = await prisma.user.count();
|
||||
const userFound = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: { teams: true },
|
||||
include: { teams: true, permission: true },
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
if (!userFound && isLogin) {
|
||||
throw {
|
||||
error: 'Wrong password or email address.'
|
||||
};
|
||||
}
|
||||
// Registration disabled if database is not seeded properly
|
||||
const { isRegistrationEnabled, id } = await db.listSettings();
|
||||
|
||||
let uid = cuid();
|
||||
let permission = 'read';
|
||||
let isAdmin = false;
|
||||
// Disable registration if we are registering the first user.
|
||||
if (users === 0) {
|
||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||
@@ -28,12 +37,12 @@ export async function login({ email, password }) {
|
||||
console.log('Network created');
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('Network already exists');
|
||||
console.log('Network already exists.');
|
||||
});
|
||||
|
||||
startCoolifyProxy('/var/run/docker.sock')
|
||||
.then(() => {
|
||||
console.log('Coolify Proxy Started');
|
||||
console.log('Coolify Proxy started.');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
@@ -50,6 +59,8 @@ export async function login({ email, password }) {
|
||||
};
|
||||
}
|
||||
uid = userFound.id;
|
||||
// permission = userFound.permission;
|
||||
isAdmin = true;
|
||||
}
|
||||
} else {
|
||||
// If registration disabled, return 403
|
||||
@@ -59,8 +70,10 @@ export async function login({ email, password }) {
|
||||
};
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
const hashedPassword = await hashPassword(password);
|
||||
if (users === 0) {
|
||||
permission = 'owner';
|
||||
isAdmin = true;
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: uid,
|
||||
@@ -97,25 +110,16 @@ export async function login({ email, password }) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// const token = jsonwebtoken.sign({}, secretKey, {
|
||||
// expiresIn: 15778800,
|
||||
// algorithm: 'HS256',
|
||||
// audience: 'coolify',
|
||||
// issuer: 'coolify',
|
||||
// jwtid: uid,
|
||||
// subject: `User:${uid}`,
|
||||
// notBefore: -1000
|
||||
// });
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
|
||||
},
|
||||
body: {
|
||||
uid,
|
||||
teamId: uid
|
||||
userId: uid,
|
||||
teamId: uid,
|
||||
permission,
|
||||
isAdmin
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Dockerode from 'dockerode';
|
||||
import { promises as fs } from 'fs';
|
||||
import { checkPnpm } from './buildPacks/common';
|
||||
import { saveBuildLog } from './common';
|
||||
|
||||
export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
@@ -13,32 +14,37 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
installCommand,
|
||||
buildCommand,
|
||||
debug,
|
||||
secrets
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
} = data;
|
||||
const isPnpm = checkPnpm(installCommand, buildCommand);
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (!secret.isBuildSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: If build command defined, install command should be the default yarn install
|
||||
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(`COPY .${baseDirectory || ''} ./`);
|
||||
if (installCommand) {
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||
try {
|
||||
await fs.stat(`${workdir}/yarn.lock`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await fs.stat(`${workdir}/pnpm-lock.yaml`);
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
|
||||
} catch (error) {}
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
}
|
||||
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
||||
@@ -59,14 +65,14 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push('RUN cargo install cargo-chef');
|
||||
Dockerfile.push('COPY . .');
|
||||
Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json');
|
||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||
Dockerfile.push('WORKDIR /usr/src/app');
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push('RUN cargo install cargo-chef');
|
||||
Dockerfile.push(`COPY --from=planner-${applicationId} /usr/src/app/recipe.json recipe.json`);
|
||||
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
||||
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
||||
|
||||
265
src/lib/haproxy/configuration.ts
Normal file
265
src/lib/haproxy/configuration.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { dev } from '$app/env';
|
||||
import got from 'got';
|
||||
import mustache from 'mustache';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import * as db from '$lib/database';
|
||||
import { checkContainer, checkHAProxy } from '.';
|
||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||
|
||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||
|
||||
let template = `program api
|
||||
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
|
||||
no option start-on-reload
|
||||
|
||||
global
|
||||
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
|
||||
log stdout format raw local0 debug
|
||||
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
timeout http-request 60s
|
||||
timeout connect 10s
|
||||
timeout client 60s
|
||||
timeout server 60s
|
||||
|
||||
userlist haproxy-dataplaneapi
|
||||
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
||||
|
||||
frontend http
|
||||
mode http
|
||||
bind :80
|
||||
bind :443 ssl crt /usr/local/etc/haproxy/ssl/ alpn h2,http/1.1
|
||||
acl is_certbot path_beg /.well-known/acme-challenge/
|
||||
{{#applications}}
|
||||
{{#isHttps}}
|
||||
http-request redirect scheme https code ${
|
||||
dev ? 302 : 301
|
||||
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
|
||||
{{/isHttps}}
|
||||
http-request redirect location {{{redirectValue}}} code ${
|
||||
dev ? 302 : 301
|
||||
} if { req.hdr(host) -i {{redirectTo}} }
|
||||
{{/applications}}
|
||||
{{#services}}
|
||||
{{#isHttps}}
|
||||
http-request redirect scheme https code ${
|
||||
dev ? 302 : 301
|
||||
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
|
||||
{{/isHttps}}
|
||||
http-request redirect location {{{redirectValue}}} code ${
|
||||
dev ? 302 : 301
|
||||
} if { req.hdr(host) -i {{redirectTo}} }
|
||||
{{/services}}
|
||||
{{#coolify}}
|
||||
{{#isHttps}}
|
||||
http-request redirect scheme https code ${
|
||||
dev ? 302 : 301
|
||||
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
|
||||
{{/isHttps}}
|
||||
http-request redirect location {{{redirectValue}}} code ${
|
||||
dev ? 302 : 301
|
||||
} if { req.hdr(host) -i {{redirectTo}} }
|
||||
{{/coolify}}
|
||||
use_backend backend-certbot if is_certbot
|
||||
use_backend %[req.hdr(host),lower]
|
||||
|
||||
frontend stats
|
||||
bind *:8404
|
||||
stats enable
|
||||
stats uri /
|
||||
stats admin if TRUE
|
||||
stats auth "\${HAPROXY_USERNAME}:\${HAPROXY_PASSWORD}"
|
||||
|
||||
backend backend-certbot
|
||||
mode http
|
||||
server certbot host.docker.internal:9080
|
||||
|
||||
{{#applications}}
|
||||
{{#isRunning}}
|
||||
# updatedAt={{updatedAt}}
|
||||
backend {{domain}}
|
||||
option forwardfor
|
||||
server {{id}} {{id}}:{{port}}
|
||||
{{/isRunning}}
|
||||
{{/applications}}
|
||||
|
||||
{{#services}}
|
||||
{{#isRunning}}
|
||||
# updatedAt={{updatedAt}}
|
||||
backend {{domain}}
|
||||
option forwardfor
|
||||
server {{id}} {{id}}:{{port}}
|
||||
{{/isRunning}}
|
||||
{{/services}}
|
||||
|
||||
{{#coolify}}
|
||||
backend {{domain}}
|
||||
option forwardfor
|
||||
option httpchk GET /undead.json
|
||||
server {{id}} {{id}}:{{port}} check fall 10
|
||||
{{/coolify}}
|
||||
`;
|
||||
export async function haproxyInstance() {
|
||||
const { proxyPassword } = await db.listSettings();
|
||||
return got.extend({
|
||||
prefixUrl: url,
|
||||
username: 'admin',
|
||||
password: proxyPassword
|
||||
});
|
||||
}
|
||||
|
||||
export async function configureHAProxy() {
|
||||
const haproxy = await haproxyInstance();
|
||||
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return 'Error: HAProxy is not running';
|
||||
}
|
||||
|
||||
try {
|
||||
const data = {
|
||||
applications: [],
|
||||
services: [],
|
||||
coolify: []
|
||||
};
|
||||
const applications = await db.prisma.application.findMany({
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
for (const application of applications) {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
port,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings: { previews },
|
||||
updatedAt
|
||||
} = application;
|
||||
if (destinationDockerId) {
|
||||
const { engine, network } = destinationDocker;
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||
if (isRunning) {
|
||||
data.applications.push({
|
||||
id,
|
||||
port: port || 3000,
|
||||
domain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain : 'www.' + domain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
});
|
||||
}
|
||||
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) {
|
||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||
data.applications.push({
|
||||
id: container,
|
||||
port: port || 3000,
|
||||
domain: previewDomain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? previewDomain : 'www.' + previewDomain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const services = await db.prisma.service.findMany({
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
}
|
||||
});
|
||||
|
||||
for (const service of services) {
|
||||
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
||||
if (destinationDockerId) {
|
||||
const { engine } = destinationDocker;
|
||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||
if (found) {
|
||||
const port = found.ports.main;
|
||||
const publicPort = service[type]?.publicPort;
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||
if (isRunning) {
|
||||
data.services.push({
|
||||
id,
|
||||
port,
|
||||
publicPort,
|
||||
domain,
|
||||
isRunning,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain : 'www.' + domain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const { fqdn } = await db.prisma.setting.findFirst();
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||
data.coolify.push({
|
||||
id: dev ? 'host.docker.internal' : 'coolify',
|
||||
port: 3000,
|
||||
domain,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain : 'www.' + domain
|
||||
});
|
||||
}
|
||||
const output = mustache.render(template, data);
|
||||
const newHash = crypto.createHash('md5').update(output).digest('hex');
|
||||
const { proxyHash, id } = await db.listSettings();
|
||||
if (proxyHash !== newHash) {
|
||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
|
||||
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
|
||||
searchParams: {
|
||||
skip_version: true
|
||||
},
|
||||
body: output,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { dev } from '$app/env';
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import got from 'got';
|
||||
import * as db from '$lib/database';
|
||||
import { letsEncrypt } from '$lib/letsencrypt';
|
||||
|
||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||
|
||||
@@ -48,121 +47,18 @@ export async function completeTransaction(transactionId) {
|
||||
const haproxy = await haproxyInstance();
|
||||
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
||||
}
|
||||
|
||||
export async function removeProxyConfiguration({ domain }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
const transactionId = await getNextTransactionId();
|
||||
const backendFound = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||
.json();
|
||||
if (backendFound) {
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
}
|
||||
export async function forceSSLOffApplication({ domain }) {
|
||||
if (!dev) {
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
const transactionId = await getNextTransactionId();
|
||||
try {
|
||||
const rules: any = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||
searchParams: {
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
if (rules.data.length > 0) {
|
||||
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`));
|
||||
if (rule) {
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
} else {
|
||||
console.log(`[DEBUG] Removing ssl for ${domain}`);
|
||||
}
|
||||
}
|
||||
export async function forceSSLOnApplication({ domain }) {
|
||||
if (!dev) {
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
const transactionId = await getNextTransactionId();
|
||||
|
||||
try {
|
||||
const rules: any = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||
searchParams: {
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
let nextRule = 0;
|
||||
if (rules.data.length > 0) {
|
||||
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`));
|
||||
if (rule) return;
|
||||
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||
}
|
||||
await haproxy
|
||||
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
},
|
||||
json: {
|
||||
index: nextRule,
|
||||
cond: 'if',
|
||||
cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`,
|
||||
type: 'redirect',
|
||||
redir_type: 'scheme',
|
||||
redir_value: 'https',
|
||||
redir_code: 301
|
||||
}
|
||||
})
|
||||
.json();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
} finally {
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
} else {
|
||||
console.log(`[DEBUG] Adding ssl for ${domain}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteProxy({ id }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return;
|
||||
return 'Error: HAProxy is not running';
|
||||
}
|
||||
const transactionId = await getNextTransactionId();
|
||||
|
||||
let transactionId;
|
||||
try {
|
||||
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
|
||||
transactionId = await getNextTransactionId();
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${id}`, {
|
||||
searchParams: {
|
||||
@@ -179,280 +75,25 @@ export async function deleteProxy({ id }) {
|
||||
})
|
||||
.json();
|
||||
} catch (error) {
|
||||
console.log(error.response.body);
|
||||
console.log(error.response?.body || error);
|
||||
} finally {
|
||||
await completeTransaction(transactionId);
|
||||
if (transactionId) await completeTransaction(transactionId);
|
||||
}
|
||||
}
|
||||
// export async function configureProxyForDatabase({ id, port, isPublic, privatePort }) {
|
||||
// const haproxy = await haproxyInstance()
|
||||
// try {
|
||||
// await checkHAProxy()
|
||||
// } catch (error) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// let alreadyConfigured = false
|
||||
// try {
|
||||
// const backend: any = await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json()
|
||||
// const server: any = await haproxy.get(`v2/services/haproxy/configuration/servers/${id}`, {
|
||||
// searchParams: {
|
||||
// backend: id
|
||||
// },
|
||||
// }).json()
|
||||
// if (backend.data.name === id) {
|
||||
// if (server.data.port === privatePort) {
|
||||
// if (server.data.check === 'enabled') {
|
||||
// if (server.data.address === id) {
|
||||
// alreadyConfigured = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log('error getting backend or server', error.response.body)
|
||||
// }
|
||||
// if (alreadyConfigured) return
|
||||
|
||||
// const transactionId = await getNextTransactionId()
|
||||
// try {
|
||||
// await haproxy.post('v2/services/haproxy/configuration/backends', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId
|
||||
// },
|
||||
// json: {
|
||||
// "init-addr": "last,libc,none",
|
||||
// "mode": "tcp",
|
||||
// "name": id
|
||||
// }
|
||||
// })
|
||||
// await haproxy.post('v2/services/haproxy/configuration/servers', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId,
|
||||
// backend: id
|
||||
// },
|
||||
// json: {
|
||||
// "address": id,
|
||||
// "check": "enabled",
|
||||
// "name": id,
|
||||
// "port": privatePort
|
||||
// }
|
||||
// })
|
||||
// await haproxy.post('v2/services/haproxy/configuration/frontends', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId,
|
||||
// backend: id
|
||||
// },
|
||||
// json: {
|
||||
// "default_backend": id,
|
||||
// "mode": "tcp",
|
||||
// "name": id
|
||||
// }
|
||||
// })
|
||||
// await haproxy.post('v2/services/haproxy/configuration/binds', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId,
|
||||
// frontend: id
|
||||
// },
|
||||
// json: {
|
||||
// "address": "*",
|
||||
// "name": id,
|
||||
// "port": port
|
||||
// }
|
||||
// })
|
||||
// } catch (error) {
|
||||
// console.log(error.response.body)
|
||||
// throw error.response.body
|
||||
// } finally {
|
||||
// try {
|
||||
// await completeTransaction(transactionId)
|
||||
// } catch (error) {
|
||||
// console.log(error.response.body)
|
||||
// }
|
||||
// }
|
||||
// await configureDatabaseVisibility({ id, isPublic })
|
||||
// }
|
||||
export async function reloadHaproxy(engine) {
|
||||
const host = getEngine(engine);
|
||||
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
||||
}
|
||||
export async function configureProxyForApplication({ domain, imageId, applicationId, port }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
|
||||
let serverConfigured = false;
|
||||
let backendAvailable: any = null;
|
||||
|
||||
try {
|
||||
backendAvailable = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||
.json();
|
||||
const server: any = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/servers/${imageId}`, {
|
||||
searchParams: {
|
||||
backend: domain
|
||||
}
|
||||
})
|
||||
.json();
|
||||
|
||||
if (backendAvailable && server) {
|
||||
// Very sophisticated way to check if the server is already configured in proxy
|
||||
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
|
||||
if (backendAvailable.data.name === domain) {
|
||||
if (server.data.check === 'enabled') {
|
||||
if (server.data.address === applicationId) {
|
||||
if (server.data.port === port) {
|
||||
serverConfigured = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//console.log('error getting backend or server', error?.response?.body);
|
||||
//
|
||||
}
|
||||
|
||||
if (serverConfigured) return;
|
||||
const transactionId = await getNextTransactionId();
|
||||
if (backendAvailable) {
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
}
|
||||
})
|
||||
.json();
|
||||
}
|
||||
try {
|
||||
await haproxy.post('v2/services/haproxy/configuration/backends', {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
},
|
||||
json: {
|
||||
'init-addr': 'last,libc,none',
|
||||
forwardfor: { enabled: 'enabled' },
|
||||
name: domain
|
||||
}
|
||||
});
|
||||
|
||||
await haproxy.post('v2/services/haproxy/configuration/servers', {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
backend: domain
|
||||
},
|
||||
json: {
|
||||
address: imageId,
|
||||
check: 'enabled',
|
||||
name: imageId,
|
||||
port: port
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
throw error?.response?.body || error;
|
||||
} finally {
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureCoolifyProxyOff({ domain }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
|
||||
try {
|
||||
const transactionId = await getNextTransactionId();
|
||||
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
if (!dev) {
|
||||
await forceSSLOffApplication({ domain });
|
||||
}
|
||||
} catch (error) {
|
||||
throw error?.response?.body || error;
|
||||
}
|
||||
}
|
||||
export async function checkHAProxy(haproxy) {
|
||||
export async function checkHAProxy(haproxy?: any) {
|
||||
if (!haproxy) haproxy = await haproxyInstance();
|
||||
try {
|
||||
await haproxy.get('v2/info');
|
||||
} catch (error) {
|
||||
throw 'HAProxy is not running, but it should be!';
|
||||
}
|
||||
}
|
||||
export async function configureCoolifyProxyOn({ domain }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
let serverConfigured = false;
|
||||
let backendAvailable: any = null;
|
||||
try {
|
||||
backendAvailable = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||
.json();
|
||||
const server: any = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/servers/coolify`, {
|
||||
searchParams: {
|
||||
backend: domain
|
||||
}
|
||||
})
|
||||
.json();
|
||||
if (backendAvailable && server) {
|
||||
// Very sophisticated way to check if the server is already configured in proxy
|
||||
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
|
||||
if (backendAvailable.data.name === domain) {
|
||||
if (server.data.check === 'enabled') {
|
||||
if (server.data.address === dev ? 'host.docker.internal' : 'coolify') {
|
||||
if (server.data.port === 3000) {
|
||||
serverConfigured = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
if (serverConfigured) return;
|
||||
const transactionId = await getNextTransactionId();
|
||||
try {
|
||||
await haproxy.post('v2/services/haproxy/configuration/backends', {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
},
|
||||
json: {
|
||||
adv_check: 'httpchk',
|
||||
httpchk_params: {
|
||||
method: 'GET',
|
||||
uri: '/undead.json'
|
||||
},
|
||||
'init-addr': 'last,libc,none',
|
||||
forwardfor: { enabled: 'enabled' },
|
||||
name: domain
|
||||
}
|
||||
});
|
||||
await haproxy.post('v2/services/haproxy/configuration/servers', {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
backend: domain
|
||||
},
|
||||
json: {
|
||||
address: dev ? 'host.docker.internal' : 'coolify',
|
||||
check: 'enabled',
|
||||
fall: 10,
|
||||
name: 'coolify',
|
||||
port: 3000
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
} finally {
|
||||
await completeTransaction(transactionId);
|
||||
throw {
|
||||
message:
|
||||
'Coolify Proxy is not running, but it should be!<br><br>Start it in the "Destinations" menu.'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,8 +122,12 @@ export async function startTcpProxy(destinationDocker, id, publicPort, privatePo
|
||||
|
||||
try {
|
||||
if (foundDB && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
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' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -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}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -499,8 +144,12 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
|
||||
|
||||
try {
|
||||
if (foundDB && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
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' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
|
||||
`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/${defaultProxyImageHttp}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -510,11 +159,16 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
|
||||
export async function startCoolifyProxy(engine) {
|
||||
const host = getEngine(engine);
|
||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||
const { proxyPassword, proxyUser } = await db.listSettings();
|
||||
const { proxyPassword, proxyUser, id } = await db.listSettings();
|
||||
if (!found) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' -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}`
|
||||
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 -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 configureNetworkCoolifyProxy(engine);
|
||||
}
|
||||
@@ -526,11 +180,9 @@ export async function checkContainer(engine, container) {
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}`
|
||||
);
|
||||
|
||||
const parsedStdout = JSON.parse(stdout);
|
||||
const status = parsedStdout.Status;
|
||||
const isRunning = parsedStdout.Running;
|
||||
|
||||
const isRunning = status === 'running' ? true : false;
|
||||
if (status === 'exited' || status === 'created') {
|
||||
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
||||
}
|
||||
@@ -547,6 +199,8 @@ export async function stopCoolifyProxy(engine) {
|
||||
const host = getEngine(engine);
|
||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||
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(
|
||||
@@ -571,64 +225,3 @@ export async function configureNetworkCoolifyProxy(engine) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
|
||||
return;
|
||||
} catch (error) {}
|
||||
try {
|
||||
const transactionId = await getNextTransactionId();
|
||||
await haproxy.post('v2/services/haproxy/configuration/backends', {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
},
|
||||
json: {
|
||||
'init-addr': 'last,libc,none',
|
||||
forwardfor: { enabled: 'enabled' },
|
||||
name: domain
|
||||
}
|
||||
});
|
||||
await haproxy.post('v2/services/haproxy/configuration/servers', {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
backend: domain
|
||||
},
|
||||
json: {
|
||||
address: id,
|
||||
check: 'enabled',
|
||||
name: id,
|
||||
port: port
|
||||
}
|
||||
});
|
||||
await completeTransaction(transactionId);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureSimpleServiceProxyOff({ domain }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
|
||||
try {
|
||||
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
|
||||
const transactionId = await getNextTransactionId();
|
||||
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
} catch (error) {}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { asyncExecShell, saveBuildLog } from '$lib/common';
|
||||
import got from 'got';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export default async function ({
|
||||
applicationId,
|
||||
@@ -14,7 +14,7 @@ export default async function ({
|
||||
buildId
|
||||
}): Promise<any> {
|
||||
try {
|
||||
saveBuildLog({ line: 'GitHub importer started', buildId, applicationId });
|
||||
saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||
|
||||
@@ -45,6 +45,7 @@ export default async function ({
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
return commit.replace('\n', '');
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
console.log({ error });
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { asyncExecShell, saveBuildLog } from '$lib/common';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export default async function ({
|
||||
applicationId,
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { dev } from '$app/env';
|
||||
import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy';
|
||||
import { asyncExecShell, getEngine } from './common';
|
||||
import * as db from '$lib/database';
|
||||
|
||||
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
|
||||
try {
|
||||
if (dev) {
|
||||
return await forceSSLOnApplication({ domain });
|
||||
} else {
|
||||
if (isCoolify) {
|
||||
await asyncExecShell(
|
||||
`docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
|
||||
);
|
||||
|
||||
const { stderr } = await asyncExecShell(
|
||||
`docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
|
||||
);
|
||||
if (stderr) throw new Error(stderr);
|
||||
return;
|
||||
}
|
||||
let data: any = await db.prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
if (!data) {
|
||||
data = await db.prisma.service.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
}
|
||||
// Set SSL with Let's encrypt
|
||||
if (data.destinationDockerId && data.destinationDocker) {
|
||||
const host = getEngine(data.destinationDocker.engine);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
|
||||
);
|
||||
const { stderr } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name bash -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
|
||||
);
|
||||
if (stderr) throw new Error(stderr);
|
||||
await forceSSLOnApplication({ domain });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
188
src/lib/letsencrypt/index.ts
Normal file
188
src/lib/letsencrypt/index.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||
import { checkContainer, reloadHaproxy } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { dev } from '$app/env';
|
||||
import cuid from 'cuid';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
|
||||
export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
||||
try {
|
||||
const data = await db.prisma.setting.findFirst();
|
||||
const { minPort, maxPort } = data;
|
||||
|
||||
const nakedDomain = domain.replace('www.', '');
|
||||
const wwwDomain = `www.${nakedDomain}`;
|
||||
const randomCuid = cuid();
|
||||
const randomPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||
|
||||
let host;
|
||||
let dualCerts = false;
|
||||
if (isCoolify) {
|
||||
dualCerts = data.dualCerts;
|
||||
host = 'unix:///var/run/docker.sock';
|
||||
} else {
|
||||
const applicationData = await db.prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
if (applicationData) {
|
||||
if (applicationData?.destinationDockerId && applicationData?.destinationDocker) {
|
||||
host = getEngine(applicationData.destinationDocker.engine);
|
||||
}
|
||||
if (applicationData?.settings?.dualCerts) {
|
||||
dualCerts = applicationData.settings.dualCerts;
|
||||
}
|
||||
}
|
||||
// Check Service
|
||||
const serviceData = await db.prisma.service.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
if (serviceData) {
|
||||
if (serviceData?.destinationDockerId && serviceData?.destinationDocker) {
|
||||
host = getEngine(serviceData.destinationDocker.engine);
|
||||
}
|
||||
if (serviceData?.dualCerts) {
|
||||
dualCerts = serviceData.dualCerts;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dualCerts) {
|
||||
let found = false;
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /app/ssl/${wwwDomain}.pem"`
|
||||
);
|
||||
found = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
if (found) return;
|
||||
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
dev ? '--test-cert' : ''
|
||||
}`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"`
|
||||
);
|
||||
await reloadHaproxy(host);
|
||||
} else {
|
||||
let found = false;
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /app/ssl/${domain}.pem"`
|
||||
);
|
||||
found = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
if (found) return;
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
dev ? '--test-cert' : ''
|
||||
}`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem"`
|
||||
);
|
||||
await reloadHaproxy(host);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 0) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateSSLCerts() {
|
||||
const ssls = [];
|
||||
const applications = await db.prisma.application.findMany({
|
||||
include: { destinationDocker: true, settings: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
for (const application of applications) {
|
||||
try {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
destinationDocker: { engine, network },
|
||||
settings: { previews }
|
||||
} = application;
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isRunning) {
|
||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||
}
|
||||
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) {
|
||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error during generateSSLCerts with ${application.fqdn}: ${error}`);
|
||||
}
|
||||
}
|
||||
const services = await db.prisma.service.findMany({
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
for (const service of services) {
|
||||
try {
|
||||
const {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDocker: { engine }
|
||||
} = service;
|
||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||
if (found) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isRunning = await checkContainer(engine, id);
|
||||
if (isRunning) {
|
||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error during generateSSLCerts with ${service.fqdn}: ${error}`);
|
||||
}
|
||||
}
|
||||
const { fqdn } = await db.prisma.setting.findFirst();
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
|
||||
}
|
||||
if (ssls.length > 0) {
|
||||
for (const ssl of ssls) {
|
||||
if (!dev) {
|
||||
console.log('Checking SSL for', ssl.domain);
|
||||
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||
} else {
|
||||
console.log('Checking SSL for', ssl.domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,23 @@ import fs from 'fs/promises';
|
||||
import * as buildpacks from '../buildPacks';
|
||||
import * as importers from '../importers';
|
||||
import { dockerInstance } from '../docker';
|
||||
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
||||
import { configureProxyForApplication, reloadHaproxy } from '../haproxy';
|
||||
import {
|
||||
asyncExecShell,
|
||||
asyncSleep,
|
||||
createDirectories,
|
||||
getDomain,
|
||||
getEngine,
|
||||
saveBuildLog
|
||||
} from '../common';
|
||||
import * as db from '$lib/database';
|
||||
import { decrypt } from '$lib/crypto';
|
||||
import { sentry } from '$lib/common';
|
||||
import {
|
||||
copyBaseConfigurationFiles,
|
||||
makeLabelForStandaloneApplication,
|
||||
setDefaultConfiguration
|
||||
} from '$lib/buildPacks/common';
|
||||
import { letsEncrypt } from '$lib/letsencrypt';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export default async function (job) {
|
||||
/*
|
||||
@@ -39,17 +46,33 @@ export default async function (job) {
|
||||
publishDirectory,
|
||||
projectId,
|
||||
secrets,
|
||||
phpModules,
|
||||
type,
|
||||
pullmergeRequestId = null,
|
||||
sourceBranch = null,
|
||||
settings
|
||||
settings,
|
||||
persistentStorage
|
||||
} = job.data;
|
||||
const { debug } = settings;
|
||||
|
||||
await asyncSleep(500);
|
||||
await db.prisma.build.updateMany({
|
||||
where: {
|
||||
status: 'queued',
|
||||
id: { not: buildId },
|
||||
applicationId,
|
||||
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
|
||||
},
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
|
||||
let volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
||||
buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
}) || [];
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
if (pullmergeRequestId) {
|
||||
branch = sourceBranch;
|
||||
@@ -63,26 +86,12 @@ export default async function (job) {
|
||||
if (destinationDockerId) {
|
||||
destinationType = 'docker';
|
||||
}
|
||||
// Not implemented yet
|
||||
// if (destinationKubernetesId) {
|
||||
// destinationType = 'kubernetes'
|
||||
// }
|
||||
|
||||
if (destinationType === 'docker') {
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const build = await db.createBuild({
|
||||
id: buildId,
|
||||
applicationId,
|
||||
destinationDockerId: destinationDocker.id,
|
||||
gitSourceId: gitSource.id,
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
type
|
||||
});
|
||||
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId: build.id });
|
||||
await db.prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
|
||||
const configuration = await setDefaultConfiguration(job.data);
|
||||
|
||||
@@ -92,6 +101,7 @@ export default async function (job) {
|
||||
startCommand = configuration.startCommand;
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
baseDirectory = configuration.baseDirectory;
|
||||
|
||||
let commit = await importers[gitSource.type]({
|
||||
applicationId,
|
||||
@@ -102,19 +112,22 @@ export default async function (job) {
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
repository,
|
||||
branch,
|
||||
buildId: build.id,
|
||||
buildId,
|
||||
apiUrl: gitSource.apiUrl,
|
||||
projectId,
|
||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
|
||||
});
|
||||
if (!commit) {
|
||||
throw new Error('No commit found?');
|
||||
}
|
||||
let tag = commit.slice(0, 7);
|
||||
if (pullmergeRequestId) {
|
||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.prisma.build.update({ where: { id: build.id }, data: { commit } });
|
||||
db.prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
@@ -165,7 +178,7 @@ export default async function (job) {
|
||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId);
|
||||
if (buildpacks[buildPack])
|
||||
await buildpacks[buildPack]({
|
||||
buildId: build.id,
|
||||
buildId,
|
||||
applicationId,
|
||||
domain,
|
||||
name,
|
||||
@@ -186,7 +199,8 @@ export default async function (job) {
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets
|
||||
secrets,
|
||||
phpModules
|
||||
});
|
||||
else {
|
||||
saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
@@ -208,8 +222,14 @@ export default async function (job) {
|
||||
const envs = [];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (!secret.isBuildSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -232,33 +252,67 @@ export default async function (job) {
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
});
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
try {
|
||||
saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
const { stderr } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --env-file=${workdir}/.env ${labels.join(
|
||||
' '
|
||||
)} --name ${imageId} --network ${
|
||||
docker.network
|
||||
} --restart always -d ${applicationId}:${tag}`
|
||||
// for await (const volume of volumes) {
|
||||
// const id = volume.split(':')[0];
|
||||
// try {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`);
|
||||
// } catch (error) {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`);
|
||||
// }
|
||||
// }
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const compose = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[imageId]: {
|
||||
image: `${applicationId}:${tag}`,
|
||||
container_name: imageId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
networks: [docker.network],
|
||||
labels: labels,
|
||||
depends_on: [],
|
||||
restart: 'always'
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[docker.network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
||||
);
|
||||
if (stderr) console.log(stderr);
|
||||
|
||||
// const { stderr } = await asyncExecShell(
|
||||
// `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
|
||||
// ' '
|
||||
// )} --name ${imageId} --network ${docker.network} --restart always ${volumes.length > 0 ? volumes : ''
|
||||
// } -d ${applicationId}:${tag}`
|
||||
// );
|
||||
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
} catch (error) {
|
||||
saveBuildLog({ line: error, buildId, applicationId });
|
||||
sentry.captureException(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
|
||||
await configureProxyForApplication({ domain, imageId, applicationId, port });
|
||||
if (isHttps) await letsEncrypt({ domain, id: applicationId });
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
|
||||
} else {
|
||||
saveBuildLog({
|
||||
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,71 @@
|
||||
import { dev } from '$app/env';
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { asyncExecShell, getEngine, version } from '$lib/common';
|
||||
import { prisma } from '$lib/database';
|
||||
|
||||
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
|
||||
export default async function () {
|
||||
if (!dev) {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
for (const destinationDocker of destinationDockers) {
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||
} catch (error) {
|
||||
//
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`);
|
||||
} catch (error) {
|
||||
//
|
||||
console.log(error);
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
for (const destinationDocker of destinationDockers) {
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
// Cleanup old coolify images
|
||||
try {
|
||||
let { stdout: images } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs `
|
||||
);
|
||||
images = images.trim();
|
||||
if (images) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
// Tagging images with labels
|
||||
// try {
|
||||
// const images = [
|
||||
// `coollabsio/${defaultProxyImageTcp}`,
|
||||
// `coollabsio/${defaultProxyImageHttp}`,
|
||||
// 'certbot/certbot:latest',
|
||||
// 'node:16.14.0-alpine',
|
||||
// 'alpine:latest',
|
||||
// 'nginx:stable-alpine',
|
||||
// 'node:lts',
|
||||
// 'php:apache',
|
||||
// 'rust:latest'
|
||||
// ];
|
||||
// for (const image of images) {
|
||||
// try {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
|
||||
// } catch (error) {
|
||||
// await asyncExecShell(
|
||||
// `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {}
|
||||
// if (!dev) {
|
||||
// // Cleanup images that are not managed by coolify
|
||||
// try {
|
||||
// await asyncExecShell(
|
||||
// `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
|
||||
// );
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
// // Cleanup old images >3 days
|
||||
// try {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ const cron = async () => {
|
||||
);
|
||||
|
||||
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
||||
// await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } });
|
||||
await queue.cleanup.add('cleanup', {}, { repeat: { every: 3600000 } });
|
||||
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||
|
||||
const events = {
|
||||
@@ -107,42 +107,39 @@ cron().catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
const buildQueueName = dev ? cuid() : 'build_queue';
|
||||
const buildQueueName = 'build_queue';
|
||||
const buildQueue = new Queue(buildQueueName, connectionOptions);
|
||||
const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), {
|
||||
concurrency: 2,
|
||||
concurrency: 1,
|
||||
...connectionOptions
|
||||
});
|
||||
|
||||
buildWorker.on('completed', async (job: Bullmq.Job) => {
|
||||
try {
|
||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
||||
await asyncExecShell(`rm -fr ${workdir}`);
|
||||
await asyncExecShell(
|
||||
`test -f /tmp/build-sources/${job.data.repository}/id.rsa && rm /tmp/build-sources/${job.data.repository}/id.rsa`
|
||||
);
|
||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
||||
console.log(failedReason);
|
||||
try {
|
||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
||||
await asyncExecShell(`rm -fr ${workdir}`);
|
||||
await asyncExecShell(
|
||||
`test -f /tmp/build-sources/${job.data.repository}/id.rsa && rm /tmp/build-sources/${job.data.repository}/id.rsa`
|
||||
);
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||
}
|
||||
saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id });
|
||||
saveBuildLog({
|
||||
line: 'Failed to deploy!',
|
||||
buildId: job.data.build_id,
|
||||
applicationId: job.data.id
|
||||
});
|
||||
saveBuildLog({
|
||||
line: `Reason: ${failedReason.toString()}`,
|
||||
buildId: job.data.build_id,
|
||||
@@ -150,29 +147,7 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
||||
});
|
||||
});
|
||||
|
||||
// const letsEncryptQueueName = dev ? cuid() : 'letsencrypt_queue'
|
||||
// const letsEncryptQueue = new Queue(letsEncryptQueueName, connectionOptions)
|
||||
|
||||
// const letsEncryptWorker = new Worker(letsEncryptQueueName, async (job) => await letsencrypt(job), {
|
||||
// concurrency: 1,
|
||||
// ...connectionOptions
|
||||
// })
|
||||
// letsEncryptWorker.on('completed', async () => {
|
||||
// // TODO: Save letsencrypt logs as build logs!
|
||||
// console.log('[DEBUG] Lets Encrypt job completed')
|
||||
// })
|
||||
|
||||
// letsEncryptWorker.on('failed', async (job: Job, failedReason: string) => {
|
||||
// try {
|
||||
// await prisma.applicationSettings.updateMany({ where: { applicationId: job.data.id }, data: { forceSSL: false } })
|
||||
// } catch (error) {
|
||||
// console.log(error)
|
||||
// }
|
||||
// console.log('[DEBUG] Lets Encrypt job failed')
|
||||
// console.log(failedReason)
|
||||
// })
|
||||
|
||||
const buildLogQueueName = dev ? cuid() : 'log_queue';
|
||||
const buildLogQueueName = 'log_queue';
|
||||
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
|
||||
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
|
||||
concurrency: 1,
|
||||
|
||||
@@ -1,64 +1,11 @@
|
||||
import { getDomain } from '$lib/common';
|
||||
import { prisma } from '$lib/database';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import {
|
||||
checkContainer,
|
||||
configureCoolifyProxyOn,
|
||||
configureProxyForApplication,
|
||||
forceSSLOnApplication,
|
||||
reloadHaproxy,
|
||||
startCoolifyProxy
|
||||
} from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { configureHAProxy } from '$lib/haproxy/configuration';
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
// Check destination containers and configure proxy if needed
|
||||
const destinationDockers = await prisma.destinationDocker.findMany({});
|
||||
for (const destination of destinationDockers) {
|
||||
if (destination.isCoolifyProxyUsed) {
|
||||
const docker = dockerInstance({ destinationDocker: destination });
|
||||
const containers = await docker.engine.listContainers();
|
||||
const configurations = containers.filter(
|
||||
(container) => container.Labels['coolify.managed']
|
||||
);
|
||||
for (const configuration of configurations) {
|
||||
const parsedConfiguration = JSON.parse(
|
||||
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
|
||||
);
|
||||
if (configuration.Labels['coolify.type'] === 'standalone-application') {
|
||||
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
await configureProxyForApplication({
|
||||
domain,
|
||||
imageId: pullmergeRequestId
|
||||
? `${applicationId}-${pullmergeRequestId}`
|
||||
: applicationId,
|
||||
applicationId,
|
||||
port
|
||||
});
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) await forceSSLOnApplication({ domain });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check Coolify FQDN and configure proxy if needed
|
||||
const { fqdn } = await db.listSettings();
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
|
||||
if (!found) await startCoolifyProxy('/var/run/docker.sock');
|
||||
await configureCoolifyProxyOn({ domain });
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) await forceSSLOnApplication({ domain });
|
||||
}
|
||||
return await configureHAProxy();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
} finally {
|
||||
// await reloadHaproxy('/var/run/docker.sock');
|
||||
console.log(error.response?.body || error);
|
||||
return ErrorHandler(error.response?.body || error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,8 @@
|
||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||
import { prisma } from '$lib/database';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import { forceSSLOnApplication } from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { dev } from '$app/env';
|
||||
import { generateSSLCerts } from '$lib/letsencrypt';
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany({});
|
||||
for (const destination of destinationDockers) {
|
||||
if (destination.isCoolifyProxyUsed) {
|
||||
const docker = dockerInstance({ destinationDocker: destination });
|
||||
const containers = await docker.engine.listContainers();
|
||||
const configurations = containers.filter(
|
||||
(container) => container.Labels['coolify.managed']
|
||||
);
|
||||
for (const configuration of configurations) {
|
||||
const parsedConfiguration = JSON.parse(
|
||||
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
|
||||
);
|
||||
if (configuration.Labels['coolify.type'] === 'standalone-application') {
|
||||
const { fqdn } = parsedConfiguration;
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) {
|
||||
if (dev) {
|
||||
console.log('DEV MODE: SSL is enabled');
|
||||
} else {
|
||||
const host = getEngine(destination.engine);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
|
||||
);
|
||||
const { stderr } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name bash -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
|
||||
);
|
||||
if (stderr) throw new Error(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const { fqdn } = await db.listSettings();
|
||||
if (fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) {
|
||||
if (dev) {
|
||||
console.log('DEV MODE: SSL is enabled');
|
||||
} else {
|
||||
await asyncExecShell(
|
||||
`docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
|
||||
);
|
||||
|
||||
const { stderr } = await asyncExecShell(
|
||||
`docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
|
||||
);
|
||||
if (stderr) throw new Error(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return await generateSSLCerts();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { asyncExecShell } from '$lib/common';
|
||||
import { reloadHaproxy } from '$lib/haproxy';
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
return await asyncExecShell(
|
||||
await asyncExecShell(
|
||||
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
||||
);
|
||||
await reloadHaproxy('unix:///var/run/docker.sock');
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
export const publicPaths = [
|
||||
'/login',
|
||||
'/register',
|
||||
'/reset',
|
||||
'/reset/password',
|
||||
'/webhooks/success',
|
||||
'/webhooks/github',
|
||||
'/webhooks/github/install',
|
||||
|
||||
6
src/lib/store.ts
Normal file
6
src/lib/store.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const gitTokens = writable({
|
||||
githubToken: null,
|
||||
gitlabToken: null
|
||||
});
|
||||
@@ -2,14 +2,14 @@
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { publicPaths } from '$lib/settings';
|
||||
|
||||
export const load: Load = async ({ fetch, url, params, session }) => {
|
||||
if (!session.uid && !publicPaths.includes(url.pathname)) {
|
||||
export const load: Load = async ({ fetch, url, session }) => {
|
||||
if (!session.userId && !publicPaths.includes(url.pathname)) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/login'
|
||||
};
|
||||
}
|
||||
if (!session.uid) {
|
||||
if (!session.userId) {
|
||||
return {};
|
||||
}
|
||||
const endpoint = `/teams.json`;
|
||||
@@ -30,7 +30,6 @@
|
||||
<script>
|
||||
export let teams;
|
||||
export let selectedTeamId;
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import '../tailwind.css';
|
||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast';
|
||||
@@ -39,18 +38,19 @@
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { asyncSleep } from '$lib/components/common';
|
||||
import { del, get, post } from '$lib/api';
|
||||
import { dev } from '$app/env';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
let isUpdateAvailable = false;
|
||||
|
||||
let updateStatus = {
|
||||
found: false,
|
||||
loading: false,
|
||||
checking: false,
|
||||
success: null
|
||||
};
|
||||
|
||||
let latestVersion = 'latest';
|
||||
onMount(async () => {
|
||||
if ($session.uid) {
|
||||
if ($session.userId) {
|
||||
const overrideVersion = browser && window.localStorage.getItem('latestVersion');
|
||||
try {
|
||||
await get(`/login.json`);
|
||||
} catch ({ error }) {
|
||||
@@ -59,16 +59,18 @@
|
||||
return errorNotification(error);
|
||||
}
|
||||
if ($session.teamId === '0') {
|
||||
updateStatus.checking = true;
|
||||
try {
|
||||
const data = await get(`/update.json`);
|
||||
if (data?.isUpdateAvailable) {
|
||||
await post(`/update.json`, { type: 'pull' });
|
||||
if (overrideVersion || data?.isUpdateAvailable) {
|
||||
latestVersion = overrideVersion || data.latestVersion;
|
||||
if (overrideVersion) {
|
||||
isUpdateAvailable = true;
|
||||
} else {
|
||||
isUpdateAvailable = data.isUpdateAvailable;
|
||||
}
|
||||
}
|
||||
isUpdateAvailable = data?.isUpdateAvailable;
|
||||
} catch (error) {
|
||||
} finally {
|
||||
updateStatus.checking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +85,7 @@
|
||||
}
|
||||
async function switchTeam() {
|
||||
try {
|
||||
await post(`/index.json?from=${$page.url.pathname}`, {
|
||||
await post(`/dashboard.json?from=${$page.url.pathname}`, {
|
||||
cookie: 'teamId',
|
||||
value: selectedTeamId
|
||||
});
|
||||
@@ -95,10 +97,14 @@
|
||||
|
||||
async function update() {
|
||||
updateStatus.loading = true;
|
||||
if (!dev) {
|
||||
try {
|
||||
await post(`/update.json`, { type: 'update' });
|
||||
toast.push('Update completed. Waiting for the new version to start...');
|
||||
try {
|
||||
if (dev) {
|
||||
console.log(`updating to ${latestVersion}`);
|
||||
await asyncSleep(4000);
|
||||
return window.location.reload();
|
||||
} else {
|
||||
await post(`/update.json`, { type: 'update', latestVersion });
|
||||
toast.push('Update completed.<br><br>Waiting for the new version to start...');
|
||||
let reachable = false;
|
||||
let tries = 0;
|
||||
do {
|
||||
@@ -117,31 +123,11 @@
|
||||
updateStatus.success = true;
|
||||
await asyncSleep(3000);
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
updateStatus.success = false;
|
||||
updateStatus.loading = false;
|
||||
}
|
||||
} else {
|
||||
let reachable = false;
|
||||
let tries = 0;
|
||||
do {
|
||||
await asyncSleep(1000);
|
||||
try {
|
||||
await get(`/undead.json`);
|
||||
reachable = true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
reachable = false;
|
||||
}
|
||||
console.log(reachable);
|
||||
if (reachable) break;
|
||||
tries++;
|
||||
} while (!reachable || tries < 120);
|
||||
toast.push('New version reachable. Reloading...');
|
||||
await asyncSleep(2000);
|
||||
window.location.reload();
|
||||
} catch ({ error }) {
|
||||
updateStatus.success = false;
|
||||
updateStatus.loading = false;
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -150,7 +136,7 @@
|
||||
<title>Coolify</title>
|
||||
</svelte:head>
|
||||
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
||||
{#if $session.uid}
|
||||
{#if $session.userId}
|
||||
<nav class="nav-main">
|
||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
||||
@@ -332,42 +318,17 @@
|
||||
|
||||
<div class="flex flex-col space-y-4 py-2">
|
||||
{#if $session.teamId === '0'}
|
||||
{#if updateStatus.checking}
|
||||
<button
|
||||
disabled
|
||||
in:fade={{ duration: 150 }}
|
||||
class="icons tooltip-right bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
||||
data-tooltip="Checking for updates..."
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-9 w-8 animate-spin"
|
||||
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 isUpdateAvailable}
|
||||
{#if isUpdateAvailable}
|
||||
<button
|
||||
disabled={updateStatus.success === false}
|
||||
data-tooltip="Update available"
|
||||
title="Update available"
|
||||
on:click={update}
|
||||
class="icons tooltip-right bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
||||
>
|
||||
{#if updateStatus.loading}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-9 lds-heart"
|
||||
class="lds-heart h-9 w-8"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
@@ -465,7 +426,8 @@
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4 py-2">
|
||||
<a
|
||||
sveltekit:prefetch
|
||||
href="/teams"
|
||||
@@ -540,20 +502,20 @@
|
||||
<path d="M7 12h14l-3 -3m0 6l3 -3" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
|
||||
>
|
||||
<a
|
||||
class="text-[10px] no-underline"
|
||||
href={`https://github.com/coollabsio/coolify/releases/tag/v${$session.version}`}
|
||||
target="_blank">v{$session.version}</a
|
||||
<div
|
||||
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
|
||||
>
|
||||
<a
|
||||
class="text-[10px] no-underline"
|
||||
href={`https://github.com/coollabsio/coolify/releases/tag/v${$session.version}`}
|
||||
target="_blank">v{$session.version}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<select
|
||||
class="fixed right-0 bottom-0 z-50 m-2 p-2 px-4"
|
||||
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4"
|
||||
bind:value={selectedTeamId}
|
||||
on:change={switchTeam}
|
||||
>
|
||||
|
||||
@@ -17,13 +17,20 @@
|
||||
const endpoint = `/applications/${params.id}.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
const { application, githubToken, ghToken, isRunning, appId } = await res.json();
|
||||
let { application, isRunning, appId, githubToken, gitlabToken } = await res.json();
|
||||
if (!application || Object.entries(application).length === 0) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/applications'
|
||||
};
|
||||
}
|
||||
if (application.gitSource?.githubAppId && !githubToken) {
|
||||
const response = await fetch(`/applications/${params.id}/configuration/githubToken.json`);
|
||||
if (response.ok) {
|
||||
const { token } = await response.json();
|
||||
githubToken = token;
|
||||
}
|
||||
}
|
||||
const configurationPhase = checkConfiguration(application);
|
||||
if (
|
||||
configurationPhase &&
|
||||
@@ -38,12 +45,12 @@
|
||||
return {
|
||||
props: {
|
||||
application,
|
||||
isRunning
|
||||
isRunning,
|
||||
githubToken,
|
||||
gitlabToken
|
||||
},
|
||||
stuff: {
|
||||
isRunning,
|
||||
ghToken,
|
||||
githubToken,
|
||||
application,
|
||||
appId
|
||||
}
|
||||
@@ -60,12 +67,19 @@
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let isRunning;
|
||||
export let githubToken;
|
||||
export let gitlabToken;
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
import { del, post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
if (githubToken) $gitTokens.githubToken = githubToken;
|
||||
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
|
||||
|
||||
let loading = false;
|
||||
const { id } = $page.params;
|
||||
@@ -73,7 +87,15 @@
|
||||
async function handleDeploySubmit() {
|
||||
try {
|
||||
const { buildId } = await post(`/applications/${id}/deploy.json`, { ...application });
|
||||
return await goto(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
toast.push('Deployment queued.');
|
||||
console.log($page.url);
|
||||
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);
|
||||
}
|
||||
@@ -98,8 +120,6 @@
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -115,7 +135,7 @@
|
||||
title="Stop application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-green-600 hover:text-white"
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Stop application'
|
||||
: 'You do not have permission to stop the application.'}
|
||||
@@ -140,7 +160,7 @@
|
||||
title="Rebuild application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-green-600 hover:text-white"
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Rebuild application'
|
||||
: 'You do not have permission to rebuild application.'}
|
||||
@@ -169,7 +189,7 @@
|
||||
title="Build and start application"
|
||||
type="submit"
|
||||
disabled={!$session.isAdmin}
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-green-600 hover:text-white"
|
||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
||||
data-tooltip={$session.isAdmin
|
||||
? 'Build and start application'
|
||||
: 'You do not have permission to Build and start application.'}
|
||||
@@ -258,6 +278,35 @@
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/storage"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||
>
|
||||
<button
|
||||
title="Persistent Storage"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Persistent Storage"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-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" />
|
||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<a
|
||||
href="/applications/{id}/previews"
|
||||
sveltekit:prefetch
|
||||
|
||||
@@ -1,28 +1,51 @@
|
||||
import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common';
|
||||
import { dev } from '$app/env';
|
||||
import { getDomain, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { promises as dns } from 'dns';
|
||||
|
||||
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, forceSave } = await event.request.json();
|
||||
fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
const domain = getDomain(fqdn);
|
||||
const found = await db.isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw {
|
||||
message: `Domain ${getDomain(fqdn)} is already configured.`
|
||||
message: `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
|
||||
};
|
||||
}
|
||||
if (!dev && !forceSave) {
|
||||
let ip = [];
|
||||
let localIp = [];
|
||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
||||
|
||||
try {
|
||||
localIp = await dns.resolve4(event.url.hostname);
|
||||
} catch (error) {}
|
||||
try {
|
||||
ip = await dns.resolve4(domain);
|
||||
} catch (error) {}
|
||||
|
||||
if (localIp?.length > 0) {
|
||||
if (ip?.length === 0 || !ip.includes(localIp[0])) {
|
||||
throw {
|
||||
message: `DNS not set or propogated for ${domain}.<br><br>Please check your DNS settings.`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { post } from '$lib/api';
|
||||
import { findBuildPack } from '$lib/components/templates';
|
||||
import { errorNotification } from '$lib/form';
|
||||
|
||||
const { id } = $page.params;
|
||||
@@ -11,10 +12,13 @@
|
||||
export let buildPack;
|
||||
export let foundConfig;
|
||||
export let scanning;
|
||||
export let packageManager;
|
||||
|
||||
async function handleSubmit(name) {
|
||||
try {
|
||||
const tempBuildPack = JSON.parse(JSON.stringify(buildPack));
|
||||
const tempBuildPack = JSON.parse(
|
||||
JSON.stringify(findBuildPack(buildPack.name, packageManager))
|
||||
);
|
||||
delete tempBuildPack.name;
|
||||
delete tempBuildPack.fancyName;
|
||||
delete tempBuildPack.color;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let githubToken;
|
||||
export let application;
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { get, post } from '$lib/api';
|
||||
import { getGithubToken } from '$lib/components/common';
|
||||
import { enhance, errorNotification } from '$lib/form';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { gitTokens } from '$lib/store';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
@@ -27,22 +25,17 @@
|
||||
let selected = {
|
||||
projectId: undefined,
|
||||
repository: undefined,
|
||||
branch: undefined
|
||||
branch: undefined,
|
||||
autodeploy: application.settings.autodeploy || true
|
||||
};
|
||||
let showSave = false;
|
||||
let token = null;
|
||||
|
||||
async function loadRepositoriesByPage(page = 0) {
|
||||
try {
|
||||
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
|
||||
Authorization: `token ${token}`
|
||||
});
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
}
|
||||
async function loadRepositories() {
|
||||
token = await getGithubToken({ apiUrl, githubToken, application });
|
||||
let page = 1;
|
||||
let reposCount = 0;
|
||||
const loadedRepos = await loadRepositoriesByPage();
|
||||
@@ -63,7 +56,7 @@
|
||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||
try {
|
||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
||||
Authorization: `token ${token}`
|
||||
Authorization: `token ${$gitTokens.githubToken}`
|
||||
});
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
@@ -74,18 +67,70 @@
|
||||
}
|
||||
async function isBranchAlreadyUsed() {
|
||||
try {
|
||||
return await get(
|
||||
const data = await get(
|
||||
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
|
||||
);
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
if (data.used) {
|
||||
const sure = confirm(
|
||||
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
|
||||
);
|
||||
if (sure) {
|
||||
selected.autodeploy = false;
|
||||
showSave = true;
|
||||
return true;
|
||||
}
|
||||
showSave = false;
|
||||
return true;
|
||||
}
|
||||
showSave = true;
|
||||
} catch ({ error }) {
|
||||
showSave = false;
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await loadRepositories();
|
||||
try {
|
||||
if (!$gitTokens.githubToken) {
|
||||
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||
$gitTokens.githubToken = token;
|
||||
}
|
||||
await loadRepositories();
|
||||
} catch (error) {
|
||||
if (
|
||||
error.error === 'invalid_token' ||
|
||||
error.error_description ===
|
||||
'Token is expired. You can either do re-authorization or token refresh.' ||
|
||||
error.message === '401 Unauthorized'
|
||||
) {
|
||||
if (application.gitSource.gitlabAppId) {
|
||||
let htmlUrl = application.gitSource.htmlUrl;
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
`${htmlUrl}/oauth/authorize?client_id=${application.gitSource.gitlabApp.appId}&redirect_uri=${window.location.origin}/webhooks/gitlab&response_type=code&scope=api+email+read_repository&state=${$page.params.id}`,
|
||||
'GitLab',
|
||||
'resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(() => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
if (error.message === 'Bad credentials') {
|
||||
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||
$gitTokens.githubToken = token;
|
||||
return await loadRepositories();
|
||||
}
|
||||
return errorNotification(error);
|
||||
}
|
||||
});
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
@@ -106,8 +151,8 @@
|
||||
<a href={`/sources/${application.gitSource.id}`}><button>Configure it now</button></a>
|
||||
</div>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div>
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
||||
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
||||
{#if loading.repositories}
|
||||
<select name="repository" disabled class="w-96">
|
||||
<option selected value="">Loading repositories...</option>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import cuid from 'cuid';
|
||||
import { goto } from '$app/navigation';
|
||||
import { del, get, post, put } from '$lib/api';
|
||||
import { gitTokens } from '$lib/store';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
@@ -29,6 +30,7 @@
|
||||
let projects = [];
|
||||
let branches = [];
|
||||
let showSave = false;
|
||||
let autodeploy = application.settings.autodeploy || true;
|
||||
|
||||
let selected = {
|
||||
group: undefined,
|
||||
@@ -36,13 +38,13 @@
|
||||
branch: undefined
|
||||
};
|
||||
onMount(async () => {
|
||||
if (!$session.gitlabToken) {
|
||||
if (!$gitTokens.gitlabToken) {
|
||||
getGitlabToken();
|
||||
} else {
|
||||
loading.base = true;
|
||||
try {
|
||||
const user = await get(`${apiUrl}/v4/user`, {
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
username = user.username;
|
||||
} catch (error) {
|
||||
@@ -50,7 +52,7 @@
|
||||
}
|
||||
try {
|
||||
groups = await get(`${apiUrl}/v4/groups?per_page=5000`, {
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
} catch (error) {
|
||||
errorNotification(error);
|
||||
@@ -88,7 +90,7 @@
|
||||
projects = await get(
|
||||
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`,
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -102,7 +104,7 @@
|
||||
projects = await get(
|
||||
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`,
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -120,7 +122,7 @@
|
||||
branches = await get(
|
||||
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`,
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -132,24 +134,27 @@
|
||||
}
|
||||
|
||||
async function isBranchAlreadyUsed() {
|
||||
const url = `/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`;
|
||||
|
||||
try {
|
||||
await get(url);
|
||||
const data = await get(
|
||||
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
|
||||
);
|
||||
if (data.used) {
|
||||
const sure = confirm(
|
||||
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
|
||||
);
|
||||
if (sure) {
|
||||
autodeploy = false;
|
||||
showSave = true;
|
||||
return true;
|
||||
}
|
||||
showSave = false;
|
||||
return true;
|
||||
}
|
||||
showSave = true;
|
||||
} catch (error) {
|
||||
showSave = false;
|
||||
return errorNotification('Branch already configured');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
// async function saveDeployKey(deployKeyId: number) {
|
||||
// try {
|
||||
// await post(updateDeployKeyIdUrl, { deployKeyId });
|
||||
// } catch (error) {
|
||||
// errorNotification(error);
|
||||
// throw new Error(error);
|
||||
// }
|
||||
// }
|
||||
async function checkSSHKey(sshkeyUrl) {
|
||||
try {
|
||||
return await post(sshkeyUrl, {});
|
||||
@@ -174,7 +179,7 @@
|
||||
merge_requests_events: true
|
||||
},
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -198,17 +203,16 @@
|
||||
publicSshKey = publicKey;
|
||||
}
|
||||
const deployKeys = await get(deployKeyUrl, {
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
|
||||
if (deployKeyFound.length > 0) {
|
||||
for (const deployKey of deployKeyFound) {
|
||||
console.log(`${deployKeyUrl}/${deployKey.id}`);
|
||||
await del(
|
||||
`${deployKeyUrl}/${deployKey.id}`,
|
||||
{},
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -221,7 +225,7 @@
|
||||
can_push: false
|
||||
},
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
await post(updateDeployKeyIdUrl, { deployKeyId: id });
|
||||
@@ -239,10 +243,14 @@
|
||||
|
||||
const url = `/applications/${id}/configuration/repository.json`;
|
||||
try {
|
||||
const repository = `${selected.group.full_path.replace('-personal', '')}/${
|
||||
selected.project.name
|
||||
}`;
|
||||
await post(url, {
|
||||
repository: `${selected.group.full_path}/${selected.project.name}`,
|
||||
repository,
|
||||
branch: selected.branch.name,
|
||||
projectId: selected.project.id,
|
||||
autodeploy,
|
||||
webhookToken
|
||||
});
|
||||
return await goto(from || `/applications/${id}/configuration/buildpack`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
@@ -21,7 +21,7 @@ export const get: RequestHandler = async (event) => {
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,6 +36,6 @@ export const post: RequestHandler = async (event) => {
|
||||
await db.configureBuildPack({ id, buildPack });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||
const { application, ghToken } = stuff;
|
||||
const { application } = stuff;
|
||||
if (application?.buildPack && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
@@ -14,8 +14,7 @@
|
||||
return {
|
||||
props: {
|
||||
...(await res.json()),
|
||||
application,
|
||||
ghToken
|
||||
application
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -30,30 +29,35 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { buildPacks, scanningTemplates } from '$lib/components/templates';
|
||||
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/components/templates';
|
||||
import BuildPack from './_BuildPack.svelte';
|
||||
import { page, session } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { gitTokens } from '$lib/store';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
let scanning = true;
|
||||
let foundConfig = null;
|
||||
let packageManager = 'npm';
|
||||
|
||||
export let apiUrl;
|
||||
export let projectId;
|
||||
export let repository;
|
||||
export let branch;
|
||||
export let ghToken;
|
||||
export let type;
|
||||
export let application;
|
||||
|
||||
function checkPackageJSONContents({ key, json }) {
|
||||
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
||||
}
|
||||
function checkTemplates({ json }) {
|
||||
function checkTemplates({ json, packageManager }) {
|
||||
for (const [key, value] of Object.entries(scanningTemplates)) {
|
||||
if (checkPackageJSONContents({ key, json })) {
|
||||
return buildPacks.find((bp) => bp.name === value.buildPack);
|
||||
foundConfig = findBuildPack(value.buildPack, packageManager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,11 +65,15 @@
|
||||
try {
|
||||
if (type === 'gitlab') {
|
||||
const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, {
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
});
|
||||
const packageJson = files.find(
|
||||
(file) => file.name === 'package.json' && file.type === 'blob'
|
||||
);
|
||||
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'blob');
|
||||
const pnpmLock = files.find(
|
||||
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'blob'
|
||||
);
|
||||
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'blob');
|
||||
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'blob');
|
||||
const requirementsTxt = files.find(
|
||||
@@ -73,35 +81,45 @@
|
||||
);
|
||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
|
||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
|
||||
|
||||
if (yarnLock) packageManager = 'yarn';
|
||||
if (pnpmLock) packageManager = 'pnpm';
|
||||
|
||||
if (dockerfile) {
|
||||
foundConfig.buildPack = 'docker';
|
||||
foundConfig = findBuildPack('docker', packageManager);
|
||||
} else if (packageJson) {
|
||||
const path = packageJson.path;
|
||||
const data = await get(
|
||||
`${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`,
|
||||
{
|
||||
Authorization: `Bearer ${$session.gitlabToken}`
|
||||
Authorization: `Bearer ${$gitTokens.gitlabToken}`
|
||||
}
|
||||
);
|
||||
const json = JSON.parse(data) || {};
|
||||
foundConfig = checkTemplates({ json });
|
||||
checkTemplates({ json, packageManager });
|
||||
} else if (cargoToml) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'rust');
|
||||
foundConfig = findBuildPack('rust');
|
||||
} else if (requirementsTxt) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'python');
|
||||
foundConfig = findBuildPack('python');
|
||||
} else if (indexHtml) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'static');
|
||||
foundConfig = findBuildPack('static', packageManager);
|
||||
} else if (indexPHP) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'php');
|
||||
foundConfig = findBuildPack('php');
|
||||
} else {
|
||||
foundConfig = findBuildPack('node', packageManager);
|
||||
}
|
||||
} else if (type === 'github') {
|
||||
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
||||
Authorization: `Bearer ${ghToken}`,
|
||||
Authorization: `Bearer ${$gitTokens.githubToken}`,
|
||||
Accept: 'application/vnd.github.v2.json'
|
||||
});
|
||||
const packageJson = files.find(
|
||||
(file) => file.name === 'package.json' && file.type === 'file'
|
||||
);
|
||||
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'file');
|
||||
const pnpmLock = files.find(
|
||||
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'file'
|
||||
);
|
||||
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'file');
|
||||
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'file');
|
||||
const requirementsTxt = files.find(
|
||||
@@ -109,26 +127,33 @@
|
||||
);
|
||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
|
||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
|
||||
|
||||
if (yarnLock) packageManager = 'yarn';
|
||||
if (pnpmLock) packageManager = 'pnpm';
|
||||
|
||||
if (dockerfile) {
|
||||
foundConfig.buildPack = 'docker';
|
||||
foundConfig = findBuildPack('docker', packageManager);
|
||||
} else if (packageJson) {
|
||||
const data = await get(`${packageJson.git_url}`, {
|
||||
Authorization: `Bearer ${ghToken}`,
|
||||
Authorization: `Bearer ${$gitTokens.githubToken}`,
|
||||
Accept: 'application/vnd.github.v2.raw'
|
||||
});
|
||||
const json = JSON.parse(data) || {};
|
||||
foundConfig = checkTemplates({ json });
|
||||
checkTemplates({ json, packageManager });
|
||||
} else if (cargoToml) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'rust');
|
||||
foundConfig = findBuildPack('rust');
|
||||
} else if (requirementsTxt) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'python');
|
||||
foundConfig = findBuildPack('python');
|
||||
} else if (indexHtml) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'static');
|
||||
foundConfig = findBuildPack('static', packageManager);
|
||||
} else if (indexPHP) {
|
||||
foundConfig = buildPacks.find((bp) => bp.name === 'php');
|
||||
foundConfig = findBuildPack('php');
|
||||
} else {
|
||||
foundConfig = findBuildPack('node', packageManager);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
scanning = true;
|
||||
if (
|
||||
error.error === 'invalid_token' ||
|
||||
error.error_description ===
|
||||
@@ -156,9 +181,14 @@
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
if (error.message === 'Bad credentials') {
|
||||
const { token } = await get(`/applications/${id}/configuration/githubToken.json`);
|
||||
$gitTokens.githubToken = token;
|
||||
browser && window.location.reload();
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
if (!foundConfig) foundConfig = buildPacks.find((bp) => bp.name === 'node');
|
||||
if (!foundConfig) foundConfig = findBuildPack('node', packageManager);
|
||||
scanning = false;
|
||||
}
|
||||
}
|
||||
@@ -176,10 +206,16 @@
|
||||
<div class="text-xl tracking-tight">Scanning repository to suggest a build pack for you...</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if packageManager === 'yarn' || packageManager === 'pnpm'}
|
||||
<div class="flex justify-center p-6">
|
||||
Found lock file for <span class="font-bold text-orange-500 pl-1">{packageManager}</span>.
|
||||
Using it for predefined commands commands.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
||||
{#each buildPacks as buildPack}
|
||||
<div class="p-2">
|
||||
<BuildPack {buildPack} {scanning} bind:foundConfig />
|
||||
<BuildPack {buildPack} {scanning} {packageManager} bind:foundConfig />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -12,6 +12,6 @@ export const post: RequestHandler = async (event) => {
|
||||
await db.updateDeployKey({ id, deployKeyId });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
|
||||
await db.configureDestinationForApplication({ id, destinationId });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: application.gitSource.githubApp.appId
|
||||
};
|
||||
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
const response = await fetch(
|
||||
`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${githubToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return {
|
||||
status: 201,
|
||||
body: { token: data.token },
|
||||
headers: {
|
||||
'Set-Cookie': `githubToken=${data.token}; Path=/; HttpOnly; Max-Age=15778800;`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
@@ -14,16 +14,14 @@ export const get: RequestHandler = async (event) => {
|
||||
|
||||
try {
|
||||
const found = await db.isBranchAlreadyUsed({ repository, branch, id });
|
||||
if (found) {
|
||||
throw {
|
||||
error: `Branch ${branch} is already used by another application`
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 200
|
||||
status: 200,
|
||||
body: {
|
||||
used: found ? true : false
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,16 +30,23 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
let { repository, branch, projectId, webhookToken } = await event.request.json();
|
||||
let { repository, branch, projectId, webhookToken, autodeploy } = await event.request.json();
|
||||
|
||||
repository = repository.toLowerCase();
|
||||
branch = branch.toLowerCase();
|
||||
projectId = Number(projectId);
|
||||
|
||||
try {
|
||||
await db.configureGitRepository({ id, repository, branch, projectId, webhookToken });
|
||||
await db.configureGitRepository({
|
||||
id,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
webhookToken,
|
||||
autodeploy
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ params, url, stuff }) => {
|
||||
const { application, githubToken, appId } = stuff;
|
||||
const { application, appId } = stuff;
|
||||
if (application?.branch && application?.repository && !url.searchParams.get('from')) {
|
||||
return {
|
||||
status: 302,
|
||||
@@ -10,7 +10,6 @@
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
githubToken,
|
||||
application,
|
||||
appId
|
||||
}
|
||||
@@ -20,8 +19,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
export let application;
|
||||
export let githubToken;
|
||||
export let appId;
|
||||
|
||||
import GithubRepositories from './_GithubRepositories.svelte';
|
||||
import GitlabRepositories from './_GitlabRepositories.svelte';
|
||||
</script>
|
||||
@@ -31,7 +30,7 @@
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#if application.gitSource.type === 'github'}
|
||||
<GithubRepositories {application} {githubToken} />
|
||||
<GithubRepositories {application} />
|
||||
{:else if application.gitSource.type === 'gitlab'}
|
||||
<GitlabRepositories {application} {appId} />
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -13,6 +13,6 @@ export const post: RequestHandler = async (event) => {
|
||||
await db.configureGitsource({ id, gitSourceId });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
import type Prisma from '@prisma/client';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { enhance, errorNotification } from '$lib/form';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { goto } from '$app/navigation';
|
||||
import { post } from '$lib/api';
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
gitlabApp: Prisma.GitlabApp;
|
||||
githubApp: Prisma.GithubApp;
|
||||
};
|
||||
sources = sources.filter(
|
||||
const filteredSources = sources.filter(
|
||||
(source) =>
|
||||
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
||||
(source.type === 'gitlab' && source.gitlabAppId)
|
||||
@@ -59,8 +59,8 @@
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
{#if !sources || sources.length === 0}
|
||||
<div class="flex flex-col justify-center">
|
||||
{#if !filteredSources || filteredSources.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="pb-2">No configurable Git Source found</div>
|
||||
<div class="flex justify-center">
|
||||
@@ -83,7 +83,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#each sources as source}
|
||||
{#each filteredSources as source}
|
||||
<div class="p-2">
|
||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
@@ -7,7 +7,7 @@ export const get: RequestHandler = async (event) => {
|
||||
try {
|
||||
return await db.getSshKey({ id });
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
export const post: RequestHandler = async (event) => {
|
||||
@@ -15,6 +15,6 @@ export const post: RequestHandler = async (event) => {
|
||||
try {
|
||||
return await db.generateSshKey({ id });
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const del: RequestHandler = async (event) => {
|
||||
@@ -14,6 +14,6 @@ export const del: RequestHandler = async (event) => {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,13 +4,14 @@ import cuid from 'cuid';
|
||||
import crypto from 'crypto';
|
||||
import { buildQueue } from '$lib/queues';
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { pullmergeRequestId = null, branch } = await event.request.json();
|
||||
try {
|
||||
const buildId = cuid();
|
||||
const applicationFound = await db.getApplication({ id, teamId });
|
||||
@@ -29,7 +30,30 @@ export const post: RequestHandler = async (event) => {
|
||||
.digest('hex');
|
||||
await db.prisma.application.update({ where: { id }, data: { configHash } });
|
||||
}
|
||||
await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound });
|
||||
await db.prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
|
||||
await db.prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'manual'
|
||||
}
|
||||
});
|
||||
if (pullmergeRequestId) {
|
||||
await buildQueue.add(buildId, {
|
||||
build_id: buildId,
|
||||
type: 'manual',
|
||||
...applicationFound,
|
||||
sourceBranch: branch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
} else {
|
||||
await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound });
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
@@ -37,6 +61,6 @@ export const post: RequestHandler = async (event) => {
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,50 +1,40 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import { getGithubToken } from '$lib/components/common';
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { get as getRequest } from '$lib/api';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
let githubToken = null;
|
||||
let ghToken = null;
|
||||
let isRunning = false;
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
let isRunning = false;
|
||||
let githubToken = event.locals.cookies?.githubToken || null;
|
||||
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
||||
try {
|
||||
const application = await db.getApplication({ id, teamId });
|
||||
const { gitSource } = application;
|
||||
if (gitSource?.type === 'github' && gitSource?.githubApp) {
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: gitSource.githubApp.appId
|
||||
};
|
||||
githubToken = jsonwebtoken.sign(payload, gitSource.githubApp.privateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
ghToken = await getGithubToken({ apiUrl: gitSource.apiUrl, application, githubToken });
|
||||
}
|
||||
if (application.destinationDockerId) {
|
||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
||||
}
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
isRunning,
|
||||
ghToken,
|
||||
githubToken,
|
||||
application,
|
||||
appId
|
||||
}
|
||||
appId,
|
||||
githubToken,
|
||||
gitlabToken
|
||||
},
|
||||
headers: {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,6 +72,6 @@ export const post: RequestHandler = async (event) => {
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,16 +42,24 @@
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import type Prisma from '@prisma/client';
|
||||
import { getDomain, notNodeDeployments, staticDeployments } from '$lib/components/common';
|
||||
import { notNodeDeployments, staticDeployments } from '$lib/components/common';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { post } from '$lib/api';
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
const { id } = $page.params;
|
||||
|
||||
let domainEl: HTMLInputElement;
|
||||
|
||||
let loading = false;
|
||||
let forceSave = false;
|
||||
let debug = application.settings.debug;
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
domainEl.focus();
|
||||
@@ -64,20 +72,48 @@
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
}
|
||||
if (name === 'autodeploy') {
|
||||
autodeploy = !autodeploy;
|
||||
}
|
||||
try {
|
||||
await post(`/applications/${id}/settings.json`, { previews, debug });
|
||||
await post(`/applications/${id}/settings.json`, {
|
||||
previews,
|
||||
debug,
|
||||
dualCerts,
|
||||
autodeploy,
|
||||
branch: application.branch,
|
||||
projectId: application.projectId
|
||||
});
|
||||
return toast.push('Settings saved.');
|
||||
} catch ({ error }) {
|
||||
if (name === 'debug') {
|
||||
debug = !debug;
|
||||
}
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
}
|
||||
if (name === 'autodeploy') {
|
||||
autodeploy = !autodeploy;
|
||||
}
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
try {
|
||||
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn });
|
||||
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
|
||||
await post(`/applications/${id}.json`, { ...application });
|
||||
return window.location.reload();
|
||||
return toast.push('Configurations saved.');
|
||||
} catch ({ error }) {
|
||||
if (error.startsWith('DNS not set')) {
|
||||
forceSave = true;
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
@@ -163,197 +199,204 @@
|
||||
<button
|
||||
type="submit"
|
||||
class:bg-green-600={!loading}
|
||||
class:bg-orange-600={forceSave}
|
||||
class:hover:bg-green-500={!loading}
|
||||
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
|
||||
class:hover:bg-orange-400={forceSave}
|
||||
disabled={loading}
|
||||
>{loading ? 'Saving...' : forceSave ? 'Are you sure to continue?' : 'Save'}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<div class="mt-2 grid grid-cols-3 items-center">
|
||||
<label for="name">Name</label>
|
||||
<div class="col-span-2 ">
|
||||
<div class="mt-2 grid grid-cols-2 items-center">
|
||||
<label for="name" class="text-base font-bold text-stone-100">Name</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="name"
|
||||
id="name"
|
||||
bind:value={application.name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="gitSource" class="text-base font-bold text-stone-100">Git Source</label>
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/source?from=/applications/${id}`
|
||||
: ''}
|
||||
class="no-underline"
|
||||
><input
|
||||
value={application.gitSource.name}
|
||||
id="gitSource"
|
||||
disabled
|
||||
class="cursor-pointer hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="repository" class="text-base font-bold text-stone-100">Git Repository</label>
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
|
||||
: ''}
|
||||
class="no-underline"
|
||||
><input
|
||||
value="{application.repository}/{application.branch}"
|
||||
id="repository"
|
||||
disabled
|
||||
class="cursor-pointer hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="buildPack" class="text-base font-bold text-stone-100">Build Pack</label>
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
|
||||
: ''}
|
||||
class="no-underline "
|
||||
>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="name"
|
||||
id="name"
|
||||
bind:value={application.name}
|
||||
required
|
||||
value={application.buildPack}
|
||||
id="buildPack"
|
||||
disabled
|
||||
class="cursor-pointer hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
|
||||
<div class="no-underline">
|
||||
<input
|
||||
value={application.destinationDocker.name}
|
||||
id="destination"
|
||||
disabled
|
||||
class="bg-transparent "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="gitSource">Git Source</label>
|
||||
<div class="col-span-2">
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/source?from=/applications/${id}`
|
||||
: ''}
|
||||
class="no-underline"
|
||||
><input
|
||||
value={application.gitSource.name}
|
||||
id="gitSource"
|
||||
disabled
|
||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="repository">Git Repository</label>
|
||||
<div class="col-span-2">
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
|
||||
: ''}
|
||||
class="no-underline"
|
||||
><input
|
||||
value="{application.repository}/{application.branch}"
|
||||
id="repository"
|
||||
disabled
|
||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="buildPack">Build Pack</label>
|
||||
<div class="col-span-2">
|
||||
<a
|
||||
href={$session.isAdmin
|
||||
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
|
||||
: ''}
|
||||
class="no-underline "
|
||||
>
|
||||
<input
|
||||
value={application.buildPack}
|
||||
id="buildPack"
|
||||
disabled
|
||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center pb-8">
|
||||
<label for="destination">Destination</label>
|
||||
<div class="col-span-2">
|
||||
<div class="no-underline">
|
||||
<input
|
||||
value={application.destinationDocker.name}
|
||||
id="destination"
|
||||
disabled
|
||||
class="bg-transparent "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">Application</div>
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2 px-10">
|
||||
<div class="grid grid-cols-3">
|
||||
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
readonly={!$session.isAdmin || isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
bind:value={application.fqdn}
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
placeholder="eg: https://coollabs.io"
|
||||
required
|
||||
/>
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="flex-col">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</label>
|
||||
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
||||
<Explainer
|
||||
text="<span class='text-white font-bold'>You can use the predefined random domain name or enter your own domain name.</span>"
|
||||
/>
|
||||
{/if}
|
||||
<Explainer
|
||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>To modify the domain, you must first stop the application."
|
||||
text="If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
readonly={!$session.isAdmin || isRunning}
|
||||
disabled={!$session.isAdmin || isRunning}
|
||||
bind:this={domainEl}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
bind:value={application.fqdn}
|
||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||
placeholder="eg: https://coollabs.io"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<Setting
|
||||
dataTooltip="Must be stopped to modify."
|
||||
disabled={isRunning}
|
||||
isCenter={false}
|
||||
bind:setting={dualCerts}
|
||||
title="Generate SSL for www and non-www?"
|
||||
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if !staticDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="port">Port</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="port"
|
||||
id="port"
|
||||
bind:value={application.port}
|
||||
placeholder="default: 3000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="installCommand">Install Command</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="installCommand"
|
||||
id="installCommand"
|
||||
bind:value={application.installCommand}
|
||||
placeholder="default: yarn install"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="buildCommand">Build Command</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="buildCommand"
|
||||
id="buildCommand"
|
||||
bind:value={application.buildCommand}
|
||||
placeholder="default: yarn build"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="startCommand" class="">Start Command</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="startCommand"
|
||||
id="startCommand"
|
||||
bind:value={application.startCommand}
|
||||
placeholder="default: yarn start"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-3">
|
||||
<label for="baseDirectory" class="pt-2">Base Directory</label>
|
||||
<div class="col-span-2">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="port" class="text-base font-bold text-stone-100">Port</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="baseDirectory"
|
||||
id="baseDirectory"
|
||||
bind:value={application.baseDirectory}
|
||||
placeholder="default: /"
|
||||
/>
|
||||
<Explainer
|
||||
text="Directory to use as the base of all commands. <br> Could be useful with monorepos."
|
||||
name="port"
|
||||
id="port"
|
||||
bind:value={application.port}
|
||||
placeholder="default: 3000"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="installCommand" class="text-base font-bold text-stone-100"
|
||||
>Install Command</label
|
||||
>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="installCommand"
|
||||
id="installCommand"
|
||||
bind:value={application.installCommand}
|
||||
placeholder="default: yarn install"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="buildCommand" class="text-base font-bold text-stone-100">Build Command</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="buildCommand"
|
||||
id="buildCommand"
|
||||
bind:value={application.buildCommand}
|
||||
placeholder="default: yarn build"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="startCommand" class="text-base font-bold text-stone-100">Start Command</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="startCommand"
|
||||
id="startCommand"
|
||||
bind:value={application.startCommand}
|
||||
placeholder="default: yarn start"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="flex-col">
|
||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||
>Base Directory</label
|
||||
>
|
||||
<Explainer
|
||||
text="Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>."
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="baseDirectory"
|
||||
id="baseDirectory"
|
||||
bind:value={application.baseDirectory}
|
||||
placeholder="default: /"
|
||||
/>
|
||||
</div>
|
||||
{#if !notNodeDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-3">
|
||||
<label for="publishDirectory" class="pt-2">Publish Directory</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="publishDirectory"
|
||||
id="publishDirectory"
|
||||
bind:value={application.publishDirectory}
|
||||
placeholder=" default: /"
|
||||
/>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="flex-col">
|
||||
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||
>Publish Directory</label
|
||||
>
|
||||
<Explainer
|
||||
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-600 font-bold'>dist</span>,<span class='text-green-600 font-bold'>_site</span> or <span class='text-green-600 font-bold'>public</span>."
|
||||
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="publishDirectory"
|
||||
id="publishDirectory"
|
||||
bind:value={application.publishDirectory}
|
||||
placeholder=" default: /"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -361,30 +404,33 @@
|
||||
<div class="flex space-x-1 pb-5 font-bold">
|
||||
<div class="title">Features</div>
|
||||
</div>
|
||||
<div class="px-4 pb-10 sm:px-6">
|
||||
<!-- <ul class="mt-2 divide-y divide-stone-800">
|
||||
<div class="px-10 pb-10">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
bind:setting={forceSSL}
|
||||
on:click={() => changeSettings('forceSSL')}
|
||||
title="Force https"
|
||||
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
|
||||
isCenter={false}
|
||||
bind:setting={autodeploy}
|
||||
on:click={() => changeSettings('autodeploy')}
|
||||
title="Enable Automatic Deployment"
|
||||
description="Enable automatic deployment through webhooks."
|
||||
/>
|
||||
</ul> -->
|
||||
<ul class="mt-2 divide-y divide-stone-800">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={previews}
|
||||
on:click={() => changeSettings('previews')}
|
||||
title="Enable MR/PR Previews"
|
||||
description="Creates previews from pull and merge requests."
|
||||
description="Enable preview deployments from pull or merge requests."
|
||||
/>
|
||||
</ul>
|
||||
<ul class="mt-2 divide-y divide-stone-800">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
isCenter={false}
|
||||
bind:setting={debug}
|
||||
on:click={() => changeSettings('debug')}
|
||||
title="Debug Logs"
|
||||
description="Enable debug logs during build phase. <br>(<span class='text-red-500'>sensitive information</span> could be visible in logs)"
|
||||
description="Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs."
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,41 +15,60 @@
|
||||
let loading = true;
|
||||
let currentStatus;
|
||||
let streamInterval;
|
||||
let followingBuild;
|
||||
let followingInterval;
|
||||
let logsEl;
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, '');
|
||||
|
||||
function followBuild() {
|
||||
followingBuild = !followingBuild;
|
||||
if (followingBuild) {
|
||||
followingInterval = setInterval(() => {
|
||||
logsEl.scrollTop = logsEl.scrollHeight;
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}, 100);
|
||||
} else {
|
||||
window.clearInterval(followingInterval);
|
||||
}
|
||||
}
|
||||
async function streamLogs(sequence = 0) {
|
||||
try {
|
||||
let { logs: responseLogs, status } = await get(
|
||||
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}`
|
||||
);
|
||||
currentStatus = status;
|
||||
logs = logs.concat(responseLogs);
|
||||
logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
|
||||
loading = false;
|
||||
streamInterval = setInterval(async () => {
|
||||
if (status !== 'running') {
|
||||
if (status !== 'running' && status !== 'queued') {
|
||||
clearInterval(streamInterval);
|
||||
return;
|
||||
}
|
||||
const nextSequence = logs[logs.length - 1].time;
|
||||
const nextSequence = logs[logs.length - 1]?.time || 0;
|
||||
try {
|
||||
const data = await get(
|
||||
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${nextSequence}`
|
||||
);
|
||||
status = data.status;
|
||||
currentStatus = status;
|
||||
logs = logs.concat(data.logs);
|
||||
|
||||
logs = logs.concat(data.logs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
|
||||
dispatch('updateBuildStatus', { status });
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}, 1000);
|
||||
} catch ({ error }) {
|
||||
console.log(error);
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
onDestroy(() => {
|
||||
clearInterval(streamInterval);
|
||||
clearInterval(followingInterval);
|
||||
});
|
||||
onMount(async () => {
|
||||
window.scrollTo(0, 0);
|
||||
@@ -60,16 +79,46 @@
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="relative">
|
||||
<div class="relative ">
|
||||
{#if currentStatus === 'running'}
|
||||
<LoadingLogs />
|
||||
{/if}
|
||||
<div
|
||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words"
|
||||
>
|
||||
{#each logs as log}
|
||||
<div>{log.line + '\n'}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if currentStatus === 'queued'}
|
||||
<div class="text-center font-bold text-xl">Queued and waiting for execution.</div>
|
||||
{:else}
|
||||
<div class="flex justify-end sticky top-0 p-2">
|
||||
<button
|
||||
on:click={followBuild}
|
||||
class="bg-transparent"
|
||||
data-tooltip="Follow logs"
|
||||
class:text-green-500={followingBuild}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-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" />
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<line x1="8" y1="12" x2="12" y2="16" />
|
||||
<line x1="12" y1="8" x2="12" y2="16" />
|
||||
<line x1="16" y1="12" x2="12" y2="16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||
bind:this={logsEl}
|
||||
>
|
||||
{#each logs as log}
|
||||
<div>{log.line + '\n'}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
@@ -19,10 +19,10 @@ export const get: RequestHandler = async (event) => {
|
||||
return {
|
||||
body: {
|
||||
logs,
|
||||
status: data?.status || 'running'
|
||||
status: data?.status
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user