Harden Tower AI deploy env handling
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-11 10:56:45 +02:00
parent b1a4074cc8
commit 5831bc7861
7 changed files with 93 additions and 18 deletions
+7
View File
@@ -7,6 +7,13 @@
# Changelog # Changelog
## Sprint 164 Tower AI deploy env hardening (2026-07-11)
- Fixed Tower deploy automation so `scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` source the remote `.env` before building the all-in-one image.
- Remote `.env` now controls `GEOINTEL_INSTALL_AI=true` by default, with explicit local deploy overrides still supported.
- Added regression coverage so future deploy changes cannot silently build a GIS-only image while runtime YOLO settings are enabled.
- No API contract, database migration, provider fetching, fake detections, model download or product feature changed.
## Sprint 163 Guarded YOLO candidate activation (2026-07-11) ## Sprint 163 Guarded YOLO candidate activation (2026-07-11)
- Added `scripts/activate_promoted_yolo_candidate.py` to validate a promotion report and exact candidate key before emitting YOLO `.env` activation updates. - Added `scripts/activate_promoted_yolo_candidate.py` to validate a promotion report and exact candidate key before emitting YOLO `.env` activation updates.
+4 -2
View File
@@ -254,8 +254,10 @@ def test_unraid_deploy_passes_ai_build_arg_and_yolo_runtime_env() -> None:
deploy_sh = (ROOT / "scripts" / "deploy_tower.sh").read_text(encoding="utf-8") deploy_sh = (ROOT / "scripts" / "deploy_tower.sh").read_text(encoding="utf-8")
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8") run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
assert "--build-arg GEOINTEL_INSTALL_AI=${GEOINTEL_INSTALL_AI:-false}" in deploy_sh assert 'DEPLOY_GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-}"' in deploy_sh
assert "--build-arg GEOINTEL_INSTALL_AI='$InstallAi'" in deploy_ps1 assert 'docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI"' in deploy_sh
assert 'docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI"' in deploy_ps1
assert "DEPLOY_GEOINTEL_INSTALL_AI" in deploy_ps1
assert "[string]$InstallAi" in deploy_ps1 assert "[string]$InstallAi" in deploy_ps1
assert 'YOLO_ENABLED="${YOLO_ENABLED:-false}"' in run_script assert 'YOLO_ENABLED="${YOLO_ENABLED:-false}"' in run_script
@@ -118,6 +118,18 @@ def test_tower_deploy_uses_single_container_unraid_compose() -> None:
assert "LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh" in script assert "LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh" in script
def test_tower_deploy_build_uses_remote_env_ai_setting_by_default() -> None:
powershell = (ROOT / "scripts" / "deploy_tower.ps1").read_text(encoding="utf-8")
bash = (ROOT / "scripts" / "deploy_tower.sh").read_text(encoding="utf-8")
for script in (powershell, bash):
assert "if [ -f .env ]; then" in script
assert ". ./.env" in script
assert "DEPLOY_GEOINTEL_INSTALL_AI" in script
assert 'GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-false}"' in script
assert 'docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI"' in script
def test_live_migration_smoke_supports_dockerman_native_container() -> None: def test_live_migration_smoke_supports_dockerman_native_container() -> None:
script = (ROOT / "scripts" / "live_migration_smoke.sh").read_text(encoding="utf-8") script = (ROOT / "scripts" / "live_migration_smoke.sh").read_text(encoding="utf-8")
+7
View File
@@ -78,6 +78,13 @@ bash deploy/unraid/run-dockerman-container.sh
The repository deploy scripts run the same flow automatically. They validate the Compose reference, build the image with the `GEOINTEL_INSTALL_AI` build arg, install the DockerMan template/icon, remove any old Compose-owned `geointel` container, preserve/migrate the PostGIS data path and start the final container with DockerMan labels. The repository deploy scripts run the same flow automatically. They validate the Compose reference, build the image with the `GEOINTEL_INSTALL_AI` build arg, install the DockerMan template/icon, remove any old Compose-owned `geointel` container, preserve/migrate the PostGIS data path and start the final container with DockerMan labels.
`scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` source the remote
`.env` before building the image. That means `GEOINTEL_INSTALL_AI=true` in
`/mnt/user/appdata/geointel/.env` is enough for the automatic deploy to build
the AI-enabled image. Set `GEOINTEL_INSTALL_AI` in the local shell or pass
`-InstallAi true/false` to the PowerShell wrapper only when you intentionally
want to override the remote `.env` for that deploy.
Database credentials are runtime configuration, not image metadata. The Database credentials are runtime configuration, not image metadata. The
all-in-one image does not bake `GEOINTEL_POSTGRES_PASSWORD` into the Dockerfile; all-in-one image does not bake `GEOINTEL_POSTGRES_PASSWORD` into the Dockerfile;
set it through `.env`, the Unraid template or `docker run -e`. set it through `.env`, the Unraid template or `docker run -e`.
+15
View File
@@ -1,3 +1,18 @@
## Sprint 164 Tower AI deploy env hardening (2026-07-11)
Changed:
- Hardened `scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` so the remote Tower `.env` is sourced before building the all-in-one image.
- `GEOINTEL_INSTALL_AI=true` in `/mnt/user/appdata/geointel/.env` now drives the automatic image build by default; explicit local overrides remain possible for one-off deploys.
- Documented the deploy behavior in `deploy/unraid/README.md`.
Why:
- A manual `docker compose up -d --build` against the multi-container compose file failed on Tower because Docker had exhausted default bridge address pools. The healthy runtime is the Unraid all-in-one container, which should be redeployed through the Dockerman-native scripts instead.
- The previous deploy script path could build a GIS-only image while the remote runtime `.env` enabled YOLO, leaving the configured detector in `dependency_unavailable`.
Tested:
- Red step: `python -m pytest backend/tests/test_sprint31_unraid_template.py::test_tower_deploy_build_uses_remote_env_ai_setting_by_default` failed because the deploy scripts did not source remote `.env` before `docker build`.
- `python -m pytest backend/tests/test_sprint31_unraid_template.py::test_tower_deploy_build_uses_remote_env_ai_setting_by_default backend/tests/test_sprint31_unraid_template.py::test_tower_deploy_uses_single_container_unraid_compose backend/tests/test_docker_runtime_config.py::test_unraid_deploy_passes_ai_build_arg_and_yolo_runtime_env` (`3 passed`).
## Sprint 150 YOLO label visible-ratio gate (2026-07-09) ## Sprint 150 YOLO label visible-ratio gate (2026-07-09)
Changed: Changed:
+33 -14
View File
@@ -5,7 +5,7 @@ param(
[string]$RemoteRepo = "gitea-widefrog:NuklearRabbit/geointel.git", [string]$RemoteRepo = "gitea-widefrog:NuklearRabbit/geointel.git",
[string]$SshKey = "$HOME/.ssh/widefrog_unraid_deploy", [string]$SshKey = "$HOME/.ssh/widefrog_unraid_deploy",
[string]$FrontendUrl = "http://192.168.10.150:1202", [string]$FrontendUrl = "http://192.168.10.150:1202",
[string]$InstallAi = $(if ($env:GEOINTEL_INSTALL_AI) { $env:GEOINTEL_INSTALL_AI } else { "false" }), [string]$InstallAi = $(if ($env:GEOINTEL_INSTALL_AI) { $env:GEOINTEL_INSTALL_AI } else { "" }),
[switch]$Bootstrap [switch]$Bootstrap
) )
@@ -13,29 +13,41 @@ $ErrorActionPreference = "Stop"
$bootstrapValue = if ($Bootstrap) { "1" } else { "0" } $bootstrapValue = if ($Bootstrap) { "1" } else { "0" }
$remoteScript = @" $remoteScript = @'
set -euo pipefail set -euo pipefail
git config --global --add safe.directory '$RemotePath' DEPLOY_GEOINTEL_INSTALL_AI='__INSTALL_AI__'
cd '$RemotePath' git config --global --add safe.directory '__REMOTE_PATH__'
cd '__REMOTE_PATH__'
if [ ! -d .git ]; then if [ ! -d .git ]; then
if [ '$bootstrapValue' != '1' ]; then if [ '__BOOTSTRAP__' != '1' ]; then
echo 'No git checkout found in $RemotePath.' echo 'No git checkout found in __REMOTE_PATH__.'
echo 'Re-run with -Bootstrap for the first deployment bootstrap.' echo 'Re-run with -Bootstrap for the first deployment bootstrap.'
exit 2 exit 2
fi fi
git init git init
git remote add origin '$RemoteRepo' git remote add origin '__REMOTE_REPO__'
fi fi
git remote get-url origin >/dev/null 2>&1 || git remote add origin '$RemoteRepo' git remote get-url origin >/dev/null 2>&1 || git remote add origin '__REMOTE_REPO__'
git fetch origin '$RemoteBranch' git fetch origin '__REMOTE_BRANCH__'
git reset --hard 'origin/$RemoteBranch' git reset --hard 'origin/__REMOTE_BRANCH__'
git branch -M '$RemoteBranch' git branch -M '__REMOTE_BRANCH__'
chmod +x scripts/*.sh backend/docker_start.sh deploy/unraid/*.sh || true chmod +x scripts/*.sh backend/docker_start.sh deploy/unraid/*.sh || true
if [ -f .env ]; then
set -a
. ./.env
set +a
fi
if [ -n "${DEPLOY_GEOINTEL_INSTALL_AI:-}" ]; then
GEOINTEL_INSTALL_AI="$DEPLOY_GEOINTEL_INSTALL_AI"
fi
GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-false}"
docker compose -f docker-compose.unraid.yml config >/dev/null docker compose -f docker-compose.unraid.yml config >/dev/null
docker build --build-arg GEOINTEL_INSTALL_AI='$InstallAi' -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest . docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
bash deploy/unraid/run-dockerman-container.sh bash deploy/unraid/run-dockerman-container.sh
if [ -x scripts/live_migration_smoke.sh ]; then if [ -x scripts/live_migration_smoke.sh ]; then
@@ -43,8 +55,15 @@ if [ -x scripts/live_migration_smoke.sh ]; then
fi fi
if [ -x scripts/verify_browser_runtime.sh ]; then if [ -x scripts/verify_browser_runtime.sh ]; then
bash scripts/verify_browser_runtime.sh '$FrontendUrl' bash scripts/verify_browser_runtime.sh '__FRONTEND_URL__'
fi fi
"@ '@
$remoteScript = $remoteScript.Replace("__INSTALL_AI__", $InstallAi)
$remoteScript = $remoteScript.Replace("__REMOTE_PATH__", $RemotePath)
$remoteScript = $remoteScript.Replace("__BOOTSTRAP__", $bootstrapValue)
$remoteScript = $remoteScript.Replace("__REMOTE_REPO__", $RemoteRepo)
$remoteScript = $remoteScript.Replace("__REMOTE_BRANCH__", $RemoteBranch)
$remoteScript = $remoteScript.Replace("__FRONTEND_URL__", $FrontendUrl)
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i $SshKey $RemoteHost $remoteScript ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i $SshKey $RemoteHost $remoteScript
+15 -2
View File
@@ -8,6 +8,7 @@ REMOTE_REPO="${REMOTE_REPO:-gitea-widefrog:NuklearRabbit/geointel.git}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/widefrog_unraid_deploy}" SSH_KEY="${SSH_KEY:-$HOME/.ssh/widefrog_unraid_deploy}"
FRONTEND_URL="${FRONTEND_URL:-http://192.168.10.150:1202}" FRONTEND_URL="${FRONTEND_URL:-http://192.168.10.150:1202}"
BOOTSTRAP="${DEPLOY_BOOTSTRAP:-0}" BOOTSTRAP="${DEPLOY_BOOTSTRAP:-0}"
DEPLOY_GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-}"
ssh_opts=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new) ssh_opts=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new)
if [[ -f "$SSH_KEY" ]]; then if [[ -f "$SSH_KEY" ]]; then
@@ -15,7 +16,7 @@ if [[ -f "$SSH_KEY" ]]; then
fi fi
ssh "${ssh_opts[@]}" "$REMOTE_HOST" \ ssh "${ssh_opts[@]}" "$REMOTE_HOST" \
"REMOTE_PATH='$REMOTE_PATH' REMOTE_BRANCH='$REMOTE_BRANCH' REMOTE_REPO='$REMOTE_REPO' FRONTEND_URL='$FRONTEND_URL' DEPLOY_BOOTSTRAP='$BOOTSTRAP' bash -s" <<'REMOTE_SCRIPT' "REMOTE_PATH='$REMOTE_PATH' REMOTE_BRANCH='$REMOTE_BRANCH' REMOTE_REPO='$REMOTE_REPO' FRONTEND_URL='$FRONTEND_URL' DEPLOY_BOOTSTRAP='$BOOTSTRAP' DEPLOY_GEOINTEL_INSTALL_AI='$DEPLOY_GEOINTEL_INSTALL_AI' bash -s" <<'REMOTE_SCRIPT'
set -euo pipefail set -euo pipefail
cd "$REMOTE_PATH" cd "$REMOTE_PATH"
@@ -34,8 +35,20 @@ git fetch origin "$REMOTE_BRANCH"
git checkout -B "$REMOTE_BRANCH" "origin/$REMOTE_BRANCH" git checkout -B "$REMOTE_BRANCH" "origin/$REMOTE_BRANCH"
chmod +x scripts/*.sh backend/docker_start.sh deploy/unraid/*.sh || true chmod +x scripts/*.sh backend/docker_start.sh deploy/unraid/*.sh || true
if [ -f .env ]; then
set -a
# shellcheck disable=SC1091
. ./.env
set +a
fi
if [ -n "${DEPLOY_GEOINTEL_INSTALL_AI:-}" ]; then
GEOINTEL_INSTALL_AI="$DEPLOY_GEOINTEL_INSTALL_AI"
fi
GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-false}"
docker compose -f docker-compose.unraid.yml config >/dev/null docker compose -f docker-compose.unraid.yml config >/dev/null
docker build --build-arg GEOINTEL_INSTALL_AI=${GEOINTEL_INSTALL_AI:-false} -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest . docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
bash deploy/unraid/run-dockerman-container.sh bash deploy/unraid/run-dockerman-container.sh
if [[ -x scripts/live_migration_smoke.sh ]]; then if [[ -x scripts/live_migration_smoke.sh ]]; then