From a3fe9deaa641bf529fb5e6ee64df048e01831035 Mon Sep 17 00:00:00 2001 From: Matthieu MALVACHE Date: Sat, 28 Feb 2026 14:59:11 +0100 Subject: [PATCH] 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). --- README.md | 1 + ROADMAP.md | 1 + VERSION | 1 + instrumentation.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ next.config.ts | 3 +++ package.json | 2 +- 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 VERSION create mode 100644 instrumentation.ts diff --git a/README.md b/README.md index 88c66bc5..49a09975 100644 --- a/README.md +++ b/README.md @@ -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) - Health check endpoint - Structured logging (text/JSON) +- Update check on startup (server logs only, no client exposure) ## Tech stack diff --git a/ROADMAP.md b/ROADMAP.md index 8b8714c6..5db27f06 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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] GitHub Actions CI/CD for automated image publishing on releases - [x] CVE remediation: remove npm from production image, upgrade Alpine packages +- [x] Server-side update check (logs newer version availability on startup) ## Planned Features diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..9084fa2f --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.0 diff --git a/instrumentation.ts b/instrumentation.ts new file mode 100644 index 00000000..92fa0ce4 --- /dev/null +++ b/instrumentation.ts @@ -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 + } +} diff --git a/next.config.ts b/next.config.ts index 1d3cbf06..ec6b506a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,9 @@ import createNextIntlPlugin from "next-intl/plugin"; const nextConfig: NextConfig = { output: "standalone", + turbopack: { + root: import.meta.dirname, + }, }; const withNextIntl = createNextIntlPlugin(); diff --git a/package.json b/package.json index 77cdf5bf..732fa945 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jmap-webmail", - "version": "1.0.0", + "version": "1.1.0", "description": "A modern JMAP webmail client built for Stalwart Mail Server", "author": "Matthieu MALVACHE ", "license": "MIT",