Complete release upgrade and operator controls
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-18 02:52:28 +02:00
parent 851d0ef109
commit 05a2c372b7
8 changed files with 229 additions and 0 deletions
+6
View File
@@ -77,6 +77,12 @@
or overwriting an existing release tag. or overwriting an existing release tag.
- Preserved the actual prior rollback image across repeated no-op deploys of - Preserved the actual prior rollback image across repeated no-op deploys of
the same immutable release. the same immutable release.
- Exposed every operator-owned all-in-one runtime limit and local YOLO setting
through advanced Unraid edit fields while keeping deployment-only bridge
variables internal.
- Added a fail-safe isolated upgrade smoke that restores a verified backup to
a temporary database, runs the release image migration chain and removes the
temporary database without touching production.
- Made production startup reject known-default PostGIS passwords and apply the - Made production startup reject known-default PostGIS passwords and apply the
configured upload limit consistently to nginx and FastAPI. configured upload limit consistently to nginx and FastAPI.
@@ -78,9 +78,24 @@ def test_readiness_checks_all_release_shell_entrypoints() -> None:
for path in ( for path in (
"scripts/deploy_tower.sh", "scripts/deploy_tower.sh",
"scripts/verify_release_fresh_install.sh", "scripts/verify_release_fresh_install.sh",
"scripts/verify_release_upgrade_smoke.sh",
"deploy/unraid/all-in-one-start.sh", "deploy/unraid/all-in-one-start.sh",
"deploy/unraid/run-dockerman-container.sh", "deploy/unraid/run-dockerman-container.sh",
"deploy/unraid/deploy-release.sh", "deploy/unraid/deploy-release.sh",
"deploy/unraid/rollback-dockerman-container.sh", "deploy/unraid/rollback-dockerman-container.sh",
): ):
assert f"bash -n {path}" in readiness assert f"bash -n {path}" in readiness
def test_upgrade_smoke_restores_and_upgrades_only_an_isolated_database() -> None:
script = (ROOT / "scripts" / "verify_release_upgrade_smoke.sh").read_text(encoding="utf-8")
assert "--confirm-isolated-upgrade" in script
assert "restore_release_backup_smoke.sh" in script
assert "--keep-database" in script
assert "geointel_restore_verify_" in script
assert 'make_url(os.environ["DATABASE_URL"])' in script
assert ".set(database=os.environ[\"TARGET_DB\"])" in script
assert "python -m alembic upgrade head" in script
assert "production_database_untouched" in script
assert 'dropdb --if-exists -U "$db_user" "$TARGET_DB"' in script
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import re
from pathlib import Path from pathlib import Path
@@ -54,6 +55,26 @@ def test_unraid_env_template_matches_single_container_compose_variables() -> Non
assert '"${GEOINTEL_FRONTEND_PORT:-1202}:80"' in compose assert '"${GEOINTEL_FRONTEND_PORT:-1202}:80"' in compose
def test_unraid_template_exposes_every_operator_owned_runtime_setting() -> None:
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
template = (ROOT / "deploy" / "unraid" / "geointel-unraid-template.xml").read_text(encoding="utf-8")
runtime_variables = set(
re.findall(r'^([A-Z][A-Z0-9_]+)="\$\{\1:-', run_script, flags=re.MULTILINE)
)
template_variables = set(re.findall(r'Target="([A-Z][A-Z0-9_]+)"', template))
bridged_or_internal = {
"GEOINTEL_FRONTEND_PORT",
"GEOINTEL_IMAGE",
"GEOINTEL_MODELS_PATH",
"GEOINTEL_POSTGIS_DATA_PATH",
"GEOINTEL_STORAGE_PATH",
}
assert runtime_variables - template_variables == bridged_or_internal
assert 'Target="/app/models"' in template
def test_unraid_readme_explains_port_changes_and_safe_cleanup() -> None: def test_unraid_readme_explains_port_changes_and_safe_cleanup() -> None:
readme = (ROOT / "deploy" / "unraid" / "README.md").read_text(encoding="utf-8") readme = (ROOT / "deploy" / "unraid" / "README.md").read_text(encoding="utf-8")
+9
View File
@@ -262,6 +262,15 @@ temporary directory:
bash scripts/verify_release_fresh_install.sh geointel-all-in-one:latest bash scripts/verify_release_fresh_install.sh geointel-all-in-one:latest
``` ```
Verify an upgrade against an isolated restore of a checksum-verified backup.
The generated temporary database is removed even when the check fails:
```bash
bash scripts/verify_release_upgrade_smoke.sh \
--backup-dir /mnt/user/appdata/geointel/backups/<verified-backup> \
--confirm-isolated-upgrade
```
Return to the image that was active immediately before the latest deployment: Return to the image that was active immediately before the latest deployment:
```bash ```bash
@@ -24,6 +24,7 @@
</Description> </Description>
<Config Name="Web UI Port" Target="80" Default="1202" Mode="tcp" Description="Host port mapped to the GeoIntel all-in-one web UI. Change this to edit the browser port." Type="Port" Display="always" Required="true" Mask="false">1202</Config> <Config Name="Web UI Port" Target="80" Default="1202" Mode="tcp" Description="Host port mapped to the GeoIntel all-in-one web UI. Change this to edit the browser port." Type="Port" Display="always" Required="true" Mask="false">1202</Config>
<Config Name="Storage Path" Target="/app/storage" Default="/mnt/user/appdata/geointel/storage" Mode="rw" Description="Persistent GeoIntel artifact storage for uploads, tiles, masks, reports and exports." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/geointel/storage</Config> <Config Name="Storage Path" Target="/app/storage" Default="/mnt/user/appdata/geointel/storage" Mode="rw" Description="Persistent GeoIntel artifact storage for uploads, tiles, masks, reports and exports." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/geointel/storage</Config>
<Config Name="AI Models Path" Target="/app/models" Default="/mnt/user/appdata/geointel/models" Mode="rw" Description="Persistent local model files mounted into the container. GeoIntel never downloads weights automatically." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/geointel/models</Config>
<Config Name="PostGIS Data Path" Target="/var/lib/postgresql/data" Default="/mnt/user/appdata/geointel/postgres-data" Mode="rw" Description="Persistent embedded PostGIS data directory for the all-in-one container." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/geointel/postgres-data</Config> <Config Name="PostGIS Data Path" Target="/var/lib/postgresql/data" Default="/mnt/user/appdata/geointel/postgres-data" Mode="rw" Description="Persistent embedded PostGIS data directory for the all-in-one container." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/geointel/postgres-data</Config>
<Config Name="Postgres Database" Target="GEOINTEL_POSTGRES_DB" Default="geointel" Mode="" Description="Embedded PostGIS database name." Type="Variable" Display="advanced" Required="true" Mask="false">geointel</Config> <Config Name="Postgres Database" Target="GEOINTEL_POSTGRES_DB" Default="geointel" Mode="" Description="Embedded PostGIS database name." Type="Variable" Display="advanced" Required="true" Mask="false">geointel</Config>
<Config Name="Postgres User" Target="GEOINTEL_POSTGRES_USER" Default="geointel" Mode="" Description="Embedded PostGIS database user." Type="Variable" Display="advanced" Required="true" Mask="false">geointel</Config> <Config Name="Postgres User" Target="GEOINTEL_POSTGRES_USER" Default="geointel" Mode="" Description="Embedded PostGIS database user." Type="Variable" Display="advanced" Required="true" Mask="false">geointel</Config>
@@ -32,8 +33,11 @@
<Config Name="Max Upload MB" Target="GEOINTEL_MAX_UPLOAD_MB" Default="500" Mode="" Description="Maximum upload size in MiB enforced consistently by nginx and the backend (1-2048)." Type="Variable" Display="advanced" Required="true" Mask="false">500</Config> <Config Name="Max Upload MB" Target="GEOINTEL_MAX_UPLOAD_MB" Default="500" Mode="" Description="Maximum upload size in MiB enforced consistently by nginx and the backend (1-2048)." Type="Variable" Display="advanced" Required="true" Mask="false">500</Config>
<Config Name="Official Orthophoto Acquisition" Target="ORTHOPHOTO_ENABLED" Default="true" Mode="" Description="Allow explicit bounded map selections to request the official Digitaal Vlaanderen orthophoto WMS." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="Official Orthophoto Acquisition" Target="ORTHOPHOTO_ENABLED" Default="true" Mode="" Description="Allow explicit bounded map selections to request the official Digitaal Vlaanderen orthophoto WMS." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="Orthophoto WMS URL" Target="ORTHOPHOTO_WMS_URL" Default="https://geo.api.vlaanderen.be/OMWRGBMRVL/wms" Mode="" Description="Official Digitaal Vlaanderen most-recent winter orthophoto WMS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/OMWRGBMRVL/wms</Config> <Config Name="Orthophoto WMS URL" Target="ORTHOPHOTO_WMS_URL" Default="https://geo.api.vlaanderen.be/OMWRGBMRVL/wms" Mode="" Description="Official Digitaal Vlaanderen most-recent winter orthophoto WMS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/OMWRGBMRVL/wms</Config>
<Config Name="Orthophoto WMS Layer" Target="ORTHOPHOTO_WMS_LAYER" Default="Ortho" Mode="" Description="Allowlisted official orthophoto WMS layer identifier." Type="Variable" Display="advanced" Required="true" Mask="false">Ortho</Config>
<Config Name="Orthophoto Resolution (m)" Target="ORTHOPHOTO_RESOLUTION_M" Default="1.0" Mode="" Description="Requested analysis sampling in metres per pixel. Keep at 1.0 for the active building model profile." Type="Variable" Display="advanced" Required="true" Mask="false">1.0</Config> <Config Name="Orthophoto Resolution (m)" Target="ORTHOPHOTO_RESOLUTION_M" Default="1.0" Mode="" Description="Requested analysis sampling in metres per pixel. Keep at 1.0 for the active building model profile." Type="Variable" Display="advanced" Required="true" Mask="false">1.0</Config>
<Config Name="Orthophoto Minimum Side (m)" Target="ORTHOPHOTO_MIN_SIDE_M" Default="128" Mode="" Description="Minimum bounded request side length." Type="Variable" Display="advanced" Required="true" Mask="false">128</Config>
<Config Name="Orthophoto Maximum Side (m)" Target="ORTHOPHOTO_MAX_SIDE_M" Default="1024" Mode="" Description="Safety limit for each selected rectangle side before external acquisition and local inference." Type="Variable" Display="advanced" Required="true" Mask="false">1024</Config> <Config Name="Orthophoto Maximum Side (m)" Target="ORTHOPHOTO_MAX_SIDE_M" Default="1024" Mode="" Description="Safety limit for each selected rectangle side before external acquisition and local inference." Type="Variable" Display="advanced" Required="true" Mask="false">1024</Config>
<Config Name="Orthophoto Cache (hours)" Target="ORTHOPHOTO_CACHE_TTL_HOURS" Default="24" Mode="" Description="Lifetime of reusable bounded orthophoto acquisitions." Type="Variable" Display="advanced" Required="true" Mask="false">24</Config>
<Config Name="Official Catalog Edition Probe" Target="SOURCE_CATALOG_PROBE_ENABLED" Default="true" Mode="" Description="Allow explicit read-only GRB, orthophoto, Statbel and ALZ edition checks. This never imports provider data." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="Official Catalog Edition Probe" Target="SOURCE_CATALOG_PROBE_ENABLED" Default="true" Mode="" Description="Allow explicit read-only GRB, orthophoto, Statbel and ALZ edition checks. This never imports provider data." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="GRB Catalog WFS URL" Target="SOURCE_CATALOG_GRB_WFS_URL" Default="https://geo.api.vlaanderen.be/GRB/wfs" Mode="" Description="Official GRB WFS used only for capabilities and linked ISO metadata checks." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/GRB/wfs</Config> <Config Name="GRB Catalog WFS URL" Target="SOURCE_CATALOG_GRB_WFS_URL" Default="https://geo.api.vlaanderen.be/GRB/wfs" Mode="" Description="Official GRB WFS used only for capabilities and linked ISO metadata checks." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/GRB/wfs</Config>
<Config Name="Statbel DCAT URL" Target="SOURCE_CATALOG_STATBEL_DCAT_URL" Default="https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl" Mode="" Description="Exact official Statbel Turtle catalog used only to identify population-by-sector releases and distribution identities." Type="Variable" Display="advanced" Required="true" Mask="false">https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl</Config> <Config Name="Statbel DCAT URL" Target="SOURCE_CATALOG_STATBEL_DCAT_URL" Default="https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl" Mode="" Description="Exact official Statbel Turtle catalog used only to identify population-by-sector releases and distribution identities." Type="Variable" Display="advanced" Required="true" Mask="false">https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl</Config>
@@ -45,26 +49,62 @@
<Config Name="Official BWK and Soil Acquisition" Target="OFFICIAL_VECTOR_ENABLED" Default="true" Mode="" Description="Allow explicit bounded BWK/Natura 2000 and DOV soil polygon acquisition after a map selection." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="Official BWK and Soil Acquisition" Target="OFFICIAL_VECTOR_ENABLED" Default="true" Mode="" Description="Allow explicit bounded BWK/Natura 2000 and DOV soil polygon acquisition after a map selection." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="BWK WFS URL" Target="BWK_WFS_URL" Default="https://geo.api.vlaanderen.be/BWK/wfs" Mode="" Description="Official allowlisted INBO BWK and Natura 2000 WFS 2.0 endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/BWK/wfs</Config> <Config Name="BWK WFS URL" Target="BWK_WFS_URL" Default="https://geo.api.vlaanderen.be/BWK/wfs" Mode="" Description="Official allowlisted INBO BWK and Natura 2000 WFS 2.0 endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/BWK/wfs</Config>
<Config Name="DOV Soil WFS URL" Target="DOV_SOIL_WFS_URL" Default="https://www.dov.vlaanderen.be/geoserver/wfs" Mode="" Description="Official allowlisted DOV WFS endpoint for historical soil type polygons." Type="Variable" Display="advanced" Required="true" Mask="false">https://www.dov.vlaanderen.be/geoserver/wfs</Config> <Config Name="DOV Soil WFS URL" Target="DOV_SOIL_WFS_URL" Default="https://www.dov.vlaanderen.be/geoserver/wfs" Mode="" Description="Official allowlisted DOV WFS endpoint for historical soil type polygons." Type="Variable" Display="advanced" Required="true" Mask="false">https://www.dov.vlaanderen.be/geoserver/wfs</Config>
<Config Name="Official Vector Minimum Side (m)" Target="OFFICIAL_VECTOR_MIN_SIDE_M" Default="10" Mode="" Description="Minimum bounded official vector request side length." Type="Variable" Display="advanced" Required="true" Mask="false">10</Config>
<Config Name="Official Vector Maximum Side (m)" Target="OFFICIAL_VECTOR_MAX_SIDE_M" Default="20000" Mode="" Description="Maximum side length for one BWK or soil selection before provider access." Type="Variable" Display="advanced" Required="true" Mask="false">20000</Config> <Config Name="Official Vector Maximum Side (m)" Target="OFFICIAL_VECTOR_MAX_SIDE_M" Default="20000" Mode="" Description="Maximum side length for one BWK or soil selection before provider access." Type="Variable" Display="advanced" Required="true" Mask="false">20000</Config>
<Config Name="Official Vector Page Size" Target="OFFICIAL_VECTOR_PAGE_SIZE" Default="1000" Mode="" Description="Maximum features requested per provider page." Type="Variable" Display="advanced" Required="true" Mask="false">1000</Config>
<Config Name="Official Vector Maximum Pages" Target="OFFICIAL_VECTOR_MAX_PAGES" Default="200" Mode="" Description="Hard page limit for one bounded provider acquisition." Type="Variable" Display="advanced" Required="true" Mask="false">200</Config>
<Config Name="Official Vector Maximum Features" Target="OFFICIAL_VECTOR_MAX_FEATURES" Default="100000" Mode="" Description="Hard feature limit for one bounded BWK or soil acquisition; larger selections fail without truncated persistence." Type="Variable" Display="advanced" Required="true" Mask="false">100000</Config> <Config Name="Official Vector Maximum Features" Target="OFFICIAL_VECTOR_MAX_FEATURES" Default="100000" Mode="" Description="Hard feature limit for one bounded BWK or soil acquisition; larger selections fail without truncated persistence." Type="Variable" Display="advanced" Required="true" Mask="false">100000</Config>
<Config Name="Official Vector Timeout (seconds)" Target="OFFICIAL_VECTOR_TIMEOUT_SECONDS" Default="180" Mode="" Description="Per-request provider timeout." Type="Variable" Display="advanced" Required="true" Mask="false">180</Config>
<Config Name="Official Vector Maximum Response (MiB)" Target="OFFICIAL_VECTOR_MAX_RESPONSE_MB" Default="20" Mode="" Description="Maximum accepted size of one provider page." Type="Variable" Display="advanced" Required="true" Mask="false">20</Config>
<Config Name="Official Vector Total Response (MiB)" Target="OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB" Default="256" Mode="" Description="Hard cumulative response-size limit for one acquisition." Type="Variable" Display="advanced" Required="true" Mask="false">256</Config>
<Config Name="Official Vector Cache (hours)" Target="OFFICIAL_VECTOR_CACHE_TTL_HOURS" Default="24" Mode="" Description="Lifetime of reusable bounded vector acquisitions." Type="Variable" Display="advanced" Required="true" Mask="false">24</Config>
<Config Name="Official DHMV Acquisition" Target="DHMV_ENABLED" Default="true" Mode="" Description="Allow bounded official DHMV II terrain and surface raster acquisition." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="Official DHMV Acquisition" Target="DHMV_ENABLED" Default="true" Mode="" Description="Allow bounded official DHMV II terrain and surface raster acquisition." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="DHMV WCS URL" Target="DHMV_WCS_URL" Default="https://geo.api.vlaanderen.be/DHMV/wcs" Mode="" Description="Official Digitaal Vlaanderen DHMV WCS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/DHMV/wcs</Config> <Config Name="DHMV WCS URL" Target="DHMV_WCS_URL" Default="https://geo.api.vlaanderen.be/DHMV/wcs" Mode="" Description="Official Digitaal Vlaanderen DHMV WCS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/DHMV/wcs</Config>
<Config Name="DHMV Analysis Resolution (m)" Target="DHMV_RESOLUTION_M" Default="5.0" Mode="" Description="Stored analysis grid resolution. Native source resolution remains recorded as 1 metre." Type="Variable" Display="advanced" Required="true" Mask="false">5.0</Config> <Config Name="DHMV Analysis Resolution (m)" Target="DHMV_RESOLUTION_M" Default="5.0" Mode="" Description="Stored analysis grid resolution. Native source resolution remains recorded as 1 metre." Type="Variable" Display="advanced" Required="true" Mask="false">5.0</Config>
<Config Name="DHMV Minimum Side (m)" Target="DHMV_MIN_SIDE_M" Default="10" Mode="" Description="Minimum bounded terrain request side length." Type="Variable" Display="advanced" Required="true" Mask="false">10</Config>
<Config Name="DHMV Maximum Side (m)" Target="DHMV_MAX_SIDE_M" Default="20000" Mode="" Description="Maximum bounded terrain request side length." Type="Variable" Display="advanced" Required="true" Mask="false">20000</Config> <Config Name="DHMV Maximum Side (m)" Target="DHMV_MAX_SIDE_M" Default="20000" Mode="" Description="Maximum bounded terrain request side length." Type="Variable" Display="advanced" Required="true" Mask="false">20000</Config>
<Config Name="DHMV Maximum Cells" Target="DHMV_MAX_PIXELS" Default="12000000" Mode="" Description="Maximum raster cells per acquisition or selection analysis." Type="Variable" Display="advanced" Required="true" Mask="false">12000000</Config> <Config Name="DHMV Maximum Cells" Target="DHMV_MAX_PIXELS" Default="12000000" Mode="" Description="Maximum raster cells per acquisition or selection analysis." Type="Variable" Display="advanced" Required="true" Mask="false">12000000</Config>
<Config Name="DHMV Timeout (seconds)" Target="DHMV_TIMEOUT_SECONDS" Default="300" Mode="" Description="Maximum wait for one bounded terrain request." Type="Variable" Display="advanced" Required="true" Mask="false">300</Config>
<Config Name="DHMV Maximum Response (MiB)" Target="DHMV_MAX_RESPONSE_MB" Default="160" Mode="" Description="Maximum accepted terrain response size." Type="Variable" Display="advanced" Required="true" Mask="false">160</Config>
<Config Name="Official VMM Flood Hazard Acquisition" Target="FLOOD_HAZARD_ENABLED" Default="true" Mode="" Description="Allow bounded official VMM flood-depth scenario acquisition." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="Official VMM Flood Hazard Acquisition" Target="FLOOD_HAZARD_ENABLED" Default="true" Mode="" Description="Allow bounded official VMM flood-depth scenario acquisition." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="VMM Flood Hazard WCS URL" Target="FLOOD_HAZARD_WCS_URL" Default="https://geoservice.waterinfo.be/OGRK/wcs" Mode="" Description="Official VMM OGRK WCS endpoint for governed flood-depth scenarios." Type="Variable" Display="advanced" Required="true" Mask="false">https://geoservice.waterinfo.be/OGRK/wcs</Config> <Config Name="VMM Flood Hazard WCS URL" Target="FLOOD_HAZARD_WCS_URL" Default="https://geoservice.waterinfo.be/OGRK/wcs" Mode="" Description="Official VMM OGRK WCS endpoint for governed flood-depth scenarios." Type="Variable" Display="advanced" Required="true" Mask="false">https://geoservice.waterinfo.be/OGRK/wcs</Config>
<Config Name="Flood Hazard Analysis Resolution (m)" Target="FLOOD_HAZARD_RESOLUTION_M" Default="5.0" Mode="" Description="Stored analysis grid resolution; official source values are converted from centimetres to metres." Type="Variable" Display="advanced" Required="true" Mask="false">5.0</Config> <Config Name="Flood Hazard Analysis Resolution (m)" Target="FLOOD_HAZARD_RESOLUTION_M" Default="5.0" Mode="" Description="Stored analysis grid resolution; official source values are converted from centimetres to metres." Type="Variable" Display="advanced" Required="true" Mask="false">5.0</Config>
<Config Name="Flood Hazard Minimum Side (m)" Target="FLOOD_HAZARD_MIN_SIDE_M" Default="10" Mode="" Description="Minimum bounded flood request side length." Type="Variable" Display="advanced" Required="true" Mask="false">10</Config>
<Config Name="Flood Hazard Maximum Side (m)" Target="FLOOD_HAZARD_MAX_SIDE_M" Default="20000" Mode="" Description="Maximum bounded flood request side length." Type="Variable" Display="advanced" Required="true" Mask="false">20000</Config>
<Config Name="Flood Hazard Maximum Cells" Target="FLOOD_HAZARD_MAX_PIXELS" Default="12000000" Mode="" Description="Maximum raster cells per flood-hazard acquisition or selection analysis." Type="Variable" Display="advanced" Required="true" Mask="false">12000000</Config> <Config Name="Flood Hazard Maximum Cells" Target="FLOOD_HAZARD_MAX_PIXELS" Default="12000000" Mode="" Description="Maximum raster cells per flood-hazard acquisition or selection analysis." Type="Variable" Display="advanced" Required="true" Mask="false">12000000</Config>
<Config Name="Flood Hazard Timeout (seconds)" Target="FLOOD_HAZARD_TIMEOUT_SECONDS" Default="300" Mode="" Description="Maximum wait for one bounded flood request." Type="Variable" Display="advanced" Required="true" Mask="false">300</Config>
<Config Name="Flood Hazard Maximum Response (MiB)" Target="FLOOD_HAZARD_MAX_RESPONSE_MB" Default="160" Mode="" Description="Maximum accepted flood raster response size." Type="Variable" Display="advanced" Required="true" Mask="false">160</Config>
<Config Name="VHA Bathymetry Profiles" Target="BATHYMETRY_PROFILES_ENABLED" Default="true" Mode="" Description="Allow explicit bounded acquisition of official VHA cross-section profile points." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="VHA Bathymetry Profiles" Target="BATHYMETRY_PROFILES_ENABLED" Default="true" Mode="" Description="Allow explicit bounded acquisition of official VHA cross-section profile points." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="VHA Profile Layer URL" Target="BATHYMETRY_PROFILES_LAYER_URL" Default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0" Mode="" Description="Official VHA cross-section profile endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0</Config>
<Config Name="VHA Watercourse Layer URL" Target="BATHYMETRY_WATERCOURSE_LAYER_URL" Default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1" Mode="" Description="Official VHA watercourse context endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1</Config>
<Config Name="VHA Profile Page Size" Target="BATHYMETRY_PROFILES_PAGE_SIZE" Default="1000" Mode="" Description="Maximum features requested per ArcGIS page." Type="Variable" Display="advanced" Required="true" Mask="false">1000</Config>
<Config Name="VHA Profile Maximum Features" Target="BATHYMETRY_PROFILES_MAX_FEATURES" Default="50000" Mode="" Description="Safety limit per municipality or bounded VHA profile request." Type="Variable" Display="advanced" Required="true" Mask="false">50000</Config> <Config Name="VHA Profile Maximum Features" Target="BATHYMETRY_PROFILES_MAX_FEATURES" Default="50000" Mode="" Description="Safety limit per municipality or bounded VHA profile request." Type="Variable" Display="advanced" Required="true" Mask="false">50000</Config>
<Config Name="VHA Profile Timeout (seconds)" Target="BATHYMETRY_PROFILES_TIMEOUT_SECONDS" Default="120" Mode="" Description="Maximum wait for one profile request." Type="Variable" Display="advanced" Required="true" Mask="false">120</Config>
<Config Name="VHA Profile Maximum Response (MiB)" Target="BATHYMETRY_PROFILES_MAX_RESPONSE_MB" Default="32" Mode="" Description="Maximum accepted profile response size." Type="Variable" Display="advanced" Required="true" Mask="false">32</Config>
<Config Name="MDK Bathymetry Readiness Probe" Target="MDK_BATHYMETRY_PROBE_ENABLED" Default="true" Mode="" Description="Enable strict-TLS read-only GetCapabilities checks. This never downloads or activates a North Sea raster." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="MDK Bathymetry Readiness Probe" Target="MDK_BATHYMETRY_PROBE_ENABLED" Default="true" Mode="" Description="Enable strict-TLS read-only GetCapabilities checks. This never downloads or activates a North Sea raster." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="MDK Bathymetry WCS URL" Target="MDK_BATHYMETRY_WCS_URL" Default="https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs" Mode="" Description="Official metadata WCS endpoint. TLS verification is mandatory and cannot be bypassed." Type="Variable" Display="advanced" Required="true" Mask="false">https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs</Config> <Config Name="MDK Bathymetry WCS URL" Target="MDK_BATHYMETRY_WCS_URL" Default="https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs" Mode="" Description="Official metadata WCS endpoint. TLS verification is mandatory and cannot be bypassed." Type="Variable" Display="advanced" Required="true" Mask="false">https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs</Config>
<Config Name="MDK Probe Timeout Seconds" Target="MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS" Default="20" Mode="" Description="Maximum wait for one read-only MDK GetCapabilities request." Type="Variable" Display="advanced" Required="true" Mask="false">20</Config> <Config Name="MDK Probe Timeout Seconds" Target="MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS" Default="20" Mode="" Description="Maximum wait for one read-only MDK GetCapabilities request." Type="Variable" Display="advanced" Required="true" Mask="false">20</Config>
<Config Name="MDK Probe Maximum Response (MiB)" Target="MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB" Default="4" Mode="" Description="Maximum accepted MDK capabilities response size." Type="Variable" Display="advanced" Required="true" Mask="false">4</Config>
<Config Name="Official Thematic Raster Acquisition" Target="THEMATIC_RASTER_ENABLED" Default="true" Mode="" Description="Allow bounded official Departement Omgeving rasters for space, population, accessibility and services." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config> <Config Name="Official Thematic Raster Acquisition" Target="THEMATIC_RASTER_ENABLED" Default="true" Mode="" Description="Allow bounded official Departement Omgeving rasters for space, population, accessibility and services." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
<Config Name="Thematic Raster WCS URL" Target="THEMATIC_RASTER_WCS_URL" Default="https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs" Mode="" Description="Official public MercatorNet WCS endpoint. Product identifiers remain server allowlisted." Type="Variable" Display="advanced" Required="true" Mask="false">https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs</Config> <Config Name="Thematic Raster WCS URL" Target="THEMATIC_RASTER_WCS_URL" Default="https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs" Mode="" Description="Official public MercatorNet WCS endpoint. Product identifiers remain server allowlisted." Type="Variable" Display="advanced" Required="true" Mask="false">https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs</Config>
<Config Name="Thematic Raster Minimum Side (m)" Target="THEMATIC_RASTER_MIN_SIDE_M" Default="100" Mode="" Description="Minimum bounded thematic raster request side length." Type="Variable" Display="advanced" Required="true" Mask="false">100</Config>
<Config Name="Thematic Raster Maximum Side (m)" Target="THEMATIC_RASTER_MAX_SIDE_M" Default="60000" Mode="" Description="Maximum side length for one bounded allowlisted thematic raster request; larger national selections must remain partitioned." Type="Variable" Display="advanced" Required="true" Mask="false">60000</Config> <Config Name="Thematic Raster Maximum Side (m)" Target="THEMATIC_RASTER_MAX_SIDE_M" Default="60000" Mode="" Description="Maximum side length for one bounded allowlisted thematic raster request; larger national selections must remain partitioned." Type="Variable" Display="advanced" Required="true" Mask="false">60000</Config>
<Config Name="Thematic Raster Maximum Cells" Target="THEMATIC_RASTER_MAX_PIXELS" Default="30000000" Mode="" Description="Maximum raster cells per allowlisted thematic acquisition or selection analysis." Type="Variable" Display="advanced" Required="true" Mask="false">30000000</Config> <Config Name="Thematic Raster Maximum Cells" Target="THEMATIC_RASTER_MAX_PIXELS" Default="30000000" Mode="" Description="Maximum raster cells per allowlisted thematic acquisition or selection analysis." Type="Variable" Display="advanced" Required="true" Mask="false">30000000</Config>
<Config Name="Thematic Raster Timeout (seconds)" Target="THEMATIC_RASTER_TIMEOUT_SECONDS" Default="300" Mode="" Description="Maximum wait for one bounded thematic raster request." Type="Variable" Display="advanced" Required="true" Mask="false">300</Config>
<Config Name="Thematic Raster Maximum Response (MiB)" Target="THEMATIC_RASTER_MAX_RESPONSE_MB" Default="160" Mode="" Description="Maximum accepted thematic raster response size." Type="Variable" Display="advanced" Required="true" Mask="false">160</Config>
<Config Name="Configured YOLO" Target="YOLO_ENABLED" Default="false" Mode="" Description="Enable only a locally mounted and explicitly configured detection model." Type="Variable" Display="advanced" Required="true" Mask="false">false</Config>
<Config Name="YOLO Models Directory" Target="YOLO_MODELS_DIR" Default="/app/models" Mode="" Description="In-container directory containing local model assets." Type="Variable" Display="advanced" Required="true" Mask="false">/app/models</Config>
<Config Name="YOLO Model Path" Target="YOLO_MODEL_PATH" Default="" Mode="" Description="Absolute in-container path to a local model asset; no download occurs." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
<Config Name="YOLO Model ID" Target="YOLO_MODEL_ID" Default="yolo-configured" Mode="" Description="Stable model identifier shown in GeoIntel." Type="Variable" Display="advanced" Required="true" Mask="false">yolo-configured</Config>
<Config Name="YOLO Display Name" Target="YOLO_MODEL_DISPLAY_NAME" Default="Configured YOLO detector" Mode="" Description="Operator-facing model name." Type="Variable" Display="advanced" Required="true" Mask="false">Configured YOLO detector</Config>
<Config Name="YOLO Model Version" Target="YOLO_MODEL_VERSION" Default="" Mode="" Description="Operator-supplied local model version." Type="Variable" Display="advanced" Required="false" Mask="false"></Config>
<Config Name="YOLO Config Directory" Target="YOLO_CONFIG_DIR" Default="/app/storage/ultralytics" Mode="" Description="Writable persistent Ultralytics settings path." Type="Variable" Display="advanced" Required="true" Mask="false">/app/storage/ultralytics</Config>
<Config Name="YOLO Device" Target="YOLO_DEVICE" Default="cpu" Mode="" Description="Inference device such as cpu or an explicitly available accelerator." Type="Variable" Display="advanced" Required="true" Mask="false">cpu</Config>
<Config Name="YOLO Image Size" Target="YOLO_IMAGE_SIZE" Default="640" Mode="" Description="Inference image size in pixels." Type="Variable" Display="advanced" Required="true" Mask="false">640</Config>
<Config Name="YOLO Maximum Tiles" Target="YOLO_MAX_TILES" Default="100" Mode="" Description="Hard tile limit per detection run." Type="Variable" Display="advanced" Required="true" Mask="false">100</Config>
<Config Name="YOLO Maximum Detections" Target="YOLO_MAX_DETECTIONS" Default="1000" Mode="" Description="Hard persisted detection limit per run." Type="Variable" Display="advanced" Required="true" Mask="false">1000</Config>
<Config Name="YOLO Duplicate IoU" Target="YOLO_DUPLICATE_IOU_THRESHOLD" Default="0.5" Mode="" Description="Cross-tile duplicate suppression threshold." Type="Variable" Display="advanced" Required="true" Mask="false">0.5</Config>
<Config Name="YOLO Batch Size" Target="YOLO_BATCH_SIZE" Default="1" Mode="" Description="Bounded inference batch size." Type="Variable" Display="advanced" Required="true" Mask="false">1</Config>
<Config Name="Local Ollama Assistant" Target="OLLAMA_ENABLED" Default="true" Mode="" Description="Enable the source-grounded GeoIntel assistant backed by Ollama on the Unraid host." Type="Variable" Display="always" Required="true" Mask="false">true</Config> <Config Name="Local Ollama Assistant" Target="OLLAMA_ENABLED" Default="true" Mode="" Description="Enable the source-grounded GeoIntel assistant backed by Ollama on the Unraid host." Type="Variable" Display="always" Required="true" Mask="false">true</Config>
<Config Name="Ollama Base URL" Target="OLLAMA_BASE_URL" Default="http://host.docker.internal:11434" Mode="" Description="Ollama API reachable from the container. The deployment maps host.docker.internal to the Unraid host gateway." Type="Variable" Display="always" Required="true" Mask="false">http://host.docker.internal:11434</Config> <Config Name="Ollama Base URL" Target="OLLAMA_BASE_URL" Default="http://host.docker.internal:11434" Mode="" Description="Ollama API reachable from the container. The deployment maps host.docker.internal to the Unraid host gateway." Type="Variable" Display="always" Required="true" Mask="false">http://host.docker.internal:11434</Config>
<Config Name="Default Ollama Model" Target="OLLAMA_DEFAULT_MODEL" Default="qwen3.5:9b" Mode="" Description="Preferred locally installed Ollama model. Users can select another installed model in GeoIntel." Type="Variable" Display="always" Required="true" Mask="false">qwen3.5:9b</Config> <Config Name="Default Ollama Model" Target="OLLAMA_DEFAULT_MODEL" Default="qwen3.5:9b" Mode="" Description="Preferred locally installed Ollama model. Users can select another installed model in GeoIntel." Type="Variable" Display="always" Required="true" Mask="false">qwen3.5:9b</Config>
+7
View File
@@ -49,6 +49,13 @@
a healthy rollback against the retained database/storage. The release tag a healthy rollback against the retained database/storage. The release tag
contract was then tightened to immutable commit-plus-profile tags so repeated contract was then tightened to immutable commit-plus-profile tags so repeated
deploys reuse rather than overwrite the same build identity. deploys reuse rather than overwrite the same build identity.
- A repeated no-op deploy reused the existing profile-tagged image and
preserved the actual previous image. The Unraid template now exposes every
operator-owned acquisition, upload, local-model and assistant setting as an
editable field, with only deployment bridge variables kept internal.
- Added an explicit release upgrade verifier that composes the checksum-backed
isolated restore drill with the deployed image's Alembic chain and destroys
only the generated verification database.
- Froze the RC geography as all Belgian land plus the separately labelled - Froze the RC geography as all Belgian land plus the separately labelled
territorial sea, EEZ and continental shelf. territorial sea, EEZ and continental shelf.
+1
View File
@@ -108,6 +108,7 @@ ${PYTHON_BIN} -m compileall backend/app
bash -n scripts/live_migration_smoke.sh bash -n scripts/live_migration_smoke.sh
bash -n scripts/deploy_tower.sh bash -n scripts/deploy_tower.sh
bash -n scripts/verify_release_fresh_install.sh bash -n scripts/verify_release_fresh_install.sh
bash -n scripts/verify_release_upgrade_smoke.sh
bash -n deploy/unraid/all-in-one-start.sh bash -n deploy/unraid/all-in-one-start.sh
bash -n deploy/unraid/run-dockerman-container.sh bash -n deploy/unraid/run-dockerman-container.sh
bash -n deploy/unraid/deploy-release.sh bash -n deploy/unraid/deploy-release.sh
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER="geointel"
BACKUP_DIR=""
OUTPUT=""
CONFIRM="false"
RESTORE_RESULT=""
TARGET_DB=""
usage() {
cat <<'EOF'
Usage: bash scripts/verify_release_upgrade_smoke.sh \
--backup-dir PATH --confirm-isolated-upgrade [options]
Restores a verified release backup into a generated temporary database, runs
the currently deployed image's Alembic upgrade against that database, verifies
PostGIS and the single expected head, then removes the temporary database.
Options:
--container NAME
--output PATH
EOF
}
while [ "$#" -gt 0 ]; do
case "$1" in
--backup-dir) BACKUP_DIR="$2"; shift 2 ;;
--container) CONTAINER="$2"; shift 2 ;;
--output) OUTPUT="$2"; shift 2 ;;
--confirm-isolated-upgrade) CONFIRM="true"; shift ;;
--help|-h) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
esac
done
if [ "$CONFIRM" != "true" ] || [ -z "$BACKUP_DIR" ]; then
echo "Explicit --confirm-isolated-upgrade and --backup-dir are required." >&2
exit 2
fi
for required in docker python3; do
command -v "$required" >/dev/null 2>&1 || {
echo "Missing required command: $required" >&2
exit 2
}
done
BACKUP_DIR="$(python3 -c 'import pathlib,sys; print(pathlib.Path(sys.argv[1]).expanduser().resolve())' "$BACKUP_DIR")"
RESTORE_RESULT="$(mktemp "${TMPDIR:-/tmp}/geointel-upgrade-restore.XXXXXX.json")"
cleanup() {
if [ -n "$TARGET_DB" ] && [[ "$TARGET_DB" =~ ^geointel_restore_verify_[0-9_]+$ ]]; then
db_user="$(docker exec "$CONTAINER" sh -c 'printf %s "${POSTGRES_USER:-${GEOINTEL_POSTGRES_USER:-geointel}}"' 2>/dev/null || true)"
if [ -n "$db_user" ]; then
docker exec "$CONTAINER" dropdb --if-exists -U "$db_user" "$TARGET_DB" >/dev/null 2>&1 || true
fi
fi
rm -f -- "$RESTORE_RESULT"
}
trap cleanup EXIT
bash "$(dirname "$0")/restore_release_backup_smoke.sh" \
--backup-dir "$BACKUP_DIR" \
--container "$CONTAINER" \
--confirm-isolated-restore \
--keep-database \
--output "$RESTORE_RESULT"
TARGET_DB="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["temporary_database"])' "$RESTORE_RESULT")"
if ! [[ "$TARGET_DB" =~ ^geointel_restore_verify_[0-9_]+$ ]]; then
echo "Unsafe temporary database returned by restore smoke: ${TARGET_DB}" >&2
exit 3
fi
docker exec -e TARGET_DB="$TARGET_DB" "$CONTAINER" sh -lc '
target_url="$(python -c '"'"'
import os
from sqlalchemy.engine import make_url
print(
make_url(os.environ["DATABASE_URL"])
.set(database=os.environ["TARGET_DB"])
.render_as_string(hide_password=False)
)
'"'"')"
export DATABASE_URL="$target_url"
python -m alembic upgrade head
'
DB_USER="$(docker exec "$CONTAINER" sh -c 'printf %s "${POSTGRES_USER:-${GEOINTEL_POSTGRES_USER:-geointel}}"' )"
ALEMBIC_HEAD="$(docker exec "$CONTAINER" psql -X -v ON_ERROR_STOP=1 -U "$DB_USER" -d "$TARGET_DB" -Atqc \
"SELECT version_num FROM alembic_version;")"
EXPECTED_HEAD="$(docker exec "$CONTAINER" sh -lc 'python -m alembic heads | awk "{print \$1}"')"
POSTGIS_VERSION="$(docker exec "$CONTAINER" psql -X -v ON_ERROR_STOP=1 -U "$DB_USER" -d "$TARGET_DB" -Atqc \
"SELECT postgis_version();")"
RELEASE_REVISION="$(docker inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' "$CONTAINER")"
if [ "$ALEMBIC_HEAD" != "$EXPECTED_HEAD" ]; then
echo "Upgraded temporary database head '${ALEMBIC_HEAD}' differs from '${EXPECTED_HEAD}'." >&2
exit 4
fi
if [ -z "$OUTPUT" ]; then
OUTPUT="${BACKUP_DIR%/}-upgrade-smoke.json"
fi
python3 - "$OUTPUT" <<PY
import json
import pathlib
payload = {
"schema_version": 1,
"verified_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"backup_dir": ${BACKUP_DIR@Q},
"release_revision": ${RELEASE_REVISION@Q},
"temporary_database": ${TARGET_DB@Q},
"production_database_untouched": True,
"temporary_database_retained": False,
"alembic_head": ${ALEMBIC_HEAD@Q},
"postgis_version": ${POSTGIS_VERSION@Q},
"upgrade_status": "passed",
}
path = pathlib.Path(__import__("sys").argv[1]).expanduser().resolve()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
PY
cleanup
trap - EXIT
echo "Isolated release upgrade passed at ${ALEMBIC_HEAD}; temporary database removed."