feat: add server-side version update check

Logs current version on startup and checks GitHub for newer releases.
Only runs in production, output stays in server logs (no client exposure).
This commit is contained in:
Matthieu MALVACHE
2026-02-28 14:59:11 +01:00
committed by Matthieu MALVACHE
parent c65a6454c5
commit a3fe9deaa6
6 changed files with 50 additions and 1 deletions
+1
View File
@@ -131,6 +131,7 @@ Stalwart is a mail server written in Rust with native JMAP support, not IMAP/SMT
- Runtime environment variables (no rebuild needed) - Runtime environment variables (no rebuild needed)
- Health check endpoint - Health check endpoint
- Structured logging (text/JSON) - Structured logging (text/JSON)
- Update check on startup (server logs only, no client exposure)
## Tech stack ## Tech stack
+1
View File
@@ -233,6 +233,7 @@ This document tracks the development status and planned features for JMAP Webmai
- [x] Pre-built Docker image on [Docker Hub](https://hub.docker.com/r/rootfr/jmap-webmail) and [GHCR](https://ghcr.io/root-fr/jmap-webmail) with multi-arch support (amd64/arm64) - [x] Pre-built Docker image on [Docker Hub](https://hub.docker.com/r/rootfr/jmap-webmail) and [GHCR](https://ghcr.io/root-fr/jmap-webmail) with multi-arch support (amd64/arm64)
- [x] GitHub Actions CI/CD for automated image publishing on releases - [x] GitHub Actions CI/CD for automated image publishing on releases
- [x] CVE remediation: remove npm from production image, upgrade Alpine packages - [x] CVE remediation: remove npm from production image, upgrade Alpine packages
- [x] Server-side update check (logs newer version availability on startup)
## Planned Features ## Planned Features
+1
View File
@@ -0,0 +1 @@
1.1.0
+43
View File
@@ -0,0 +1,43 @@
import { readFileSync } from "fs";
const VERSION_CHECK_URL =
"https://raw.githubusercontent.com/root-fr/jmap-webmail/main/VERSION";
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
function compareVersions(current: string, remote: string): number {
const a = current.split(".").map(Number);
const b = remote.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if ((b[i] ?? 0) > (a[i] ?? 0)) return 1;
if ((b[i] ?? 0) < (a[i] ?? 0)) return -1;
}
return 0;
}
export async function register() {
const pkg = JSON.parse(
readFileSync(`${process.cwd()}/package.json`, "utf-8")
);
const current: string = pkg.version ?? "0.0.0";
console.info(`JMAP Webmail v${current}`);
if (process.env.NODE_ENV !== "production") return;
try {
const res = await fetch(VERSION_CHECK_URL, {
cache: "no-store",
signal: AbortSignal.timeout(5000),
});
if (!res.ok) return;
const remote = (await res.text()).trim();
if (!SEMVER_RE.test(remote)) return;
if (compareVersions(current, remote) > 0) {
console.info(
`Update available: v${remote} — https://github.com/root-fr/jmap-webmail`
);
}
} catch {
// Network unavailable — not critical
}
}
+3
View File
@@ -3,6 +3,9 @@ import createNextIntlPlugin from "next-intl/plugin";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
turbopack: {
root: import.meta.dirname,
},
}; };
const withNextIntl = createNextIntlPlugin(); const withNextIntl = createNextIntlPlugin();
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jmap-webmail", "name": "jmap-webmail",
"version": "1.0.0", "version": "1.1.0",
"description": "A modern JMAP webmail client built for Stalwart Mail Server", "description": "A modern JMAP webmail client built for Stalwart Mail Server",
"author": "Matthieu MALVACHE <matthieu@root.cloud>", "author": "Matthieu MALVACHE <matthieu@root.cloud>",
"license": "MIT", "license": "MIT",