#!/usr/bin/env bash
# Local TLS/SNI demonstration; no ACME request and no production changes.
# Requires Linux, Bash, OpenSSL, GNU timeout and a local Docker daemon.
# Tested with Traefik 3.7.16 (linux/amd64); image pinned by digest.
set -euo pipefail
umask 077
for command in docker openssl timeout mktemp; do command -v "$command" >/dev/null; done
lab_dir=$(mktemp -d)
container=""
cleanup() {
  if [[ -n "$container" ]]; then docker rm -f "$container" >/dev/null 2>&1 || true; fi
  rm -rf "$lab_dir"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
image='traefik@sha256:c8a121e4bb1347e8fd52657fab12f2cb70b68f8a018895e2068f92c9e519b9c2'
mkdir "$lab_dir/dynamic"
for name in fallback unrelated renewal; do
  openssl req -x509 -newkey rsa:2048 -nodes -days 2 \
    -subj "/CN=$name.test" -addext "subjectAltName=DNS:$name.test" \
    -keyout "$lab_dir/$name.key" -out "$lab_dir/$name.crt" 2>/dev/null
done
write_config() {
  # Write outside the watched directory, then atomically replace its config.
  # Mount the parent directory so renaming a file does not break a file bind.
  cat > "$lab_dir/next.yml" <<'YAML'
http:
  routers:
    demo:
      rule: Host(`renewal.test`)
      entryPoints: [websecure]
      service: noop@internal
      tls: {}
tls:
  stores:
    default:
      defaultCertificate:
        certFile: /lab/fallback.crt
        keyFile: /lab/fallback.key
  certificates:
YAML
  printf '    - certFile: /lab/%s.crt\n      keyFile: /lab/%s.key\n' "$1" "$1" >> "$lab_dir/next.yml"
  mv "$lab_dir/next.yml" "$lab_dir/dynamic/tls.yml"
}
write_config unrelated
container=$(docker run -d --rm --read-only --cap-drop ALL \
  --user "$(id -u):$(id -g)" \
  --security-opt no-new-privileges:true \
  -p 127.0.0.1:0:8443 \
  --mount "type=bind,src=$lab_dir,dst=/lab,readonly" \
  "$image" --entrypoints.websecure.address=:8443 \
  --providers.file.directory=/lab/dynamic --providers.file.watch=true \
  --global.checknewversion=false --global.sendanonymoususage=false)
address=$(docker port "$container" 8443/tcp)
file_fingerprint() { openssl x509 -in "$1" -noout -sha256 -fingerprint; }
probe() {
  # Inspection only: the deliberately self-signed certificates are not trusted.
  timeout 3 openssl s_client -connect "$address" "$@" </dev/null 2>/dev/null |
    openssl x509 -noout -sha256 -fingerprint
}
expect_fingerprint() {
  local expected=$1
  shift
  for attempt in {1..40}; do
    if observed=$(probe "$@") && [[ "$observed" == "$expected" ]]; then return; fi
    sleep 0.25
  done
  echo 'FAIL: expected certificate was not observed' >&2
  docker logs "$container" >&2
  return 1
}
fallback=$(file_fingerprint "$lab_dir/fallback.crt")
unrelated=$(file_fingerprint "$lab_dir/unrelated.crt")
renewal=$(file_fingerprint "$lab_dir/renewal.crt")
[[ "$fallback" != "$unrelated" && "$fallback" != "$renewal" ]]
expect_fingerprint "$fallback" -servername renewal.test
printf 'BEFORE (renewal.test -> fallback): %s\n' "$observed"
expect_fingerprint "$unrelated" -servername unrelated.test
printf 'CONTROL (unrelated.test):          %s\n' "$observed"
started=$(docker inspect --format '{{.State.StartedAt}}' "$container")
write_config renewal
expect_fingerprint "$renewal" -servername renewal.test
printf 'AFTER (renewal.test -> renewal):   %s\n' "$observed"
expect_fingerprint "$fallback" -noservername
printf 'NO_SNI (fallback):                 %s\n' "$observed"
[[ "$started" == "$(docker inspect --format '{{.State.StartedAt}}' "$container")" ]]
printf 'PASS: matching SNI selects the certificate; dynamic config applied without restart.\n'
