From 11d74c0c1f363964725538891d2237903a2d75c8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Apr 2022 09:54:28 +0200 Subject: [PATCH] feat: Coolify auto-updater --- .env.template | 6 ++- .../migration.sql | 22 +++++++++++ prisma/schema.prisma | 1 + prisma/seed.cjs | 14 +++++++ src/lib/locales/en.json | 4 +- src/lib/queues/autoUpdater.ts | 39 +++++++++++++++++++ src/lib/queues/index.ts | 17 +++++++- src/routes/settings/index.json.ts | 8 +++- src/routes/settings/index.svelte | 16 ++++++-- 9 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 prisma/migrations/20220425075326_auto_update_coolify/migration.sql create mode 100644 src/lib/queues/autoUpdater.ts diff --git a/.env.template b/.env.template index 0fa7427f3..3237d9b2c 100644 --- a/.env.template +++ b/.env.template @@ -2,5 +2,7 @@ COOLIFY_APP_ID= COOLIFY_SECRET_KEY=12341234123412341234123412341234 COOLIFY_DATABASE_URL=file:../db/dev.db COOLIFY_SENTRY_DSN= -COOLIFY_IS_ON="docker" -COOLIFY_WHITE_LABELED="false" \ No newline at end of file +COOLIFY_IS_ON=docker +COOLIFY_WHITE_LABELED=false +COOLIFY_WHITE_LABELED_ICON= +COOLIFY_AUTO_UPDATE=false \ No newline at end of file diff --git a/prisma/migrations/20220425075326_auto_update_coolify/migration.sql b/prisma/migrations/20220425075326_auto_update_coolify/migration.sql new file mode 100644 index 000000000..a102973ee --- /dev/null +++ b/prisma/migrations/20220425075326_auto_update_coolify/migration.sql @@ -0,0 +1,22 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Setting" ( + "id" TEXT NOT NULL PRIMARY KEY, + "fqdn" TEXT, + "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "minPort" INTEGER NOT NULL DEFAULT 9000, + "maxPort" INTEGER NOT NULL DEFAULT 9100, + "proxyPassword" TEXT NOT NULL, + "proxyUser" TEXT NOT NULL, + "proxyHash" TEXT, + "isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting"; +DROP TABLE "Setting"; +ALTER TABLE "new_Setting" RENAME TO "Setting"; +CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 493cdd3fe..82ba9baa8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,6 +18,7 @@ model Setting { proxyPassword String proxyUser String proxyHash String? + isAutoUpdateEnabled Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/prisma/seed.cjs b/prisma/seed.cjs index 61f3928f6..1e6b160c6 100644 --- a/prisma/seed.cjs +++ b/prisma/seed.cjs @@ -50,6 +50,20 @@ async function main() { } }); } + + // Set auto-update based on env variable + const isAutoUpdateEnabled = process.env['COOLIFY_AUTO_UPDATE'] === 'true'; + const settings = await prisma.setting.findFirst({}); + if (settings) { + await prisma.setting.update({ + where: { + id: settings.id + }, + data: { + isAutoUpdateEnabled + } + }); + } } main() .catch((e) => { diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index dc98ab2c3..573ca00e8 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -302,7 +302,9 @@ "registration_allowed": "Registration allowed?", "registration_allowed_explainer": "Allow further registrations to the application.
It's turned off after the first registration.", "coolify_proxy_settings": "Coolify Proxy Settings", - "credential_stat_explainer": "Credentials for stats page." + "credential_stat_explainer": "Credentials for stats page.", + "auto_update_enabled": "Auto update enabled?", + "auto_update_enabled_explainer": "Enable automatic updates for Coolify." }, "team": { "pending_invitations": "Pending invitations", diff --git a/src/lib/queues/autoUpdater.ts b/src/lib/queues/autoUpdater.ts new file mode 100644 index 000000000..835010177 --- /dev/null +++ b/src/lib/queues/autoUpdater.ts @@ -0,0 +1,39 @@ +import { prisma } from '$lib/database'; +import { buildQueue } from '.'; +import got from 'got'; +import { asyncExecShell, version } from '$lib/common'; +import compare from 'compare-versions'; +import { dev } from '$app/env'; + +export default async function (): Promise { + const currentVersion = version; + const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); + if (isAutoUpdateEnabled) { + const versions = await got + .get( + `https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}` + ) + .json(); + const latestVersion = versions['coolify'].main.version; + const isUpdateAvailable = compare(latestVersion, currentVersion); + if (isUpdateAvailable === 1) { + const activeCount = await buildQueue.getActiveCount(); + if (activeCount === 0) { + if (!dev) { + console.log('Updating...'); + await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); + await asyncExecShell(`env | grep COOLIFY > .env`); + await asyncExecShell( + `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"` + ); + } else { + console.log('Updating (not really in dev mode).'); + } + } + } else { + console.log('No update available.'); + } + } else { + console.log('Auto update is disabled.'); + } +} diff --git a/src/lib/queues/index.ts b/src/lib/queues/index.ts index 84d5f7cbc..5f8ebfb11 100644 --- a/src/lib/queues/index.ts +++ b/src/lib/queues/index.ts @@ -10,6 +10,7 @@ import proxy from './proxy'; import proxyTcpHttp from './proxyTcpHttp'; import ssl from './ssl'; import sslrenewal from './sslrenewal'; +import autoUpdater from './autoUpdater'; import { asyncExecShell, saveBuildLog } from '$lib/common'; @@ -34,19 +35,22 @@ const cron = async (): Promise => { new QueueScheduler('cleanup', connectionOptions); new QueueScheduler('ssl', connectionOptions); new QueueScheduler('sslRenew', connectionOptions); + new QueueScheduler('autoUpdater', connectionOptions); const queue = { proxy: new Queue('proxy', { ...connectionOptions }), proxyTcpHttp: new Queue('proxyTcpHttp', { ...connectionOptions }), cleanup: new Queue('cleanup', { ...connectionOptions }), ssl: new Queue('ssl', { ...connectionOptions }), - sslRenew: new Queue('sslRenew', { ...connectionOptions }) + sslRenew: new Queue('sslRenew', { ...connectionOptions }), + autoUpdater: new Queue('autoUpdater', { ...connectionOptions }) }; await queue.proxy.drain(); await queue.proxyTcpHttp.drain(); await queue.cleanup.drain(); await queue.ssl.drain(); await queue.sslRenew.drain(); + await queue.autoUpdater.drain(); new Worker( 'proxy', @@ -98,11 +102,22 @@ const cron = async (): Promise => { } ); + new Worker( + 'autoUpdater', + async () => { + await autoUpdater(); + }, + { + ...connectionOptions + } + ); + await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } }); await queue.proxyTcpHttp.add('proxyTcpHttp', {}, { repeat: { every: 10000 } }); await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } }); if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } }); await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } }); + await queue.autoUpdater.add('autoUpdater', {}, { repeat: { every: 60000 } }); }; cron().catch((error) => { console.log('cron failed to start'); diff --git a/src/routes/settings/index.json.ts b/src/routes/settings/index.json.ts index 2f15c5066..210f12782 100644 --- a/src/routes/settings/index.json.ts +++ b/src/routes/settings/index.json.ts @@ -64,10 +64,14 @@ export const post: RequestHandler = async (event) => { }; if (status === 401) return { status, body }; - const { fqdn, isRegistrationEnabled, dualCerts, minPort, maxPort } = await event.request.json(); + const { fqdn, isRegistrationEnabled, dualCerts, minPort, maxPort, isAutoUpdateEnabled } = + await event.request.json(); try { const { id } = await db.listSettings(); - await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled, dualCerts } }); + await db.prisma.setting.update({ + where: { id }, + data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled } + }); if (fqdn) { await db.prisma.setting.update({ where: { id }, data: { fqdn } }); } diff --git a/src/routes/settings/index.svelte b/src/routes/settings/index.svelte index f52cab13f..5040089ba 100644 --- a/src/routes/settings/index.svelte +++ b/src/routes/settings/index.svelte @@ -40,10 +40,9 @@ import { toast } from '@zerodevx/svelte-toast'; import { t } from '$lib/translations'; - import Language from './_Language.svelte'; - let isRegistrationEnabled = settings.isRegistrationEnabled; let dualCerts = settings.dualCerts; + let isAutoUpdateEnabled = settings.isAutoUpdateEnabled; let minPort = settings.minPort; let maxPort = settings.maxPort; @@ -76,7 +75,10 @@ if (name === 'dualCerts') { dualCerts = !dualCerts; } - await post(`/settings.json`, { isRegistrationEnabled, dualCerts }); + if (name === 'isAutoUpdateEnabled') { + isAutoUpdateEnabled = !isAutoUpdateEnabled; + } + await post(`/settings.json`, { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled }); return toast.push(t.get('application.settings_saved')); } catch ({ error }) { return errorNotification(error); @@ -192,6 +194,14 @@ on:click={() => changeSettings('isRegistrationEnabled')} /> +
+ changeSettings('isAutoUpdateEnabled')} + /> +