Mark WilkensXL build versions
Some checks failed
Container Image / build-and-push (push) Has been cancelled
Some checks failed
Container Image / build-and-push (push) Has been cancelled
This commit is contained in:
19
app.py
19
app.py
@@ -924,8 +924,23 @@ async def serve_login(request: Request):
|
|||||||
|
|
||||||
@app.get("/api/version")
|
@app.get("/api/version")
|
||||||
async def get_version():
|
async def get_version():
|
||||||
from core.constants import APP_VERSION
|
from core.constants import (
|
||||||
return {"version": APP_VERSION}
|
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")
|
@app.get("/api/health")
|
||||||
async def health_check() -> Dict[str, str]:
|
async def health_check() -> Dict[str, str]:
|
||||||
|
|||||||
36
docs/wilkensxl-build.md
Normal file
36
docs/wilkensxl-build.md
Normal 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.
|
||||||
@@ -5,6 +5,14 @@ import os
|
|||||||
from src.runtime_paths import get_app_root, get_default_data_dir
|
from src.runtime_paths import get_app_root, get_default_data_dir
|
||||||
|
|
||||||
APP_VERSION = "1.0.1"
|
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 paths
|
||||||
BASE_DIR = os.path.join(get_app_root(), "")
|
BASE_DIR = os.path.join(get_app_root(), "")
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ def check_readiness() -> Dict[str, object]:
|
|||||||
``local_first`` is informational — a remote database is a valid deployment, so
|
``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.
|
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 core.database import DATABASE_URL, engine
|
||||||
from sqlalchemy import text as sql_text
|
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())
|
ready = all(bool(c.get("ok")) for c in checks.values())
|
||||||
return {
|
return {
|
||||||
"ready": ready,
|
"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,
|
"checks": checks,
|
||||||
"timestamp": datetime.utcnow().isoformat(),
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -994,7 +994,10 @@
|
|||||||
el.textContent = 'Type /setup, then choose Local models or API.';
|
el.textContent = 'Type /setup, then choose Local models or API.';
|
||||||
}
|
}
|
||||||
fetch('/api/version').then(function(r){return r.json()}).then(function(d){
|
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(){});
|
}).catch(function(){});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -305,7 +305,11 @@
|
|||||||
const vr = await fetch('/api/version');
|
const vr = await fetch('/api/version');
|
||||||
if (vr.ok) {
|
if (vr.ok) {
|
||||||
const vd = await vr.json();
|
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) {}
|
} catch(e) {}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,20 @@ from src.readiness import check_readiness
|
|||||||
def test_readiness_reports_core_subsystems():
|
def test_readiness_reports_core_subsystems():
|
||||||
result = check_readiness()
|
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"]
|
checks = result["checks"]
|
||||||
for name in ("database", "data_dir", "local_first"):
|
for name in ("database", "data_dir", "local_first"):
|
||||||
assert name in checks, f"missing check: {name}"
|
assert name in checks, f"missing check: {name}"
|
||||||
|
|||||||
11
tests/test_version_metadata.py
Normal file
11
tests/test_version_metadata.py
Normal 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
|
||||||
Reference in New Issue
Block a user