#!/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 ITWorx # -> prompts for the password (nothing lands in shell history) # # GEOINTEL_OPERATOR_PASSWORD='...' bash scripts/configure_operator_login.sh \ # --username ITWorx --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"