Harden immutable release deployment
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:31:28 +02:00
parent 611ad0cd57
commit 5699006a5e
20 changed files with 477 additions and 151 deletions
+11 -4
View File
@@ -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
View File
@@ -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:
+21
View File
@@ -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"
+107
View File
@@ -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"
+4 -4
View File
@@ -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>
+3 -1
View File
@@ -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 -1
View File
@@ -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
+37 -2
View File
@@ -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