From 54170d075a4cccd624809ff2e52356e3fb09238c Mon Sep 17 00:00:00 2001 From: FreddleSpl0it <75116288+FreddleSpl0it@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:32:18 +0200 Subject: [PATCH] [Web] Move mailcow update check to server side --- data/web/inc/ajax/update_check.php | 67 ++++++++++++++++++++++++++++++ data/web/js/site/dashboard.js | 51 ++++++++--------------- 2 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 data/web/inc/ajax/update_check.php diff --git a/data/web/inc/ajax/update_check.php b/data/web/inc/ajax/update_check.php new file mode 100644 index 000000000..6bf016001 --- /dev/null +++ b/data/web/inc/ajax/update_check.php @@ -0,0 +1,67 @@ + 'error', 'message' => 'access denied')); + exit; +} + +$owner = $GLOBALS['MAILCOW_GIT_OWNER']; +$repo = $GLOBALS['MAILCOW_GIT_REPO']; +$current = $GLOBALS['MAILCOW_GIT_VERSION']; + +// Cache key is bound to the running version, so an update invalidates it naturally +$cache_key = 'MAILCOW_UPDATE_CHECK/' . $current; + +// Serve cached result if present (one GitHub call per TTL, not one per page load) +$cached = $redis->get($cache_key); +if ($cached !== false) { + echo $cached; + exit; +} + +// GitHub requires a User-Agent and rate-limits unauthenticated requests to 60/h per IP +function github_get($url) { + $ch = curl_init($url); + curl_setopt_array($ch, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERAGENT => 'mailcow-update-check', + CURLOPT_TIMEOUT => 10, + CURLOPT_HTTPHEADER => array('Accept: application/vnd.github+json'), + )); + $body = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($body === false || $code !== 200) return false; + $json = json_decode($body, true); + return is_array($json) ? $json : false; +} + +$latest = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/latest"); +$release = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/tags/{$current}"); + +// Any failure (rate limit, offline, unexpected payload) -> honest error, cached briefly +if ($latest === false || $release === false || + empty($latest['tag_name']) || empty($latest['created_at']) || empty($release['created_at'])) { + $result = json_encode(array('status' => 'error', 'message' => 'could not reach GitHub API')); + $redis->setex($cache_key, 300, $result); + echo $result; + exit; +} + +$date_latest = strtotime($latest['created_at']); +$date_current = strtotime($release['created_at']); + +if ($date_latest <= $date_current) { + $result = json_encode(array('status' => 'no_update')); +} else { + $result = json_encode(array('status' => 'update_available', 'latest_tag' => $latest['tag_name'])); +} + +// Cache the positive result for an hour +$redis->setex($cache_key, 3600, $result); +echo $result; diff --git a/data/web/js/site/dashboard.js b/data/web/js/site/dashboard.js index aee361710..eb50d698b 100644 --- a/data/web/js/site/dashboard.js +++ b/data/web/js/site/dashboard.js @@ -30,7 +30,7 @@ $(document).ready(function() { // create host cpu and mem charts createHostCpuAndMemChart(); // check for new version - if (mailcow_info.branch === "master"){ + if (mailcow_info.branch === "master" && mailcow_cc_role === "admin"){ check_update(mailcow_info.version_tag, mailcow_info.project_url); } $("#mailcow_version").click(function(){ @@ -1669,41 +1669,26 @@ function createHostCpuAndMemChart(){ function check_update(current_version, github_repo_url){ if (!current_version || !github_repo_url) return false; - var github_account = github_repo_url.split("/")[3]; - var github_repo_name = github_repo_url.split("/")[4]; - // get details about latest release - window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/latest", {method:'GET',cache:'no-cache'}).then(function(response) { + window.fetch("/inc/ajax/update_check.php", {method:'GET',cache:'no-cache'}).then(function(response) { + if (!response.ok) throw new Error("update check returned " + response.status); return response.json(); - }).then(function(latest_data) { - // get details about current release - window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/tags/"+current_version, {method:'GET',cache:'no-cache'}).then(function(response) { - return response.json(); - }).then(function(current_data) { - // compare releases - var date_current = new Date(current_data.created_at); - var date_latest = new Date(latest_data.created_at); - if (date_latest.getTime() <= date_current.getTime()){ - // no update available - $("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success"); - $("#mailcow_update").html("" + lang_debug.no_update_available + ""); - } else { - // update available - $("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning"); - $("#mailcow_update").html(lang_debug.update_available + ` `+latest_data.tag_name+``); - $("#mailcow_update_changelog").click(function(){ - if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin") - return; + }).then(function(data) { + if (data.status === "no_update") { + $("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success"); + $("#mailcow_update").html("" + lang_debug.no_update_available + ""); + } else if (data.status === "update_available") { + $("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning"); + $("#mailcow_update").html(lang_debug.update_available + ` `+data.latest_tag+``); + $("#mailcow_update_changelog").click(function(){ + if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin") + return; - showVersionModal("New Release " + latest_data.tag_name, latest_data.tag_name); - }) - } - }).catch(err => { - // err - console.log(err); - $("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger"); - $("#mailcow_update").html(""+ lang_debug.update_failed +""); - }); + showVersionModal("New Release " + data.latest_tag, data.latest_tag); + }) + } else { + throw new Error(data.message || "update check failed"); + } }).catch(err => { // err console.log(err);