From d9a1976cc0a3a1113232078c1c751c7097f8053e Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 14:01:55 +0100 Subject: [PATCH 01/17] fix: check updates message fixes --- .../dashboard/settings/web-server/update-server.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index 48a61c7a4..a2e4a9faa 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -76,18 +76,18 @@ export const UpdateServer = () => { className="w-full" onClick={async () => { await checkAndUpdateImage() - .then(async (e) => { - setIsUpdateAvailable(e); + .then(async (updateAvailable) => { + setIsUpdateAvailable(updateAvailable); + toast.info(updateAvailable ? "Update is available" : "No updates available"); }) .catch(() => { setIsUpdateAvailable(false); - toast.error("Error to check updates"); + toast.error("An error occurred while checking for updates, please try again."); }); - toast.success("Check updates"); }} isLoading={isLoading} > - Check Updates + {isLoading ? "Checking for updates..." : "Check for updates"} )} From dd64b063408432f2bcd0a81824b0e0d5b8841d91 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 14:09:05 +0100 Subject: [PATCH 02/17] style: format with biome --- .../dashboard/settings/web-server/update-server.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index a2e4a9faa..06d4a3f19 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -78,11 +78,17 @@ export const UpdateServer = () => { await checkAndUpdateImage() .then(async (updateAvailable) => { setIsUpdateAvailable(updateAvailable); - toast.info(updateAvailable ? "Update is available" : "No updates available"); + toast.info( + updateAvailable + ? "Update is available" + : "No updates available", + ); }) .catch(() => { setIsUpdateAvailable(false); - toast.error("An error occurred while checking for updates, please try again."); + toast.error( + "An error occurred while checking for updates, please try again.", + ); }); }} isLoading={isLoading} From b842887bc388f32726c77ba2c4431a83c4720a77 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 16:43:05 +0100 Subject: [PATCH 03/17] feat: add toggle for auto updates checking --- .../web-server/toggle-auto-check-updates.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx diff --git a/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx b/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx new file mode 100644 index 000000000..d115672af --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx @@ -0,0 +1,27 @@ +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { useState } from "react"; + +export const ToggleAutoCheckUpdates = () => { + const [enabled, setEnabled] = useState( + localStorage.getItem("enableAutoCheckUpdates") === "true", + ); + + const handleToggle = async (checked: boolean) => { + setEnabled(checked); + localStorage.setItem("enableAutoCheckUpdates", String(checked)); + }; + + return ( +
+ + +
+ ); +}; From a5cd8f18cdc9d3ff09f792721701e3e49487b271 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 17:23:02 +0100 Subject: [PATCH 04/17] feat: show auto check update toggle --- .../dashboard/settings/web-server/update-server.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index 06d4a3f19..8d1ed2e00 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -14,13 +14,14 @@ import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; import { UpdateWebServer } from "./update-webserver"; +import { ToggleAutoCheckUpdates } from "./toggle-auto-check-updates"; export const UpdateServer = () => { const [isUpdateAvailable, setIsUpdateAvailable] = useState( null, ); - const { mutateAsync: checkAndUpdateImage, isLoading } = - api.settings.checkAndUpdateImage.useMutation(); + const { mutateAsync: checkServerUpdates, isLoading } = + api.settings.checkServerUpdates.useMutation(); const [isOpen, setIsOpen] = useState(false); return ( @@ -61,6 +62,7 @@ export const UpdateServer = () => {
+ {isUpdateAvailable === false && (
@@ -75,7 +77,7 @@ export const UpdateServer = () => { @@ -36,19 +63,12 @@ export const UpdateWebServer = () => { Are you absolutely sure? This action cannot be undone. This will update the web server to the - new version. + new version. The page will be reloaded once the update is finished. Cancel - { - await updateServer(); - toast.success("Please reload the browser to see the changes"); - }} - > - Confirm - + Confirm From 2804748118511dd0a46b48c202c94f6d419ed94c Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 17:27:51 +0100 Subject: [PATCH 07/17] refactor: rename action, move pull to updateServer --- apps/dokploy/server/api/routers/settings.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 7c777b170..001da65e9 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -45,6 +45,7 @@ import { stopService, stopServiceRemote, updateAdmin, + checkIsUpdateAvailable, updateLetsEncryptEmail, updateServerById, updateServerTraefik, @@ -342,17 +343,20 @@ export const settingsRouter = createTRPCRouter({ writeConfig("middlewares", input.traefikConfig); return true; }), - - checkAndUpdateImage: adminProcedure.mutation(async () => { + checkForUpdate: adminProcedure.mutation(async () => { if (IS_CLOUD) { return true; } - return await pullLatestRelease(); + + return await checkIsUpdateAvailable(); }), updateServer: adminProcedure.mutation(async () => { if (IS_CLOUD) { return true; } + + await pullLatestRelease(); + await spawnAsync("docker", [ "service", "update", @@ -361,6 +365,7 @@ export const settingsRouter = createTRPCRouter({ getDokployImage(), "dokploy", ]); + return true; }), From 256534570b450c4dade3e27e2728a080c17ada32 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 17:29:01 +0100 Subject: [PATCH 08/17] refactor: add image tag helper, refactor update check logic, remove try/catch --- packages/server/src/services/settings.ts | 54 +++++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index 8261843ad..fb8e6a656 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -3,37 +3,49 @@ import { join } from "node:path"; import { docker } from "@dokploy/server/constants"; import { getServiceContainer } from "@dokploy/server/utils/docker/utils"; import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync"; +import { spawnAsync } from "../utils/process/spawnAsync"; // import packageInfo from "../../../package.json"; -const updateIsAvailable = async () => { - try { - const service = await getServiceContainer("dokploy"); +/** Returns current Dokploy docker image tag or `latest` by default. */ +export const getDokployImageTag = () => { + return process.env.RELEASE_TAG || "latest"; +}; - const localImage = await docker.getImage(getDokployImage()).inspect(); - return localImage.Id !== service?.ImageID; - } catch (error) { - return false; - } +/** Checks if server update is available by comparing current image's digest against digest for provided image tag via Docker hub API */ +export const checkIsUpdateAvailable = async () => { + const commandResult = await spawnAsync("docker", [ + "inspect", + "--format={{index .RepoDigests 0}}", + getDokployImage(), + ]); + + const currentDigest = commandResult.toString().trim().split("@")[1]; + + const url = `https://hub.docker.com/v2/repositories/dokploy/dokploy/tags/${getDokployImageTag()}`; + const response = await fetch(url, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }); + + const data = (await response.json()) as { digest: string }; + const { digest } = data; + + return digest !== currentDigest; }; export const getDokployImage = () => { - return `dokploy/dokploy:${process.env.RELEASE_TAG || "latest"}`; + return `dokploy/dokploy:${getDokployImageTag()}`; }; export const pullLatestRelease = async () => { - try { - const stream = await docker.pull(getDokployImage(), {}); - await new Promise((resolve, reject) => { - docker.modem.followProgress(stream, (err, res) => - err ? reject(err) : resolve(res), - ); - }); - const newUpdateIsAvailable = await updateIsAvailable(); - return newUpdateIsAvailable; - } catch (error) {} - - return false; + const stream = await docker.pull(getDokployImage()); + await new Promise((resolve, reject) => { + docker.modem.followProgress(stream, (err, res) => + err ? reject(err) : resolve(res), + ); + }); }; + export const getDokployVersion = () => { // return packageInfo.version; }; From a06dd17aa136bab2276b24c7e8ecdbff6ca160cc Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 17:30:14 +0100 Subject: [PATCH 09/17] feat(navbar): add automatic update checking interval, add update available button --- apps/dokploy/components/layouts/navbar.tsx | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/apps/dokploy/components/layouts/navbar.tsx b/apps/dokploy/components/layouts/navbar.tsx index cead46836..b08369394 100644 --- a/apps/dokploy/components/layouts/navbar.tsx +++ b/apps/dokploy/components/layouts/navbar.tsx @@ -15,8 +15,13 @@ import { useRouter } from "next/router"; import { Logo } from "../shared/logo"; import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; import { buttonVariants } from "../ui/button"; +import { useEffect, useRef, useState } from "react"; +import { UpdateWebServer } from "../dashboard/settings/web-server/update-webserver"; + +const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 15; export const Navbar = () => { + const [isUpdateAvailable, setIsUpdateAvailable] = useState(false); const router = useRouter(); const { data } = api.auth.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); @@ -29,6 +34,55 @@ export const Navbar = () => { }, ); const { mutateAsync } = api.auth.logout.useMutation(); + const { mutateAsync: checkForUpdate } = + api.settings.checkForUpdate.useMutation(); + + const checkUpdatesIntervalRef = useRef(null); + + useEffect(() => { + // Handling of automatic check for server updates + if (!localStorage.getItem("enableAutoCheckUpdates")) { + // Enable auto update checking by default if user didn't change it + localStorage.setItem("enableAutoCheckUpdates", "true"); + } + + const clearUpdatesInterval = () => { + if (checkUpdatesIntervalRef.current) { + clearInterval(checkUpdatesIntervalRef.current); + } + }; + + const checkUpdates = async () => { + try { + if (localStorage.getItem("enableAutoCheckUpdates") !== "true") { + return; + } + + const updateAvailable = await checkForUpdate(); + + if (updateAvailable) { + // Stop interval when update is available + clearUpdatesInterval(); + setIsUpdateAvailable(true); + } + } catch (error) { + console.error("Error auto-checking for updates:", error); + } + }; + + checkUpdatesIntervalRef.current = setInterval( + checkUpdates, + AUTO_CHECK_UPDATES_INTERVAL_MINUTES * 60000, + ); + + // Also check for updates on initial page load + checkUpdates(); + + return () => { + clearUpdatesInterval(); + }; + }, []); + return (
+ {isUpdateAvailable && ( +
+ +
+ )} Date: Fri, 20 Dec 2024 17:32:10 +0100 Subject: [PATCH 10/17] refactor: remove unused async --- .../dashboard/settings/web-server/toggle-auto-check-updates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx b/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx index d115672af..5c07d5dfd 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/toggle-auto-check-updates.tsx @@ -7,7 +7,7 @@ export const ToggleAutoCheckUpdates = () => { localStorage.getItem("enableAutoCheckUpdates") === "true", ); - const handleToggle = async (checked: boolean) => { + const handleToggle = (checked: boolean) => { setEnabled(checked); localStorage.setItem("enableAutoCheckUpdates", String(checked)); }; From 4565b3d7a2af0681f7e8a419e12a0910c529b99c Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 18:26:54 +0100 Subject: [PATCH 11/17] refactor: add latestVersion information to update data --- .../settings/web-server/update-server.tsx | 8 ++--- apps/dokploy/components/layouts/navbar.tsx | 6 ++-- apps/dokploy/server/api/routers/settings.ts | 8 ++--- packages/server/src/services/settings.ts | 35 +++++++++++++++---- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index 14a66749f..1b8798e41 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -20,16 +20,16 @@ export const UpdateServer = () => { const [isUpdateAvailable, setIsUpdateAvailable] = useState( null, ); - const { mutateAsync: checkForUpdate, isLoading } = - api.settings.checkForUpdate.useMutation(); + const { mutateAsync: getUpdateData, isLoading } = + api.settings.getUpdateData.useMutation(); const [isOpen, setIsOpen] = useState(false); const handleCheckUpdates = async () => { try { - const updateAvailable = await checkForUpdate(); + const { updateAvailable, latestVersion } = await getUpdateData(); setIsUpdateAvailable(updateAvailable); if (updateAvailable) { - toast.success("Update is available!"); + toast.success(`${latestVersion} update is available!`); } else { toast.info("No updates available"); } diff --git a/apps/dokploy/components/layouts/navbar.tsx b/apps/dokploy/components/layouts/navbar.tsx index b08369394..a5a8b74cc 100644 --- a/apps/dokploy/components/layouts/navbar.tsx +++ b/apps/dokploy/components/layouts/navbar.tsx @@ -34,8 +34,8 @@ export const Navbar = () => { }, ); const { mutateAsync } = api.auth.logout.useMutation(); - const { mutateAsync: checkForUpdate } = - api.settings.checkForUpdate.useMutation(); + const { mutateAsync: getUpdateData } = + api.settings.getUpdateData.useMutation(); const checkUpdatesIntervalRef = useRef(null); @@ -58,7 +58,7 @@ export const Navbar = () => { return; } - const updateAvailable = await checkForUpdate(); + const { updateAvailable } = await getUpdateData(); if (updateAvailable) { // Stop interval when update is available diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 001da65e9..2861511e5 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -45,7 +45,7 @@ import { stopService, stopServiceRemote, updateAdmin, - checkIsUpdateAvailable, + getUpdateData, updateLetsEncryptEmail, updateServerById, updateServerTraefik, @@ -343,12 +343,12 @@ export const settingsRouter = createTRPCRouter({ writeConfig("middlewares", input.traefikConfig); return true; }), - checkForUpdate: adminProcedure.mutation(async () => { + getUpdateData: adminProcedure.mutation(async () => { if (IS_CLOUD) { - return true; + return { latestVersion: null, updateAvailable: false }; } - return await checkIsUpdateAvailable(); + return await getUpdateData(); }), updateServer: adminProcedure.mutation(async () => { if (IS_CLOUD) { diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index fb8e6a656..3000f8321 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -1,7 +1,6 @@ import { readdirSync } from "node:fs"; import { join } from "node:path"; import { docker } from "@dokploy/server/constants"; -import { getServiceContainer } from "@dokploy/server/utils/docker/utils"; import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync"; import { spawnAsync } from "../utils/process/spawnAsync"; // import packageInfo from "../../../package.json"; @@ -11,8 +10,11 @@ export const getDokployImageTag = () => { return process.env.RELEASE_TAG || "latest"; }; -/** Checks if server update is available by comparing current image's digest against digest for provided image tag via Docker hub API */ -export const checkIsUpdateAvailable = async () => { +/** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */ +export const getUpdateData = async (): Promise<{ + latestVersion: string | null; + updateAvailable: boolean; +}> => { const commandResult = await spawnAsync("docker", [ "inspect", "--format={{index .RepoDigests 0}}", @@ -21,16 +23,35 @@ export const checkIsUpdateAvailable = async () => { const currentDigest = commandResult.toString().trim().split("@")[1]; - const url = `https://hub.docker.com/v2/repositories/dokploy/dokploy/tags/${getDokployImageTag()}`; + const url = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags"; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); - const data = (await response.json()) as { digest: string }; - const { digest } = data; + const data = (await response.json()) as { + results: [{ digest: string; name: string }]; + }; + const { results } = data; + const latestTagDigest = results.find((t) => t.name === "latest")?.digest; - return digest !== currentDigest; + if (!latestTagDigest) { + return { latestVersion: null, updateAvailable: false }; + } + + const versionedTag = results.find( + (t) => t.digest === latestTagDigest && t.name.startsWith("v"), + ); + + if (!versionedTag) { + return { latestVersion: null, updateAvailable: false }; + } + + const { name: latestVersion, digest } = versionedTag; + + const updateAvailable = digest !== currentDigest; + + return { latestVersion, updateAvailable }; }; export const getDokployImage = () => { From ab9aa56c48ba7eb15c6a3c5f2bb05f3b83cf253d Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Fri, 20 Dec 2024 18:57:28 +0100 Subject: [PATCH 12/17] refactor: disable automatic updates for cloud version --- apps/dokploy/components/layouts/navbar.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/dokploy/components/layouts/navbar.tsx b/apps/dokploy/components/layouts/navbar.tsx index a5a8b74cc..3d17b3e92 100644 --- a/apps/dokploy/components/layouts/navbar.tsx +++ b/apps/dokploy/components/layouts/navbar.tsx @@ -41,6 +41,10 @@ export const Navbar = () => { useEffect(() => { // Handling of automatic check for server updates + if (isCloud) { + return; + } + if (!localStorage.getItem("enableAutoCheckUpdates")) { // Enable auto update checking by default if user didn't change it localStorage.setItem("enableAutoCheckUpdates", "true"); From f40e80233174ff4532da490bd1b17a79b00a60d3 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sat, 21 Dec 2024 08:47:21 +0100 Subject: [PATCH 13/17] fix: pull latest release in case of no image when checking update --- packages/server/src/services/settings.ts | 54 ++++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index 3000f8321..b77dbbc93 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -10,11 +10,21 @@ export const getDokployImageTag = () => { return process.env.RELEASE_TAG || "latest"; }; -/** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */ -export const getUpdateData = async (): Promise<{ - latestVersion: string | null; - updateAvailable: boolean; -}> => { +export const getDokployImage = () => { + return `dokploy/dokploy:${getDokployImageTag()}`; +}; + +export const pullLatestRelease = async () => { + const stream = await docker.pull(getDokployImage()); + await new Promise((resolve, reject) => { + docker.modem.followProgress(stream, (err, res) => + err ? reject(err) : resolve(res), + ); + }); +}; + +/** Returns current docker image digest */ +export const getCurrentImageDigest = async () => { const commandResult = await spawnAsync("docker", [ "inspect", "--format={{index .RepoDigests 0}}", @@ -23,6 +33,27 @@ export const getUpdateData = async (): Promise<{ const currentDigest = commandResult.toString().trim().split("@")[1]; + return currentDigest; +}; + +/** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */ +export const getUpdateData = async (): Promise<{ + latestVersion: string | null; + updateAvailable: boolean; +}> => { + let currentDigest: string | undefined; + try { + currentDigest = await getCurrentImageDigest(); + } catch { + // In case image doesn't exist yet, pull latest release + await pullLatestRelease(); + currentDigest = await getCurrentImageDigest(); + } + + if (!currentDigest) { + throw new Error("Could not get current image digest"); + } + const url = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags"; const response = await fetch(url, { method: "GET", @@ -54,19 +85,6 @@ export const getUpdateData = async (): Promise<{ return { latestVersion, updateAvailable }; }; -export const getDokployImage = () => { - return `dokploy/dokploy:${getDokployImageTag()}`; -}; - -export const pullLatestRelease = async () => { - const stream = await docker.pull(getDokployImage()); - await new Promise((resolve, reject) => { - docker.modem.followProgress(stream, (err, res) => - err ? reject(err) : resolve(res), - ); - }); -}; - export const getDokployVersion = () => { // return packageInfo.version; }; From 18eae9f7d73f2ac15046861ae5aa1f7a4a838361 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sat, 21 Dec 2024 09:04:25 +0100 Subject: [PATCH 14/17] refactor: use service image sha instead of image itself for checking updates --- packages/server/src/services/settings.ts | 34 ++++++++---------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index b77dbbc93..a3bd3f090 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -1,8 +1,10 @@ import { readdirSync } from "node:fs"; import { join } from "node:path"; import { docker } from "@dokploy/server/constants"; -import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync"; -import { spawnAsync } from "../utils/process/spawnAsync"; +import { + execAsync, + execAsyncRemote, +} from "@dokploy/server/utils/process/execAsync"; // import packageInfo from "../../../package.json"; /** Returns current Dokploy docker image tag or `latest` by default. */ @@ -23,15 +25,12 @@ export const pullLatestRelease = async () => { }); }; -/** Returns current docker image digest */ -export const getCurrentImageDigest = async () => { - const commandResult = await spawnAsync("docker", [ - "inspect", - "--format={{index .RepoDigests 0}}", - getDokployImage(), - ]); - - const currentDigest = commandResult.toString().trim().split("@")[1]; +/** Returns Dokploy docker service image digest */ +export const getServiceImageDigest = async () => { + const { stdout } = await execAsync( + "docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'", + ); + const currentDigest = stdout.trim().split("@")[1]; return currentDigest; }; @@ -41,18 +40,7 @@ export const getUpdateData = async (): Promise<{ latestVersion: string | null; updateAvailable: boolean; }> => { - let currentDigest: string | undefined; - try { - currentDigest = await getCurrentImageDigest(); - } catch { - // In case image doesn't exist yet, pull latest release - await pullLatestRelease(); - currentDigest = await getCurrentImageDigest(); - } - - if (!currentDigest) { - throw new Error("Could not get current image digest"); - } + const currentDigest = await getServiceImageDigest(); const url = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags"; const response = await fetch(url, { From 8699e024ee1f80e53bd9495d7c8776aebfddaa5d Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sat, 21 Dec 2024 10:05:31 +0100 Subject: [PATCH 15/17] refactor: add try catch, add default update data --- apps/dokploy/server/api/routers/settings.ts | 3 +- packages/server/src/services/settings.ts | 34 ++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 2861511e5..937c93d3d 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -52,6 +52,7 @@ import { writeConfig, writeMainConfig, writeTraefikConfigInPath, + DEFAULT_UPDATE_DATA, } from "@dokploy/server"; import { checkGPUStatus, setupGPUSupport } from "@dokploy/server"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; @@ -345,7 +346,7 @@ export const settingsRouter = createTRPCRouter({ }), getUpdateData: adminProcedure.mutation(async () => { if (IS_CLOUD) { - return { latestVersion: null, updateAvailable: false }; + return DEFAULT_UPDATE_DATA; } return await getUpdateData(); diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index a3bd3f090..0d2ec9671 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -7,6 +7,16 @@ import { } from "@dokploy/server/utils/process/execAsync"; // import packageInfo from "../../../package.json"; +export interface IUpdateData { + latestVersion: string | null; + updateAvailable: boolean; +} + +export const DEFAULT_UPDATE_DATA: IUpdateData = { + latestVersion: null, + updateAvailable: false, +}; + /** Returns current Dokploy docker image tag or `latest` by default. */ export const getDokployImageTag = () => { return process.env.RELEASE_TAG || "latest"; @@ -30,17 +40,27 @@ export const getServiceImageDigest = async () => { const { stdout } = await execAsync( "docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'", ); + const currentDigest = stdout.trim().split("@")[1]; + if (!currentDigest) { + throw new Error("Could not get current service image digest"); + } + return currentDigest; }; /** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */ -export const getUpdateData = async (): Promise<{ - latestVersion: string | null; - updateAvailable: boolean; -}> => { - const currentDigest = await getServiceImageDigest(); +export const getUpdateData = async (): Promise => { + let currentDigest: string; + try { + currentDigest = await getServiceImageDigest(); + } catch { + // Docker service might not exist locally + // You can run the # Installation command for docker service create mentioned in the below docs to test it locally: + // https://docs.dokploy.com/docs/core/manual-installation + return DEFAULT_UPDATE_DATA; + } const url = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags"; const response = await fetch(url, { @@ -55,7 +75,7 @@ export const getUpdateData = async (): Promise<{ const latestTagDigest = results.find((t) => t.name === "latest")?.digest; if (!latestTagDigest) { - return { latestVersion: null, updateAvailable: false }; + return DEFAULT_UPDATE_DATA; } const versionedTag = results.find( @@ -63,7 +83,7 @@ export const getUpdateData = async (): Promise<{ ); if (!versionedTag) { - return { latestVersion: null, updateAvailable: false }; + return DEFAULT_UPDATE_DATA; } const { name: latestVersion, digest } = versionedTag; From 6c9b12cee904072e6a46d41b9fc3054ac4b79d21 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sat, 21 Dec 2024 18:33:22 +0100 Subject: [PATCH 16/17] refactor: use dynamic tag for comparing latest tag digest --- packages/server/src/services/settings.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index 0d2ec9671..0ab9744e5 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -72,7 +72,9 @@ export const getUpdateData = async (): Promise => { results: [{ digest: string; name: string }]; }; const { results } = data; - const latestTagDigest = results.find((t) => t.name === "latest")?.digest; + const latestTagDigest = results.find( + (t) => t.name === getDokployImageTag(), + )?.digest; if (!latestTagDigest) { return DEFAULT_UPDATE_DATA; From 18e89df9a590b92f76db28ec6a752cefcaa1375e Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 21 Dec 2024 12:45:14 -0600 Subject: [PATCH 17/17] Update apps/dokploy/components/layouts/navbar.tsx --- apps/dokploy/components/layouts/navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/layouts/navbar.tsx b/apps/dokploy/components/layouts/navbar.tsx index 3d17b3e92..a8eb4574a 100644 --- a/apps/dokploy/components/layouts/navbar.tsx +++ b/apps/dokploy/components/layouts/navbar.tsx @@ -18,7 +18,7 @@ import { buttonVariants } from "../ui/button"; import { useEffect, useRef, useState } from "react"; import { UpdateWebServer } from "../dashboard/settings/web-server/update-webserver"; -const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 15; +const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 5; export const Navbar = () => { const [isUpdateAvailable, setIsUpdateAvailable] = useState(false);