Compare commits

...

109 Commits

Author SHA1 Message Date
Andras Bacsai
2b0d162226 Merge pull request #232 from coollabsio/v2.1.1
v2.1.1
2022-03-25 15:41:26 +01:00
Andras Bacsai
2c5f09a8bb fix: Cleanup only 2 hours+ old images 2022-03-25 15:34:14 +01:00
Andras Bacsai
ef073e586b Test nocheck proxy 2022-03-25 10:48:11 +01:00
Andras Bacsai
82bfdb87e3 UI fixes 2022-03-25 10:36:47 +01:00
Andras Bacsai
767e7b80cb chore: version++ 2022-03-25 09:14:55 +01:00
Andras Bacsai
8d26ea9063 Update packages 2022-03-25 09:14:32 +01:00
Andras Bacsai
1a7c4310d0 Merge pull request #230 from coollabsio/v2.1.0
v2.1.0
2022-03-23 11:54:15 +01:00
Andras Bacsai
4e8fe79e2b feat: Be able to redeploy PRs 2022-03-23 11:49:40 +01:00
Andras Bacsai
a8c5551292 fix: Volumes 2022-03-23 11:14:38 +01:00
Andras Bacsai
2bf73109b2 feat: Use compose instead of normal docker cmd 2022-03-23 10:25:32 +01:00
Andras Bacsai
f0ab3750bd Disable PHP modules, as the new image has all activated by default 2022-03-22 15:56:03 +01:00
Andras Bacsai
58a11e37fe Add schema 2022-03-22 14:58:08 +01:00
Andras Bacsai
927bf46304 fix: skip ssl cert in case of error 2022-03-22 10:37:33 +01:00
Andras Bacsai
6b89857697 chore: version++ 2022-03-22 10:24:52 +01:00
Andras Bacsai
b72e5ccef6 Merge branch 'main' into v2.0.32 2022-03-22 10:23:11 +01:00
Andras Bacsai
6617b7811b log ssl errors 2022-03-22 10:22:20 +01:00
Andras Bacsai
e1c1988db4 Merge pull request #229 from coollabsio/importer-error
Add debug for GH importer
2022-03-22 09:42:49 +01:00
Andras Bacsai
af99ea4678 Add debug for GH importer 2022-03-22 09:35:24 +01:00
Andras Bacsai
a6d5316090 WIP - Persistent storage 2022-03-21 21:46:49 +01:00
Andras Bacsai
f5e7a84fa6 Update buildpacks for static sites 2022-03-21 21:25:01 +01:00
Andras Bacsai
c013764b61 WIP Persistent storage 2022-03-21 16:58:13 +01:00
Andras Bacsai
2320ab0dfc WIP - Persistent storage 2022-03-20 23:51:50 +01:00
Andras Bacsai
1281a0f7e4 Merge pull request #226 from coollabsio/v2.0.31
v2.0.31
2022-03-20 15:21:57 +01:00
Andras Bacsai
d8350cd4ee Migration file 2022-03-20 15:14:33 +01:00
Andras Bacsai
e3b7c23ed9 chore: Version++ 2022-03-20 15:05:05 +01:00
Andras Bacsai
eae1ea21d6 fix: Add nginx + htaccess files 2022-03-20 15:03:24 +01:00
Andras Bacsai
541aa76b64 fix: Only cleanup same app 2022-03-20 14:21:11 +01:00
Andras Bacsai
7b8555d524 fix: Cleanup old builds 2022-03-20 14:20:29 +01:00
Andras Bacsai
fdf998c181 css fix for select 2022-03-20 14:03:52 +01:00
Andras Bacsai
3d6b343adc remove mysql 2022-03-19 23:47:05 +01:00
Andras Bacsai
e338cecc14 feat: Add PHP modules 2022-03-19 23:46:33 +01:00
Andras Bacsai
e5537a33fb Merge pull request #223 from coollabsio/v2.0.30
v2.0.30
2022-03-19 15:11:42 +01:00
Andras Bacsai
35384deb68 fix: Remove build logs in case of app removed 2022-03-19 15:06:25 +01:00
Andras Bacsai
547ca60c2a fix: Better queue system + more support on monorepos 2022-03-19 15:04:52 +01:00
Andras Bacsai
376f6f7455 fix: Basedir for dockerfiles 2022-03-19 13:33:31 +01:00
Andras Bacsai
abe92dedff fix: no webhook secret found? 2022-03-15 17:35:37 +01:00
Andras Bacsai
4b521ceedc chore: version++ 2022-03-15 17:25:17 +01:00
Andras Bacsai
6dfcb9e52b fix: No error if GitSource is missing 2022-03-15 17:22:28 +01:00
Andras Bacsai
335e3216e2 fix: Missing session data 2022-03-15 17:21:18 +01:00
Andras Bacsai
5b22bb4818 fix: No cookie found 2022-03-15 17:04:15 +01:00
Andras Bacsai
0097004882 Merge pull request #217 from coollabsio/v2.0.29
v2.0.29
2022-03-12 00:28:26 +01:00
Andras Bacsai
1bc9e4c2d3 fix: Autodeploy true by default for GH repos 2022-03-11 23:56:11 +01:00
Andras Bacsai
36c7e1a3c3 feat: Install pnpm into docker image if pnpm lock file is used 2022-03-11 23:55:57 +01:00
Andras Bacsai
c6b4d04e26 Revert double build 2022-03-11 22:48:55 +01:00
Andras Bacsai
fa6cf068c7 feat: Autodeploy pause 2022-03-11 22:36:21 +01:00
Andras Bacsai
7c273a3a48 feat: Check ssl for new apps/services first 2022-03-11 21:28:27 +01:00
Andras Bacsai
3de2ea1523 chore: version++ 2022-03-11 21:19:03 +01:00
Andras Bacsai
c5c9f84503 feat: Webhooks inititate all applications with the correct branch 2022-03-11 21:18:12 +01:00
Andras Bacsai
16ea9a3e07 Update options request 2022-03-11 20:52:11 +01:00
Andras Bacsai
48f952c798 fix: Personal Gitlab repos 2022-03-11 20:47:26 +01:00
Andras Bacsai
f78ea5de07 Remove colors Tailwind 2022-03-11 20:47:13 +01:00
Andras Bacsai
5adbd5e784 Merge pull request #210 from coollabsio/v2.0.28
v2.0.28
2022-03-04 15:39:35 +01:00
Andras Bacsai
5b2afa79d7 chore: version++ 2022-03-04 15:20:03 +01:00
Andras Bacsai
dc4e6d02b7 feat: Service secrets 2022-03-04 15:14:25 +01:00
Andras Bacsai
8ae61c8f78 fix: do not error if proxy is not running 2022-03-04 14:20:20 +01:00
Andras Bacsai
684b8e0914 Merge pull request #207 from coollabsio/v2.0.27
v2.0.27
2022-03-02 21:15:32 +01:00
Andras Bacsai
7c3314abae force image deletion 2022-03-02 21:14:53 +01:00
Andras Bacsai
ab9f8ff356 Before latest image 2022-03-02 21:08:36 +01:00
Andras Bacsai
892d8cd5c1 Reload proxy after ssl renewal 2022-03-02 21:04:42 +01:00
Andras Bacsai
8b8b45778d fix: application state in UI 2022-03-02 21:01:25 +01:00
Andras Bacsai
6655fb182c fix: cleanup coolify images 2022-03-02 20:57:28 +01:00
Andras Bacsai
0926d40247 fix: Reload haproxy if new cert is added 2022-03-02 20:43:16 +01:00
Andras Bacsai
ddc4d36688 Fix cleanup process of old coolify images 2022-03-02 20:32:29 +01:00
Andras Bacsai
53e1f22eb1 fix: check when a container is running 2022-03-02 20:32:18 +01:00
Andras Bacsai
3d2a34737b prevent restarting container check 2022-03-02 15:52:22 +01:00
Andras Bacsai
ebde77008c Cleanup coolify image fix 2022-03-02 15:52:06 +01:00
Andras Bacsai
3d27fd04ba cleanup old images of coolify 2022-03-02 14:53:43 +01:00
Andras Bacsai
d9fcaf3473 update packages 2022-03-02 14:53:33 +01:00
Andras Bacsai
d266f761aa Stats open with auth 2022-03-02 14:38:51 +01:00
Andras Bacsai
1d01405412 chore: Version++ 2022-03-02 13:38:58 +01:00
Andras Bacsai
7c62eb5bd6 feat: Send version with update request 2022-03-02 13:38:45 +01:00
Andras Bacsai
4dcc76d366 fix: Update process 2022-03-02 13:37:06 +01:00
Andras Bacsai
d2fad19a11 Update package.json 2022-03-02 12:23:40 +01:00
Andras Bacsai
7c92c4c964 Merge pull request #206 from coollabsio/feat/languagetool
v2.0.26
2022-03-02 12:22:44 +01:00
Andras Bacsai
5a71d33236 chore: Version++ 2022-03-02 12:20:20 +01:00
Andras Bacsai
1b4db4f793 fix 2022-03-02 12:20:02 +01:00
Andras Bacsai
c084b22815 fix: volume name 2022-03-02 12:17:48 +01:00
Andras Bacsai
acacef95cd fix: reload proxy on ssl cert 2022-03-02 12:10:12 +01:00
Andras Bacsai
5d722183d3 feat: Languagetool service 2022-03-02 11:57:03 +01:00
Andras Bacsai
ac19ea5407 Merge pull request #202 from coollabsio/fix
v2.0.24
2022-03-02 11:18:51 +01:00
Andras Bacsai
d19b05b970 fix: update process 2022-03-02 11:00:08 +01:00
Andras Bacsai
a0795136ac fix 2022-03-01 15:30:39 +01:00
Andras Bacsai
d2566e345a fix 2022-03-01 15:27:33 +01:00
Andras Bacsai
66cd7cf90e remove debug logging 2022-03-01 15:27:06 +01:00
Andras Bacsai
9a599981ef fix 2022-03-01 15:25:13 +01:00
Andras Bacsai
f51f7bc82a fix 2022-03-01 15:22:11 +01:00
Andras Bacsai
dbcbac0137 cleanup 2022-03-01 14:29:15 +01:00
Andras Bacsai
e722f8a87c fix: Reconfigure proxy on restart 2022-03-01 14:02:46 +01:00
Andras Bacsai
61679749eb fix: null proxyhash on restart 2022-03-01 13:15:23 +01:00
Andras Bacsai
23e12c9c44 fix: ssl + sslrenew 2022-03-01 13:07:34 +01:00
Andras Bacsai
6da78cd3e5 remove proxy refresh 2022-03-01 11:45:29 +01:00
Andras Bacsai
78ce8100a3 migrate file 2022-03-01 11:19:40 +01:00
Andras Bacsai
76ba338b45 fix 2022-03-01 11:17:34 +01:00
Andras Bacsai
823fe2deb2 refactor 2022-03-01 11:10:10 +01:00
Andras Bacsai
cb90f692f2 WIP proxy 2022-03-01 00:20:28 +01:00
Andras Bacsai
0325343ede save 2022-03-01 00:12:54 +01:00
Andras Bacsai
69d1556a1d WIP better automatic proxy conf 2022-03-01 00:08:54 +01:00
Andras Bacsai
2daa043840 WIP 2022-02-28 16:55:02 +01:00
Andras Bacsai
f340ca9d05 WIP 2022-02-28 16:06:44 +01:00
Andras Bacsai
02abd038fa fix: Better proxy check 2022-02-28 11:20:46 +01:00
Andras Bacsai
b9da68ec28 Merge pull request #199 from coollabsio/fixes
v2.0.23
2022-02-28 10:25:26 +01:00
Andras Bacsai
88b3910d80 fix: Cleanup old images, > 3 days 2022-02-28 10:12:04 +01:00
Andras Bacsai
160412f6e4 fix: Add coolify-image label for build images 2022-02-28 10:09:34 +01:00
Andras Bacsai
59a86b25fc UI: Application start 2022-02-28 10:00:09 +01:00
Andras Bacsai
49e58b39f5 chore: version++ 2022-02-28 09:57:36 +01:00
Andras Bacsai
58e0757bbd fix: Default npm command 2022-02-28 09:50:47 +01:00
Andras Bacsai
5ff4197572 fix: missing fqdn for services 2022-02-28 09:48:24 +01:00
Andras Bacsai
b56e28d27a UI: colorful states 2022-02-28 09:48:12 +01:00
Andras Bacsai
c3d39e1dd4 fix: Be sure .env exists 2022-02-28 09:31:36 +01:00
112 changed files with 2528 additions and 2171 deletions

View File

@@ -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

View File

@@ -1,10 +1,10 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.22",
"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",
@@ -25,56 +25,59 @@
"prepare": "husky install"
},
"devDependencies": {
"@sveltejs/adapter-node": "1.0.0-next.69",
"@sveltejs/adapter-static": "1.0.0-next.28",
"@sveltejs/kit": "1.0.0-next.283",
"@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.20",
"@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.7.0",
"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.4.0",
"eslint-plugin-svelte3": "3.4.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-svelte3": "3.4.1",
"husky": "7.0.4",
"lint-staged": "12.3.4",
"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.10.0",
"prettier-plugin-tailwindcss": "0.1.8",
"prisma": "3.11.1",
"svelte": "3.46.4",
"svelte-check": "2.4.5",
"svelte-check": "2.4.6",
"svelte-preprocess": "4.10.4",
"svelte-select": "^4.4.7",
"tailwindcss": "3.0.23",
"ts-node": "10.5.0",
"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.10.0",
"@sentry/node": "6.17.9",
"@prisma/client": "3.11.1",
"@sentry/node": "6.19.2",
"bcrypt": "5.0.1",
"bullmq": "1.74.2",
"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.1.1",
"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",
"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"

573
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "proxyHash" TEXT;

View File

@@ -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");

View 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;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Application" ADD COLUMN "phpModules" TEXT;

View File

@@ -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");

View File

@@ -16,6 +16,7 @@ model Setting {
maxPort Int @default(9100)
proxyPassword String
proxyUser String
proxyHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -71,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?
@@ -85,15 +86,17 @@ 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 {
@@ -103,10 +106,22 @@ model ApplicationSettings {
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
@@ -121,6 +136,18 @@ model Secret {
@@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 {
id String @id @default(cuid())
applicationId String?
@@ -251,6 +278,7 @@ model Service {
minio Minio?
vscodeserver Vscodeserver?
wordpress Wordpress?
serviceSecret ServiceSecret[]
}
model PlausibleAnalytics {

View File

@@ -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' }

6
src/app.d.ts vendored
View File

@@ -7,7 +7,11 @@ declare namespace App {
}
interface Platform {}
interface Session extends SessionData {}
interface Stuff {}
interface Stuff {
application: any;
isRunning: boolean;
appId: string;
}
}
interface SessionData {

View File

@@ -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,
@@ -84,7 +84,15 @@ export function makeLabelForServices(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;
@@ -97,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,
@@ -104,7 +116,8 @@ export const setDefaultConfiguration = async (data) => {
installCommand,
startCommand,
buildCommand,
publishDirectory
publishDirectory,
baseDirectory
};
};
@@ -122,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(
@@ -129,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;
}
@@ -160,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;
}
}
@@ -175,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')
);
}

View File

@@ -16,6 +16,7 @@ export default async function ({
let file = `${workdir}/Dockerfile`;
if (baseDirectory) {
file = `${workdir}/${baseDirectory}/Dockerfile`;
workdir = `${workdir}/${baseDirectory}`;
}
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))

View File

@@ -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);

View File

@@ -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'));

View File

@@ -1,5 +1,6 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
@@ -13,9 +14,10 @@ const createDockerfile = async (data, image): Promise<void> => {
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) {
@@ -31,17 +33,13 @@ const createDockerfile = async (data, image): Promise<void> => {
}
});
}
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}`);
}

View File

@@ -1,5 +1,6 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
@@ -13,9 +14,11 @@ const createDockerfile = async (data, image): Promise<void> => {
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) {
@@ -31,17 +34,12 @@ const createDockerfile = async (data, image): Promise<void> => {
}
});
}
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}`);
}

View File

@@ -1,5 +1,6 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
@@ -13,9 +14,10 @@ const createDockerfile = async (data, image): Promise<void> => {
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) {
@@ -31,17 +33,12 @@ const createDockerfile = async (data, image): Promise<void> => {
}
});
}
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}`);
}

View File

@@ -4,20 +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"]');
Dockerfile.push('RUN chown -R www-data /var/www/html');
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) {

View File

@@ -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);

View File

@@ -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'));
};

View File

@@ -15,7 +15,8 @@ const createDockerfile = async (data, image): Promise<void> => {
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) {
@@ -32,20 +33,18 @@ const createDockerfile = async (data, image): Promise<void> => {
});
}
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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -11,6 +11,7 @@ import { version as currentVersion } from '../../package.json';
import dayjs from 'dayjs';
import Cookie from 'cookie';
import os from 'os';
import cuid from 'cuid';
try {
if (!dev) {
@@ -68,9 +69,9 @@ export const isTeamIdTokenAvailable = (request) => {
export const getTeam = (event) => {
const cookies = Cookie.parse(event.request.headers.get('cookie'));
if (cookies.teamId) {
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;

View File

@@ -75,7 +75,7 @@
/>
{/if}
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
<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)}>

View File

@@ -9,7 +9,16 @@ export const dateOptions: DateTimeFormatOptions = {
hour12: false
};
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) {

View 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
>

View File

@@ -1,7 +1,7 @@
function defaultBuildAndDeploy(packageManager) {
return {
installCommand:
packageManager === 'npm' ? `${packageManager} run install` : `${packageManager} install`,
packageManager === 'npm' ? `${packageManager} install` : `${packageManager} install`,
buildCommand:
packageManager === 'npm' ? `${packageManager} run build` : `${packageManager} build`,
startCommand:
@@ -13,7 +13,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
if (pack === 'node') {
return {
...metaData,
installCommand: null,
...defaultBuildAndDeploy(packageManager),
buildCommand: null,
startCommand: null,
publishDirectory: null,

View File

@@ -1,5 +1,4 @@
import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy';
import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common';
@@ -59,29 +58,22 @@ export async function removeApplication({ id, teamId }) {
const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine });
try {
if (preview) {
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
} else {
await removeProxyConfiguration({ domain });
}
} catch (error) {
console.log(error);
}
}
}
}
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,
@@ -89,30 +81,41 @@ 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 } };
}
@@ -132,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) {
@@ -158,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 }) {
@@ -210,10 +231,14 @@ export async function configureApplication({
});
}
export async function setApplicationSettings({ id, debug, previews, dualCerts }) {
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, dualCerts } } },
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
include: { destinationDocker: true }
});
}
@@ -240,3 +265,7 @@ export async function createBuild({
}
});
}
export async function getPersistentStorage(id) {
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
}

View File

@@ -15,6 +15,9 @@ export async function isDockerNetworkExists({ network }) {
return await prisma.destinationDocker.findFirst({ where: { network } });
}
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 } });
}

View File

@@ -156,6 +156,15 @@ export const supportedServiceTypesAndVersions = [
ports: {
main: 80
}
},
{
name: 'languagetool',
fancyName: 'LanguageTool',
baseImage: 'silviof/docker-languagetool',
versions: ['latest'],
ports: {
main: 8010
}
}
];

View File

@@ -1,6 +1,19 @@
import { encrypt, decrypt } from '$lib/crypto';
import { prisma } from './common';
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 },
@@ -14,6 +27,12 @@ export async function listSecrets(applicationId: string) {
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({
@@ -21,10 +40,24 @@ export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecre
});
}
export async function updateServiceSecret({ id, name, value }) {
value = encrypt(value);
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 } });
console.log(found);
if (found) {
return await prisma.secret.updateMany({
@@ -38,6 +71,10 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
}
}
export async function removeServiceSecret({ id, name }) {
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
}
export async function removeSecret({ id, name }) {
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
}

View File

@@ -19,7 +19,8 @@ export async function getService({ id, teamId }) {
plausibleAnalytics: true,
minio: true,
vscodeserver: true,
wordpress: true
wordpress: true,
serviceSecret: true
}
});
@@ -42,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 };
}
@@ -105,6 +112,13 @@ export async function configureServiceType({ id, type }) {
type
}
});
} else if (type === 'languagetool') {
await prisma.service.update({
where: { id },
data: {
type
}
});
}
}
export async function setServiceVersion({ id, version }) {
@@ -128,6 +142,9 @@ 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 } });
}
@@ -149,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 } });
}

View File

@@ -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) {
@@ -16,9 +17,11 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
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) {
@@ -34,20 +37,14 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
}
});
}
// 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 });
@@ -68,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 });

View 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;
}
}

View File

@@ -1,5 +1,5 @@
import { dev } from '$app/env';
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { asyncExecShell, getEngine } from '$lib/common';
import got from 'got';
import * as db from '$lib/database';
@@ -47,116 +47,14 @@ export async function completeTransaction(transactionId) {
const haproxy = await haproxyInstance();
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
}
export async function removeProxyConfiguration(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance();
const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
if (backendFound) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
}
await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
}
export async function forceSSLOffApplication(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
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(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) {
transactionId = await getNextTransactionId();
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 {
if (transactionId) await completeTransaction(transactionId);
}
}
export async function forceSSLOnApplication(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
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(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
transactionId = await getNextTransactionId();
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: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}
export async function deleteProxy({ id }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return 'Error: HAProxy is not running';
}
let transactionId;
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
@@ -177,7 +75,7 @@ export async function deleteProxy({ id }) {
})
.json();
} catch (error) {
console.log(error.response.body);
console.log(error.response?.body || error);
} finally {
if (transactionId) await completeTransaction(transactionId);
}
@@ -187,169 +85,6 @@ 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 checkProxyConfigurations() {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
const stats: any = await haproxy.get(`v2/services/haproxy/stats/native`).json();
for (const stat of stats[0].stats) {
if (stat.stats.status === 'DOWN' && stat.type === 'server') {
const {
name,
backend_name: backendName,
stats: { lastchg }
} = stat;
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
if (backendName === domain) {
return;
}
}
const application = await db.getApplicationById(name);
if (!application) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
return await completeTransaction(transactionId);
}
const found = await checkContainer(application.destinationDocker.engine, name);
if (!found) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
return await completeTransaction(transactionId);
}
if (lastchg > 120) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
}
}
}
} catch (error) {
console.log(error);
}
}
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 === imageId) {
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(fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
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);
if (isHttps) await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
} catch (error) {
throw error?.response?.body || error;
}
}
export async function checkHAProxy(haproxy?: any) {
if (!haproxy) haproxy = await haproxyInstance();
try {
@@ -361,76 +96,6 @@ export async function checkHAProxy(haproxy?: any) {
};
}
}
export async function configureCoolifyProxyOn(fqdn) {
const domain = getDomain(fqdn);
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);
}
}
export async function stopTcpHttpProxy(destinationDocker, publicPort) {
const { engine } = destinationDocker;
@@ -494,7 +159,7 @@ 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) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
@@ -503,6 +168,7 @@ export async function startCoolifyProxy(engine) {
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);
}
@@ -514,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}`);
}
@@ -535,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(
@@ -559,168 +225,3 @@ export async function configureNetworkCoolifyProxy(engine) {
}
});
}
export async function configureSimpleServiceProxyOn({ id, domain, 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/${id}`, {
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 === id) {
if (server.data.port === port) {
serverConfigured = true;
}
}
}
}
}
}
} catch (error) {}
if (serverConfigured) return;
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);
}
export async function configureSimpleServiceProxyOff(fqdn) {
const domain = getDomain(fqdn);
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) {}
await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
return;
}
export async function removeWwwRedirection(fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
const haproxy = await haproxyInstance();
await checkHAProxy();
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.redir_value.includes(redirectValue));
if (rule) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
await completeTransaction(transactionId);
}
}
}
export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
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.redir_value.includes(redirectValue));
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
transactionId = await getNextTransactionId();
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: contTest,
type: 'redirect',
redir_type: 'location',
redir_value: redirectValue,
redir_code: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}

View File

@@ -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) {
console.log({ error });
return ErrorHandler(error);
}
}

View File

@@ -1,79 +0,0 @@
import { dev } from '$app/env';
import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common';
import * as db from '$lib/database';
import cuid from 'cuid';
import getPort, { portNumbers } from 'get-port';
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
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;
}
}
}
await forceSSLOffApplication(domain);
if (dualCerts) {
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"`
);
} else {
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"`
);
}
} catch (error) {
if (error.code !== 0) {
throw error;
}
} finally {
if (!isCoolify) {
await forceSSLOnApplication(domain);
}
}
}

View 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);
}
}
}
}

View File

@@ -3,13 +3,14 @@ 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 {
checkProxyConfigurations,
configureProxyForApplication,
reloadHaproxy,
setWwwRedirection
} from '../haproxy';
asyncExecShell,
asyncSleep,
createDirectories,
getDomain,
getEngine,
saveBuildLog
} from '../common';
import * as db from '$lib/database';
import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common';
@@ -18,7 +19,7 @@ import {
makeLabelForStandaloneApplication,
setDefaultConfiguration
} from '$lib/buildPacks/common';
import { letsEncrypt } from '$lib/letsencrypt';
import yaml from 'js-yaml';
export default async function (job) {
/*
@@ -45,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;
@@ -73,17 +90,8 @@ export default async function (job) {
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);
@@ -93,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,
@@ -103,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);
}
@@ -166,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,
@@ -187,7 +199,8 @@ export default async function (job) {
buildCommand,
startCommand,
baseDirectory,
secrets
secrets,
phpModules
});
else {
saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
@@ -239,42 +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);
}
try {
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await checkProxyConfigurations();
await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId });
await setWwwRedirection(fqdn);
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
});
}
} catch (error) {
saveBuildLog({ line: error.stdout || error, buildId, applicationId });
sentry.captureException(error);
throw new Error(error);
}
saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
}
}

View File

@@ -1,55 +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 () {
const destinationDockers = await prisma.destinationDocker.findMany();
for (const destinationDocker of destinationDockers) {
const host = getEngine(destinationDocker.engine);
// Tagging images with labels
// Cleanup old coolify images
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}" -`
);
}
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) {}
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
} catch (error) {
console.log(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 dangling images
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`);
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);
// }
// }
}
}

View File

@@ -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: 600000 } });
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 = {
@@ -110,18 +110,18 @@ cron().catch((error) => {
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}/`;
await asyncExecShell(`rm -fr ${workdir}`);
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
}
return;
});
@@ -133,7 +133,7 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
console.log(error);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}`;
await asyncExecShell(`rm -fr ${workdir}`);
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
}
saveBuildLog({
line: 'Failed to deploy!',

View File

@@ -1,116 +1,11 @@
import { getDomain } from '$lib/common';
import { getApplicationById, prisma, supportedServiceTypesAndVersions } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import {
checkContainer,
checkProxyConfigurations,
configureCoolifyProxyOn,
configureProxyForApplication,
configureSimpleServiceProxyOn,
forceSSLOnApplication,
reloadHaproxy,
setWwwRedirection,
startCoolifyProxy,
startHttpProxy
} from '$lib/haproxy';
import * as db from '$lib/database';
// import { generateRemoteEngine } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { configureHAProxy } from '$lib/haproxy/configuration';
export default async function () {
try {
await checkProxyConfigurations();
return await configureHAProxy();
} catch (error) {
console.log(error);
}
try {
// Check destination containers and configure proxy if needed
const destinationDockers = await prisma.destinationDocker.findMany({});
for (const destination of destinationDockers) {
if (destination.isCoolifyProxyUsed) {
// if (destination.remoteEngine) {
// const engine = generateRemoteEngine(destination);
// }
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) {
if (configuration.Labels['coolify.configuration']) {
const parsedConfiguration = JSON.parse(
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
);
if (
parsedConfiguration &&
configuration.Labels['coolify.type'] === 'standalone-application'
) {
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
if (fqdn) {
const found = await getApplicationById({ id: applicationId });
if (found) {
const domain = getDomain(fqdn);
await configureProxyForApplication({
domain,
imageId: pullmergeRequestId
? `${applicationId}-${pullmergeRequestId}`
: applicationId,
applicationId,
port
});
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
await setWwwRedirection(fqdn);
}
}
}
}
}
for (const container of containers) {
const image = container.Image.split(':')[0];
const found = supportedServiceTypesAndVersions.find((a) => a.baseImage === image);
if (found) {
const type = found.name;
const mainPort = found.ports.main;
const id = container.Names[0].replace('/', '');
const service = await db.prisma.service.findUnique({
where: { id },
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true
}
});
const { fqdn } = service;
const domain = getDomain(fqdn);
await configureSimpleServiceProxyOn({ id, domain, port: mainPort });
const publicPort = service[type]?.publicPort;
if (publicPort) {
const containerFound = await checkContainer(
destination.engine,
`haproxy-for-${publicPort}`
);
if (!containerFound) {
await startHttpProxy(destination, id, publicPort, 9000);
}
}
}
}
}
}
const services = await prisma.service.findMany({});
// Check Coolify FQDN and configure proxy if needed
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
}
} catch (error) {
throw error;
console.log(error.response?.body || error);
return ErrorHandler(error.response?.body || error);
}
}

View File

@@ -1,74 +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 getPort, { portNumbers } from 'get-port';
import cuid from 'cuid';
import { generateSSLCerts } from '$lib/letsencrypt';
export default async function () {
try {
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const randomCuid = cuid();
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-${randomCuid} -p 9080:${publicPort} -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 ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(
`DOCKER_HOST=${host} 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);
}
}
}
}
}
}
}
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-${randomCuid} -p 9080:${publicPort} -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 ${publicPort} -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;

View File

@@ -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;
}

View File

@@ -38,13 +38,13 @@
import { errorNotification } from '$lib/form';
import { asyncSleep } from '$lib/components/common';
import { del, get, post } from '$lib/api';
import { browser } from '$app/env';
import { fade } from 'svelte/transition';
import { browser, dev } from '$app/env';
let isUpdateAvailable = false;
let updateStatus = {
found: false,
loading: false,
checking: false,
success: null
};
let latestVersion = 'latest';
@@ -60,16 +60,17 @@
}
if ($session.teamId === '0') {
try {
updateStatus.checking = true;
const data = await get(`/update.json`);
if (overrideVersion || data?.isUpdateAvailable) {
latestVersion = overrideVersion || data.latestVersion;
isUpdateAvailable = overrideVersion ? true : data?.isUpdateAvailable;
await post(`/update.json`, { type: 'pull', latestVersion });
if (overrideVersion) {
isUpdateAvailable = true;
} else {
isUpdateAvailable = data.isUpdateAvailable;
}
}
} catch (error) {
} finally {
updateStatus.checking = false;
}
}
}
@@ -97,26 +98,32 @@
async function update() {
updateStatus.loading = true;
try {
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 {
if (dev) {
console.log(`updating to ${latestVersion}`);
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
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 {
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
}
} catch ({ error }) {
updateStatus.success = false;
updateStatus.loading = false;
@@ -311,35 +318,10 @@
<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"
>

View File

@@ -76,6 +76,7 @@
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;
@@ -86,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);
}
@@ -111,8 +120,6 @@
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
} finally {
loading = false;
}
}
</script>
@@ -128,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.'}
@@ -153,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.'}
@@ -182,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.'}
@@ -271,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

View File

@@ -25,9 +25,11 @@
let selected = {
projectId: undefined,
repository: undefined,
branch: undefined
branch: undefined,
autodeploy: application.settings.autodeploy || true
};
let showSave = false;
async function loadRepositoriesByPage(page = 0) {
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
Authorization: `token ${$gitTokens.githubToken}`
@@ -69,7 +71,14 @@
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
);
if (data.used) {
errorNotification('This branch is already used by another application.');
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;
}
@@ -142,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>

View File

@@ -30,6 +30,7 @@
let projects = [];
let branches = [];
let showSave = false;
let autodeploy = application.settings.autodeploy || true;
let selected = {
group: undefined,
@@ -138,7 +139,14 @@
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
);
if (data.used) {
errorNotification('This branch is already used by another application.');
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;
}
@@ -235,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`);

View File

@@ -30,14 +30,21 @@ 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 ErrorHandler(error);

View File

@@ -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

View File

@@ -11,6 +11,7 @@ export const post: RequestHandler = async (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: {

View File

@@ -56,7 +56,7 @@
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`;
}
@@ -75,10 +75,32 @@
if (name === 'dualCerts') {
dualCerts = !dualCerts;
}
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
try {
await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts });
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);
}
}
@@ -87,7 +109,7 @@
try {
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;
@@ -340,7 +362,6 @@
/>
</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"
@@ -383,22 +404,23 @@
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div>
</div>
<!-- <ul class="mt-2 divide-y divide-stone-800">
<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."
/>
</ul> -->
<div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={autodeploy}
on:click={() => changeSettings('autodeploy')}
title="Enable Automatic Deployment"
description="Enable automatic deployment through webhooks."
/>
</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."
/>
</div>
<div class="grid grid-cols-2 items-center">

View File

@@ -43,11 +43,11 @@
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}`
@@ -83,38 +83,42 @@
{#if currentStatus === 'running'}
<LoadingLogs />
{/if}
<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"
{#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}
>
<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>
<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}

View File

@@ -19,7 +19,7 @@ export const get: RequestHandler = async (event) => {
return {
body: {
logs,
status: data?.status || 'running'
status: data?.status
}
};
} catch (error) {

View File

@@ -51,10 +51,9 @@
build.took = data.builds[0].took;
build.since = data.builds[0].since;
}
window.location.reload();
return build;
});
return;
} catch ({ error }) {
return errorNotification(error);
}
@@ -121,6 +120,8 @@
<div class="w-48 text-center text-xs">
{#if build.status === 'running'}
<div class="font-bold">Running</div>
{:else if build.status === 'queued'}
<div class="font-bold">Queued</div>
{:else}
<div>{build.since}</div>
<div>Finished in <span class="font-bold">{build.took}s</span></div>

View File

@@ -26,15 +26,28 @@
export let applicationSecrets;
import { getDomain } from '$lib/components/common';
import Secret from '../secrets/_Secret.svelte';
import { get } from '$lib/api';
import { get, post } from '$lib/api';
import { page } from '$app/stores';
import Explainer from '$lib/components/Explainer.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets.json`);
PRMRSecrets = [...data.secrets];
}
async function redeploy(container) {
try {
await post(`/applications/${id}/deploy.json`, {
pullmergeRequestId: container.pullmergeRequestId,
branch: container.branch
});
toast.push('Application redeployed queued.');
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
@@ -43,53 +56,41 @@
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto">
<thead class=" rounded-xl border-b border-coolgray-500">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white">Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
/>
</tr>
</thead>
<tbody class="">
{#each applicationSecrets as secret}
{#key secret.id}
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
<Secret
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
isPRMRSecret
name={secret.name}
value={secret.value}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
</tbody>
</table>
</div>
{#if applicationSecrets.length !== 0}
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
<th scope="col" class="w-96 text-center">Action</th>
</tr>
</thead>
<tbody>
{#each applicationSecrets as secret}
{#key secret.id}
<tr>
<Secret
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
isPRMRSecret
name={secret.name}
value={secret.value}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
</tbody>
</table>
</div>
{/if}
<div class="flex justify-center py-4 text-center">
<Explainer
customClass="w-full"
text={applicationSecrets.length === 0
? "<span class='font-bold text-white text-xl'>Please add secrets to the application first.</span> <br><br>These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
? "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
: "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
/>
</div>
@@ -102,6 +103,11 @@
<div class="truncate text-center text-xl font-bold">{getDomain(container.fqdn)}</div>
</div>
</a>
<div class="flex items-center justify-center">
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
>Redeploy</button
>
</div>
{/each}
{:else}
<div class="flex-col">

View File

@@ -8,10 +8,17 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body };
const { id } = event.params;
const { debug, previews, dualCerts } = await event.request.json();
const { debug, previews, dualCerts, autodeploy, branch, projectId } = await event.request.json();
try {
await db.setApplicationSettings({ id, debug, previews, dualCerts });
const isDouble = await db.checkDoubleBranch(branch, projectId);
if (isDouble && autodeploy) {
throw {
message:
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
};
}
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });
return { status: 201 };
} catch (error) {
return ErrorHandler(error);

View File

@@ -1,8 +1,7 @@
import { getDomain, getUserDetails } from '$lib/common';
import { asyncExecShell, getEngine, getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { removeProxyConfiguration } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -17,10 +16,12 @@ export const post: RequestHandler = async (event) => {
teamId
});
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
await docker.engine.getContainer(id).stop();
const { engine } = destinationDocker;
const found = await checkContainer(engine, id);
if (found) {
await removeDestinationDocker({ id, engine });
}
}
await removeProxyConfiguration(fqdn);
return {
status: 200
};

View File

@@ -0,0 +1,73 @@
<script lang="ts">
export let isNew = false;
export let storage = {
id: null,
path: null
};
import { del, post } from '$lib/api';
import { page } from '$app/stores';
import { createEventDispatcher } from 'svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
const { id } = $page.params;
const dispatch = createEventDispatcher();
async function saveStorage(newStorage = false) {
try {
if (!storage.path) return errorNotification('Path is required.');
storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`;
storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path;
storage.path.replace(/\/\//g, '/');
await post(`/applications/${id}/storage.json`, {
path: storage.path,
storageId: storage.id,
newStorage
});
dispatch('refresh');
if (isNew) {
storage.path = null;
storage.id = null;
}
if (newStorage) toast.push('Storage saved.');
else toast.push('Storage updated.');
} catch ({ error }) {
return errorNotification(error);
}
}
async function removeStorage() {
try {
await del(`/applications/${id}/storage.json`, { path: storage.path });
dispatch('refresh');
toast.push('Storage deleted.');
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<td>
<input
bind:value={storage.path}
required
placeholder="eg: /sqlite.db"
class=" border border-dashed border-coolgray-300"
/>
</td>
<td>
{#if isNew}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}>Add</button
>
</div>
{:else}
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => saveStorage(false)}>Set</button>
</div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}>Remove</button>
</div>
</div>
{/if}
</td>

View File

@@ -0,0 +1,63 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event, false);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const persistentStorages = await db.getPersistentStorage(id);
return {
body: {
persistentStorages
}
};
} catch (error) {
return ErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { path, newStorage, storageId } = await event.request.json();
try {
if (newStorage) {
await db.prisma.applicationPersistentStorage.create({
data: { path, application: { connect: { id } } }
});
} else {
await db.prisma.applicationPersistentStorage.update({
where: { id: storageId },
data: { path }
});
}
return {
status: 201
};
} catch (error) {
return ErrorHandler(error);
}
};
export const del: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { path } = await event.request.json();
try {
await db.prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id, path } });
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,73 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff }) => {
let endpoint = `/applications/${params.id}/storage.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
application: stuff.application,
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${endpoint}`)
};
};
</script>
<script lang="ts">
export let application;
export let persistentStorages;
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import Storage from './_Storage.svelte';
import { get } from '$lib/api';
import Explainer from '$lib/components/Explainer.svelte';
const { id } = $page.params;
async function refreshStorage() {
const data = await get(`/applications/${id}/storage.json`);
persistentStorages = [...data.persistentStorages];
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
Persistent storage for <a href={application.fqdn} target="_blank"
>{getDomain(application.fqdn)}</a
>
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<div class="flex justify-center py-4 text-center">
<Explainer
customClass="w-full"
text={'You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache.'}
/>
</div>
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Path</th>
</tr>
</thead>
<tbody>
{#each persistentStorages as storage}
{#key storage.id}
<tr>
<Storage on:refresh={refreshStorage} {storage} />
</tr>
{/key}
{/each}
<tr>
<Storage on:refresh={refreshStorage} isNew />
</tr>
</tbody>
</table>
</div>

View File

@@ -1,34 +1,19 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch }) => {
const endpoint = '/applications.json';
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${endpoint}`)
};
};
</script>
<script lang="ts">
export let applications: Array<Application>;
import { session } from '$app/stores';
import Application from './_Application.svelte';
import { post } from '$lib/api';
import { goto } from '$app/navigation';
async function newApplication() {
const { id } = await post('/applications/new', {});
return await goto(`/applications/${id}`, { replaceState: true });
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl ">Applications</div>
{#if $session.isAdmin}
<a href="/new/application" class="add-icon bg-green-600 hover:bg-green-500">
<div on:click={newApplication} class="add-icon cursor-pointer bg-green-600 hover:bg-green-500">
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
@@ -42,7 +27,7 @@
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/></svg
>
</a>
</div>
{/if}
</div>
<div class="flex flex-wrap justify-center">

View File

@@ -6,10 +6,7 @@ import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { name } = await event.request.json();
if (!name) return { status: 400, body: { error: 'Missing name.' } };
const name = uniqueName();
try {
const { id } = await db.newApplication({ name, teamId });
return { status: 201, body: { id } };

View File

@@ -8,12 +8,22 @@ export const get: RequestHandler = async (event) => {
if (status === 401) return { status, body };
try {
const applicationsCount = await (await db.listApplications(teamId)).length;
const sourcesCount = await (await db.listSources(teamId)).length;
const destinationsCount = await (await db.listDestinations(teamId)).length;
const teamsCount = await (await db.getMyTeams({ userId })).length;
const databasesCount = await (await db.listDatabases(teamId)).length;
const servicesCount = await (await db.listServices(teamId)).length;
const applicationsCount = await db.prisma.application.count({
where: { teams: { some: { id: teamId } } }
});
const sourcesCount = await db.prisma.gitSource.count({
where: { teams: { some: { id: teamId } } }
});
const destinationsCount = await db.prisma.destinationDocker.count({
where: { teams: { some: { id: teamId } } }
});
const teamsCount = await db.prisma.permission.count({ where: { userId } });
const databasesCount = await db.prisma.database.count({
where: { teams: { some: { id: teamId } } }
});
const servicesCount = await db.prisma.service.count({
where: { teams: { some: { id: teamId } } }
});
return {
body: {
applicationsCount,

View File

@@ -120,7 +120,7 @@
title="Stop database"
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-purple-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 database'
: 'You do not have permission to stop the database.'}
@@ -146,7 +146,7 @@
title="Start database"
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-purple-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
? 'Start database'
: 'You do not have permission to start the database.'}

View File

@@ -1,24 +1,3 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch }) => {
const url = `/databases.json`;
const res = await fetch(url);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
export let databases;
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
@@ -27,11 +6,18 @@
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
import Redis from '$lib/components/svg/databases/Redis.svelte';
import { post } from '$lib/api';
import { goto } from '$app/navigation';
async function newDatabase() {
const { id } = await post('/databases/new', {});
return await goto(`/databases/${id}`, { replaceState: true });
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Databases</div>
<a href="/new/database" class="add-icon bg-purple-600 hover:bg-purple-500">
<div on:click={newDatabase} class="add-icon cursor-pointer bg-purple-600 hover:bg-purple-500">
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
@@ -45,7 +31,7 @@
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/></svg
>
</a>
</div>
</div>
<div class="flex flex-wrap justify-center">

View File

@@ -6,6 +6,7 @@ import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
try {
const databases = await db.listDatabases(teamId);
return {

View File

@@ -1,4 +1,4 @@
import { getUserDetails } from '$lib/common';
import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
@@ -6,9 +6,7 @@ import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { name } = await event.request.json();
const name = uniqueName();
try {
const { id } = await db.newDatabase({ name, teamId });
return { status: 201, body: { id } };

View File

@@ -117,6 +117,8 @@
setTimeout(() => {
window.location.reload();
}, 5000);
} finally {
restarting = false;
}
}
}

View File

@@ -1,30 +1,19 @@
import { getDomain, getUserDetails } from '$lib/common';
import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database';
import * as db from '$lib/database';
import {
configureCoolifyProxyOn,
forceSSLOnApplication,
setWwwRedirection,
startCoolifyProxy,
stopCoolifyProxy
} from '$lib/haproxy';
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { engine, fqdn } = await event.request.json();
const { engine } = await event.request.json();
try {
const domain = getDomain(fqdn);
await stopCoolifyProxy(engine);
await startCoolifyProxy(engine);
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
return {
status: 200
};

View File

@@ -71,12 +71,14 @@
class:text-stone-600={loading}
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
>
<button
on:click|preventDefault={() => goto('/register')}
class="hover:opacity-90 text-white">Register</button
class="bg-transparent hover:bg-coolgray-300 text-white ">Register</button
>
<button class="bg-transparent" on:click|preventDefault={() => goto('/reset')}
>Reset password</button
<button
class="bg-transparent hover:bg-coolgray-300"
on:click|preventDefault={() => goto('/reset')}>Reset password</button
>
</div>
</form>

View File

@@ -1,53 +0,0 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch }) => {
const url = `/common/getUniqueName.json`;
const res = await fetch(url);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
export let name;
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { errorNotification } from '$lib/form';
let nameEl: HTMLInputElement;
onMount(() => {
nameEl.focus();
});
async function handleSubmit() {
try {
const { id } = await post('/new/application.json', { name });
return await goto(`/applications/${id}`);
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Add New Application</div>
</div>
<div class="pt-10">
<form on:submit|preventDefault={handleSubmit}>
<div class="flex flex-col items-center space-y-4">
<input name="name" placeholder="Application name" bind:this={nameEl} bind:value={name} />
<button type="submit" class="bg-green-600 hover:bg-green-500">Save</button>
</div>
</form>
</div>

View File

@@ -1,59 +0,0 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, session }) => {
const url = `/common/getUniqueName.json`;
const res = await fetch(url);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
export let name;
import { errorNotification } from '$lib/form';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
let autofocus;
onMount(() => {
autofocus.focus();
});
async function handleSubmit() {
try {
const { id } = await post('/new/database.json', { name });
return await goto(`/databases/${id}`);
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Add New Database</div>
</div>
<div class="pt-10">
<form on:submit|preventDefault={handleSubmit}>
<div class="flex flex-col items-center space-y-4">
<input
name="name"
placeholder="Database name"
required
bind:this={autofocus}
bind:value={name}
/>
<button type="submit" class="bg-purple-600 hover:bg-purple-500">Save</button>
</div>
</form>
</div>

View File

@@ -1,59 +0,0 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, session }) => {
const url = `/common/getUniqueName.json`;
const res = await fetch(url);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
export let name;
import { enhance, errorNotification } from '$lib/form';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
let autofocus;
onMount(() => {
autofocus.focus();
});
async function handleSubmit() {
try {
const { id } = await post(`/new/service.json`, { name });
return await goto(`/services/${id}`);
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Add New Service</div>
</div>
<div class="pt-10">
<form on:submit|preventDefault={handleSubmit}>
<div class="flex flex-col items-center space-y-4">
<input
name="name"
placeholder="Service name"
required
bind:this={autofocus}
bind:value={name}
/>
<button type="submit" class="bg-pink-600 hover:bg-pink-500">Save</button>
</div>
</form>
</div>

View File

@@ -25,7 +25,7 @@
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);`
: null}>{service.wordpress.extraConfig || 'N/A'}</textarea
: 'N/A'}>{service.wordpress.extraConfig}</textarea
>
</div>
<div class="flex space-x-1 py-5 font-bold">

View File

@@ -57,13 +57,13 @@
</script>
<script>
import { session } from '$app/stores';
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 { onMount } from 'svelte';
const { id } = $page.params;
export let service;
export let isRunning;
@@ -110,23 +110,6 @@
loading = false;
}
}
// onMount(async () => {
// if (
// service.type &&
// service.destinationDockerId &&
// service.version &&
// service.fqdn &&
// !isRunning
// ) {
// try {
// await post(`/services/${service.id}/${service.type}/stop.json`, {});
// } catch ({ error }) {
// return errorNotification(error);
// } finally {
// loading = false;
// }
// }
// });
</script>
<nav class="nav-side">
@@ -140,7 +123,7 @@
title="Stop Service"
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-pink-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 Service'
: 'You do not have permission to stop the service.'}
@@ -166,7 +149,7 @@
title="Start Service"
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:bg-pink-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
? 'Start Service'
: 'You do not have permission to start the service.'}
@@ -185,6 +168,76 @@
</svg>
</button>
{/if}
<div class="border border-stone-700 h-8" />
{/if}
{#if service.type && service.destinationDockerId && service.version}
<a
href="/services/{id}"
sveltekit:prefetch
class="hover:text-yellow-500 rounded"
class:text-yellow-500={$page.url.pathname === `/services/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
>
<button
title="Configurations"
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Configurations"
>
<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="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg></button
></a
>
<a
href="/services/{id}/secrets"
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/services/${id}/secrets`}
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
>
<button
title="Secrets"
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Secrets"
>
<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" />
<path
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
/>
<circle cx="12" cy="11" r="1" />
<line x1="12" y1="12" x2="12" y2="14.5" />
</svg></button
></a
>
<div class="border border-stone-700 h-8" />
{/if}
<button
on:click={deleteService}

View File

@@ -37,6 +37,7 @@
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -74,6 +75,8 @@
<Wordpress isAbsolute />
{:else if type.name === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if type.name === 'languagetool'}
<LanguageTool isAbsolute />
{/if}{type.fancyName}
</button>
</form>

View File

@@ -35,10 +35,10 @@
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import Services from './_Services/_Services.svelte';
import { getDomain } from '$lib/components/common';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import cuid from 'cuid';
import { browser } from '$app/env';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
export let service;
export let isRunning;
@@ -105,6 +105,10 @@
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
<VaultWarden />
</a>
{:else if service.type === 'languagetool'}
<a href="https://languagetool.org/dev" target="_blank">
<LanguageTool />
</a>
{/if}
</div>
</div>

View File

@@ -4,25 +4,17 @@ import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
const { status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
let { name, fqdn, port, buildCommand, startCommand, installCommand } = await event.request.json();
const { id } = event.params;
let { name, fqdn } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase();
if (port) port = Number(port);
try {
const { id } = await db.importApplication({
name,
teamId,
fqdn,
port,
buildCommand,
startCommand,
installCommand
});
return { status: 201, body: { id } };
await db.updateLanguageToolService({ id, fqdn, name });
return { status: 201 };
} catch (error) {
return ErrorHandler(error);
}

View File

@@ -0,0 +1,78 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
volume: `${id}-ngrams:/ngrams`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
networks: [network],
environment: config.environmentVariables,
restart: 'always',
volumes: [`${id}-ngrams:/ngrams`],
labels: makeLabelForServices('languagetool')
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[`${id}-ngrams`]: {
external: true
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}-ngrams`);
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,35 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
if (found) {
await removeDestinationDocker({ id, engine });
}
} catch (error) {
console.error(error);
}
}
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -3,18 +3,10 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection,
startHttpProxy
} from '$lib/haproxy';
import { startHttpProxy } from '$lib/haproxy';
import getPort, { portNumbers } from 'get-port';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -24,7 +16,6 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
@@ -32,15 +23,13 @@ export const post: RequestHandler = async (event) => {
fqdn,
destinationDockerId,
destinationDocker,
minio: { rootUser, rootUserPassword }
minio: { rootUser, rootUserPassword },
serviceSecret
} = service;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
@@ -50,9 +39,10 @@ export const post: RequestHandler = async (event) => {
const apiPort = 9000;
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `minio/minio:${version}`,
image: `${image}:${version}`,
volume: `${id}-minio-data:/data`,
environmentVariables: {
MINIO_ROOT_USER: rootUser,
@@ -60,12 +50,17 @@ export const post: RequestHandler = async (event) => {
MINIO_BROWSER_REDIRECT_URL: fqdn
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: `minio/minio:${version}`,
image: config.image,
command: `server /data --console-address ":${consolePort}"`,
environment: config.environmentVariables,
networks: [network],
@@ -96,16 +91,8 @@ export const post: RequestHandler = async (event) => {
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
await db.updateMinioService({ id, publicPort });
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getEngine, getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff, stopTcpHttpProxy } from '$lib/haproxy';
import { checkContainer, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -21,7 +19,6 @@ export const post: RequestHandler = async (event) => {
minio: { publicPort }
} = service;
await db.updateMinioService({ id, publicPort: null });
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -35,7 +32,6 @@ export const post: RequestHandler = async (event) => {
}
try {
await stopTcpHttpProxy(destinationDocker, publicPort);
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,25 +13,31 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const { type, version, fqdn, destinationDockerId, destinationDocker } = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: `nocodb/nocodb:${version}`,
image: config.image,
networks: [network],
environment: config.environmentVariables,
restart: 'always',
labels: makeLabelForServices('nocodb')
}
@@ -56,14 +53,6 @@ export const post: RequestHandler = async (event) => {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -27,11 +24,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,7 +13,6 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
@@ -30,6 +20,7 @@ export const post: RequestHandler = async (event) => {
fqdn,
destinationDockerId,
destinationDocker,
serviceSecret,
plausibleAnalytics: {
id: plausibleDbId,
username,
@@ -41,13 +32,11 @@ export const post: RequestHandler = async (event) => {
secretKeyBase
}
} = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const image = getServiceImage(type);
const config = {
plausibleAnalytics: {
image: `plausible/analytics:${version}`,
image: `${image}:${version}`,
environmentVariables: {
ADMIN_USER_EMAIL: email,
ADMIN_USER_NAME: username,
@@ -81,9 +70,13 @@ export const post: RequestHandler = async (event) => {
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.plausibleAnalytics.environmentVariables[secret.name] = secret.value;
});
}
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const engine = destinationDocker.engine;
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -187,14 +180,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 8000 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -36,12 +33,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {

View File

@@ -0,0 +1,87 @@
<script>
export let name = '';
export let value = '';
export let isNewSecret = false;
import { page } from '$app/stores';
import { del, post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const { id } = $page.params;
async function removeSecret() {
try {
await del(`/services/${id}/secrets.json`, { name });
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) {
return errorNotification(error);
}
}
async function saveSecret(isNew = false) {
if (!name) return errorNotification('Name is required.');
if (!value) return errorNotification('Value is required.');
try {
await post(`/services/${id}/secrets.json`, {
name,
value,
isNew
});
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
toast.push('Secret saved.');
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<td>
<input
id={isNewSecret ? 'secretName' : 'secretNameNew'}
bind:value={name}
required
placeholder="EXAMPLE_VARIABLE"
class=" border border-dashed border-coolgray-300"
readonly={!isNewSecret}
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</td>
<td>
<CopyPasswordField
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
isPasswordField={true}
bind:value
required
placeholder="J$#@UIO%HO#$U%H"
/>
</td>
<td>
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
</div>
{:else}
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => saveSecret(false)}>Set</button>
</div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
</div>
{/if}
</td>

View File

@@ -0,0 +1,70 @@
import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const secrets = await db.listServiceSecrets(id);
return {
status: 200,
body: {
secrets: secrets.sort((a, b) => {
return ('' + a.name).localeCompare(b.name);
})
}
};
} catch (error) {
return ErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
try {
if (isNew) {
const found = await db.isServiceSecretExists({ id, name });
if (found) {
throw {
error: `Secret ${name} already exists.`
};
} else {
await db.createServiceSecret({ id, name, value });
return {
status: 201
};
}
} else {
await db.updateServiceSecret({ id, name, value });
return {
status: 201
};
}
} catch (error) {
return ErrorHandler(error);
}
};
export const del: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name } = await event.request.json();
try {
await db.removeServiceSecret({ id, name });
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,67 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff }) => {
let endpoint = `/services/${params.id}/secrets.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
service: stuff.service,
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${endpoint}`)
};
};
</script>
<script lang="ts">
export let secrets;
export let service;
import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/services/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
Secrets {#if service.fqdn}
<a href={service.fqdn} target="_blank">{getDomain(service.fqdn)}</a>
{/if}
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-96 text-center">Action</th>
</tr>
</thead>
<tbody>
{#each secrets as secret}
{#key secret.id}
<tr>
<Secret name={secret.name} value={secret.value} on:refresh={refreshSecrets} />
</tr>
{/key}
{/each}
<tr>
<Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div>

View File

@@ -3,15 +3,6 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { getServiceImage, ErrorHandler } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
@@ -22,30 +13,32 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const { type, version, fqdn, destinationDockerId, destinationDocker } = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const baseImage = getServiceImage(type);
const image = getServiceImage(type);
const config = {
image: `${baseImage}:${version}`,
volume: `${id}-vaultwarden-data:/data/`
image: `${image}:${version}`,
volume: `${id}-vaultwarden-data:/data/`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
environment: config.environmentVariables,
networks: [network],
volumes: [config.volume],
restart: 'always',
@@ -74,14 +67,6 @@ export const post: RequestHandler = async (event) => {
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -27,13 +24,7 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {
status: 200
};

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,31 +13,34 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
version,
fqdn,
destinationDockerId,
destinationDocker,
serviceSecret,
vscodeserver: { password }
} = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `codercom/code-server:${version}`,
image: `${image}:${version}`,
volume: `${id}-vscodeserver-data:/home/coder`,
environmentVariables: {
PASSWORD: password
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
@@ -84,14 +78,6 @@ export const post: RequestHandler = async (event) => {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -27,11 +24,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {
status: 200

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,13 +13,13 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
version,
fqdn,
destinationDockerId,
serviceSecret,
destinationDocker,
wordpress: {
mysqlDatabase,
@@ -40,16 +31,14 @@ export const post: RequestHandler = async (event) => {
}
} = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const image = getServiceImage(type);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const config = {
wordpress: {
image: `wordpress:${version}`,
image: `${image}:${version}`,
volume: `${id}-wordpress-data:/var/www/html`,
environmentVariables: {
WORDPRESS_DB_HOST: `${id}-mysql`,
@@ -71,6 +60,11 @@ export const post: RequestHandler = async (event) => {
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.wordpress.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
@@ -121,14 +115,6 @@ export const post: RequestHandler = async (event) => {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
@@ -30,11 +27,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {

View File

@@ -1,24 +1,3 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch }) => {
const url = `/services.json`;
const res = await fetch(url);
if (res.ok) {
return {
props: {
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
import PlausibleAnalytics from '$lib/components/svg/services/PlausibleAnalytics.svelte';
import NocoDb from '$lib/components/svg/services/NocoDB.svelte';
@@ -26,13 +5,20 @@
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
import { post } from '$lib/api';
import { goto } from '$app/navigation';
export let services;
async function newService() {
const { id } = await post('/services/new', {});
return await goto(`/services/${id}`, { replaceState: true });
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Services</div>
<a href="/new/service" class="add-icon bg-pink-600 hover:bg-pink-500">
<div on:click={newService} class="add-icon cursor-pointer bg-pink-600 hover:bg-pink-500">
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
@@ -46,7 +32,7 @@
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/></svg
>
</a>
</div>
</div>
<div class="flex flex-wrap justify-center">
@@ -70,6 +56,8 @@
<Wordpress isAbsolute />
{:else if service.type === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if service.type === 'languagetool'}
<LanguageTool isAbsolute />
{/if}
<div class="font-bold text-xl text-center truncate">
{service.name}

Some files were not shown because too many files have changed in this diff Show More