mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-03 12:34:08 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80a519b80 | ||
|
|
2fa7ffc931 | ||
|
|
4abec14a21 | ||
|
|
18d0623011 | ||
|
|
aa634c78d1 | ||
|
|
a2d4373104 | ||
|
|
702e16d643 | ||
|
|
3b25c8f96b | ||
|
|
1c8c567791 | ||
|
|
807a3c9d66 | ||
|
|
2abd7bd7bb | ||
|
|
343957ab8b | ||
|
|
49261308f7 | ||
|
|
d037409237 | ||
|
|
338cbf62a1 | ||
|
|
4c51bffc7b | ||
|
|
fd98ba8812 | ||
|
|
930251e9c8 | ||
|
|
7cd441266a | ||
|
|
990fb8ec15 | ||
|
|
3fe982b2f4 | ||
|
|
9dd874e959 | ||
|
|
b91368223b | ||
|
|
139670372b | ||
|
|
1c0769ad75 | ||
|
|
e6cbcf98cb | ||
|
|
64b0481055 | ||
|
|
ce15161926 | ||
|
|
4003d4d894 | ||
|
|
6e011025a7 | ||
|
|
6c0544adb2 | ||
|
|
8e4f7c9065 | ||
|
|
e71f890b54 | ||
|
|
4dc35dea97 | ||
|
|
b63dfb4bcd | ||
|
|
b2ffd9183b | ||
|
|
1fbcfcaf74 | ||
|
|
79c98657b1 | ||
|
|
d1be7e44af | ||
|
|
eefc2a3d0e | ||
|
|
d14ca724e9 | ||
|
|
7b05aaffc3 |
5
.github/workflows/staging-release.yml
vendored
5
.github/workflows/staging-release.yml
vendored
@@ -4,9 +4,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- "main"
|
||||
- "v4"
|
||||
branches:
|
||||
- "v3"
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -18,5 +18,14 @@
|
||||
"ts",
|
||||
"json"
|
||||
],
|
||||
"i18n-ally.extract.autoDetect": true
|
||||
"i18n-ally.extract.autoDetect": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true
|
||||
},
|
||||
"hide-files.files": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
|
||||
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
|
||||
|
||||
-- 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,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "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;
|
||||
@@ -135,6 +135,8 @@ model Application {
|
||||
dockerRegistryId String?
|
||||
dockerRegistryImageName String?
|
||||
simpleDockerfile String?
|
||||
basicAuthUser String?
|
||||
basicAuthPw String?
|
||||
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
secrets Secret[]
|
||||
@@ -187,6 +189,7 @@ model ApplicationSettings {
|
||||
isDBBranching Boolean @default(false)
|
||||
isCustomSSL Boolean @default(false)
|
||||
isHttp2 Boolean @default(false)
|
||||
basicAuth Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
|
||||
@@ -95,8 +95,19 @@ async function main() {
|
||||
}
|
||||
async function reEncryptSecrets() {
|
||||
const { execaCommand } = await import('execa');
|
||||
const image = await execaCommand("docker inspect coolify --format '{{ .Config.Image }}'", {
|
||||
shell: true
|
||||
});
|
||||
const version = image.stdout.split(':')[1] ?? null;
|
||||
const date = new Date().getTime();
|
||||
await execaCommand('env | grep COOLIFY > .env', { shell: true });
|
||||
|
||||
let backupfile = `/app/db/prod.db_${date}`;
|
||||
if (version) {
|
||||
backupfile = `/app/db/prod.db_${version}_${date}`;
|
||||
}
|
||||
await execaCommand('env | grep "^COOLIFY" | sort > .env', {
|
||||
shell: true
|
||||
});
|
||||
const secretOld = process.env['COOLIFY_SECRET_KEY'];
|
||||
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||
if (!secretNew) {
|
||||
@@ -108,6 +119,8 @@ async function reEncryptSecrets() {
|
||||
secretNew = newKey;
|
||||
}
|
||||
if (secretOld !== secretNew) {
|
||||
console.log(`Backup database to ${backupfile}.`);
|
||||
await execaCommand(`cp /app/db/prod.db ${backupfile}`, { shell: true });
|
||||
console.log(
|
||||
'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...'
|
||||
);
|
||||
@@ -120,171 +133,209 @@ async function reEncryptSecrets() {
|
||||
await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, {
|
||||
shell: true
|
||||
});
|
||||
console.log(`Backup database to /app/db/prod.db_${date}.`);
|
||||
await execaCommand(`cp /app/db/prod.db /app/db/prod.db_${date}`, { shell: true });
|
||||
const transactions = [];
|
||||
const secrets = await prisma.secret.findMany();
|
||||
if (secrets.length > 0) {
|
||||
for (const secret of secrets) {
|
||||
const value = decrypt(secret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.secret.update({
|
||||
where: { id: secret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(secret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.secret.update({
|
||||
where: { id: secret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const serviceSecrets = await prisma.serviceSecret.findMany();
|
||||
if (serviceSecrets.length > 0) {
|
||||
for (const secret of serviceSecrets) {
|
||||
const value = decrypt(secret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.serviceSecret.update({
|
||||
where: { id: secret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(secret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.serviceSecret.update({
|
||||
where: { id: secret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const gitlabApps = await prisma.gitlabApp.findMany();
|
||||
if (gitlabApps.length > 0) {
|
||||
for (const gitlabApp of gitlabApps) {
|
||||
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
||||
const newAppSecret = encrypt(appSecret, secretNew);
|
||||
transactions.push(
|
||||
prisma.gitlabApp.update({
|
||||
where: { id: gitlabApp.id },
|
||||
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
||||
const newAppSecret = encrypt(appSecret, secretNew);
|
||||
transactions.push(
|
||||
prisma.gitlabApp.update({
|
||||
where: { id: gitlabApp.id },
|
||||
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const githubApps = await prisma.githubApp.findMany();
|
||||
if (githubApps.length > 0) {
|
||||
for (const githubApp of githubApps) {
|
||||
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
||||
const newClientSecret = encrypt(clientSecret, secretNew);
|
||||
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
||||
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
||||
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
||||
const newPrivateKey = encrypt(privateKey, secretNew);
|
||||
try {
|
||||
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
||||
const newClientSecret = encrypt(clientSecret, secretNew);
|
||||
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
||||
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
||||
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
||||
const newPrivateKey = encrypt(privateKey, secretNew);
|
||||
|
||||
transactions.push(
|
||||
prisma.githubApp.update({
|
||||
where: { id: githubApp.id },
|
||||
data: {
|
||||
clientSecret: newClientSecret,
|
||||
webhookSecret: newWebhookSecret,
|
||||
privateKey: newPrivateKey
|
||||
}
|
||||
})
|
||||
);
|
||||
transactions.push(
|
||||
prisma.githubApp.update({
|
||||
where: { id: githubApp.id },
|
||||
data: {
|
||||
clientSecret: newClientSecret,
|
||||
webhookSecret: newWebhookSecret,
|
||||
privateKey: newPrivateKey
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const databases = await prisma.database.findMany();
|
||||
if (databases.length > 0) {
|
||||
for (const database of databases) {
|
||||
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
||||
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
||||
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
||||
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
||||
transactions.push(
|
||||
prisma.database.update({
|
||||
where: { id: database.id },
|
||||
data: {
|
||||
dbUserPassword: newDbUserPassword,
|
||||
rootUserPassword: newRootUserPassword
|
||||
}
|
||||
})
|
||||
);
|
||||
try {
|
||||
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
||||
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
||||
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
||||
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
||||
transactions.push(
|
||||
prisma.database.update({
|
||||
where: { id: database.id },
|
||||
data: {
|
||||
dbUserPassword: newDbUserPassword,
|
||||
rootUserPassword: newRootUserPassword
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const databaseSecrets = await prisma.databaseSecret.findMany();
|
||||
if (databaseSecrets.length > 0) {
|
||||
for (const databaseSecret of databaseSecrets) {
|
||||
const value = decrypt(databaseSecret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.databaseSecret.update({
|
||||
where: { id: databaseSecret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(databaseSecret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.databaseSecret.update({
|
||||
where: { id: databaseSecret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const wordpresses = await prisma.wordpress.findMany();
|
||||
if (wordpresses.length > 0) {
|
||||
for (const wordpress of wordpresses) {
|
||||
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
||||
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
||||
let newFtpPassword = undefined;
|
||||
if (wordpress.ftpPassword != null) {
|
||||
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
||||
newFtpPassword = encrypt(ftpPassword, secretNew);
|
||||
}
|
||||
try {
|
||||
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
||||
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
||||
let newFtpPassword = undefined;
|
||||
if (wordpress.ftpPassword != null) {
|
||||
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
||||
newFtpPassword = encrypt(ftpPassword, secretNew);
|
||||
}
|
||||
|
||||
transactions.push(
|
||||
prisma.wordpress.update({
|
||||
where: { id: wordpress.id },
|
||||
data: {
|
||||
ftpHostKey: newValue,
|
||||
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
||||
ftpPassword: newFtpPassword
|
||||
}
|
||||
})
|
||||
);
|
||||
transactions.push(
|
||||
prisma.wordpress.update({
|
||||
where: { id: wordpress.id },
|
||||
data: {
|
||||
ftpHostKey: newValue,
|
||||
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
||||
ftpPassword: newFtpPassword
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sshKeys = await prisma.sshKey.findMany();
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
const value = decrypt(key.privateKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.sshKey.update({
|
||||
where: { id: key.id },
|
||||
data: {
|
||||
privateKey: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(key.privateKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.sshKey.update({
|
||||
where: { id: key.id },
|
||||
data: {
|
||||
privateKey: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const dockerRegistries = await prisma.dockerRegistry.findMany();
|
||||
if (dockerRegistries.length > 0) {
|
||||
for (const registry of dockerRegistries) {
|
||||
const value = decrypt(registry.password, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.dockerRegistry.update({
|
||||
where: { id: registry.id },
|
||||
data: {
|
||||
password: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(registry.password, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.dockerRegistry.update({
|
||||
where: { id: registry.id },
|
||||
data: {
|
||||
password: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const certificates = await prisma.certificate.findMany();
|
||||
if (certificates.length > 0) {
|
||||
for (const certificate of certificates) {
|
||||
const value = decrypt(certificate.key, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.certificate.update({
|
||||
where: { id: certificate.id },
|
||||
data: {
|
||||
key: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
try {
|
||||
const value = decrypt(certificate.key, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.certificate.update({
|
||||
where: { id: certificate.id },
|
||||
data: {
|
||||
key: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.$transaction(transactions);
|
||||
@@ -306,18 +357,16 @@ const encrypt = (text, secret) => {
|
||||
};
|
||||
const decrypt = (hashString, secret) => {
|
||||
if (hashString && secret) {
|
||||
try {
|
||||
const hash = JSON.parse(hashString);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([
|
||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||
decipher.final()
|
||||
]);
|
||||
return decrpyted.toString();
|
||||
} catch (error) {
|
||||
console.log({ decryptionError: error.message });
|
||||
return hashString;
|
||||
const hash = JSON.parse(hashString);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([
|
||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||
decipher.final()
|
||||
]);
|
||||
if (/<2F>/.test(decrpyted.toString())) {
|
||||
throw new Error('Invalid secret. Skipping...');
|
||||
}
|
||||
return decrpyted.toString();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import Fastify from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
import serve from '@fastify/static';
|
||||
import env from '@fastify/env';
|
||||
import cookie from '@fastify/cookie';
|
||||
import multipart from '@fastify/multipart';
|
||||
import path, { join } from 'path';
|
||||
import autoLoad from '@fastify/autoload';
|
||||
import cookie from '@fastify/cookie';
|
||||
import cors from '@fastify/cors';
|
||||
import env from '@fastify/env';
|
||||
import multipart from '@fastify/multipart';
|
||||
import serve from '@fastify/static';
|
||||
import Fastify from 'fastify';
|
||||
import socketIO from 'fastify-socket.io';
|
||||
import path, { join } from 'path';
|
||||
import socketIOServer from './realtime';
|
||||
|
||||
import Graceful from '@ladjs/graceful';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||
import {
|
||||
cleanupDockerStorage,
|
||||
createRemoteEngineConfiguration,
|
||||
@@ -22,14 +27,9 @@ import {
|
||||
startTraefikTCPProxy,
|
||||
version
|
||||
} from './lib/common';
|
||||
import { scheduler } from './lib/scheduler';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import Graceful from '@ladjs/graceful';
|
||||
import yaml from 'js-yaml';
|
||||
import fs from 'fs/promises';
|
||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||
import { checkContainer } from './lib/docker';
|
||||
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||
import { scheduler } from './lib/scheduler';
|
||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
@@ -167,7 +167,7 @@ const host = '0.0.0.0';
|
||||
// autoUpdater
|
||||
setInterval(async () => {
|
||||
await autoUpdater();
|
||||
}, 60000 * 15);
|
||||
}, 60000 * 60);
|
||||
|
||||
// cleanupStorage
|
||||
setInterval(async () => {
|
||||
@@ -209,7 +209,9 @@ const host = '0.0.0.0';
|
||||
getTagsTemplates(),
|
||||
getArch(),
|
||||
getIPAddress(),
|
||||
configureRemoteDockers()
|
||||
configureRemoteDockers(),
|
||||
refreshTemplates(),
|
||||
refreshTags()
|
||||
// cleanupStuckedContainers()
|
||||
]);
|
||||
} catch (error) {
|
||||
@@ -402,16 +404,21 @@ async function autoUpdater() {
|
||||
if (!isDev) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
if (isAutoUpdateEnabled) {
|
||||
await executeCommand({
|
||||
command: `docker pull ghcr.io/coollabsio/coolify:${latestVersion}`
|
||||
});
|
||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||
try {
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
} catch (error) {
|
||||
image = `coollabsio/coolify:${latestVersion}`;
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
}
|
||||
|
||||
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .env` });
|
||||
await executeCommand({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ghcr.io/coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -548,7 +555,11 @@ async function copySSLCertificates() {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||
try {
|
||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ export default async function (data) {
|
||||
dockerComposeConfiguration,
|
||||
dockerComposeFileLocation
|
||||
} = data;
|
||||
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
||||
const baseDir = `${workdir}${baseDirectory}`;
|
||||
const envFile = `${baseDir}/.env`;
|
||||
const fileYaml = `${baseDir}${dockerComposeFileLocation}`;
|
||||
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
||||
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
||||
if (!dockerComposeYaml.services) {
|
||||
@@ -31,7 +33,7 @@ export default async function (data) {
|
||||
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||
}
|
||||
|
||||
await fs.writeFile(envFile, envs.join('\n'));
|
||||
const composeVolumes = [];
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
@@ -50,32 +52,38 @@ export default async function (data) {
|
||||
if (value['env_file']) {
|
||||
delete value['env_file'];
|
||||
}
|
||||
value['env_file'] = [envFile];
|
||||
|
||||
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||
console.log({ key, environment });
|
||||
if (Object.keys(environment).length > 0) {
|
||||
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
||||
}
|
||||
value['environment'] = [...environment, ...envs];
|
||||
// let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||
// let finalEnvs = [...envs];
|
||||
// if (Object.keys(environment).length > 0) {
|
||||
// for (const arg of Object.keys(environment)) {
|
||||
// const [key, _] = arg.split('=');
|
||||
// if (finalEnvs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
// finalEnvs.push(arg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// value['environment'] = [...finalEnvs];
|
||||
|
||||
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
||||
if (typeof build === 'string') {
|
||||
build = { context: build };
|
||||
}
|
||||
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||
let finalArgs = [...buildEnvs];
|
||||
let finalBuildArgs = [...buildEnvs];
|
||||
if (Object.keys(buildArgs).length > 0) {
|
||||
for (const arg of Object.keys(buildArgs)) {
|
||||
const [key, _] = arg.split('=');
|
||||
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
finalArgs.push(arg);
|
||||
if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
finalBuildArgs.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (build.length > 0 || buildArgs.length > 0) {
|
||||
value['build'] = {
|
||||
...build,
|
||||
args: finalArgs
|
||||
args: finalBuildArgs
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,7 +130,6 @@ export default async function (data) {
|
||||
.replace(/^\./, `~`)
|
||||
.replace(/^\.\./, '~')
|
||||
.replace(/^\$PWD/, '~');
|
||||
console.log({ source });
|
||||
} else {
|
||||
if (!target) {
|
||||
target = source;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { exec } from 'node:child_process';
|
||||
import util from 'util';
|
||||
import fs from 'fs/promises';
|
||||
import fsNormal from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import forge from 'node-forge';
|
||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||
@@ -17,8 +16,9 @@ import { day } from './dayjs';
|
||||
import { saveBuildLog } from './buildPacks/common';
|
||||
import { scheduler } from './scheduler';
|
||||
import type { ExecaChildProcess } from 'execa';
|
||||
import { FastifyReply } from 'fastify';
|
||||
|
||||
export const version = '3.12.33';
|
||||
export const version = '3.12.39';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
||||
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
|
||||
@@ -1762,7 +1762,7 @@ export function convertTolOldVolumeNames(type) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanupDockerStorage(dockerId) {
|
||||
export async function cleanupDockerStorage(dockerId, volumes = false) {
|
||||
// Cleanup images that are not used by any container
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||
@@ -1780,6 +1780,11 @@ export async function cleanupDockerStorage(dockerId) {
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||
} catch (error) { }
|
||||
if (volumes) {
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker volume prune -af` });
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
|
||||
export function persistentVolumes(id, persistentStorage, config) {
|
||||
@@ -1942,3 +1947,49 @@ export function generateSecrets(
|
||||
}
|
||||
return envs;
|
||||
}
|
||||
|
||||
export async function backupDatabaseNow(database, reply) {
|
||||
const backupFolder = '/tmp'
|
||||
const fileName = `${database.id}-${new Date().getTime()}.gz`
|
||||
const backupFileName = `${backupFolder}/${fileName}`
|
||||
const backupStorageFilename = `/app/backups/${fileName}`
|
||||
let command = null
|
||||
switch (database?.type) {
|
||||
case 'postgresql':
|
||||
command = `docker exec ${database.id} sh -c "PGPASSWORD=${database.rootUserPassword} pg_dumpall -U postgres | gzip > ${backupFileName}"`
|
||||
break;
|
||||
case 'mongodb':
|
||||
command = `docker exec ${database.id} sh -c "mongodump --archive=${backupFileName} --gzip --username=${database.rootUser} --password=${database.rootUserPassword}"`
|
||||
break;
|
||||
case 'mysql':
|
||||
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
|
||||
break;
|
||||
case 'mariadb':
|
||||
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
|
||||
break;
|
||||
case 'couchdb':
|
||||
command = `docker exec ${database.id} sh -c "tar -czvf ${backupFileName} /bitnami/couchdb/data"`
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
await executeCommand({
|
||||
dockerId: database.destinationDockerId,
|
||||
command,
|
||||
});
|
||||
const copyCommand = `docker cp ${database.id}:${backupFileName} ${backupFileName}`
|
||||
await executeCommand({
|
||||
dockerId: database.destinationDockerId,
|
||||
command: copyCommand
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: database.destinationDockerId,
|
||||
command: `docker cp ${database.id}:${backupFileName} /app/backups/`
|
||||
});
|
||||
const stream = fsNormal.createReadStream(backupFileName);
|
||||
reply.header('Content-Type', 'application/octet-stream');
|
||||
reply.header('Content-Disposition', `attachment; filename=${fileName}`);
|
||||
reply.header('Content-Length', fsNormal.statSync(backupFileName).size);
|
||||
reply.header('Content-Transfer-Encoding', 'binary');
|
||||
return reply.send(stream)
|
||||
}
|
||||
|
||||
@@ -624,7 +624,7 @@ export const glitchTip = [{
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpUseSsl',
|
||||
name: 'emailSmtpUseTls',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
|
||||
@@ -398,7 +398,9 @@ export async function saveApplication(
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
simpleDockerfile,
|
||||
dockerRegistryImageName
|
||||
dockerRegistryImageName,
|
||||
basicAuthPw,
|
||||
basicAuthUser,
|
||||
} = request.body;
|
||||
if (port) port = Number(port);
|
||||
if (exposePort) {
|
||||
@@ -453,6 +455,8 @@ export async function saveApplication(
|
||||
dockerComposeConfiguration,
|
||||
simpleDockerfile,
|
||||
dockerRegistryImageName,
|
||||
basicAuthPw,
|
||||
basicAuthUser,
|
||||
...defaultConfiguration,
|
||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||
}
|
||||
@@ -476,6 +480,8 @@ export async function saveApplication(
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
simpleDockerfile,
|
||||
basicAuthPw,
|
||||
basicAuthUser,
|
||||
dockerRegistryImageName,
|
||||
...defaultConfiguration
|
||||
}
|
||||
@@ -499,12 +505,11 @@ export async function saveApplicationSettings(
|
||||
previews,
|
||||
dualCerts,
|
||||
autodeploy,
|
||||
branch,
|
||||
projectId,
|
||||
isBot,
|
||||
isDBBranching,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
} = request.body;
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
|
||||
isBot,
|
||||
isDBBranching,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -28,6 +28,8 @@ export interface SaveApplication extends OnlyId {
|
||||
dockerComposeConfiguration: string;
|
||||
simpleDockerfile: string;
|
||||
dockerRegistryImageName: string;
|
||||
basicAuthPw: string;
|
||||
basicAuthUser: string;
|
||||
};
|
||||
}
|
||||
export interface SaveApplicationSettings extends OnlyId {
|
||||
@@ -43,6 +45,7 @@ export interface SaveApplicationSettings extends OnlyId {
|
||||
isDBBranching: boolean;
|
||||
isCustomSSL: boolean;
|
||||
isHttp2: boolean;
|
||||
basicAuth: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeleteApplication extends OnlyId {
|
||||
|
||||
@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
|
||||
import fs from 'fs/promises';
|
||||
import {
|
||||
ComposeFile,
|
||||
backupDatabaseNow,
|
||||
createDirectories,
|
||||
decrypt,
|
||||
defaultComposeConfiguration,
|
||||
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function backupDatabase(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
return await backupDatabaseNow(database, reply);
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||
import { backupDatabase, cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
|
||||
@@ -39,6 +39,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
|
||||
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
|
||||
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -19,8 +19,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { Login, Update } from '.';
|
||||
import type { GetCurrentUser } from './types';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const saltRounds = 15;
|
||||
export async function hashPassword(password: string, saltRounds = 15): Promise<string> {
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
}
|
||||
|
||||
@@ -58,7 +57,7 @@ export async function cleanupManually(request: FastifyRequest) {
|
||||
const destination = await prisma.destinationDocker.findUnique({
|
||||
where: { id: serverId }
|
||||
});
|
||||
await cleanupDockerStorage(destination.id);
|
||||
await cleanupDockerStorage(destination.id, true);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -77,7 +76,7 @@ export async function refreshTags() {
|
||||
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||
@@ -102,7 +101,7 @@ export async function refreshTemplates() {
|
||||
if (await fs.stat('./testTemplate.yaml')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||
} else {
|
||||
@@ -163,13 +162,13 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
}
|
||||
|
||||
await executeCommand({ shell: true, command: `ls .env || env | grep COOLIFY > .env` });
|
||||
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .env` });
|
||||
await executeCommand({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
|
||||
@@ -50,6 +50,7 @@ import type {
|
||||
SetWordpressSettings
|
||||
} from './types';
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import { refreshTags, refreshTemplates } from '../handlers';
|
||||
|
||||
export async function listServices(request: FastifyRequest) {
|
||||
try {
|
||||
@@ -476,7 +477,7 @@ export async function saveServiceType(
|
||||
const [volumeName, path] = volume.split(':');
|
||||
if (!volumeName.startsWith('/')) {
|
||||
const found = await prisma.servicePersistentStorage.findFirst({
|
||||
where: { volumeName, serviceId: id }
|
||||
where: { volumeName, serviceId: id, path }
|
||||
});
|
||||
if (!found) {
|
||||
await prisma.servicePersistentStorage.create({
|
||||
@@ -985,11 +986,22 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
|
||||
const teamId = request.user.teamId;
|
||||
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
|
||||
if (destinationDockerId) {
|
||||
await executeCommand({
|
||||
const logTables = await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`,
|
||||
shell: true
|
||||
command: `docker exec ${id}-clickhouse clickhouse-client -q "SELECT name FROM system.tables;"`,
|
||||
shell: false
|
||||
});
|
||||
if (logTables.stdout !== '') {
|
||||
const tables = logTables.stdout.split('\n').filter((t) => t.includes('_log'));
|
||||
for (const table of tables) {
|
||||
console.log(`Truncating table ${table}`)
|
||||
await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker exec ${id}-clickhouse clickhouse-client -q "TRUNCATE TABLE system.${table};"`,
|
||||
shell: false
|
||||
});
|
||||
}
|
||||
}
|
||||
return await reply.code(201).send();
|
||||
}
|
||||
throw { status: 500, message: 'Could cleanup logs.' };
|
||||
@@ -1105,17 +1117,14 @@ export async function activateWordpressFtp(
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
const volumes = [
|
||||
`${id}-wordpress-data:/home/${ftpUser}/wordpress`,
|
||||
`${
|
||||
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
|
||||
`${
|
||||
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
|
||||
`${
|
||||
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
||||
];
|
||||
|
||||
@@ -1185,6 +1194,6 @@ export async function activateWordpressFtp(
|
||||
await executeCommand({
|
||||
command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
|
||||
import { errorHandler, executeCommand, getDomain, isDev, prisma } from '../../../lib/common';
|
||||
import { getTemplates } from '../../../lib/services';
|
||||
import { OnlyId } from '../../../types';
|
||||
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||
import { hashPassword } from '../../api/v1/handlers';
|
||||
|
||||
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||
if (isHttp2) {
|
||||
@@ -39,7 +40,7 @@ function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps
|
||||
}
|
||||
};
|
||||
}
|
||||
function generateRouters(
|
||||
async function generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
@@ -48,20 +49,22 @@ function generateRouters(
|
||||
isWWW,
|
||||
isDualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2 = false
|
||||
) {
|
||||
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
let ruleWWW = `Host(\`www.${nakedDomain}\`)${
|
||||
pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||
}`;
|
||||
let http: any = {
|
||||
isHttp2 = false,
|
||||
httpBasicAuth = null,
|
||||
}) {
|
||||
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||
}`;
|
||||
|
||||
|
||||
const http: any = {
|
||||
entrypoints: ['web'],
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
};
|
||||
let https: any = {
|
||||
const https: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
@@ -71,14 +74,14 @@ function generateRouters(
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
let httpWWW: any = {
|
||||
const httpWWW: any = {
|
||||
entrypoints: ['web'],
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
};
|
||||
let httpsWWW: any = {
|
||||
const httpsWWW: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
@@ -97,6 +100,10 @@ function generateRouters(
|
||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (httpBasicAuth) {
|
||||
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. http + www only
|
||||
@@ -108,6 +115,10 @@ function generateRouters(
|
||||
https.middlewares.push('redirect-to-www');
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (httpBasicAuth) {
|
||||
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 5. https + non-www only
|
||||
if (isHttps && !isWWW) {
|
||||
@@ -136,6 +147,10 @@ function generateRouters(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (httpBasicAuth) {
|
||||
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 6. https + www only
|
||||
if (isHttps && isWWW) {
|
||||
@@ -145,6 +160,11 @@ function generateRouters(
|
||||
http.middlewares.push('redirect-to-www');
|
||||
https.middlewares.push('redirect-to-www');
|
||||
}
|
||||
|
||||
if (httpBasicAuth) {
|
||||
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
|
||||
if (isCustomSSL) {
|
||||
if (isDualCerts) {
|
||||
https.tls = true;
|
||||
@@ -166,23 +186,23 @@ function generateRouters(
|
||||
}
|
||||
}
|
||||
if (isHttp2) {
|
||||
let http2 = {
|
||||
const http2 = {
|
||||
...http,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let http2WWW = {
|
||||
const http2WWW = {
|
||||
...httpWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let https2 = {
|
||||
const https2 = {
|
||||
...https,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
|
||||
let https2WWW = {
|
||||
const https2WWW = {
|
||||
...httpsWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
@@ -198,14 +218,17 @@ function generateRouters(
|
||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
const result = {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote = false) {
|
||||
const traefik = {
|
||||
tls: {
|
||||
certificates: []
|
||||
@@ -298,7 +321,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
});
|
||||
}
|
||||
|
||||
let parsedCertificates = [];
|
||||
const parsedCertificates = [];
|
||||
for (const certificate of certificates) {
|
||||
parsedCertificates.push({
|
||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||
@@ -369,7 +392,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
dockerComposeConfiguration,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings
|
||||
settings,
|
||||
basicAuthUser,
|
||||
basicAuthPw,
|
||||
settings: { basicAuth: isBasicAuthEnabled }
|
||||
} = application;
|
||||
if (!destinationDockerId) {
|
||||
continue;
|
||||
@@ -382,6 +408,14 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let httpBasicAuth = null;
|
||||
if (basicAuthUser && basicAuthPw && isBasicAuthEnabled) {
|
||||
httpBasicAuth = {
|
||||
basicAuth: {
|
||||
users: [basicAuthUser + ':' + await hashPassword(basicAuthPw, 1)]
|
||||
}
|
||||
};
|
||||
}
|
||||
if (buildPack === 'compose') {
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||
if (services.length > 0) {
|
||||
@@ -404,27 +438,33 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, containerId, port)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||
const { network, id: dockerId } = destinationDocker;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
@@ -437,22 +477,28 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
)
|
||||
isHttp2,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
if (previews) {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
@@ -466,29 +512,35 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
const previewDomain = `${container.split('-')[1]}${
|
||||
coolifySettings.previewSeparator
|
||||
}${domain}`;
|
||||
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
|
||||
}${domain}`;
|
||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||
const pathPrefix = '/';
|
||||
const serviceId = `${container}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
previewDomain,
|
||||
domain: previewDomain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2: false,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, container, port, isHttp2)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,7 +594,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
if (isDomainAndProxyConfiguration.length > 0) {
|
||||
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||
for (let configuration of proxy) {
|
||||
for (const configuration of proxy) {
|
||||
if (configuration.hostPort) {
|
||||
continue;
|
||||
}
|
||||
@@ -582,16 +634,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
@@ -619,16 +671,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
@@ -660,16 +712,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
|
||||
@@ -4,9 +4,9 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
||||
import { OtherProxyConfiguration } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/main.json', async (request) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) =>
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request) =>
|
||||
otherProxyConfiguration(request)
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
import { dev } from '$app/env';
|
||||
import Cookies from 'js-cookie';
|
||||
import { dashify } from './common';
|
||||
|
||||
export function getAPIUrl() {
|
||||
if (GITPOD_WORKSPACE_URL) {
|
||||
@@ -72,17 +73,19 @@ async function send({
|
||||
...headers
|
||||
};
|
||||
}
|
||||
if (token && !path.startsWith('https://')) {
|
||||
|
||||
if (token && !path.startsWith('https://') && !path.startsWith('http://')) {
|
||||
opts.headers = {
|
||||
...opts.headers,
|
||||
Authorization: `Bearer ${token}`
|
||||
};
|
||||
}
|
||||
if (!path.startsWith('https://')) {
|
||||
|
||||
if (!path.startsWith('https://') && !path.startsWith('http://')) {
|
||||
path = `/api/v1${path}`;
|
||||
}
|
||||
|
||||
if (dev && !path.startsWith('https://')) {
|
||||
if (dev && !path.startsWith('https://') && !path.startsWith('http://')) {
|
||||
path = `${getAPIUrl()}${path}`;
|
||||
}
|
||||
if (method === 'POST' && data && !opts.body) {
|
||||
@@ -100,6 +103,14 @@ async function send({
|
||||
responseData = await response.json();
|
||||
} else if (contentType?.indexOf('text/plain') !== -1) {
|
||||
responseData = await response.text();
|
||||
} else if (contentType?.indexOf('application/octet-stream') !== -1) {
|
||||
responseData = await response.blob();
|
||||
const fileName = dashify(data.id + '-' + data.name)
|
||||
const fileLink = document.createElement('a');
|
||||
fileLink.href = URL.createObjectURL(new Blob([responseData]))
|
||||
fileLink.download = fileName + '.gz';
|
||||
fileLink.click();
|
||||
fileLink.remove();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -196,6 +196,9 @@
|
||||
"domain_fqdn": "Domain (FQDN)",
|
||||
"https_explainer": "If you specify <span class='text-settings '>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-settings '>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white '>You must set your DNS to point to the server IP in advance.</span>",
|
||||
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
||||
"basic_auth": "Basic Auth",
|
||||
"basic_auth_user": "User",
|
||||
"basic_auth_pw": "Password",
|
||||
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class=' text-settings'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
||||
"install_command": "Install Command",
|
||||
"build_command": "Build Command",
|
||||
|
||||
@@ -260,7 +260,7 @@
|
||||
<PublicRepository />
|
||||
<div class="flex flex-row items-center pt-10">
|
||||
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
|
||||
<DocLink url="https://docs.coollabs.io/coolify-v3/applications/#dockerfile" />
|
||||
<DocLink url="https://docs.coollabs.io/coolify-v3/applications/#simple-dockerfile" />
|
||||
</div>
|
||||
<div class="mx-auto max-w-screen-2xl">
|
||||
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
||||
|
||||
@@ -29,27 +29,28 @@
|
||||
export let application: any;
|
||||
export let settings: any;
|
||||
|
||||
import yaml from 'js-yaml';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
import { get, getAPIUrl, post } from '$lib/api';
|
||||
import cuid from 'cuid';
|
||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||
import Beta from '$lib/components/Beta.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import {
|
||||
addToast,
|
||||
appSession,
|
||||
checkIfDeploymentEnabledApplications,
|
||||
setLocation,
|
||||
status,
|
||||
features,
|
||||
isDeploymentEnabled,
|
||||
features
|
||||
setLocation,
|
||||
status
|
||||
} from '$lib/store';
|
||||
import { t } from '$lib/translations';
|
||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import Beta from '$lib/components/Beta.svelte';
|
||||
import cuid from 'cuid';
|
||||
import yaml from 'js-yaml';
|
||||
import { onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
import { saveForm } from './utils';
|
||||
|
||||
const { id } = $page.params;
|
||||
@@ -77,6 +78,7 @@
|
||||
let isCustomSSL = application.settings?.isCustomSSL;
|
||||
let autodeploy = application.settings?.autodeploy;
|
||||
let isBot = application.settings?.isBot;
|
||||
let basicAuth = application.settings?.basicAuth;
|
||||
let isDBBranching = application.settings?.isDBBranching;
|
||||
let htmlUrl = application.gitSource?.htmlUrl;
|
||||
let isHttp2 = application.settings.isHttp2;
|
||||
@@ -186,6 +188,9 @@
|
||||
if (name === 'isCustomSSL') {
|
||||
isCustomSSL = !isCustomSSL;
|
||||
}
|
||||
if (name === 'basicAuth') {
|
||||
basicAuth = !basicAuth;
|
||||
}
|
||||
if (name === 'isBot') {
|
||||
if ($status.application.overallStatus !== 'stopped') return;
|
||||
isBot = !isBot;
|
||||
@@ -210,7 +215,8 @@
|
||||
isCustomSSL,
|
||||
isHttp2,
|
||||
branch: application.branch,
|
||||
projectId: application.projectId
|
||||
projectId: application.projectId,
|
||||
basicAuth
|
||||
});
|
||||
return addToast({
|
||||
message: $t('application.settings_saved'),
|
||||
@@ -232,6 +238,9 @@
|
||||
if (name === 'isBot') {
|
||||
isBot = !isBot;
|
||||
}
|
||||
if (name === 'basicAuth') {
|
||||
basicAuth = !basicAuth;
|
||||
}
|
||||
if (name === 'isDBBranching') {
|
||||
isDBBranching = !isDBBranching;
|
||||
}
|
||||
@@ -272,6 +281,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(application);
|
||||
await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
|
||||
setLocation(application, settings);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||
@@ -498,7 +508,7 @@
|
||||
<div class="title font-bold pb-3">General</div>
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
class="btn btn-sm btn-primary"
|
||||
type="submit"
|
||||
class:loading={loading.save}
|
||||
class:bg-orange-600={forceSave}
|
||||
@@ -751,7 +761,7 @@
|
||||
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{#if isHttps && application.buildPack !== 'compose'}
|
||||
<div class="grid grid-cols-2 items-center pb-4">
|
||||
<Setting
|
||||
@@ -774,6 +784,46 @@
|
||||
on:click={() => changeSettings('isHttp2')}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
id="basicAuth"
|
||||
isCenter={false}
|
||||
bind:setting={basicAuth}
|
||||
title={$t('application.basic_auth')}
|
||||
description="Activate basic authentication for your application. <br>Useful if you want to protect your application with a password. <br><br>Use the <span class='font-bold text-settings'>username</span> and <span class='font-bold text-settings'>password</span> fields to set the credentials."
|
||||
on:click={() => changeSettings('basicAuth')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if basicAuth}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="basicAuthUser">{$t('application.basic_auth_user')}</label>
|
||||
<input
|
||||
bind:this={fqdnEl}
|
||||
class="w-full"
|
||||
required={!application.settings?.basicAuth}
|
||||
name="basicAuthUser"
|
||||
id="basicAuthUser"
|
||||
class:border={!application.settings?.basicAuth && !application.basicAuthUser}
|
||||
class:border-red-500={!application.settings?.basicAuth &&
|
||||
!application.basicAuthUser}
|
||||
bind:value={application.basicAuthUser}
|
||||
placeholder="eg: admin"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="basicAuthPw">{$t('application.basic_auth_pw')}</label>
|
||||
<CopyPasswordField
|
||||
bind:this={fqdnEl}
|
||||
isPasswordField={true}
|
||||
required={!application.settings?.basicAuth}
|
||||
name="basicAuthPw"
|
||||
id="basicAuthPw"
|
||||
bind:value={application.basicAuthPw}
|
||||
placeholder="**********"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if isSimpleDockerfile}
|
||||
@@ -782,7 +832,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<label for="simpleDockerfile">Dockerfile</label>
|
||||
<div class="flex gap-2">
|
||||
<textarea
|
||||
|
||||
@@ -13,17 +13,19 @@
|
||||
import Redis from './_Redis.svelte';
|
||||
import CouchDb from './_CouchDb.svelte';
|
||||
import EdgeDB from './_EdgeDB.svelte';
|
||||
import { post } from '$lib/api';
|
||||
import { get, post } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import { addToast, appSession, status } from '$lib/store';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
let loading = {
|
||||
main: false,
|
||||
public: false
|
||||
public: false,
|
||||
backup: false
|
||||
};
|
||||
let publicUrl = '';
|
||||
let appendOnly = database.settings.appendOnly;
|
||||
@@ -109,6 +111,7 @@
|
||||
if ($status.database.isPublic) {
|
||||
database.publicPort = publicPort;
|
||||
}
|
||||
generateUrl();
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
@@ -130,6 +133,22 @@
|
||||
loading.main = false;
|
||||
}
|
||||
}
|
||||
async function backupDatabase() {
|
||||
try {
|
||||
loading.backup = true;
|
||||
addToast({
|
||||
message:
|
||||
'Backup will be downloaded soon and saved to /var/lib/docker/volumes/coolify-local-backup/_data/ on the host system.',
|
||||
type: 'success',
|
||||
timeout: 15000
|
||||
});
|
||||
return await post(`/databases/${id}/backup`, { id, name: database.name });
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading.backup = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-6xl p-4">
|
||||
@@ -144,6 +163,19 @@
|
||||
class:bg-databases={!loading.main}
|
||||
disabled={loading.main}>{$t('forms.save')}</button
|
||||
>
|
||||
{#if database.type !== 'redis' && database.type !== 'edgedb'}
|
||||
{#if $status.database.isRunning}
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
on:click={backupDatabase}
|
||||
class:loading={loading.backup}
|
||||
class:bg-databases={!loading.backup}
|
||||
disabled={loading.backup}>Backup Database</button
|
||||
>
|
||||
{:else}
|
||||
<button disabled class="btn btn-sm">Backup Database (start the database)</button>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid gap-2 grid-cols-2 auto-rows-max lg:px-10 px-2">
|
||||
|
||||
@@ -56,23 +56,23 @@
|
||||
|
||||
async function rollback() {
|
||||
if (rollbackVersion) {
|
||||
const sure = confirm(`Are you sure you want rollback Coolify to ${rollbackVersion}?`);
|
||||
const sure = confirm(`Are you sure you want upgrade Coolify to ${rollbackVersion}?`);
|
||||
if (sure) {
|
||||
try {
|
||||
loading.rollback = true;
|
||||
console.log('loading.rollback', loading.rollback);
|
||||
if (dev) {
|
||||
console.log('rolling back to', rollbackVersion);
|
||||
console.log('Upgrading to ', rollbackVersion);
|
||||
await asyncSleep(4000);
|
||||
return window.location.reload();
|
||||
} else {
|
||||
addToast({
|
||||
message: 'Rollback started...',
|
||||
message: 'Upgrade started...',
|
||||
type: 'success'
|
||||
});
|
||||
await post(`/update`, { type: 'update', latestVersion: rollbackVersion });
|
||||
addToast({
|
||||
message: 'Rollback completed.<br><br>Waiting for the new version to start...',
|
||||
message: 'Upgrade completed.<br><br>Waiting for the new version to start...',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -381,12 +381,12 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center">
|
||||
<div class="grid grid-cols-4 items-center pb-12">
|
||||
<div class="col-span-2">
|
||||
Rollback Coolify to a specific version
|
||||
Upgrade Coolify to a specific version
|
||||
<Explainer
|
||||
position="dropdown-bottom"
|
||||
explanation="You can rollback to a specific version of Coolify. This will not affect your current running resources.<br><br><a href='https://github.com/coollabsio/coolify/releases' target='_blank'>See available versions</a>"
|
||||
explanation="You can upgrade to a specific version of Coolify. This will not affect your current running resources, but could cause issues if you downgrade to an older version where the database layout was different..<br><br><a href='https://github.com/coollabsio/coolify/releases' target='_blank'>See available versions</a>"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
@@ -401,7 +401,7 @@
|
||||
class:loading={loading.rollback}
|
||||
class="btn btn-primary ml-2"
|
||||
disabled={!rollbackVersion || loading.rollback}
|
||||
on:click|preventDefault|stopPropagation={rollback}>Rollback</button
|
||||
on:click|preventDefault|stopPropagation={rollback}>Upgrade</button
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "3.12.33",
|
||||
"version": "3.12.39",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "github:coollabsio/coolify",
|
||||
"scripts": {
|
||||
|
||||
1279
pnpm-lock.yaml
generated
1279
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user