UI fixing
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-28 04:44:16 +02:00
parent c76a746cd7
commit 19cf2b5211
8 changed files with 503 additions and 10 deletions
+174
View File
@@ -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 <value> Operator login name. Required.
# --env-file <path> Target env file. Default: .env
# --guest-access <bool> 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"