From 19cf2b5211d993069bb611011bce77bbfa23462d Mon Sep 17 00:00:00 2001 From: Jens Date: Tue, 28 Jul 2026 04:44:16 +0200 Subject: [PATCH] UI fixing --- .dockerignore | 34 +++- .env.example | 2 +- deploy/unraid/MANUAL_DEPLOY.md | 210 +++++++++++++++++++++++ deploy/unraid/deploy-release.sh | 57 +++++- deploy/unraid/geointel.env.example | 10 +- deploy/unraid/run-dockerman-container.sh | 9 +- frontend/src/styles/app.css | 17 +- scripts/configure_operator_login.sh | 174 +++++++++++++++++++ 8 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 deploy/unraid/MANUAL_DEPLOY.md create mode 100644 scripts/configure_operator_login.sh diff --git a/.dockerignore b/.dockerignore index 239ad9ea..086577c8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,19 +1,49 @@ +# Build context exclusions for deploy/unraid/Dockerfile.all-in-one. +# Everything listed here is NOT sent to the Docker daemon. +# Keep this aggressive: the all-in-one image only needs +# backend/, frontend/, fixtures/, scripts/, deploy/ and VERSION. + .git +.gitea +.github .venv venv __pycache__ *.pyc .pytest_cache +.mypy_cache +.ruff_cache -frontend/node_modules +# Node dependencies at every level (root test harness + frontend). +node_modules +**/node_modules frontend/dist frontend/*.tsbuildinfo +**/*.tsbuildinfo + backend/.pytest_cache backend/**/*.pyc backend/**/__pycache__ +# Accidental self-copy of the repository into itself. +# Without this the build context is duplicated and the build stalls. +/geointel + +# Local work products, never needed inside the image. +.codex-input +test-results +playwright-report +artifacts +RELEASE_NOTES +adr +docs +checklists +*.log + +# Runtime data and secrets: mounted at runtime, never baked in. storage postgres-data +backups datasets/raw datasets/processed datasets/cache @@ -21,3 +51,5 @@ exports models .env +.env.* +!.env.example diff --git a/.env.example b/.env.example index c37c8566..4193deb7 100644 --- a/.env.example +++ b/.env.example @@ -167,7 +167,7 @@ GEOINTEL_POSTGIS_DATA_PATH=./postgres-data GEOINTEL_POSTGRES_DB=geointel GEOINTEL_POSTGRES_USER=geointel GEOINTEL_POSTGRES_PASSWORD=geointel -GEOINTEL_CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202 +GEOINTEL_CORS_ORIGINS=https://geointel.itworx.tech,http://geointel.itworx.tech,http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202 GEOINTEL_MAX_UPLOAD_MB=500 GEOINTEL_AOI_WORKER_ENABLED=false GEOINTEL_AOI_WORKER_POLL_SECONDS=2 diff --git a/deploy/unraid/MANUAL_DEPLOY.md b/deploy/unraid/MANUAL_DEPLOY.md new file mode 100644 index 00000000..e44eff6f --- /dev/null +++ b/deploy/unraid/MANUAL_DEPLOY.md @@ -0,0 +1,210 @@ +# Handmatige deploy naar Unraid (1 container, poort 1202) + +Voor als de bestaande deploy stuk is en je de code met de hand kopieert +(zip/scp/SMB) in plaats van via `git clone`. Eindresultaat: **één** container +met de naam `geointel`, bereikbaar op `http://192.168.10.150:1202`. + +--- + +## 1. Kopiëren + +Kopieer de **volledige** map `C:\Projects\geointel` naar +`/mnt/user/appdata/geointel` op de server. Verder niets uitzoeken. + +`.dockerignore` regelt de rest: `node_modules/`, `.git/`, de dubbele +`geointel/`-map, `docs/`, `artifacts/` en testoutput gaan niet mee de +build-context in, ook al staan ze in de map. + +Eén waarschuwing bij het overschrijven: laat `storage/`, `postgres-data/`, +`backups/` en `models/` op de server **staan**. Dat is je bestaande data, en +die staat niet in je Windows-map. Kopieer dus overschrijvend, niet met een +"mirror"/"purge"-optie die bestanden aan de doelzijde weggooit. + +```powershell +# Vanaf Windows, veilig: kopieert en overschrijft, verwijdert niets aan de doelkant. +robocopy C:\Projects\geointel \\192.168.10.150\appdata\geointel /E +``` + +--- + +## 2. Rechten en regeleindes goedzetten + +Na een kopie via Windows zijn de execute-bits weg. Op de Unraid-shell: + +```bash +cd /mnt/user/appdata/geointel +find . -name '*.sh' -exec sed -i 's/\r$//' {} + +sed -i 's/\r$//' deploy/unraid/gosu-setpriv +chmod +x deploy/unraid/*.sh scripts/*.sh deploy/unraid/gosu-setpriv +``` + +--- + +## 3. `.env` aanmaken + +```bash +cd /mnt/user/appdata/geointel +cp deploy/unraid/geointel.env.example .env +nano .env +``` + +Verplicht aan te passen: + +- `GEOINTEL_POSTGRES_PASSWORD` — de deploy **weigert** te starten met een lege + of standaardwaarde (`geointel`, `postgres`, `changeme`, + `change-me-before-shared-use`). Gebruik hier dezelfde waarde als je vorige + installatie, anders kan de bestaande PostGIS-data niet worden geopend. + +Controleer dat deze op de bestaande data wijzen: + +```env +GEOINTEL_FRONTEND_PORT=1202 +GEOINTEL_STORAGE_PATH=/mnt/user/appdata/geointel/storage +GEOINTEL_POSTGIS_DATA_PATH=/mnt/user/appdata/geointel/postgres-data +GEOINTEL_MODELS_PATH=/mnt/user/appdata/geointel/models +GEOINTEL_BACKUPS_PATH=/mnt/user/appdata/geointel/backups +GEOINTEL_CORS_ORIGINS=https://geointel.itworx.tech,http://geointel.itworx.tech,http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202 +``` + +Staat Nginx Proxy Manager voor poort 1202 op `geointel.itworx.tech`, dan moet +die hostname in `GEOINTEL_CORS_ORIGINS` staan — anders blokkeert de browser de +API-calls vanaf het publieke adres. De backend doet geen host-validatie, dus +verder is er niets nodig aan applicatiekant. + +Voor GPU-inferentie (optioneel, kan ook later): + +```env +GEOINTEL_INSTALL_AI=true +YOLO_ENABLED=true +YOLO_DEVICE=cuda:0 +YOLO_REQUIRE_CUDA=true +YOLO_MODEL_PATH=/app/models/.pt +``` + +Laat `GEOINTEL_INSTALL_AI=false` staan als je eerst gewoon wilt dat de app +draait — dat scheelt een paar GB aan PyTorch-lagen in de build. + +--- + +## 4. Login instellen + +De app slaat geen plaintext wachtwoord op: `.env` bevat alleen een +pbkdf2_sha256-hash. Dit script maakt die hash plus een sessiegeheim en zet ze +in `.env`: + +```bash +cd /mnt/user/appdata/geointel +bash scripts/configure_operator_login.sh --username jens@itworx.tech --guest-access true +``` + +`--guest-access true` is nodig om de demo-ingang te behouden; zonder die vlag +zet het script `GEOINTEL_GUEST_ACCESS_ENABLED=false`. + +Het script vraagt het wachtwoord tweemaal (je typt het niet zichtbaar en het +komt niet in je shell-history), en zet: + +```env +GEOINTEL_AUTH_ENABLED=true +GEOINTEL_AUTH_USERNAME=jens@itworx.tech +GEOINTEL_AUTH_PASSWORD_HASH=pbkdf2_sha256$600000$... +GEOINTEL_AUTH_SESSION_SECRET=<48 willekeurige tekens> +GEOINTEL_GUEST_ACCESS_ENABLED=false +``` + +Gastentoegang wordt bewust uitgezet — met auth aan zou anders de publieke +demo-ingang "Als gast verkennen" op de loginpagina blijven staan. Wil je die +wel: `--guest-access true`. + +Wachtwoord later wijzigen: draai hetzelfde commando opnieuw en deploy. Er +wordt dan ook een nieuw sessiegeheim gemaakt, dus openstaande browsersessies +worden ongeldig. + +> Het wachtwoord dat je me in de chat gaf staat nu in die chatgeschiedenis. +> Draai het script met een nieuw wachtwoord zodra de deploy staat. + +--- + +## 5. Deployen + +```bash +cd /mnt/user/appdata/geointel +docker compose -f docker-compose.unraid.yml config >/dev/null # syntaxcheck +bash deploy/unraid/deploy-release.sh +``` + +Het script: + +1. ruimt een eventueel achtergebleven Compose-stack op (ook de oude + 3-container dev-stack die óók poort 1202 pakt); +2. bewaart de huidige image als `geointel-all-in-one:previous`; +3. bouwt `deploy/unraid/Dockerfile.all-in-one`; +4. start één container `geointel` met `-p 1202:80` en `--gpus all`; +5. rolt automatisch terug naar `:previous` als de healthcheck of smoke faalt. + +De eerste build duurt lang (PostGIS + GDAL + npm build). Volgende deploys +hergebruiken de Docker-layercache. + +--- + +## 6. Controleren + +```bash +docker ps --filter name=geointel # exact 1 regel verwacht +curl -fsS http://127.0.0.1:1202/health/live +curl -fsS http://127.0.0.1:1202/health/ready +curl -fsS http://127.0.0.1:1202/api/v1/system/capabilities +``` + +Browser: `http://192.168.10.150:1202` + +Bij problemen: + +```bash +docker logs --tail 200 geointel +``` + +--- + +## 7. Belangrijk: dubbele projectmap lokaal + +In `C:\Projects\geointel` staat een tweede, volledige kopie van het project +onder `C:\Projects\geointel\geointel\`. Die bevat dezelfde bestanden en +dezelfde datum, maar staat buiten git. Zolang die er staat: + +- wordt de Docker build-context onnodig verdubbeld; +- weet je bij het bewerken van bijvoorbeeld `docker-compose.unraid.yml` niet + welke versie je te pakken hebt. + +`.dockerignore` sluit hem nu uit, maar ruim hem op zodra je zeker weet dat er +niets unieks in staat. Vergelijk eerst: + +```powershell +robocopy C:\Projects\geointel\geointel C:\Projects\geointel /L /E /NJH /NJS /NDL /XF *.pyc +``` + +Regels die als `New File` verschijnen bestaan alleen in de kopie. + +--- + +## 8. Terugrollen + +```bash +cd /mnt/user/appdata/geointel +bash deploy/unraid/rollback-dockerman-container.sh +``` + +Rollback hergebruikt dezelfde PostGIS- en storage-paden en draait nooit een +Alembic-downgrade. + +--- + +## Wat er is aangepast om dit mogelijk te maken + +| Bestand | Aanpassing | +|---|---| +| `.dockerignore` | Sluit root-`node_modules`, de dubbele `geointel/`-map, `.git`, `docs/`, `artifacts/` en testoutput uit de build-context | +| `deploy/unraid/deploy-release.sh` | `git rev-parse HEAD` crashte op een kopie zonder `.git`. Valt nu terug op `GEOINTEL_BUILD_SHA`, een `RELEASE_SHA`-bestand of een content-hash van de broncode | +| `deploy/unraid/deploy-release.sh` | Smoke-scripts worden op bestaan getest in plaats van op de execute-bit, die bij een Windows-kopie verloren gaat | +| `deploy/unraid/run-dockerman-container.sh` | Ruimt expliciet zowel `docker-compose.yml` (3 containers) als `docker-compose.unraid.yml` op, zodat poort 1202 gegarandeerd vrij is | +| `deploy/unraid/geointel.env.example` | `COMPOSE_FILE=docker-compose.unraid.yml`, zodat een kaal `docker compose up -d` nooit per ongeluk de 3-container dev-stack start | +| `scripts/configure_operator_login.sh` | Nieuw. Zet de operator-login in `.env` met een correcte pbkdf2_sha256-hash (600.000 iteraties, identiek aan `AuthService.hash_password`) en een vers sessiegeheim | diff --git a/deploy/unraid/deploy-release.sh b/deploy/unraid/deploy-release.sh index fa80f617..568b1f2e 100644 --- a/deploy/unraid/deploy-release.sh +++ b/deploy/unraid/deploy-release.sh @@ -32,7 +32,58 @@ if ! [[ "$GEOINTEL_APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] echo "Invalid semantic version in VERSION: ${GEOINTEL_APP_VERSION}" >&2 exit 2 fi -GEOINTEL_BUILD_SHA="$(git rev-parse HEAD)" +resolve_build_sha() { + # 1. Explicit override wins. + if [ -n "${GEOINTEL_BUILD_SHA:-}" ]; then + printf '%s' "$GEOINTEL_BUILD_SHA" + return 0 + fi + + # 2. Real git checkout. + if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then + git rev-parse HEAD + return 0 + fi + + # 3. Manually copied tree with a RELEASE_SHA marker file. + if [ -f RELEASE_SHA ]; then + tr -d '[:space:]' < RELEASE_SHA + return 0 + fi + + # 4. Manually copied tree without git: derive a stable content hash so + # an unchanged redeploy reuses the existing immutable image tag. + if command -v sha1sum >/dev/null 2>&1; then + local tree_hash + tree_hash="$( + find backend frontend deploy scripts fixtures VERSION \ + -type f \ + ! -path '*/node_modules/*' \ + ! -path '*/__pycache__/*' \ + ! -path '*/.pytest_cache/*' \ + ! -name '*.pyc' \ + -print0 2>/dev/null \ + | sort -z \ + | xargs -0 sha1sum 2>/dev/null \ + | sha1sum \ + | cut -c1-40 + )" || tree_hash="" + if [ -n "$tree_hash" ]; then + printf '%s' "$tree_hash" + return 0 + fi + fi + + # 5. Last resort: unique per deploy. + printf 'manual%s' "$(date -u +%Y%m%d%H%M%S)" +} + +GEOINTEL_BUILD_SHA="$(resolve_build_sha)" +if [ -z "$GEOINTEL_BUILD_SHA" ]; then + echo "Could not determine a build revision for this deployment." >&2 + exit 2 +fi +echo "Build revision: ${GEOINTEL_BUILD_SHA}" GEOINTEL_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" GEOINTEL_IMAGE_REPOSITORY="${GEOINTEL_IMAGE_REPOSITORY:-geointel-all-in-one}" if [ "$GEOINTEL_INSTALL_AI" = "true" ]; then @@ -138,14 +189,14 @@ if ! start_image "$GEOINTEL_RELEASE_IMAGE"; then exit 1 fi -if [ -x scripts/live_migration_smoke.sh ]; then +if [ -f 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 [ -f scripts/verify_browser_runtime.sh ]; then if ! bash scripts/verify_browser_runtime.sh "$FRONTEND_URL"; then rollback_previous || true exit 1 diff --git a/deploy/unraid/geointel.env.example b/deploy/unraid/geointel.env.example index 9b3c3eb2..ab0f1bf0 100644 --- a/deploy/unraid/geointel.env.example +++ b/deploy/unraid/geointel.env.example @@ -1,6 +1,11 @@ # GeoIntel Unraid all-in-one environment template. # Copy this file to /mnt/user/appdata/geointel/.env and edit values there. +# GeoIntel runs as ONE container. This makes a bare `docker compose ...` in this +# directory use the single-container file instead of the multi-container +# development stack in docker-compose.yml. +COMPOSE_FILE=docker-compose.unraid.yml + # Browser URL: http://: GEOINTEL_FRONTEND_PORT=1202 @@ -24,7 +29,8 @@ GEOINTEL_POSTGRES_USER=geointel 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 +# Neem hier ook de publieke hostname op zodra Nginx Proxy Manager ervoor staat. +GEOINTEL_CORS_ORIGINS=https://geointel.itworx.tech,http://geointel.itworx.tech,http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202 # Upload guard in MiB. The same 1-2048 limit is applied by nginx and FastAPI. GEOINTEL_MAX_UPLOAD_MB=500 @@ -43,6 +49,8 @@ GEOINTEL_AUTH_SESSION_TTL_SECONDS=43200 # Guest access is enabled by default whenever operator authentication is active. # It opens the seeded GeoIntel demo in a temporary, API-enforced restricted # session. Set this to false on installations containing private project data. +# LET OP: scripts/configure_operator_login.sh zet dit op false tenzij je +# expliciet --guest-access true meegeeft. GEOINTEL_GUEST_ACCESS_ENABLED=true GEOINTEL_GUEST_DISPLAY_NAME=Gast GEOINTEL_GUEST_SESSION_TTL_SECONDS=7200 diff --git a/deploy/unraid/run-dockerman-container.sh b/deploy/unraid/run-dockerman-container.sh index 0196c4d3..034bb985 100644 --- a/deploy/unraid/run-dockerman-container.sh +++ b/deploy/unraid/run-dockerman-container.sh @@ -269,7 +269,14 @@ migrate_compose_volume_if_needed() { validate_runtime_config install_dockerman_metadata -docker compose down --remove-orphans || true +# GeoIntel runs as ONE container started by docker run below. +# Tear down any leftover Compose stack first, including the multi-container +# development stack (db + backend + frontend) that also claims port 1202. +for compose_file in docker-compose.yml docker-compose.unraid.yml; do + if [ -f "$compose_file" ]; then + docker compose -f "$compose_file" down --remove-orphans || true + fi +done if docker ps -a --format '{{.Names}}' | grep -qx geointel; then docker rm -f geointel >/dev/null 2>&1 || true diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 46684750..cf80d4a7 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -5623,15 +5623,26 @@ section { padding: 0.78rem; } +/* Het icoon is optioneel. Een vast tweekolommenraster dwong een kop zonder + icoon in de 1.65rem-icoonkolom, waardoor de tekst per letter afbrak. + Flex laat de tekst altijd de resterende breedte nemen, met of zonder icoon. */ .geo-panel-heading { - display: grid; - grid-template-columns: 1.65rem minmax(0, 1fr); + display: flex; gap: 0.55rem; - align-items: start; + align-items: flex-start; +} + +.geo-panel-heading > * { + min-width: 0; +} + +.geo-panel-heading > div { + flex: 1 1 auto; } .geo-panel-heading > span { display: grid; + flex: 0 0 auto; place-items: center; width: 1.65rem; height: 1.65rem; diff --git a/scripts/configure_operator_login.sh b/scripts/configure_operator_login.sh new file mode 100644 index 00000000..d9b694dd --- /dev/null +++ b/scripts/configure_operator_login.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# Configure the GeoIntel operator login in .env. +# +# Writes GEOINTEL_AUTH_ENABLED, GEOINTEL_AUTH_USERNAME, a pbkdf2_sha256 +# password hash and a fresh random session secret. The plaintext password is +# never written to disk, never printed and never passed as a command argument. +# +# Usage: +# bash scripts/configure_operator_login.sh --username jens@itworx.tech +# -> prompts for the password (nothing lands in shell history) +# +# GEOINTEL_OPERATOR_PASSWORD='...' bash scripts/configure_operator_login.sh \ +# --username jens@itworx.tech --non-interactive +# +# Options: +# --username Operator login name. Required. +# --env-file Target env file. Default: .env +# --guest-access Keep the public guest demo. Default: false +# --non-interactive Read the password from GEOINTEL_OPERATOR_PASSWORD. + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +USERNAME="" +ENV_FILE=".env" +GUEST_ACCESS="false" +NON_INTERACTIVE="false" + +while [ "$#" -gt 0 ]; do + case "$1" in + --username) USERNAME="${2:-}"; shift 2 ;; + --env-file) ENV_FILE="${2:-}"; shift 2 ;; + --guest-access) GUEST_ACCESS="${2:-}"; shift 2 ;; + --non-interactive) NON_INTERACTIVE="true"; shift ;; + -h|--help) sed -n '2,22p' "$0"; exit 0 ;; + *) echo "Unknown argument: $1" >&2; exit 2 ;; + esac +done + +if [ -z "$USERNAME" ]; then + echo "--username is required." >&2 + exit 2 +fi + +case "$GUEST_ACCESS" in + true|false) ;; + *) echo "--guest-access must be true or false." >&2; exit 2 ;; +esac + +if [ ! -f "$ENV_FILE" ]; then + if [ -f deploy/unraid/geointel.env.example ]; then + echo "Creating ${ENV_FILE} from deploy/unraid/geointel.env.example." + cp deploy/unraid/geointel.env.example "$ENV_FILE" + else + echo "${ENV_FILE} does not exist and no template was found." >&2 + exit 2 + fi +fi + +if [ "$NON_INTERACTIVE" = "true" ]; then + if [ -z "${GEOINTEL_OPERATOR_PASSWORD:-}" ]; then + echo "GEOINTEL_OPERATOR_PASSWORD must be set for --non-interactive." >&2 + exit 2 + fi +else + printf 'Password for %s: ' "$USERNAME" >&2 + read -r -s GEOINTEL_OPERATOR_PASSWORD + printf '\n' >&2 + printf 'Repeat password: ' >&2 + read -r -s password_repeat + printf '\n' >&2 + if [ "$GEOINTEL_OPERATOR_PASSWORD" != "$password_repeat" ]; then + echo "Passwords do not match." >&2 + exit 2 + fi + unset password_repeat +fi + +if [ "${#GEOINTEL_OPERATOR_PASSWORD}" -lt 12 ]; then + echo "Refusing to configure an operator password shorter than 12 characters." >&2 + exit 2 +fi +export GEOINTEL_OPERATOR_PASSWORD + +HASH_SCRIPT="$(mktemp)" +trap 'rm -f "$HASH_SCRIPT"' EXIT +cat >"$HASH_SCRIPT" <<'PY' +import base64 +import hashlib +import os +import secrets + +# Must stay identical to AuthService.hash_password in +# backend/app/services/auth_service.py. +HASH_NAME = "pbkdf2_sha256" +ITERATIONS = 600_000 + + +def b64(value: bytes) -> str: + return base64.urlsafe_b64encode(value).decode("ascii").rstrip("=") + + +password = os.environ["GEOINTEL_OPERATOR_PASSWORD"] +salt = secrets.token_bytes(18) +digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, ITERATIONS) + +print("$".join((HASH_NAME, str(ITERATIONS), b64(salt), b64(digest)))) +print(secrets.token_urlsafe(48)) +PY + +generated="" +if command -v python3 >/dev/null 2>&1; then + generated="$(python3 "$HASH_SCRIPT")" +elif command -v docker >/dev/null 2>&1; then + echo "No local python3 found; deriving the hash in a throwaway python container." + generated="$( + docker run --rm -i \ + -e GEOINTEL_OPERATOR_PASSWORD \ + python:3.11-slim python - <"$HASH_SCRIPT" + )" +else + echo "Need either python3 or docker to derive the password hash." >&2 + exit 2 +fi + +unset GEOINTEL_OPERATOR_PASSWORD + +PASSWORD_HASH="$(printf '%s\n' "$generated" | sed -n '1p')" +SESSION_SECRET="$(printf '%s\n' "$generated" | sed -n '2p')" + +case "$PASSWORD_HASH" in + pbkdf2_sha256\$*) ;; + *) echo "Password hash generation failed." >&2; exit 1 ;; +esac +if [ "${#SESSION_SECRET}" -lt 32 ]; then + echo "Session secret generation failed." >&2 + exit 1 +fi + +backup="${ENV_FILE}.bak.$(date -u +%Y%m%d%H%M%S)" +cp "$ENV_FILE" "$backup" + +tmp_env="$(mktemp)" +grep -v -E '^(GEOINTEL_AUTH_ENABLED|GEOINTEL_AUTH_USERNAME|GEOINTEL_AUTH_PASSWORD_HASH|GEOINTEL_AUTH_SESSION_SECRET|GEOINTEL_GUEST_ACCESS_ENABLED)=' \ + "$ENV_FILE" >"$tmp_env" || true + +{ + printf '\n' + printf '# Operator login, written by scripts/configure_operator_login.sh on %s.\n' \ + "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + # Single quotes zijn verplicht: de pbkdf2-hash bevat '$'-tekens. Ongequote + # zou Docker Compose ze als variabelen interpoleren en `source .env` onder + # `set -u` afbreken met "unbound variable". + printf 'GEOINTEL_AUTH_ENABLED=true\n' + printf "GEOINTEL_AUTH_USERNAME='%s'\n" "$USERNAME" + printf "GEOINTEL_AUTH_PASSWORD_HASH='%s'\n" "$PASSWORD_HASH" + printf "GEOINTEL_AUTH_SESSION_SECRET='%s'\n" "$SESSION_SECRET" + printf 'GEOINTEL_GUEST_ACCESS_ENABLED=%s\n' "$GUEST_ACCESS" +} >>"$tmp_env" + +cat "$tmp_env" >"$ENV_FILE" +rm -f "$tmp_env" +chmod 600 "$ENV_FILE" + +echo "Operator login configured in ${ENV_FILE}." +echo " username: ${USERNAME}" +echo " password hash: pbkdf2_sha256, 600000 iterations" +echo " session secret: regenerated (existing browser sessions are invalidated)" +echo " guest access: ${GUEST_ACCESS}" +echo " backup of the previous env file: ${backup}" +echo +echo "Apply with: bash deploy/unraid/deploy-release.sh"