From aa1293323e8d4a40b4918471c17686aac58afa4b Mon Sep 17 00:00:00 2001 From: MrSphay Date: Wed, 15 Jul 2026 22:06:24 +0200 Subject: [PATCH] Mark WilkensXL build versions --- app.py | 19 ++++++++++++++++-- docs/wilkensxl-build.md | 36 ++++++++++++++++++++++++++++++++++ src/constants.py | 8 ++++++++ src/readiness.py | 14 +++++++++++-- static/index.html | 5 ++++- static/login.html | 6 +++++- tests/test_readiness.py | 15 +++++++++++++- tests/test_version_metadata.py | 11 +++++++++++ 8 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 docs/wilkensxl-build.md create mode 100644 tests/test_version_metadata.py diff --git a/app.py b/app.py index c3794dd..6bb2f7a 100644 --- a/app.py +++ b/app.py @@ -924,8 +924,23 @@ async def serve_login(request: Request): @app.get("/api/version") async def get_version(): - from core.constants import APP_VERSION - return {"version": APP_VERSION} + from core.constants import ( + APP_VERSION, + BUILD_FLAVOR, + BUILD_REVISION, + BUILD_VERSION, + FULL_VERSION, + UPSTREAM_VERSION, + ) + return { + "version": BUILD_VERSION, + "upstream_version": UPSTREAM_VERSION, + "app_version": APP_VERSION, + "build_flavor": BUILD_FLAVOR, + "build_revision": BUILD_REVISION, + "build_version": BUILD_VERSION, + "full_version": FULL_VERSION, + } @app.get("/api/health") async def health_check() -> Dict[str, str]: diff --git a/docs/wilkensxl-build.md b/docs/wilkensxl-build.md new file mode 100644 index 0000000..6ef77fc --- /dev/null +++ b/docs/wilkensxl-build.md @@ -0,0 +1,36 @@ +# WilkensXL build versioning + +Odysseus keeps the upstream application version separate from the local +WilkensXL build version. + +- `APP_VERSION` and `UPSTREAM_VERSION` track the upstream release. +- `BUILD_VERSION` and `FULL_VERSION` identify this custom image. +- `/api/version` returns both values so Dockge, logs, and the UI can show which + build is running. + +The default custom build is: + +```text +1.0.1-wilkensxl.2 +``` + +The value can be overridden at runtime with: + +- `ODYSSEUS_BUILD_FLAVOR` +- `ODYSSEUS_BUILD_REVISION` +- `ODYSSEUS_BUILD_VERSION` + +## Update workflow + +When upstream publishes a new Odysseus version: + +1. Merge or rebase the upstream changes into the WilkensXL branch. +2. Keep the addon loader small and stable. +3. Put local behavior changes into addons wherever possible. +4. Run the addon, integration, readiness, and version metadata tests. +5. Bump `BUILD_REVISION` or set `ODYSSEUS_BUILD_VERSION` for the new image. + +The loader does not remove all porting work, but it reduces it. Most local +behavior should live behind addon manifests, so future upstream updates only +need the loader compatibility checked plus any addons that touch changed +internals. diff --git a/src/constants.py b/src/constants.py index c38f190..842aa5c 100644 --- a/src/constants.py +++ b/src/constants.py @@ -5,6 +5,14 @@ import os from src.runtime_paths import get_app_root, get_default_data_dir APP_VERSION = "1.0.1" +UPSTREAM_VERSION = APP_VERSION +BUILD_FLAVOR = os.getenv("ODYSSEUS_BUILD_FLAVOR", "wilkensxl") +BUILD_REVISION = os.getenv("ODYSSEUS_BUILD_REVISION", "2") +BUILD_VERSION = os.getenv( + "ODYSSEUS_BUILD_VERSION", + f"{APP_VERSION}-{BUILD_FLAVOR}.{BUILD_REVISION}" if BUILD_FLAVOR else APP_VERSION, +) +FULL_VERSION = BUILD_VERSION # Base paths BASE_DIR = os.path.join(get_app_root(), "") diff --git a/src/readiness.py b/src/readiness.py index 9c5baa0..81c1e77 100644 --- a/src/readiness.py +++ b/src/readiness.py @@ -19,7 +19,13 @@ def check_readiness() -> Dict[str, object]: ``local_first`` is informational — a remote database is a valid deployment, so it never fails readiness, it only reports whether storage stays on this host. """ - from core.constants import APP_VERSION, DATA_DIR + from core.constants import ( + APP_VERSION, + BUILD_FLAVOR, + BUILD_VERSION, + DATA_DIR, + UPSTREAM_VERSION, + ) from core.database import DATABASE_URL, engine from sqlalchemy import text as sql_text @@ -55,7 +61,11 @@ def check_readiness() -> Dict[str, object]: ready = all(bool(c.get("ok")) for c in checks.values()) return { "ready": ready, - "version": APP_VERSION, + "version": BUILD_VERSION, + "upstream_version": UPSTREAM_VERSION, + "app_version": APP_VERSION, + "build_flavor": BUILD_FLAVOR, + "build_version": BUILD_VERSION, "checks": checks, "timestamp": datetime.utcnow().isoformat(), } diff --git a/static/index.html b/static/index.html index 898c747..f6b80d7 100644 --- a/static/index.html +++ b/static/index.html @@ -994,7 +994,10 @@ el.textContent = 'Type /setup, then choose Local models or API.'; } fetch('/api/version').then(function(r){return r.json()}).then(function(d){ - if (d.version) window._appVersion = d.version; + var fullVersion = d.full_version || d.build_version || d.version; + if (fullVersion) window._appVersion = fullVersion; + if (d.upstream_version) window._upstreamVersion = d.upstream_version; + if (d.build_flavor) window._buildFlavor = d.build_flavor; }).catch(function(){}); })(); diff --git a/static/login.html b/static/login.html index eeece7c..41d9c14 100644 --- a/static/login.html +++ b/static/login.html @@ -305,7 +305,11 @@ const vr = await fetch('/api/version'); if (vr.ok) { const vd = await vr.json(); - document.getElementById('version-label').textContent = 'v' + vd.version; + const version = vd.full_version || vd.build_version || vd.version; + const upstream = vd.upstream_version && vd.upstream_version !== version + ? ' (upstream ' + vd.upstream_version + ')' + : ''; + document.getElementById('version-label').textContent = version ? 'v' + version + upstream : ''; } } catch(e) {} diff --git a/tests/test_readiness.py b/tests/test_readiness.py index 1dc8288..96d3352 100644 --- a/tests/test_readiness.py +++ b/tests/test_readiness.py @@ -6,7 +6,20 @@ from src.readiness import check_readiness def test_readiness_reports_core_subsystems(): result = check_readiness() - assert {"ready", "version", "checks", "timestamp"}.issubset(result.keys()) + assert { + "ready", + "version", + "upstream_version", + "app_version", + "build_flavor", + "build_version", + "checks", + "timestamp", + }.issubset(result.keys()) + assert result["upstream_version"] == "1.0.1" + assert result["app_version"] == result["upstream_version"] + assert result["build_flavor"] == "wilkensxl" + assert result["version"] == result["build_version"] checks = result["checks"] for name in ("database", "data_dir", "local_first"): assert name in checks, f"missing check: {name}" diff --git a/tests/test_version_metadata.py b/tests/test_version_metadata.py new file mode 100644 index 0000000..963018c --- /dev/null +++ b/tests/test_version_metadata.py @@ -0,0 +1,11 @@ +"""Tests for WilkensXL build metadata.""" + +from src.constants import APP_VERSION, BUILD_FLAVOR, BUILD_VERSION, FULL_VERSION, UPSTREAM_VERSION + + +def test_custom_build_version_is_distinct_from_upstream_version(): + assert APP_VERSION == "1.0.1" + assert UPSTREAM_VERSION == APP_VERSION + assert BUILD_FLAVOR == "wilkensxl" + assert BUILD_VERSION.startswith(f"{APP_VERSION}-wilkensxl.") + assert FULL_VERSION == BUILD_VERSION