Mark WilkensXL build versions
Some checks failed
Container Image / build-and-push (push) Has been cancelled

This commit is contained in:
2026-07-15 22:06:24 +02:00
parent caffdaa38c
commit aa1293323e
8 changed files with 107 additions and 7 deletions

19
app.py
View File

@@ -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]:

36
docs/wilkensxl-build.md Normal file
View File

@@ -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.

View File

@@ -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(), "")

View File

@@ -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(),
}

View File

@@ -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(){});
})();
</script>

View File

@@ -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) {}

View File

@@ -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}"

View File

@@ -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