#!/usr/bin/env sh
# ---------------------------------------------------------------------------
# Generates the MQTT fixture's password file + TLS material into ./secrets/.
#
# NOTHING this script writes is committed: ./secrets/ is excluded by the sibling
# .gitignore. The SCRIPT is the artifact in git, the OUTPUT never is. Re-run it on any
# machine that needs the fixture (or on the Docker host); the material is throwaway by
# construction — a self-issued CA, a 2048-bit server key, and whatever password the
# operator supplies in the environment.
#
# Usage:
#   MQTT_FIXTURE_PASSWORD='<pick-one>' ./gen-fixture-material.sh
#
# Environment:
#   MQTT_FIXTURE_PASSWORD   (required) broker password. No default — a committed or
#                           defaulted password is exactly what the plan's
#                           "secrets from env, never committed" rule forbids.
#   MQTT_FIXTURE_USERNAME   broker username                       (default: otopcua)
#   MQTT_FIXTURE_SAN_IPS    comma-separated IP SANs for the server certificate
#                           (default: 10.100.0.35,127.0.0.1)
#   MQTT_FIXTURE_SAN_DNS    comma-separated DNS SANs
#                           (default: localhost,mosquitto)
#   MQTT_FIXTURE_CERT_CN    server certificate CN         (default: first IP SAN)
#   MQTT_FIXTURE_CERT_DAYS  certificate lifetime in days             (default: 825)
#   MQTT_FIXTURE_SECRETS_DIR  output directory        (default: <script dir>/secrets)
#   MOSQUITTO_IMAGE         image used to run mosquitto_passwd
#                                                 (default: eclipse-mosquitto:2.0.22)
#
# The SANs matter: MqttConnection pins the TLS target host with
# `.WithTargetHost(options.Host)`, so hostname verification is enforced and the
# certificate MUST carry a SAN matching whatever host the tests dial — the shared
# Docker host's IP literal (10.100.0.35) in the normal case. A cert without the right
# SAN fails the handshake even with a correct CA pin, and the failure looks like a pin
# bug.
#
# Requires: openssl, docker (only for hashing the password — mosquitto 2.0 will not
# read a plaintext password file, and mosquitto_passwd is the only supported way to
# produce its PBKDF2-SHA512 format).
# ---------------------------------------------------------------------------
set -eu

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
OUT=${MQTT_FIXTURE_SECRETS_DIR:-"$SCRIPT_DIR/secrets"}
USERNAME=${MQTT_FIXTURE_USERNAME:-otopcua}
SAN_IPS=${MQTT_FIXTURE_SAN_IPS:-10.100.0.35,127.0.0.1}
SAN_DNS=${MQTT_FIXTURE_SAN_DNS:-localhost,mosquitto}
DAYS=${MQTT_FIXTURE_CERT_DAYS:-825}
MOSQUITTO_IMAGE=${MOSQUITTO_IMAGE:-eclipse-mosquitto:2.0.22}

if [ -z "${MQTT_FIXTURE_PASSWORD:-}" ]; then
    echo "error: MQTT_FIXTURE_PASSWORD is not set." >&2
    echo "       Supply the broker password in the environment; this script never" >&2
    echo "       defaults or commits one. Example:" >&2
    echo "         MQTT_FIXTURE_PASSWORD='\$(openssl rand -base64 18)' $0" >&2
    exit 2
fi

command -v openssl >/dev/null 2>&1 || { echo "error: openssl not found on PATH." >&2; exit 2; }
command -v docker  >/dev/null 2>&1 || {
    echo "error: docker not found on PATH — needed to run mosquitto_passwd." >&2
    echo "       Run this script on the Docker host (10.100.0.35) instead, or install docker." >&2
    exit 2
}

CN=${MQTT_FIXTURE_CERT_CN:-$(printf '%s' "$SAN_IPS" | cut -d, -f1)}

mkdir -p "$OUT"
chmod 0755 "$OUT"

echo "==> generating fixture material into $OUT"
echo "    username : $USERNAME"
echo "    cert CN  : $CN"
echo "    IP SANs  : $SAN_IPS"
echo "    DNS SANs : $SAN_DNS"

# --- CA -------------------------------------------------------------------
openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "$OUT/ca.key" -out "$OUT/ca.crt" -days "$DAYS" \
    -subj "/CN=OtOpcUa MQTT fixture CA/O=OtOpcUa test fixture" \
    -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
    -addext "keyUsage=critical,keyCertSign,cRLSign" \
    >/dev/null 2>&1

# --- server leaf ----------------------------------------------------------
# The SAN list is assembled into a temp extfile rather than a process substitution,
# which is a bashism this /bin/sh script cannot use.
EXT=$(mktemp)
trap 'rm -f "$EXT" "$OUT/server.csr" "$OUT/ca.srl"' EXIT

SAN=""
OLD_IFS=$IFS
IFS=,
for ip in $SAN_IPS;  do [ -n "$ip"  ] && SAN="$SAN,IP:$ip";   done
for dn in $SAN_DNS;  do [ -n "$dn"  ] && SAN="$SAN,DNS:$dn";  done
IFS=$OLD_IFS
SAN=${SAN#,}

cat >"$EXT" <<EOF
subjectAltName = $SAN
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
EOF

openssl req -newkey rsa:2048 -nodes \
    -keyout "$OUT/server.key" -out "$OUT/server.csr" \
    -subj "/CN=$CN/O=OtOpcUa test fixture" \
    >/dev/null 2>&1

openssl x509 -req -in "$OUT/server.csr" \
    -CA "$OUT/ca.crt" -CAkey "$OUT/ca.key" -CAcreateserial \
    -out "$OUT/server.crt" -days "$DAYS" -sha256 -extfile "$EXT" \
    >/dev/null 2>&1

# --- password file --------------------------------------------------------
# `--user` so the file lands owned by the invoking user rather than by root: the host
# user must be able to chmod it below, and (unlike a root-owned 0600 file) the broker
# container's `mosquitto` user must be able to READ it.
rm -f "$OUT/passwd"
docker run --rm --user "$(id -u):$(id -g)" -v "$OUT":/out "$MOSQUITTO_IMAGE" \
    mosquitto_passwd -b -c /out/passwd "$USERNAME" "$MQTT_FIXTURE_PASSWORD" >/dev/null

# mosquitto runs as uid 1883 inside the container and reads these through a read-only
# bind mount, so they must be world-readable. This is a throwaway fixture key by
# construction — see the banner.
chmod 0644 "$OUT/ca.crt" "$OUT/server.crt" "$OUT/server.key" "$OUT/passwd"
chmod 0600 "$OUT/ca.key"

echo "==> done. Files written:"
ls -l "$OUT"
echo
echo "Next:"
echo "  1. Export the same creds for the test suite:"
echo "       export MQTT_FIXTURE_USERNAME='$USERNAME'"
echo "       export MQTT_FIXTURE_PASSWORD='<the same password>'"
echo "       export MQTT_FIXTURE_CA_CERT='$OUT/ca.crt'"
echo "  2. Push this directory to the Docker host and bring the stack up."
