mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-05 05:02:06 +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
|
cancel-in-progress: true
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches:
|
||||||
- "main"
|
- "v3"
|
||||||
- "v4"
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: "coollabsio/coolify"
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|||||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -18,5 +18,14 @@
|
|||||||
"ts",
|
"ts",
|
||||||
"json"
|
"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?
|
dockerRegistryId String?
|
||||||
dockerRegistryImageName String?
|
dockerRegistryImageName String?
|
||||||
simpleDockerfile String?
|
simpleDockerfile String?
|
||||||
|
basicAuthUser String?
|
||||||
|
basicAuthPw String?
|
||||||
|
|
||||||
persistentStorage ApplicationPersistentStorage[]
|
persistentStorage ApplicationPersistentStorage[]
|
||||||
secrets Secret[]
|
secrets Secret[]
|
||||||
@@ -187,6 +189,7 @@ model ApplicationSettings {
|
|||||||
isDBBranching Boolean @default(false)
|
isDBBranching Boolean @default(false)
|
||||||
isCustomSSL Boolean @default(false)
|
isCustomSSL Boolean @default(false)
|
||||||
isHttp2 Boolean @default(false)
|
isHttp2 Boolean @default(false)
|
||||||
|
basicAuth Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
|
|||||||
@@ -95,8 +95,19 @@ async function main() {
|
|||||||
}
|
}
|
||||||
async function reEncryptSecrets() {
|
async function reEncryptSecrets() {
|
||||||
const { execaCommand } = await import('execa');
|
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();
|
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'];
|
const secretOld = process.env['COOLIFY_SECRET_KEY'];
|
||||||
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
|
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||||
if (!secretNew) {
|
if (!secretNew) {
|
||||||
@@ -108,6 +119,8 @@ async function reEncryptSecrets() {
|
|||||||
secretNew = newKey;
|
secretNew = newKey;
|
||||||
}
|
}
|
||||||
if (secretOld !== secretNew) {
|
if (secretOld !== secretNew) {
|
||||||
|
console.log(`Backup database to ${backupfile}.`);
|
||||||
|
await execaCommand(`cp /app/db/prod.db ${backupfile}`, { shell: true });
|
||||||
console.log(
|
console.log(
|
||||||
'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...'
|
'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`, {
|
await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, {
|
||||||
shell: true
|
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 transactions = [];
|
||||||
const secrets = await prisma.secret.findMany();
|
const secrets = await prisma.secret.findMany();
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
for (const secret of secrets) {
|
for (const secret of secrets) {
|
||||||
const value = decrypt(secret.value, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(secret.value, secretOld);
|
||||||
transactions.push(
|
const newValue = encrypt(value, secretNew);
|
||||||
prisma.secret.update({
|
transactions.push(
|
||||||
where: { id: secret.id },
|
prisma.secret.update({
|
||||||
data: { value: newValue }
|
where: { id: secret.id },
|
||||||
})
|
data: { value: newValue }
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const serviceSecrets = await prisma.serviceSecret.findMany();
|
const serviceSecrets = await prisma.serviceSecret.findMany();
|
||||||
if (serviceSecrets.length > 0) {
|
if (serviceSecrets.length > 0) {
|
||||||
for (const secret of serviceSecrets) {
|
for (const secret of serviceSecrets) {
|
||||||
const value = decrypt(secret.value, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(secret.value, secretOld);
|
||||||
transactions.push(
|
const newValue = encrypt(value, secretNew);
|
||||||
prisma.serviceSecret.update({
|
transactions.push(
|
||||||
where: { id: secret.id },
|
prisma.serviceSecret.update({
|
||||||
data: { value: newValue }
|
where: { id: secret.id },
|
||||||
})
|
data: { value: newValue }
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const gitlabApps = await prisma.gitlabApp.findMany();
|
const gitlabApps = await prisma.gitlabApp.findMany();
|
||||||
if (gitlabApps.length > 0) {
|
if (gitlabApps.length > 0) {
|
||||||
for (const gitlabApp of gitlabApps) {
|
for (const gitlabApp of gitlabApps) {
|
||||||
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
||||||
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
const newValue = encrypt(value, secretNew);
|
||||||
const newAppSecret = encrypt(appSecret, secretNew);
|
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
||||||
transactions.push(
|
const newAppSecret = encrypt(appSecret, secretNew);
|
||||||
prisma.gitlabApp.update({
|
transactions.push(
|
||||||
where: { id: gitlabApp.id },
|
prisma.gitlabApp.update({
|
||||||
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
where: { id: gitlabApp.id },
|
||||||
})
|
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const githubApps = await prisma.githubApp.findMany();
|
const githubApps = await prisma.githubApp.findMany();
|
||||||
if (githubApps.length > 0) {
|
if (githubApps.length > 0) {
|
||||||
for (const githubApp of githubApps) {
|
for (const githubApp of githubApps) {
|
||||||
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
try {
|
||||||
const newClientSecret = encrypt(clientSecret, secretNew);
|
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
||||||
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
const newClientSecret = encrypt(clientSecret, secretNew);
|
||||||
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
||||||
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
||||||
const newPrivateKey = encrypt(privateKey, secretNew);
|
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
||||||
|
const newPrivateKey = encrypt(privateKey, secretNew);
|
||||||
|
|
||||||
transactions.push(
|
transactions.push(
|
||||||
prisma.githubApp.update({
|
prisma.githubApp.update({
|
||||||
where: { id: githubApp.id },
|
where: { id: githubApp.id },
|
||||||
data: {
|
data: {
|
||||||
clientSecret: newClientSecret,
|
clientSecret: newClientSecret,
|
||||||
webhookSecret: newWebhookSecret,
|
webhookSecret: newWebhookSecret,
|
||||||
privateKey: newPrivateKey
|
privateKey: newPrivateKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const databases = await prisma.database.findMany();
|
const databases = await prisma.database.findMany();
|
||||||
if (databases.length > 0) {
|
if (databases.length > 0) {
|
||||||
for (const database of databases) {
|
for (const database of databases) {
|
||||||
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
try {
|
||||||
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
||||||
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
||||||
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
||||||
transactions.push(
|
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
||||||
prisma.database.update({
|
transactions.push(
|
||||||
where: { id: database.id },
|
prisma.database.update({
|
||||||
data: {
|
where: { id: database.id },
|
||||||
dbUserPassword: newDbUserPassword,
|
data: {
|
||||||
rootUserPassword: newRootUserPassword
|
dbUserPassword: newDbUserPassword,
|
||||||
}
|
rootUserPassword: newRootUserPassword
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const databaseSecrets = await prisma.databaseSecret.findMany();
|
const databaseSecrets = await prisma.databaseSecret.findMany();
|
||||||
if (databaseSecrets.length > 0) {
|
if (databaseSecrets.length > 0) {
|
||||||
for (const databaseSecret of databaseSecrets) {
|
for (const databaseSecret of databaseSecrets) {
|
||||||
const value = decrypt(databaseSecret.value, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(databaseSecret.value, secretOld);
|
||||||
transactions.push(
|
const newValue = encrypt(value, secretNew);
|
||||||
prisma.databaseSecret.update({
|
transactions.push(
|
||||||
where: { id: databaseSecret.id },
|
prisma.databaseSecret.update({
|
||||||
data: { value: newValue }
|
where: { id: databaseSecret.id },
|
||||||
})
|
data: { value: newValue }
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const wordpresses = await prisma.wordpress.findMany();
|
const wordpresses = await prisma.wordpress.findMany();
|
||||||
if (wordpresses.length > 0) {
|
if (wordpresses.length > 0) {
|
||||||
for (const wordpress of wordpresses) {
|
for (const wordpress of wordpresses) {
|
||||||
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
||||||
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
const newValue = encrypt(value, secretNew);
|
||||||
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
||||||
let newFtpPassword = undefined;
|
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
||||||
if (wordpress.ftpPassword != null) {
|
let newFtpPassword = undefined;
|
||||||
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
if (wordpress.ftpPassword != null) {
|
||||||
newFtpPassword = encrypt(ftpPassword, secretNew);
|
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
||||||
}
|
newFtpPassword = encrypt(ftpPassword, secretNew);
|
||||||
|
}
|
||||||
|
|
||||||
transactions.push(
|
transactions.push(
|
||||||
prisma.wordpress.update({
|
prisma.wordpress.update({
|
||||||
where: { id: wordpress.id },
|
where: { id: wordpress.id },
|
||||||
data: {
|
data: {
|
||||||
ftpHostKey: newValue,
|
ftpHostKey: newValue,
|
||||||
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
||||||
ftpPassword: newFtpPassword
|
ftpPassword: newFtpPassword
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const sshKeys = await prisma.sshKey.findMany();
|
const sshKeys = await prisma.sshKey.findMany();
|
||||||
if (sshKeys.length > 0) {
|
if (sshKeys.length > 0) {
|
||||||
for (const key of sshKeys) {
|
for (const key of sshKeys) {
|
||||||
const value = decrypt(key.privateKey, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(key.privateKey, secretOld);
|
||||||
transactions.push(
|
const newValue = encrypt(value, secretNew);
|
||||||
prisma.sshKey.update({
|
transactions.push(
|
||||||
where: { id: key.id },
|
prisma.sshKey.update({
|
||||||
data: {
|
where: { id: key.id },
|
||||||
privateKey: newValue
|
data: {
|
||||||
}
|
privateKey: newValue
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dockerRegistries = await prisma.dockerRegistry.findMany();
|
const dockerRegistries = await prisma.dockerRegistry.findMany();
|
||||||
if (dockerRegistries.length > 0) {
|
if (dockerRegistries.length > 0) {
|
||||||
for (const registry of dockerRegistries) {
|
for (const registry of dockerRegistries) {
|
||||||
const value = decrypt(registry.password, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(registry.password, secretOld);
|
||||||
transactions.push(
|
const newValue = encrypt(value, secretNew);
|
||||||
prisma.dockerRegistry.update({
|
transactions.push(
|
||||||
where: { id: registry.id },
|
prisma.dockerRegistry.update({
|
||||||
data: {
|
where: { id: registry.id },
|
||||||
password: newValue
|
data: {
|
||||||
}
|
password: newValue
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const certificates = await prisma.certificate.findMany();
|
const certificates = await prisma.certificate.findMany();
|
||||||
if (certificates.length > 0) {
|
if (certificates.length > 0) {
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
const value = decrypt(certificate.key, secretOld);
|
try {
|
||||||
const newValue = encrypt(value, secretNew);
|
const value = decrypt(certificate.key, secretOld);
|
||||||
transactions.push(
|
const newValue = encrypt(value, secretNew);
|
||||||
prisma.certificate.update({
|
transactions.push(
|
||||||
where: { id: certificate.id },
|
prisma.certificate.update({
|
||||||
data: {
|
where: { id: certificate.id },
|
||||||
key: newValue
|
data: {
|
||||||
}
|
key: newValue
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await prisma.$transaction(transactions);
|
await prisma.$transaction(transactions);
|
||||||
@@ -306,18 +357,16 @@ const encrypt = (text, secret) => {
|
|||||||
};
|
};
|
||||||
const decrypt = (hashString, secret) => {
|
const decrypt = (hashString, secret) => {
|
||||||
if (hashString && secret) {
|
if (hashString && secret) {
|
||||||
try {
|
const hash = JSON.parse(hashString);
|
||||||
const hash = JSON.parse(hashString);
|
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
||||||
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
const decrpyted = Buffer.concat([
|
||||||
const decrpyted = Buffer.concat([
|
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
decipher.final()
|
||||||
decipher.final()
|
]);
|
||||||
]);
|
if (/<2F>/.test(decrpyted.toString())) {
|
||||||
return decrpyted.toString();
|
throw new Error('Invalid secret. Skipping...');
|
||||||
} catch (error) {
|
|
||||||
console.log({ decryptionError: error.message });
|
|
||||||
return hashString;
|
|
||||||
}
|
}
|
||||||
|
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 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 socketIO from 'fastify-socket.io';
|
||||||
|
import path, { join } from 'path';
|
||||||
import socketIOServer from './realtime';
|
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 {
|
import {
|
||||||
cleanupDockerStorage,
|
cleanupDockerStorage,
|
||||||
createRemoteEngineConfiguration,
|
createRemoteEngineConfiguration,
|
||||||
@@ -22,14 +27,9 @@ import {
|
|||||||
startTraefikTCPProxy,
|
startTraefikTCPProxy,
|
||||||
version
|
version
|
||||||
} from './lib/common';
|
} 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 { 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';
|
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
@@ -167,7 +167,7 @@ const host = '0.0.0.0';
|
|||||||
// autoUpdater
|
// autoUpdater
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await autoUpdater();
|
await autoUpdater();
|
||||||
}, 60000 * 15);
|
}, 60000 * 60);
|
||||||
|
|
||||||
// cleanupStorage
|
// cleanupStorage
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
@@ -209,7 +209,9 @@ const host = '0.0.0.0';
|
|||||||
getTagsTemplates(),
|
getTagsTemplates(),
|
||||||
getArch(),
|
getArch(),
|
||||||
getIPAddress(),
|
getIPAddress(),
|
||||||
configureRemoteDockers()
|
configureRemoteDockers(),
|
||||||
|
refreshTemplates(),
|
||||||
|
refreshTags()
|
||||||
// cleanupStuckedContainers()
|
// cleanupStuckedContainers()
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -402,16 +404,21 @@ async function autoUpdater() {
|
|||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
if (isAutoUpdateEnabled) {
|
if (isAutoUpdateEnabled) {
|
||||||
await executeCommand({
|
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||||
command: `docker pull ghcr.io/coollabsio/coolify:${latestVersion}`
|
try {
|
||||||
});
|
await executeCommand({ command: `docker pull ${image}` });
|
||||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
} 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({
|
await executeCommand({
|
||||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||||
});
|
});
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
shell: true,
|
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 {
|
} else {
|
||||||
@@ -548,7 +555,11 @@ async function copySSLCertificates() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} 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,
|
dockerComposeConfiguration,
|
||||||
dockerComposeFileLocation
|
dockerComposeFileLocation
|
||||||
} = data;
|
} = 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 dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
||||||
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
||||||
if (!dockerComposeYaml.services) {
|
if (!dockerComposeYaml.services) {
|
||||||
@@ -31,7 +33,7 @@ export default async function (data) {
|
|||||||
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||||
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||||
}
|
}
|
||||||
|
await fs.writeFile(envFile, envs.join('\n'));
|
||||||
const composeVolumes = [];
|
const composeVolumes = [];
|
||||||
if (volumes.length > 0) {
|
if (volumes.length > 0) {
|
||||||
for (const volume of volumes) {
|
for (const volume of volumes) {
|
||||||
@@ -50,32 +52,38 @@ export default async function (data) {
|
|||||||
if (value['env_file']) {
|
if (value['env_file']) {
|
||||||
delete value['env_file'];
|
delete value['env_file'];
|
||||||
}
|
}
|
||||||
|
value['env_file'] = [envFile];
|
||||||
|
|
||||||
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
// let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||||
console.log({ key, environment });
|
// let finalEnvs = [...envs];
|
||||||
if (Object.keys(environment).length > 0) {
|
// if (Object.keys(environment).length > 0) {
|
||||||
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
// for (const arg of Object.keys(environment)) {
|
||||||
}
|
// const [key, _] = arg.split('=');
|
||||||
value['environment'] = [...environment, ...envs];
|
// if (finalEnvs.filter((env) => env.startsWith(key)).length === 0) {
|
||||||
|
// finalEnvs.push(arg);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// value['environment'] = [...finalEnvs];
|
||||||
|
|
||||||
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
||||||
if (typeof build === 'string') {
|
if (typeof build === 'string') {
|
||||||
build = { context: build };
|
build = { context: build };
|
||||||
}
|
}
|
||||||
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||||
let finalArgs = [...buildEnvs];
|
let finalBuildArgs = [...buildEnvs];
|
||||||
if (Object.keys(buildArgs).length > 0) {
|
if (Object.keys(buildArgs).length > 0) {
|
||||||
for (const arg of Object.keys(buildArgs)) {
|
for (const arg of Object.keys(buildArgs)) {
|
||||||
const [key, _] = arg.split('=');
|
const [key, _] = arg.split('=');
|
||||||
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
|
if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||||
finalArgs.push(arg);
|
finalBuildArgs.push(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (build.length > 0 || buildArgs.length > 0) {
|
if (build.length > 0 || buildArgs.length > 0) {
|
||||||
value['build'] = {
|
value['build'] = {
|
||||||
...build,
|
...build,
|
||||||
args: finalArgs
|
args: finalBuildArgs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +130,6 @@ export default async function (data) {
|
|||||||
.replace(/^\./, `~`)
|
.replace(/^\./, `~`)
|
||||||
.replace(/^\.\./, '~')
|
.replace(/^\.\./, '~')
|
||||||
.replace(/^\$PWD/, '~');
|
.replace(/^\$PWD/, '~');
|
||||||
console.log({ source });
|
|
||||||
} else {
|
} else {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
target = source;
|
target = source;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { exec } from 'node:child_process';
|
|
||||||
import util from 'util';
|
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
import fsNormal from 'fs';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||||
@@ -17,8 +16,9 @@ import { day } from './dayjs';
|
|||||||
import { saveBuildLog } from './buildPacks/common';
|
import { saveBuildLog } from './buildPacks/common';
|
||||||
import { scheduler } from './scheduler';
|
import { scheduler } from './scheduler';
|
||||||
import type { ExecaChildProcess } from 'execa';
|
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 isDev = process.env.NODE_ENV === 'development';
|
||||||
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
||||||
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_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
|
// Cleanup images that are not used by any container
|
||||||
try {
|
try {
|
||||||
await executeCommand({ dockerId, command: `docker image prune -af` });
|
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||||
@@ -1780,6 +1780,11 @@ export async function cleanupDockerStorage(dockerId) {
|
|||||||
try {
|
try {
|
||||||
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
|
if (volumes) {
|
||||||
|
try {
|
||||||
|
await executeCommand({ dockerId, command: `docker volume prune -af` });
|
||||||
|
} catch (error) { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function persistentVolumes(id, persistentStorage, config) {
|
export function persistentVolumes(id, persistentStorage, config) {
|
||||||
@@ -1942,3 +1947,49 @@ export function generateSecrets(
|
|||||||
}
|
}
|
||||||
return envs;
|
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
|
isEncrypted: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'emailSmtpUseSsl',
|
name: 'emailSmtpUseTls',
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
isLowerCase: false,
|
isLowerCase: false,
|
||||||
isNumber: false,
|
isNumber: false,
|
||||||
|
|||||||
@@ -398,7 +398,9 @@ export async function saveApplication(
|
|||||||
dockerComposeFileLocation,
|
dockerComposeFileLocation,
|
||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
dockerRegistryImageName
|
dockerRegistryImageName,
|
||||||
|
basicAuthPw,
|
||||||
|
basicAuthUser,
|
||||||
} = request.body;
|
} = request.body;
|
||||||
if (port) port = Number(port);
|
if (port) port = Number(port);
|
||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
@@ -453,6 +455,8 @@ export async function saveApplication(
|
|||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
dockerRegistryImageName,
|
dockerRegistryImageName,
|
||||||
|
basicAuthPw,
|
||||||
|
basicAuthUser,
|
||||||
...defaultConfiguration,
|
...defaultConfiguration,
|
||||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||||
}
|
}
|
||||||
@@ -476,6 +480,8 @@ export async function saveApplication(
|
|||||||
dockerComposeFileLocation,
|
dockerComposeFileLocation,
|
||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
|
basicAuthPw,
|
||||||
|
basicAuthUser,
|
||||||
dockerRegistryImageName,
|
dockerRegistryImageName,
|
||||||
...defaultConfiguration
|
...defaultConfiguration
|
||||||
}
|
}
|
||||||
@@ -499,12 +505,11 @@ export async function saveApplicationSettings(
|
|||||||
previews,
|
previews,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
autodeploy,
|
autodeploy,
|
||||||
branch,
|
|
||||||
projectId,
|
|
||||||
isBot,
|
isBot,
|
||||||
isDBBranching,
|
isDBBranching,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2
|
isHttp2,
|
||||||
|
basicAuth,
|
||||||
} = request.body;
|
} = request.body;
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
|
|||||||
isBot,
|
isBot,
|
||||||
isDBBranching,
|
isDBBranching,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2
|
isHttp2,
|
||||||
|
basicAuth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export interface SaveApplication extends OnlyId {
|
|||||||
dockerComposeConfiguration: string;
|
dockerComposeConfiguration: string;
|
||||||
simpleDockerfile: string;
|
simpleDockerfile: string;
|
||||||
dockerRegistryImageName: string;
|
dockerRegistryImageName: string;
|
||||||
|
basicAuthPw: string;
|
||||||
|
basicAuthUser: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
@@ -43,6 +45,7 @@ export interface SaveApplicationSettings extends OnlyId {
|
|||||||
isDBBranching: boolean;
|
isDBBranching: boolean;
|
||||||
isCustomSSL: boolean;
|
isCustomSSL: boolean;
|
||||||
isHttp2: boolean;
|
isHttp2: boolean;
|
||||||
|
basicAuth: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import {
|
import {
|
||||||
ComposeFile,
|
ComposeFile,
|
||||||
|
backupDatabaseNow,
|
||||||
createDirectories,
|
createDirectories,
|
||||||
decrypt,
|
decrypt,
|
||||||
defaultComposeConfiguration,
|
defaultComposeConfiguration,
|
||||||
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
return errorHandler({ status, message });
|
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>) {
|
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
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';
|
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/start', async (request) => await startDatabase(request));
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(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;
|
export default root;
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
|||||||
import type { Login, Update } from '.';
|
import type { Login, Update } from '.';
|
||||||
import type { GetCurrentUser } from './types';
|
import type { GetCurrentUser } from './types';
|
||||||
|
|
||||||
export async function hashPassword(password: string): Promise<string> {
|
export async function hashPassword(password: string, saltRounds = 15): Promise<string> {
|
||||||
const saltRounds = 15;
|
|
||||||
return bcrypt.hash(password, saltRounds);
|
return bcrypt.hash(password, saltRounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +57,7 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
const destination = await prisma.destinationDocker.findUnique({
|
const destination = await prisma.destinationDocker.findUnique({
|
||||||
where: { id: serverId }
|
where: { id: serverId }
|
||||||
});
|
});
|
||||||
await cleanupDockerStorage(destination.id);
|
await cleanupDockerStorage(destination.id, true);
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -77,7 +76,7 @@ export async function refreshTags() {
|
|||||||
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
await fs.writeFile('./tags.json', tags);
|
await fs.writeFile('./tags.json', tags);
|
||||||
} else {
|
} else {
|
||||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
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')) {
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||||
} else {
|
} else {
|
||||||
@@ -163,13 +162,13 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
await executeCommand({ command: `docker pull ${image}` });
|
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({
|
await executeCommand({
|
||||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||||
});
|
});
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
shell: true,
|
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 {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import type {
|
|||||||
SetWordpressSettings
|
SetWordpressSettings
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
|
import { refreshTags, refreshTemplates } from '../handlers';
|
||||||
|
|
||||||
export async function listServices(request: FastifyRequest) {
|
export async function listServices(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -476,7 +477,7 @@ export async function saveServiceType(
|
|||||||
const [volumeName, path] = volume.split(':');
|
const [volumeName, path] = volume.split(':');
|
||||||
if (!volumeName.startsWith('/')) {
|
if (!volumeName.startsWith('/')) {
|
||||||
const found = await prisma.servicePersistentStorage.findFirst({
|
const found = await prisma.servicePersistentStorage.findFirst({
|
||||||
where: { volumeName, serviceId: id }
|
where: { volumeName, serviceId: id, path }
|
||||||
});
|
});
|
||||||
if (!found) {
|
if (!found) {
|
||||||
await prisma.servicePersistentStorage.create({
|
await prisma.servicePersistentStorage.create({
|
||||||
@@ -985,11 +986,22 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
|
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
await executeCommand({
|
const logTables = await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
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.{};\"`,
|
command: `docker exec ${id}-clickhouse clickhouse-client -q "SELECT name FROM system.tables;"`,
|
||||||
shell: true
|
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();
|
return await reply.code(201).send();
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Could cleanup logs.' };
|
throw { status: 500, message: 'Could cleanup logs.' };
|
||||||
@@ -1105,17 +1117,14 @@ export async function activateWordpressFtp(
|
|||||||
shell: true
|
shell: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
const volumes = [
|
const volumes = [
|
||||||
`${id}-wordpress-data:/home/${ftpUser}/wordpress`,
|
`${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`,
|
}/${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`,
|
}/${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`
|
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1185,6 +1194,6 @@ export async function activateWordpressFtp(
|
|||||||
await executeCommand({
|
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`
|
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 { 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 { getTemplates } from '../../../lib/services';
|
||||||
import { OnlyId } from '../../../types';
|
import { OnlyId } from '../../../types';
|
||||||
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||||
|
import { hashPassword } from '../../api/v1/handlers';
|
||||||
|
|
||||||
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||||
if (isHttp2) {
|
if (isHttp2) {
|
||||||
@@ -39,7 +40,7 @@ function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function generateRouters(
|
async function generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
@@ -48,20 +49,22 @@ function generateRouters(
|
|||||||
isWWW,
|
isWWW,
|
||||||
isDualCerts,
|
isDualCerts,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2 = false
|
isHttp2 = false,
|
||||||
) {
|
httpBasicAuth = null,
|
||||||
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
}) {
|
||||||
let ruleWWW = `Host(\`www.${nakedDomain}\`)${
|
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||||
pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||||
}`;
|
}`;
|
||||||
let http: any = {
|
|
||||||
|
|
||||||
|
const http: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
};
|
};
|
||||||
let https: any = {
|
const https: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
@@ -71,14 +74,14 @@ function generateRouters(
|
|||||||
},
|
},
|
||||||
middlewares: []
|
middlewares: []
|
||||||
};
|
};
|
||||||
let httpWWW: any = {
|
const httpWWW: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule: ruleWWW,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
};
|
};
|
||||||
let httpsWWW: any = {
|
const httpsWWW: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule: ruleWWW,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
@@ -97,6 +100,10 @@ function generateRouters(
|
|||||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||||
delete https.tls;
|
delete https.tls;
|
||||||
delete httpsWWW.tls;
|
delete httpsWWW.tls;
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. http + www only
|
// 3. http + www only
|
||||||
@@ -108,6 +115,10 @@ function generateRouters(
|
|||||||
https.middlewares.push('redirect-to-www');
|
https.middlewares.push('redirect-to-www');
|
||||||
delete https.tls;
|
delete https.tls;
|
||||||
delete httpsWWW.tls;
|
delete httpsWWW.tls;
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 5. https + non-www only
|
// 5. https + non-www only
|
||||||
if (isHttps && !isWWW) {
|
if (isHttps && !isWWW) {
|
||||||
@@ -136,6 +147,10 @@ function generateRouters(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 6. https + www only
|
// 6. https + www only
|
||||||
if (isHttps && isWWW) {
|
if (isHttps && isWWW) {
|
||||||
@@ -145,6 +160,11 @@ function generateRouters(
|
|||||||
http.middlewares.push('redirect-to-www');
|
http.middlewares.push('redirect-to-www');
|
||||||
https.middlewares.push('redirect-to-www');
|
https.middlewares.push('redirect-to-www');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
|
|
||||||
if (isCustomSSL) {
|
if (isCustomSSL) {
|
||||||
if (isDualCerts) {
|
if (isDualCerts) {
|
||||||
https.tls = true;
|
https.tls = true;
|
||||||
@@ -166,23 +186,23 @@ function generateRouters(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isHttp2) {
|
if (isHttp2) {
|
||||||
let http2 = {
|
const http2 = {
|
||||||
...http,
|
...http,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
};
|
};
|
||||||
let http2WWW = {
|
const http2WWW = {
|
||||||
...httpWWW,
|
...httpWWW,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
};
|
};
|
||||||
let https2 = {
|
const https2 = {
|
||||||
...https,
|
...https,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
};
|
};
|
||||||
|
|
||||||
let https2WWW = {
|
const https2WWW = {
|
||||||
...httpsWWW,
|
...httpsWWW,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
@@ -198,14 +218,17 @@ function generateRouters(
|
|||||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
|
const result = {
|
||||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
[`${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 = {
|
const traefik = {
|
||||||
tls: {
|
tls: {
|
||||||
certificates: []
|
certificates: []
|
||||||
@@ -298,7 +321,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedCertificates = [];
|
const parsedCertificates = [];
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
parsedCertificates.push({
|
parsedCertificates.push({
|
||||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||||
@@ -369,7 +392,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
settings
|
settings,
|
||||||
|
basicAuthUser,
|
||||||
|
basicAuthPw,
|
||||||
|
settings: { basicAuth: isBasicAuthEnabled }
|
||||||
} = application;
|
} = application;
|
||||||
if (!destinationDockerId) {
|
if (!destinationDockerId) {
|
||||||
continue;
|
continue;
|
||||||
@@ -382,6 +408,14 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let httpBasicAuth = null;
|
||||||
|
if (basicAuthUser && basicAuthPw && isBasicAuthEnabled) {
|
||||||
|
httpBasicAuth = {
|
||||||
|
basicAuth: {
|
||||||
|
users: [basicAuthUser + ':' + await hashPassword(basicAuthPw, 1)]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
if (buildPack === 'compose') {
|
if (buildPack === 'compose') {
|
||||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||||
if (services.length > 0) {
|
if (services.length > 0) {
|
||||||
@@ -404,27 +438,33 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
|
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
)
|
httpBasicAuth
|
||||||
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
...generateServices(serviceId, containerId, port)
|
...generateServices(serviceId, containerId, port)
|
||||||
};
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||||
const { network, id: dockerId } = destinationDocker;
|
const { network, id: dockerId } = destinationDocker;
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
continue;
|
continue;
|
||||||
@@ -437,22 +477,28 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${id}-${port || 'default'}`;
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2
|
isHttp2,
|
||||||
)
|
httpBasicAuth
|
||||||
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||||
};
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
if (previews) {
|
if (previews) {
|
||||||
const { stdout } = await executeCommand({
|
const { stdout } = await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
@@ -466,29 +512,35 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
.map((c) => c.replace(/"/g, ''));
|
.map((c) => c.replace(/"/g, ''));
|
||||||
if (containers.length > 0) {
|
if (containers.length > 0) {
|
||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
const previewDomain = `${container.split('-')[1]}${
|
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
|
||||||
coolifySettings.previewSeparator
|
}${domain}`;
|
||||||
}${domain}`;
|
|
||||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||||
const pathPrefix = '/';
|
const pathPrefix = '/';
|
||||||
const serviceId = `${container}-${port || 'default'}`;
|
const serviceId = `${container}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
previewDomain,
|
domain: previewDomain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
)
|
isHttp2: false,
|
||||||
|
httpBasicAuth
|
||||||
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
...generateServices(serviceId, container, port, isHttp2)
|
...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) {
|
if (isDomainAndProxyConfiguration.length > 0) {
|
||||||
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||||
const { proxy } = template.services[oneService] || found.services[oneService];
|
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||||
for (let configuration of proxy) {
|
for (const configuration of proxy) {
|
||||||
if (configuration.hostPort) {
|
if (configuration.hostPort) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -582,16 +634,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${oneService}-${port || 'default'}`;
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
)
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
@@ -619,16 +671,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${oneService}-${port || 'default'}`;
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL
|
||||||
)
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
@@ -660,16 +712,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${id}-${port || 'default'}`;
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL
|
||||||
)
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
|||||||
import { OtherProxyConfiguration } from './types';
|
import { OtherProxyConfiguration } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
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<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)
|
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 { dev } from '$app/env';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import { dashify } from './common';
|
||||||
|
|
||||||
export function getAPIUrl() {
|
export function getAPIUrl() {
|
||||||
if (GITPOD_WORKSPACE_URL) {
|
if (GITPOD_WORKSPACE_URL) {
|
||||||
@@ -72,17 +73,19 @@ async function send({
|
|||||||
...headers
|
...headers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (token && !path.startsWith('https://')) {
|
|
||||||
|
if (token && !path.startsWith('https://') && !path.startsWith('http://')) {
|
||||||
opts.headers = {
|
opts.headers = {
|
||||||
...opts.headers,
|
...opts.headers,
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!path.startsWith('https://')) {
|
|
||||||
|
if (!path.startsWith('https://') && !path.startsWith('http://')) {
|
||||||
path = `/api/v1${path}`;
|
path = `/api/v1${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev && !path.startsWith('https://')) {
|
if (dev && !path.startsWith('https://') && !path.startsWith('http://')) {
|
||||||
path = `${getAPIUrl()}${path}`;
|
path = `${getAPIUrl()}${path}`;
|
||||||
}
|
}
|
||||||
if (method === 'POST' && data && !opts.body) {
|
if (method === 'POST' && data && !opts.body) {
|
||||||
@@ -100,6 +103,14 @@ async function send({
|
|||||||
responseData = await response.json();
|
responseData = await response.json();
|
||||||
} else if (contentType?.indexOf('text/plain') !== -1) {
|
} else if (contentType?.indexOf('text/plain') !== -1) {
|
||||||
responseData = await response.text();
|
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 {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,9 @@
|
|||||||
"domain_fqdn": "Domain (FQDN)",
|
"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>",
|
"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?",
|
"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.",
|
"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",
|
"install_command": "Install Command",
|
||||||
"build_command": "Build Command",
|
"build_command": "Build Command",
|
||||||
|
|||||||
@@ -260,7 +260,7 @@
|
|||||||
<PublicRepository />
|
<PublicRepository />
|
||||||
<div class="flex flex-row items-center pt-10">
|
<div class="flex flex-row items-center pt-10">
|
||||||
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
|
<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>
|
||||||
<div class="mx-auto max-w-screen-2xl">
|
<div class="mx-auto max-w-screen-2xl">
|
||||||
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
||||||
|
|||||||
@@ -29,27 +29,28 @@
|
|||||||
export let application: any;
|
export let application: any;
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
|
|
||||||
import yaml from 'js-yaml';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import Select from 'svelte-select';
|
|
||||||
import { get, getAPIUrl, post } from '$lib/api';
|
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 {
|
import {
|
||||||
addToast,
|
addToast,
|
||||||
appSession,
|
appSession,
|
||||||
checkIfDeploymentEnabledApplications,
|
checkIfDeploymentEnabledApplications,
|
||||||
setLocation,
|
features,
|
||||||
status,
|
|
||||||
isDeploymentEnabled,
|
isDeploymentEnabled,
|
||||||
features
|
setLocation,
|
||||||
|
status
|
||||||
} from '$lib/store';
|
} from '$lib/store';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
import cuid from 'cuid';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import yaml from 'js-yaml';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import Select from 'svelte-select';
|
||||||
import Beta from '$lib/components/Beta.svelte';
|
|
||||||
import { saveForm } from './utils';
|
import { saveForm } from './utils';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -77,6 +78,7 @@
|
|||||||
let isCustomSSL = application.settings?.isCustomSSL;
|
let isCustomSSL = application.settings?.isCustomSSL;
|
||||||
let autodeploy = application.settings?.autodeploy;
|
let autodeploy = application.settings?.autodeploy;
|
||||||
let isBot = application.settings?.isBot;
|
let isBot = application.settings?.isBot;
|
||||||
|
let basicAuth = application.settings?.basicAuth;
|
||||||
let isDBBranching = application.settings?.isDBBranching;
|
let isDBBranching = application.settings?.isDBBranching;
|
||||||
let htmlUrl = application.gitSource?.htmlUrl;
|
let htmlUrl = application.gitSource?.htmlUrl;
|
||||||
let isHttp2 = application.settings.isHttp2;
|
let isHttp2 = application.settings.isHttp2;
|
||||||
@@ -186,6 +188,9 @@
|
|||||||
if (name === 'isCustomSSL') {
|
if (name === 'isCustomSSL') {
|
||||||
isCustomSSL = !isCustomSSL;
|
isCustomSSL = !isCustomSSL;
|
||||||
}
|
}
|
||||||
|
if (name === 'basicAuth') {
|
||||||
|
basicAuth = !basicAuth;
|
||||||
|
}
|
||||||
if (name === 'isBot') {
|
if (name === 'isBot') {
|
||||||
if ($status.application.overallStatus !== 'stopped') return;
|
if ($status.application.overallStatus !== 'stopped') return;
|
||||||
isBot = !isBot;
|
isBot = !isBot;
|
||||||
@@ -210,7 +215,8 @@
|
|||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2,
|
isHttp2,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
projectId: application.projectId
|
projectId: application.projectId,
|
||||||
|
basicAuth
|
||||||
});
|
});
|
||||||
return addToast({
|
return addToast({
|
||||||
message: $t('application.settings_saved'),
|
message: $t('application.settings_saved'),
|
||||||
@@ -232,6 +238,9 @@
|
|||||||
if (name === 'isBot') {
|
if (name === 'isBot') {
|
||||||
isBot = !isBot;
|
isBot = !isBot;
|
||||||
}
|
}
|
||||||
|
if (name === 'basicAuth') {
|
||||||
|
basicAuth = !basicAuth;
|
||||||
|
}
|
||||||
if (name === 'isDBBranching') {
|
if (name === 'isDBBranching') {
|
||||||
isDBBranching = !isDBBranching;
|
isDBBranching = !isDBBranching;
|
||||||
}
|
}
|
||||||
@@ -272,6 +281,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(application);
|
||||||
await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
|
await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
|
||||||
setLocation(application, settings);
|
setLocation(application, settings);
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||||
@@ -498,7 +508,7 @@
|
|||||||
<div class="title font-bold pb-3">General</div>
|
<div class="title font-bold pb-3">General</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
class:loading={loading.save}
|
class:loading={loading.save}
|
||||||
class:bg-orange-600={forceSave}
|
class:bg-orange-600={forceSave}
|
||||||
@@ -751,7 +761,7 @@
|
|||||||
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isHttps && application.buildPack !== 'compose'}
|
{#if isHttps && application.buildPack !== 'compose'}
|
||||||
<div class="grid grid-cols-2 items-center pb-4">
|
<div class="grid grid-cols-2 items-center pb-4">
|
||||||
<Setting
|
<Setting
|
||||||
@@ -774,6 +784,46 @@
|
|||||||
on:click={() => changeSettings('isHttp2')}
|
on:click={() => changeSettings('isHttp2')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if isSimpleDockerfile}
|
{#if isSimpleDockerfile}
|
||||||
@@ -782,7 +832,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
<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>
|
<label for="simpleDockerfile">Dockerfile</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@@ -13,17 +13,19 @@
|
|||||||
import Redis from './_Redis.svelte';
|
import Redis from './_Redis.svelte';
|
||||||
import CouchDb from './_CouchDb.svelte';
|
import CouchDb from './_CouchDb.svelte';
|
||||||
import EdgeDB from './_EdgeDB.svelte';
|
import EdgeDB from './_EdgeDB.svelte';
|
||||||
import { post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { addToast, appSession, status } from '$lib/store';
|
import { addToast, appSession, status } from '$lib/store';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = {
|
let loading = {
|
||||||
main: false,
|
main: false,
|
||||||
public: false
|
public: false,
|
||||||
|
backup: false
|
||||||
};
|
};
|
||||||
let publicUrl = '';
|
let publicUrl = '';
|
||||||
let appendOnly = database.settings.appendOnly;
|
let appendOnly = database.settings.appendOnly;
|
||||||
@@ -109,6 +111,7 @@
|
|||||||
if ($status.database.isPublic) {
|
if ($status.database.isPublic) {
|
||||||
database.publicPort = publicPort;
|
database.publicPort = publicPort;
|
||||||
}
|
}
|
||||||
|
generateUrl();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -130,6 +133,22 @@
|
|||||||
loading.main = false;
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto max-w-6xl p-4">
|
<div class="mx-auto max-w-6xl p-4">
|
||||||
@@ -144,6 +163,19 @@
|
|||||||
class:bg-databases={!loading.main}
|
class:bg-databases={!loading.main}
|
||||||
disabled={loading.main}>{$t('forms.save')}</button
|
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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 grid-cols-2 auto-rows-max lg:px-10 px-2">
|
<div class="grid gap-2 grid-cols-2 auto-rows-max lg:px-10 px-2">
|
||||||
|
|||||||
@@ -56,23 +56,23 @@
|
|||||||
|
|
||||||
async function rollback() {
|
async function rollback() {
|
||||||
if (rollbackVersion) {
|
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) {
|
if (sure) {
|
||||||
try {
|
try {
|
||||||
loading.rollback = true;
|
loading.rollback = true;
|
||||||
console.log('loading.rollback', loading.rollback);
|
console.log('loading.rollback', loading.rollback);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
console.log('rolling back to', rollbackVersion);
|
console.log('Upgrading to ', rollbackVersion);
|
||||||
await asyncSleep(4000);
|
await asyncSleep(4000);
|
||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
addToast({
|
addToast({
|
||||||
message: 'Rollback started...',
|
message: 'Upgrade started...',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
await post(`/update`, { type: 'update', latestVersion: rollbackVersion });
|
await post(`/update`, { type: 'update', latestVersion: rollbackVersion });
|
||||||
addToast({
|
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'
|
type: 'success'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -381,12 +381,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-4 items-center">
|
<div class="grid grid-cols-4 items-center pb-12">
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
Rollback Coolify to a specific version
|
Upgrade Coolify to a specific version
|
||||||
<Explainer
|
<Explainer
|
||||||
position="dropdown-bottom"
|
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>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
class:loading={loading.rollback}
|
class:loading={loading.rollback}
|
||||||
class="btn btn-primary ml-2"
|
class="btn btn-primary ml-2"
|
||||||
disabled={!rollbackVersion || loading.rollback}
|
disabled={!rollbackVersion || loading.rollback}
|
||||||
on:click|preventDefault|stopPropagation={rollback}>Rollback</button
|
on:click|preventDefault|stopPropagation={rollback}>Upgrade</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.12.33",
|
"version": "3.12.39",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": "github:coollabsio/coolify",
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"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