Harden immutable release deployment
This commit is contained in:
@@ -12,13 +12,9 @@ ARG GEOINTEL_INSTALL_AI=false
|
||||
ARG GEOINTEL_TORCH_INDEX_URL=https://download.pytorch.org/whl/cpu
|
||||
ARG GEOINTEL_TORCH_VERSION=2.13.0
|
||||
ARG GEOINTEL_TORCHVISION_VERSION=0.28.0
|
||||
ARG GEOINTEL_BUILD_SHA=unknown
|
||||
ARG GEOINTEL_BUILD_TIME=unknown
|
||||
|
||||
ENV GEOINTEL_ENV=production \
|
||||
GEOINTEL_API_PREFIX=/api/v1 \
|
||||
GEOINTEL_BUILD_SHA="${GEOINTEL_BUILD_SHA}" \
|
||||
GEOINTEL_BUILD_TIME="${GEOINTEL_BUILD_TIME}" \
|
||||
GEOINTEL_STORAGE_ROOT=/app/storage \
|
||||
STORAGE_ROOT=/app/storage \
|
||||
GEOINTEL_ALL_IN_ONE=1 \
|
||||
@@ -154,6 +150,17 @@ RUN chmod +x /usr/local/bin/geointel-all-in-one-start \
|
||||
/app/scripts/run_background_corpus_split_matrix.sh \
|
||||
/app/scripts/run_split_background_promotion_workflow.sh
|
||||
|
||||
ARG GEOINTEL_BUILD_SHA=unknown
|
||||
ARG GEOINTEL_BUILD_TIME=unknown
|
||||
|
||||
ENV GEOINTEL_BUILD_SHA="${GEOINTEL_BUILD_SHA}" \
|
||||
GEOINTEL_BUILD_TIME="${GEOINTEL_BUILD_TIME}"
|
||||
|
||||
LABEL org.opencontainers.image.title="GeoIntel" \
|
||||
org.opencontainers.image.description="GeoIntel workbench for Belgium and the Belgian North Sea" \
|
||||
org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}" \
|
||||
org.opencontainers.image.created="${GEOINTEL_BUILD_TIME}"
|
||||
|
||||
VOLUME ["/var/lib/postgresql/data", "/app/storage"]
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
+60
-7
@@ -72,11 +72,19 @@ cd /mnt/user/appdata/geointel
|
||||
cp deploy/unraid/geointel.env.example .env
|
||||
nano .env
|
||||
docker compose -f docker-compose.unraid.yml config
|
||||
docker build --build-arg GEOINTEL_INSTALL_AI=${GEOINTEL_INSTALL_AI:-false} -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
||||
bash deploy/unraid/run-dockerman-container.sh
|
||||
bash deploy/unraid/deploy-release.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.
|
||||
Set `GEOINTEL_POSTGRES_PASSWORD` to a unique value before that first start.
|
||||
Production startup fails before replacing the active container when the
|
||||
password is empty or one of the documented defaults.
|
||||
|
||||
The repository deploy scripts run the same flow automatically. They validate
|
||||
the Compose reference, preserve the current image as
|
||||
`geointel-all-in-one:previous`, build an immutable commit-SHA tag plus `latest`,
|
||||
install the DockerMan metadata and start the SHA-tagged image. A failed start,
|
||||
live migration smoke or browser/API smoke automatically attempts the previous
|
||||
image without changing the configured PostGIS or storage paths.
|
||||
|
||||
`scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` source the remote
|
||||
`.env` before building the image. That means `GEOINTEL_INSTALL_AI=true` in
|
||||
@@ -99,8 +107,9 @@ libraries needed for Ultralytics imports; it still never downloads model weights
|
||||
The documented CPU runtime installs pinned `torch==2.13.0` and
|
||||
`torchvision==0.28.0` from `https://download.pytorch.org/whl/cpu`, avoiding the
|
||||
unused CUDA runtime wheels included by the general Linux package index. The
|
||||
Dockerfile copies dependency metadata before backend source, so normal code-only
|
||||
redeploys can reuse the expensive dependency layer.
|
||||
Dockerfile copies dependency metadata before backend source and applies
|
||||
commit/build metadata only after the heavy file layers. Normal code-only
|
||||
redeploys therefore reuse the apt, GIS and optional PyTorch dependency layers.
|
||||
`YOLO_CONFIG_DIR` defaults to `/app/storage/ultralytics`, a writable persistent
|
||||
path, so Ultralytics settings do not fall back to root user config directories.
|
||||
|
||||
@@ -173,8 +182,7 @@ GEOINTEL_CORS_ORIGINS=http://localhost:1203,http://127.0.0.1:1203,http://192.168
|
||||
Apply:
|
||||
|
||||
```bash
|
||||
docker build --build-arg GEOINTEL_INSTALL_AI=${GEOINTEL_INSTALL_AI:-false} -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
||||
bash deploy/unraid/run-dockerman-container.sh
|
||||
bash deploy/unraid/deploy-release.sh
|
||||
```
|
||||
|
||||
## Persistent paths
|
||||
@@ -226,10 +234,55 @@ curl http://127.0.0.1:1202/api/v1/assistant/models
|
||||
cd /mnt/user/appdata/geointel
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
bash deploy/unraid/deploy-release.sh
|
||||
```
|
||||
|
||||
The equivalent low-level build remains available for debugging:
|
||||
|
||||
```bash
|
||||
docker build --build-arg GEOINTEL_INSTALL_AI=${GEOINTEL_INSTALL_AI:-false} -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
||||
bash deploy/unraid/run-dockerman-container.sh
|
||||
```
|
||||
|
||||
## Release identity, fresh install and rollback
|
||||
|
||||
Inspect the running immutable revision and retained images:
|
||||
|
||||
```bash
|
||||
docker inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' geointel
|
||||
docker image ls geointel-all-in-one
|
||||
```
|
||||
|
||||
Run a fresh install in isolated temporary PostGIS, storage and model paths. The
|
||||
script binds only a random loopback port and removes its own container and
|
||||
temporary directory:
|
||||
|
||||
```bash
|
||||
bash scripts/verify_release_fresh_install.sh geointel-all-in-one:latest
|
||||
```
|
||||
|
||||
Return to the image that was active immediately before the latest deployment:
|
||||
|
||||
```bash
|
||||
bash deploy/unraid/rollback-dockerman-container.sh
|
||||
```
|
||||
|
||||
For an older retained commit, select its immutable tag explicitly:
|
||||
|
||||
```bash
|
||||
GEOINTEL_ROLLBACK_IMAGE=geointel-all-in-one:<commit-sha> \
|
||||
bash deploy/unraid/rollback-dockerman-container.sh
|
||||
```
|
||||
|
||||
Rollback reuses the configured PostGIS and storage mounts and never runs an
|
||||
Alembic downgrade. If a future release has a backward-incompatible migration,
|
||||
restore its verified pre-release backup instead of forcing an older app
|
||||
against a newer schema.
|
||||
|
||||
The configured upload limit is shared by FastAPI and the generated nginx
|
||||
runtime configuration. Values outside `1..2048` MiB are rejected before the
|
||||
active application is replaced.
|
||||
|
||||
## Safe cleanup
|
||||
|
||||
Safe cache cleanup if Docker build cache fills the Unraid Docker image:
|
||||
|
||||
@@ -13,6 +13,27 @@ export YOLO_MODELS_DIR="${YOLO_MODELS_DIR:-/app/models}"
|
||||
export YOLO_CONFIG_DIR="${YOLO_CONFIG_DIR:-$STORAGE_ROOT/ultralytics}"
|
||||
export GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP="${GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP:-true}"
|
||||
|
||||
case "$MAX_UPLOAD_MB" in
|
||||
''|*[!0-9]*)
|
||||
echo "GEOINTEL_MAX_UPLOAD_MB must be a whole number between 1 and 2048." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
if [ "$MAX_UPLOAD_MB" -lt 1 ] || [ "$MAX_UPLOAD_MB" -gt 2048 ]; then
|
||||
echo "GEOINTEL_MAX_UPLOAD_MB must be between 1 and 2048." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case "${GEOINTEL_ENV:-production}:${POSTGRES_PASSWORD}" in
|
||||
production:|production:geointel|production:postgres|production:password|production:changeme|production:change-me-before-shared-use)
|
||||
echo "Refusing to start production with an empty or known-default PostGIS password." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
sed -i "s/__GEOINTEL_MAX_UPLOAD_MB__/${MAX_UPLOAD_MB}/g" /etc/nginx/conf.d/default.conf
|
||||
nginx -t
|
||||
|
||||
mkdir -p "$PGDATA" "$STORAGE_ROOT" "$YOLO_CONFIG_DIR" /run/nginx /var/log/nginx
|
||||
chown -R postgres:postgres "$PGDATA"
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
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}"
|
||||
GEOINTEL_BUILD_SHA="$(git rev-parse HEAD)"
|
||||
GEOINTEL_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
GEOINTEL_IMAGE_REPOSITORY="${GEOINTEL_IMAGE_REPOSITORY:-geointel-all-in-one}"
|
||||
GEOINTEL_RELEASE_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:${GEOINTEL_BUILD_SHA}"
|
||||
GEOINTEL_PREVIOUS_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:previous"
|
||||
FRONTEND_URL="${FRONTEND_URL:-http://127.0.0.1:${GEOINTEL_FRONTEND_PORT:-1202}}"
|
||||
|
||||
wait_for_geointel_health() {
|
||||
local status=""
|
||||
for attempt in $(seq 1 90); do
|
||||
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' geointel 2>/dev/null || true)"
|
||||
if [ "$status" = "healthy" ]; then
|
||||
echo "GeoIntel container is healthy after attempt ${attempt}."
|
||||
return 0
|
||||
fi
|
||||
if [ "$status" = "unhealthy" ] || [ "$status" = "exited" ] || [ "$status" = "dead" ]; then
|
||||
echo "GeoIntel container entered terminal state: ${status}" >&2
|
||||
docker logs --tail 120 geointel >&2 || true
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "GeoIntel container did not become healthy (last state: ${status:-missing})." >&2
|
||||
docker logs --tail 120 geointel >&2 || true
|
||||
return 1
|
||||
}
|
||||
|
||||
start_image() {
|
||||
local image="$1"
|
||||
GEOINTEL_IMAGE="$image" bash deploy/unraid/run-dockerman-container.sh
|
||||
wait_for_geointel_health
|
||||
}
|
||||
|
||||
rollback_previous() {
|
||||
if ! docker image inspect "$GEOINTEL_PREVIOUS_IMAGE" >/dev/null 2>&1; then
|
||||
echo "Automatic rollback unavailable: ${GEOINTEL_PREVIOUS_IMAGE} does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Rolling back to ${GEOINTEL_PREVIOUS_IMAGE}..."
|
||||
start_image "$GEOINTEL_PREVIOUS_IMAGE"
|
||||
}
|
||||
|
||||
docker compose -f docker-compose.unraid.yml config >/dev/null
|
||||
|
||||
current_image_id="$(docker inspect --format '{{.Image}}' geointel 2>/dev/null || true)"
|
||||
if [ -n "$current_image_id" ] && docker image inspect "$current_image_id" >/dev/null 2>&1; then
|
||||
current_revision="$(
|
||||
docker image inspect \
|
||||
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}' \
|
||||
"$current_image_id" 2>/dev/null || true
|
||||
)"
|
||||
if [ -n "$current_revision" ] && [ "$current_revision" != "<no value>" ] && [ "$current_revision" != "unknown" ]; then
|
||||
docker tag "$current_image_id" "${GEOINTEL_IMAGE_REPOSITORY}:${current_revision}"
|
||||
fi
|
||||
docker tag "$current_image_id" "$GEOINTEL_PREVIOUS_IMAGE"
|
||||
fi
|
||||
|
||||
docker build \
|
||||
--build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" \
|
||||
--build-arg GEOINTEL_BUILD_SHA="$GEOINTEL_BUILD_SHA" \
|
||||
--build-arg GEOINTEL_BUILD_TIME="$GEOINTEL_BUILD_TIME" \
|
||||
-f deploy/unraid/Dockerfile.all-in-one \
|
||||
-t "$GEOINTEL_RELEASE_IMAGE" \
|
||||
-t "${GEOINTEL_IMAGE_REPOSITORY}:latest" \
|
||||
.
|
||||
|
||||
if ! start_image "$GEOINTEL_RELEASE_IMAGE"; then
|
||||
rollback_previous || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -x scripts/live_migration_smoke.sh ]; then
|
||||
if ! LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh; then
|
||||
rollback_previous || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -x scripts/verify_browser_runtime.sh ]; then
|
||||
if ! bash scripts/verify_browser_runtime.sh "$FRONTEND_URL"; then
|
||||
rollback_previous || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Deployed immutable image ${GEOINTEL_RELEASE_IMAGE}."
|
||||
docker image inspect \
|
||||
--format 'revision={{index .Config.Labels "org.opencontainers.image.revision"}} created={{index .Config.Labels "org.opencontainers.image.created"}}' \
|
||||
"$GEOINTEL_RELEASE_IMAGE"
|
||||
@@ -7,8 +7,8 @@
|
||||
<Shell>bash</Shell>
|
||||
<Privileged>false</Privileged>
|
||||
<Support>http://192.168.10.150:1202</Support>
|
||||
<Project>GeoIntel Kempen</Project>
|
||||
<Overview>GeoIntel all-in-one runs the complete GeoIntel Kempen V1 stack in one Docker container: embedded PostGIS, FastAPI backend, nginx frontend and MapLibre UI. Use docker-compose.unraid.yml or this template so the web port, storage path and database path can be edited from Unraid.</Overview>
|
||||
<Project>GeoIntel Belgium and North Sea</Project>
|
||||
<Overview>GeoIntel all-in-one runs the Belgium and Belgian North Sea workbench in one Docker container: embedded PostGIS, FastAPI backend, nginx frontend and MapLibre UI. Use docker-compose.unraid.yml or this template so the web port, storage path and database path can be edited from Unraid.</Overview>
|
||||
<Category>Productivity: Tools: GIS:</Category>
|
||||
<WebUI>http://[IP]:[PORT:80]/</WebUI>
|
||||
<TemplateURL>deploy/unraid/geointel-unraid-template.xml</TemplateURL>
|
||||
@@ -29,7 +29,7 @@
|
||||
<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 Password" Target="GEOINTEL_POSTGRES_PASSWORD" Default="change-me-before-shared-use" Mode="" Description="Embedded PostGIS database password. Change before shared use." Type="Variable" Display="advanced" Required="true" Mask="true">change-me-before-shared-use</Config>
|
||||
<Config Name="CORS Origins" Target="GEOINTEL_CORS_ORIGINS" Default="http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202" Mode="" Description="Comma-separated browser origins allowed to call the backend directly." Type="Variable" Display="advanced" Required="false" Mask="false">http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202</Config>
|
||||
<Config Name="Max Upload MB" Target="GEOINTEL_MAX_UPLOAD_MB" Default="500" Mode="" Description="Maximum upload size in MiB enforced by the backend settings." 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="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 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>
|
||||
@@ -63,7 +63,7 @@
|
||||
<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="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 Maximum Side (m)" Target="THEMATIC_RASTER_MAX_SIDE_M" Default="60000" Mode="" Description="Maximum side length for one allowlisted thematic raster scope; supports the complete Kempen work area while WCS transfers remain tiled." 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="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>
|
||||
|
||||
@@ -14,6 +14,8 @@ GEOINTEL_MODELS_PATH=/mnt/user/appdata/geointel/models
|
||||
GEOINTEL_POSTGIS_DATA_PATH=/mnt/user/appdata/geointel/postgres-data
|
||||
|
||||
# Internal embedded PostGIS settings. The database is not published to the LAN.
|
||||
# Replace the placeholder with a unique secret before the first start. Production
|
||||
# startup rejects empty and known-default passwords.
|
||||
GEOINTEL_POSTGRES_DB=geointel
|
||||
GEOINTEL_POSTGRES_USER=geointel
|
||||
GEOINTEL_POSTGRES_PASSWORD=change-me-before-shared-use
|
||||
@@ -21,7 +23,7 @@ GEOINTEL_POSTGRES_PASSWORD=change-me-before-shared-use
|
||||
# Browser origins allowed when directly calling the backend API.
|
||||
GEOINTEL_CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202
|
||||
|
||||
# Upload guard in MiB.
|
||||
# Upload guard in MiB. The same 1-2048 limit is applied by nginx and FastAPI.
|
||||
GEOINTEL_MAX_UPLOAD_MB=500
|
||||
|
||||
# Explicit, bounded acquisition from the official Digitaal Vlaanderen WMS.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
client_max_body_size 250m;
|
||||
client_max_body_size __GEOINTEL_MAX_UPLOAD_MB__m;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
GEOINTEL_ROLLBACK_IMAGE="${GEOINTEL_ROLLBACK_IMAGE:-geointel-all-in-one:previous}"
|
||||
|
||||
if ! docker image inspect "$GEOINTEL_ROLLBACK_IMAGE" >/dev/null 2>&1; then
|
||||
echo "Rollback image does not exist: ${GEOINTEL_ROLLBACK_IMAGE}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Starting rollback image ${GEOINTEL_ROLLBACK_IMAGE} without changing persistent volumes..."
|
||||
GEOINTEL_IMAGE="$GEOINTEL_ROLLBACK_IMAGE" bash deploy/unraid/run-dockerman-container.sh
|
||||
|
||||
for attempt in $(seq 1 90); do
|
||||
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' geointel 2>/dev/null || true)"
|
||||
if [ "$status" = "healthy" ]; then
|
||||
LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh
|
||||
echo "Rollback completed with healthy image ${GEOINTEL_ROLLBACK_IMAGE}."
|
||||
exit 0
|
||||
fi
|
||||
if [ "$status" = "unhealthy" ] || [ "$status" = "exited" ] || [ "$status" = "dead" ]; then
|
||||
docker logs --tail 120 geointel >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Rollback container did not become healthy." >&2
|
||||
docker logs --tail 120 geointel >&2 || true
|
||||
exit 1
|
||||
@@ -12,12 +12,13 @@ if [ -f .env ]; then
|
||||
fi
|
||||
|
||||
GEOINTEL_FRONTEND_PORT="${GEOINTEL_FRONTEND_PORT:-1202}"
|
||||
GEOINTEL_IMAGE="${GEOINTEL_IMAGE:-geointel-all-in-one:latest}"
|
||||
GEOINTEL_STORAGE_PATH="${GEOINTEL_STORAGE_PATH:-/mnt/user/appdata/geointel/storage}"
|
||||
GEOINTEL_MODELS_PATH="${GEOINTEL_MODELS_PATH:-/mnt/user/appdata/geointel/models}"
|
||||
GEOINTEL_POSTGIS_DATA_PATH="${GEOINTEL_POSTGIS_DATA_PATH:-/mnt/user/appdata/geointel/postgres-data}"
|
||||
GEOINTEL_POSTGRES_DB="${GEOINTEL_POSTGRES_DB:-geointel}"
|
||||
GEOINTEL_POSTGRES_USER="${GEOINTEL_POSTGRES_USER:-geointel}"
|
||||
GEOINTEL_POSTGRES_PASSWORD="${GEOINTEL_POSTGRES_PASSWORD:-geointel}"
|
||||
GEOINTEL_POSTGRES_PASSWORD="${GEOINTEL_POSTGRES_PASSWORD:-}"
|
||||
GEOINTEL_CORS_ORIGINS="${GEOINTEL_CORS_ORIGINS:-http://localhost:${GEOINTEL_FRONTEND_PORT},http://127.0.0.1:${GEOINTEL_FRONTEND_PORT},http://192.168.10.150:${GEOINTEL_FRONTEND_PORT}}"
|
||||
GEOINTEL_MAX_UPLOAD_MB="${GEOINTEL_MAX_UPLOAD_MB:-500}"
|
||||
ORTHOPHOTO_ENABLED="${ORTHOPHOTO_ENABLED:-true}"
|
||||
@@ -101,6 +102,39 @@ OLLAMA_TIMEOUT_SECONDS="${OLLAMA_TIMEOUT_SECONDS:-120}"
|
||||
OLLAMA_MAX_OUTPUT_TOKENS="${OLLAMA_MAX_OUTPUT_TOKENS:-1200}"
|
||||
OLLAMA_CONTEXT_TOKENS="${OLLAMA_CONTEXT_TOKENS:-16384}"
|
||||
|
||||
validate_runtime_config() {
|
||||
case "$GEOINTEL_FRONTEND_PORT" in
|
||||
''|*[!0-9]*)
|
||||
echo "GEOINTEL_FRONTEND_PORT must be a whole number." >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
if [ "$GEOINTEL_FRONTEND_PORT" -lt 1 ] || [ "$GEOINTEL_FRONTEND_PORT" -gt 65535 ]; then
|
||||
echo "GEOINTEL_FRONTEND_PORT must be between 1 and 65535." >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
case "$GEOINTEL_MAX_UPLOAD_MB" in
|
||||
''|*[!0-9]*)
|
||||
echo "GEOINTEL_MAX_UPLOAD_MB must be a whole number." >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
if [ "$GEOINTEL_MAX_UPLOAD_MB" -lt 1 ] || [ "$GEOINTEL_MAX_UPLOAD_MB" -gt 2048 ]; then
|
||||
echo "GEOINTEL_MAX_UPLOAD_MB must be between 1 and 2048." >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
case "$GEOINTEL_POSTGRES_PASSWORD" in
|
||||
''|geointel|postgres|password|changeme|change-me-before-shared-use)
|
||||
echo "Refusing deployment with an empty or known-default PostGIS password." >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
|
||||
docker image inspect "$GEOINTEL_IMAGE" >/dev/null
|
||||
}
|
||||
|
||||
install_dockerman_metadata() {
|
||||
if [ -d /boot/config/plugins/dockerMan ]; then
|
||||
mkdir -p /boot/config/plugins/dockerMan/templates-user /boot/config/plugins/dockerMan/images
|
||||
@@ -125,6 +159,7 @@ migrate_compose_volume_if_needed() {
|
||||
cp -a "${compose_volume_path}/." "$GEOINTEL_POSTGIS_DATA_PATH/"
|
||||
}
|
||||
|
||||
validate_runtime_config
|
||||
install_dockerman_metadata
|
||||
|
||||
docker compose down --remove-orphans || true
|
||||
@@ -233,6 +268,6 @@ docker run -d \
|
||||
-v "${GEOINTEL_POSTGIS_DATA_PATH}:/var/lib/postgresql/data" \
|
||||
-v "${GEOINTEL_STORAGE_PATH}:/app/storage" \
|
||||
-v "${GEOINTEL_MODELS_PATH}:/app/models" \
|
||||
geointel-all-in-one:latest
|
||||
"$GEOINTEL_IMAGE"
|
||||
|
||||
docker ps --filter name=geointel
|
||||
|
||||
Reference in New Issue
Block a user