#!/bin/sh
set -eu

# Certbot runs deploy hooks as root. Keep command lookup deterministic; the
# override exists only so the checked-in regression test can supply mocks.
PATH=${PGPIPE_TLS_HOOK_PATH:-/usr/sbin:/usr/bin:/sbin:/bin}
export PATH
LC_ALL=C
export LC_ALL
# Do not let inherited process environment silently broaden the configured
# trust boundary. Command-line CA options are supplied explicitly below.
unset SSL_CERT_DIR SSL_CERT_FILE CURL_CA_BUNDLE

# Set TLS_HOST and EXPECTED_LINEAGE in the root-owned deployment copy, or pass
# their PGPIPE_TLS_* equivalents from a root-owned Certbot configuration.
# Intentionally having no example hostname/lineage default makes an unconfigured
# production copy fail closed instead of silently rotating the wrong identity.
TLS_HOST=${PGPIPE_TLS_HOST:-}
TLS_PORT=${PGPIPE_TLS_PORT:-8080}
TLS_CONNECT_IP=${PGPIPE_TLS_CONNECT_IP:-127.0.0.1}
EXPECTED_LINEAGE=${PGPIPE_TLS_EXPECTED_LINEAGE:-}
TLS_DIR=${PGPIPE_TLS_DIR:-/etc/pgpipe-tls}
STAGING_ROOT=${PGPIPE_TLS_STAGING_ROOT:-/run/pgpipe-tls-renewal}
SERVICE_NAME=${PGPIPE_TLS_SERVICE_NAME:-pgpipe}
MAX_ATTEMPTS=${PGPIPE_TLS_MAX_ATTEMPTS:-30}
RETRY_DELAY=${PGPIPE_TLS_RETRY_DELAY:-1}
TRUST_FILE=${PGPIPE_TLS_CA_FILE:-}

CERT_FILE="$TLS_DIR/fullchain.pem"
KEY_FILE="$TLS_DIR/privkey.pem"
LOCK_DIR="$STAGING_ROOT/.${SERVICE_NAME}.lock"

die() {
  echo "pgpipe TLS renewal: $*" >&2
  exit 1
}

case "$TLS_HOST" in
  ''|.*|-*|*..*|*[!A-Za-z0-9.-]*)
    die "PGPIPE_TLS_HOST must be set to a DNS hostname"
    ;;
esac
case "$TLS_PORT" in
  ''|*[!0-9]*) die "PGPIPE_TLS_PORT must be an integer from 1 to 65535" ;;
esac
test "$TLS_PORT" -ge 1 && test "$TLS_PORT" -le 65535 ||
  die "PGPIPE_TLS_PORT must be an integer from 1 to 65535"
test -n "$TLS_CONNECT_IP" ||
  die "PGPIPE_TLS_CONNECT_IP must be set to pgpipe's local listener address"
case "$TLS_CONNECT_IP" in
  \[*\])
    TLS_CONNECT_AUTHORITY="$TLS_CONNECT_IP:$TLS_PORT"
    TLS_RESOLVE_ADDRESS=$TLS_CONNECT_IP
    ;;
  *:*)
    TLS_CONNECT_AUTHORITY="[$TLS_CONNECT_IP]:$TLS_PORT"
    TLS_RESOLVE_ADDRESS="[$TLS_CONNECT_IP]"
    ;;
  *)
    TLS_CONNECT_AUTHORITY="$TLS_CONNECT_IP:$TLS_PORT"
    TLS_RESOLVE_ADDRESS=$TLS_CONNECT_IP
    ;;
esac
case "$SERVICE_NAME" in
  ''|-*|*[!A-Za-z0-9_.@-]*)
    die "PGPIPE_TLS_SERVICE_NAME contains unsupported characters"
    ;;
esac
case "$MAX_ATTEMPTS" in
  ''|*[!0-9]*) die "PGPIPE_TLS_MAX_ATTEMPTS must be a positive integer" ;;
esac
test "$MAX_ATTEMPTS" -gt 0 ||
  die "PGPIPE_TLS_MAX_ATTEMPTS must be greater than zero"
case "$RETRY_DELAY" in
  ''|*[!0-9]*) die "PGPIPE_TLS_RETRY_DELAY must be a non-negative integer" ;;
esac

# A system-wide Certbot hook may run for several certificate names.
: "${RENEWED_LINEAGE:?Certbot must set RENEWED_LINEAGE for a deploy hook}"
test -n "$EXPECTED_LINEAGE" ||
  die "PGPIPE_TLS_EXPECTED_LINEAGE must be configured"
test "$RENEWED_LINEAGE" = "$EXPECTED_LINEAGE" || exit 0

for required_command in openssl curl cmp install systemctl mktemp awk grep stat \
  id rm rmdir mkdir sleep timeout; do
  command -v "$required_command" >/dev/null ||
    die "required command not found: $required_command"
done

openssl_verify_help=$(openssl verify -help 2>&1 || true)
echo "$openssl_verify_help" | grep -q -- '-no-CAfile' &&
  echo "$openssl_verify_help" | grep -q -- '-no-CApath' ||
  die "openssl verify must support -no-CAfile and -no-CApath"
OPENSSL_TRUST_ISOLATION_FLAGS="-no-CAfile -no-CApath"
if echo "$openssl_verify_help" | grep -q -- '-no-CAstore'; then
  OPENSSL_TRUST_ISOLATION_FLAGS="$OPENSSL_TRUST_ISOLATION_FLAGS -no-CAstore"
fi

pgpipe_uid=$(id -u pgpipe)
pgpipe_gid=$(id -g pgpipe)

validate_absolute_path_syntax() {
  safe_path=$1
  safe_label=$2
  case "$safe_path" in
    /*) ;;
    *) die "$safe_label must be an absolute path" ;;
  esac
  case "$safe_path" in
    *//*|*/./*|*/../*|*/.|*/..)
      die "$safe_label must not contain empty, dot, or parent components"
      ;;
  esac
}

# Every component must be root-owned and immutable to group/other users.
# Checking only the leaf is insufficient: a writable parent could be renamed
# while this privileged hook is copying keys. Symlinks are rejected so the
# checked pathname is the pathname later used at the mutation boundary.
validate_trusted_path_chain() {
  trusted_target=$1
  trusted_label=$2
  validate_absolute_path_syntax "$trusted_target" "$trusted_label"

  trusted_remainder=${trusted_target#/}
  trusted_current=
  trusted_old_ifs=$IFS
  IFS=/
  set -f
  # shellcheck disable=SC2086 # Split only on '/' after pathname expansion is disabled.
  set -- $trusted_remainder
  set +f
  IFS=$trusted_old_ifs

  if [ "$trusted_target" = "/" ]; then
    set -- ""
  fi
  for trusted_component do
    if [ -n "$trusted_component" ]; then
      trusted_current="$trusted_current/$trusted_component"
    else
      trusted_current=/
    fi
    test -e "$trusted_current" ||
      die "$trusted_label component $trusted_current does not exist"
    test ! -L "$trusted_current" ||
      die "$trusted_label component $trusted_current must not be a symbolic link"
    test "$(stat -c %u "$trusted_current")" = 0 ||
      die "$trusted_label component $trusted_current must be owned by root"
    trusted_mode=$(stat -c %a "$trusted_current")
    case "$trusted_mode" in
      ''|*[!0-7]*)
        die "could not validate mode for $trusted_label component $trusted_current"
        ;;
    esac
    trusted_mode_value=$((0$trusted_mode))
    test $((trusted_mode_value & 0022)) -eq 0 ||
      die "$trusted_label component $trusted_current must not be group- or world-writable"
  done
}

validate_tls_storage() {
  validate_trusted_path_chain "$TLS_DIR" "PGPIPE_TLS_DIR"
  test -d "$TLS_DIR" || die "$TLS_DIR is not a directory"
  test "$(stat -c %g "$TLS_DIR")" = "$pgpipe_gid" ||
    die "$TLS_DIR must have group pgpipe"
  test "$(stat -c %a "$TLS_DIR")" = 750 ||
    die "$TLS_DIR must have mode 0750"

  for live_file in "$CERT_FILE" "$KEY_FILE"; do
    test -f "$live_file" || die "$live_file is not a regular file"
    test ! -L "$live_file" ||
      die "$live_file must not be a symbolic link"
  done
  test "$(stat -c %u "$CERT_FILE")" = 0 ||
    die "$CERT_FILE must be owned by root"
  test "$(stat -c %g "$CERT_FILE")" = 0 ||
    die "$CERT_FILE must have group root"
  test "$(stat -c %a "$CERT_FILE")" = 644 ||
    die "$CERT_FILE must have mode 0644"
  test "$(stat -c %u "$KEY_FILE")" = "$pgpipe_uid" ||
    die "$KEY_FILE must be owned by pgpipe"
  test "$(stat -c %g "$KEY_FILE")" = "$pgpipe_gid" ||
    die "$KEY_FILE must have group pgpipe"
  test "$(stat -c %a "$KEY_FILE")" = 600 ||
    die "$KEY_FILE must have mode 0600"
}

validate_absolute_path_syntax "$STAGING_ROOT" "PGPIPE_TLS_STAGING_ROOT"
staging_parent=${STAGING_ROOT%/*}
test -n "$staging_parent" || staging_parent=/
validate_trusted_path_chain "$staging_parent" "PGPIPE_TLS_STAGING_ROOT parent"
test ! -L "$STAGING_ROOT" ||
  die "$STAGING_ROOT must not be a symbolic link"
if [ -e "$STAGING_ROOT" ] && [ ! -d "$STAGING_ROOT" ]; then
  die "$STAGING_ROOT exists but is not a directory"
fi
install -d -o root -g root -m 0700 "$STAGING_ROOT"
validate_trusted_path_chain "$STAGING_ROOT" "PGPIPE_TLS_STAGING_ROOT"
test "$(stat -c %g "$STAGING_ROOT")" = 0 ||
  die "$STAGING_ROOT must have group root"
test "$(stat -c %a "$STAGING_ROOT")" = 700 ||
  die "$STAGING_ROOT must have mode 0700"

validate_tls_storage

if [ -z "$TRUST_FILE" ]; then
  for trust_candidate in \
    /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem \
    /etc/ssl/certs/ca-certificates.crt \
    /etc/pki/tls/certs/ca-bundle.crt \
    /etc/ssl/cert.pem; do
    if [ -r "$trust_candidate" ] && [ ! -L "$trust_candidate" ]; then
      TRUST_FILE=$trust_candidate
      break
    fi
  done
fi
test -n "$TRUST_FILE" ||
  die "no system CA bundle found; set PGPIPE_TLS_CA_FILE to a root-controlled CA bundle"
validate_trusted_path_chain "$TRUST_FILE" "PGPIPE_TLS_CA_FILE"
test -f "$TRUST_FILE" || die "$TRUST_FILE is not a regular CA bundle"
test ! -L "$TRUST_FILE" ||
  die "$TRUST_FILE must not be a symbolic link"

umask 077
lock_acquired=0
WORK_DIR=
rotation_started=0
rotation_finished=0
previous_strictly_valid=0
was_active=0

release_lock() {
  if [ "$lock_acquired" -eq 1 ]; then
    rm -f "$LOCK_DIR/pid"
    if ! rmdir "$LOCK_DIR"; then
      echo "pgpipe TLS renewal: could not release lock $LOCK_DIR" >&2
      return 1
    fi
    lock_acquired=0
  fi
}

initial_cleanup() {
  initial_status=$?
  trap - EXIT
  trap '' HUP INT TERM
  set +e
  if [ -n "$WORK_DIR" ]; then
    if ! rm -rf "$WORK_DIR"; then
      echo "pgpipe TLS renewal: could not remove staging directory $WORK_DIR" >&2
      initial_status=1
    fi
  fi
  release_lock || initial_status=1
  exit "$initial_status"
}
trap initial_cleanup EXIT
trap '' HUP INT TERM
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
  trap 'exit 1' HUP INT TERM
  die "another pgpipe TLS renewal is running (or stale lock $LOCK_DIR remains)"
fi
lock_acquired=1
trap 'exit 1' HUP INT TERM
printf '%s\n' "$$" >"$LOCK_DIR/pid"

WORK_DIR=$(mktemp -d "$STAGING_ROOT/renewal.XXXXXX")
EMPTY_CA_DIR="$WORK_DIR/empty-ca"
install -d -o root -g root -m 0700 "$EMPTY_CA_DIR"

validate_pair() {
  key_header=$(awk '/^-----BEGIN .*PRIVATE KEY-----$/ { print; exit }' "$2") ||
    return 1
  case "$key_header" in
    '-----BEGIN PRIVATE KEY-----'|\
    '-----BEGIN RSA PRIVATE KEY-----'|\
    '-----BEGIN EC PRIVATE KEY-----')
      ;;
    *)
      return 1
      ;;
  esac
  if grep -q '^Proc-Type:.*ENCRYPTED' "$2"; then
    return 1
  fi
  openssl x509 -in "$1" -noout >/dev/null 2>&1 || return 1
  openssl x509 -in "$1" -noout -text >"$3.cert-text" || return 1
  grep -Eq 'Public Key Algorithm: (rsaEncryption|rsassaPss|id-ecPublicKey|ED25519)' \
    "$3.cert-text" || return 1
  if grep -q 'Public Key Algorithm: id-ecPublicKey' "$3.cert-text"; then
    grep -Eq 'ASN1 OID: (prime256v1|secp384r1|secp521r1)' \
      "$3.cert-text" || return 1
  fi
  if grep -Eq 'Public Key Algorithm: (rsaEncryption|rsassaPss)' \
    "$3.cert-text"; then
    rsa_bits=$(
      awk '/Public-Key: \([0-9]+ bit\)/ {
        value = $2
        gsub(/[^0-9]/, "", value)
        print value
        exit
      }' "$3.cert-text"
    )
    test -n "$rsa_bits" && test "$rsa_bits" -ge 2048 || return 1
  fi
  openssl pkey -in "$2" -passin pass: -noout >/dev/null 2>&1 || return 1
  openssl x509 -in "$1" -pubkey -noout \
    >"$3.cert-public.pem" || return 1
  openssl pkey -pubin -in "$3.cert-public.pem" -outform DER \
    >"$3.cert-public.der" || return 1
  openssl pkey -in "$2" -passin pass: -pubout -outform DER \
    >"$3.key-public.der" || return 1
  cmp -s "$3.cert-public.der" "$3.key-public.der"
}

split_fullchain() {
  awk -v leaf_file="$2" -v chain_file="$3" '
    /-----BEGIN CERTIFICATE-----/ { certificate_number++ }
    certificate_number == 1 { print > leaf_file }
    certificate_number > 1 { print > chain_file }
  ' "$1" || return 1
  test -s "$2" || return 1
}

# This offline check runs even when pgpipe is inactive. It prevents a
# not-yet-valid, expired, wrong-host, or untrusted renewal from replacing the
# last known pair merely because no HTTPS listener is available to probe.
validate_renewed_identity() {
  split_fullchain "$1" "$3.leaf.pem" "$3.intermediates.pem" || return 1
  openssl x509 -in "$3.leaf.pem" -noout -checkend 2592000 ||
    return 1
  if [ -s "$3.intermediates.pem" ]; then
    # shellcheck disable=SC2086 # Fixed, internally constructed option tokens.
    openssl verify -purpose sslserver -verify_hostname "$TLS_HOST" \
      $OPENSSL_TRUST_ISOLATION_FLAGS \
      -CAfile "$TRUST_FILE" -untrusted "$3.intermediates.pem" \
      "$3.leaf.pem" >"$3.verify.out" 2>"$3.verify.err"
  else
    # shellcheck disable=SC2086 # Fixed, internally constructed option tokens.
    openssl verify -purpose sslserver -verify_hostname "$TLS_HOST" \
      $OPENSSL_TRUST_ISOLATION_FLAGS \
      -CAfile "$TRUST_FILE" \
      "$3.leaf.pem" >"$3.verify.out" 2>"$3.verify.err"
  fi
}

capture_served_leaf() {
  timeout 7 openssl s_client \
    -connect "$TLS_CONNECT_AUTHORITY" \
    -servername "$TLS_HOST" \
    -showcerts </dev/null >"$2.handshake" 2>"$2.handshake.err" ||
    return 1
  awk '
    /-----BEGIN CERTIFICATE-----/ { emitting = 1 }
    emitting { print }
    /-----END CERTIFICATE-----/ && emitting { exit }
  ' "$2.handshake" >"$1" || return 1
  test -s "$1" || return 1
  openssl x509 -in "$1" -noout >/dev/null 2>&1
}

served_leaf_matches_file() {
  capture_served_leaf "$2.live-leaf.pem" "$2" || return 1
  openssl x509 -in "$1" -outform DER >"$2.expected-leaf.der" ||
    return 1
  openssl x509 -in "$2.live-leaf.pem" -outform DER >"$2.live-leaf.der" ||
    return 1
  cmp -s "$2.expected-leaf.der" "$2.live-leaf.der"
}

strict_https_once() {
  curl --disable --fail --silent --show-error \
    --cacert "$TRUST_FILE" \
    --capath "$EMPTY_CA_DIR" \
    --noproxy '*' --connect-timeout 2 --max-time 5 \
    --resolve "$TLS_HOST:$TLS_PORT:$TLS_RESOLVE_ADDRESS" \
    "https://$TLS_HOST:$TLS_PORT/health" >/dev/null
}

# `--insecure` is deliberately limited to pre-change and rollback liveness.
# Identity is checked separately by exact leaf-DER comparison. Acceptance of
# the renewed certificate always uses strict_https_once without --insecure.
identity_preserving_liveness_once() {
  curl --disable --insecure --fail --silent --show-error \
    --noproxy '*' --connect-timeout 2 --max-time 5 \
    --resolve "$TLS_HOST:$TLS_PORT:$TLS_RESOLVE_ADDRESS" \
    "https://$TLS_HOST:$TLS_PORT/health" >/dev/null
}

wait_for_strict_https() {
  attempt=0
  while [ "$attempt" -lt "$MAX_ATTEMPTS" ]; do
    if strict_https_once; then
      return 0
    fi
    attempt=$((attempt + 1))
    if [ "$attempt" -lt "$MAX_ATTEMPTS" ]; then
      sleep "$RETRY_DELAY"
    fi
  done
  return 1
}

wait_for_identity_preserving_liveness() {
  attempt=0
  while [ "$attempt" -lt "$MAX_ATTEMPTS" ]; do
    if identity_preserving_liveness_once; then
      return 0
    fi
    attempt=$((attempt + 1))
    if [ "$attempt" -lt "$MAX_ATTEMPTS" ]; then
      sleep "$RETRY_DELAY"
    fi
  done
  return 1
}

read_service_state() {
  service_active_state=$(
    systemctl show "$SERVICE_NAME" --property=ActiveState --value
  ) || return 1
  service_sub_state=$(
    systemctl show "$SERVICE_NAME" --property=SubState --value
  ) || return 1
  service_state="$service_active_state:$service_sub_state"
}

if ! read_service_state; then
  die "could not read systemd state for $SERVICE_NAME"
fi
case "$service_state" in
  active:running)
    was_active=1
    original_service_state=active:running
    ;;
  inactive:dead)
    was_active=0
    original_service_state=inactive:dead
    ;;
  *)
    die "$SERVICE_NAME is in unsafe transitional or failed state $service_state; wait for a stable active/running or inactive/dead state"
    ;;
esac

# Preserve and validate the on-disk pair. Validity dates are intentionally not
# enforced here: an expired certificate must still be renewable. A matching
# key plus exact live-leaf comparison proves that this is the pair the active
# process cached and therefore the truthful rollback state.
install -o root -g root -m 0644 \
  "$CERT_FILE" "$WORK_DIR/fullchain.previous.pem"
install -o root -g root -m 0600 \
  "$KEY_FILE" "$WORK_DIR/privkey.previous.pem"
if ! validate_pair \
  "$WORK_DIR/fullchain.previous.pem" \
  "$WORK_DIR/privkey.previous.pem" \
  "$WORK_DIR/previous"; then
  die "current on-disk certificate and key are not a valid matching pair"
fi

# Stage and validate the renewed pair entirely under the root-only /run tree.
install -o root -g root -m 0644 \
  "$RENEWED_LINEAGE/fullchain.pem" "$WORK_DIR/fullchain.new.pem"
install -o root -g root -m 0600 \
  "$RENEWED_LINEAGE/privkey.pem" "$WORK_DIR/privkey.new.pem"
if ! validate_pair \
  "$WORK_DIR/fullchain.new.pem" \
  "$WORK_DIR/privkey.new.pem" \
  "$WORK_DIR/new"; then
  die "renewed certificate and key are not a valid matching pair"
fi
if ! validate_renewed_identity \
  "$WORK_DIR/fullchain.new.pem" \
  "$WORK_DIR/privkey.new.pem" \
  "$WORK_DIR/new-identity"; then
  die "renewed certificate is not currently valid, trusted by $TRUST_FILE, and valid for $TLS_HOST"
fi

if [ "$was_active" -eq 1 ]; then
  if ! wait_for_identity_preserving_liveness; then
    die "current pgpipe HTTPS endpoint is not reachable before renewal"
  fi
  if ! served_leaf_matches_file \
    "$WORK_DIR/fullchain.previous.pem" "$WORK_DIR/prechange"; then
    die "served TLS leaf does not match the current on-disk certificate; restart or repair pgpipe before renewing"
  fi
  if strict_https_once 2>"$WORK_DIR/previous-strict.err"; then
    previous_strictly_valid=1
  else
    echo "pgpipe TLS renewal: current HTTPS trust/expiry check fails; identity matches, so renewal will proceed." >&2
  fi
fi

# Any command failure or signal from this point restores both previous files.
on_exit() {
  status=$?
  trap - EXIT
  # Once rollback begins, defer further catchable termination requests until
  # the certificate and key are restored as one pair. SIGKILL remains
  # inherently uncatchable and leaves the root-only recovery directory.
  trap '' HUP INT TERM
  set +e

  if [ "$rotation_started" -eq 1 ] && [ "$rotation_finished" -eq 0 ]; then
    status=1
    echo "pgpipe TLS renewal failed; restoring the previous pair." >&2
    restore_status=0
    restored_state_verified=1
    service_restarted=1
    safe_to_restore=1

    systemctl stop "$SERVICE_NAME" >/dev/null 2>&1 ||
      safe_to_restore=0
    if ! read_service_state || [ "$service_state" != inactive:dead ]; then
      safe_to_restore=0
    fi
    (validate_tls_storage) >/dev/null 2>&1 ||
      safe_to_restore=0

    if [ "$safe_to_restore" -eq 1 ]; then
      install -o root -g root -m 0644 \
        "$WORK_DIR/fullchain.previous.pem" "$CERT_FILE" || restore_status=1
      install -o pgpipe -g pgpipe -m 0600 \
        "$WORK_DIR/privkey.previous.pem" "$KEY_FILE" || restore_status=1
    else
      restore_status=1
    fi

    if [ "$was_active" -eq 1 ] && [ "$restore_status" -eq 0 ]; then
      systemctl start "$SERVICE_NAME" || {
        restore_status=1
        service_restarted=0
      }
      if [ "$service_restarted" -eq 1 ]; then
        wait_for_identity_preserving_liveness ||
          restored_state_verified=0
        served_leaf_matches_file \
          "$WORK_DIR/fullchain.previous.pem" "$WORK_DIR/rollback" ||
          restored_state_verified=0
        if [ "$previous_strictly_valid" -eq 1 ]; then
          wait_for_strict_https || restored_state_verified=0
        fi
      else
        restored_state_verified=0
      fi
    fi

    if [ "$restore_status" -eq 0 ] &&
      [ "$restored_state_verified" -eq 1 ]; then
      if [ "$was_active" -eq 0 ]; then
        echo "Previous pgpipe TLS pair restored; pgpipe remains inactive." >&2
      elif [ "$previous_strictly_valid" -eq 1 ]; then
        echo "Previous pgpipe TLS pair restored and strictly verified." >&2
      else
        echo "Previous pgpipe TLS pair restored; listener and leaf identity match the pre-renewal state." >&2
        echo "Its pre-existing trust or expiry failure remains and still needs operator attention." >&2
      fi
      rm -rf "$WORK_DIR"
      WORK_DIR=
    else
      echo "Automatic restore needs operator attention." >&2
      echo "Recovery files remain in: $WORK_DIR" >&2
    fi
  else
    if [ -n "$WORK_DIR" ]; then
      rm -rf "$WORK_DIR"
      WORK_DIR=
    fi
  fi

  release_lock || status=1
  exit "$status"
}
trap on_exit EXIT
trap 'exit 1' HUP INT TERM

# Re-check both the stable state and every path at the mutation boundary. Then
# issue stop even for an inactive unit: this fences a queued restart job. No
# file is replaced until systemd confirms inactive/dead.
validate_tls_storage
if ! read_service_state || [ "$service_state" != "$original_service_state" ]; then
  die "$SERVICE_NAME changed state before renewal; no certificate files were changed"
fi
rotation_started=1
systemctl stop "$SERVICE_NAME"
if ! read_service_state || [ "$service_state" != inactive:dead ]; then
  die "$SERVICE_NAME did not reach inactive/dead; refusing to replace certificate files"
fi
validate_tls_storage

install -o root -g root -m 0644 \
  "$WORK_DIR/fullchain.new.pem" "$CERT_FILE"
install -o pgpipe -g pgpipe -m 0600 \
  "$WORK_DIR/privkey.new.pem" "$KEY_FILE"

if [ "$was_active" -eq 1 ]; then
  systemctl start "$SERVICE_NAME"
  # Type=simple start success is not proof that TLS loaded the renewed files.
  wait_for_strict_https
  served_leaf_matches_file \
    "$WORK_DIR/fullchain.new.pem" "$WORK_DIR/postchange"
fi

rotation_finished=1
if [ "$was_active" -eq 1 ]; then
  echo "pgpipe TLS certificate renewed; strict HTTPS and served-leaf identity verified."
else
  echo "pgpipe was inactive; renewed files were installed after offline trust, time, and hostname validation."
  echo "pgpipe remains inactive and will serve the new certificate on its next start."
fi
