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);