#!/bin/bash
set -Eeuo pipefail

readonly LESSON_SHA='0749ed759da1741688320959ab78aaeb088239a0ac3c14eb626e40a31a5b91ae'
readonly CONTRACT_SHA='3923c3d5f60d78439f1425b56fb914d343a6a4364a85d28da24686c9895033a3'
readonly PROJECT='compose-lesson'
readonly RELEASE='/srv/homelab/releases/compose-lesson/lesson-1'
readonly STAGE="${RELEASE}.staging"
readonly PORT='18080'
readonly DOCKER_HOST='unix:///var/run/docker.sock'

evidence='the terminal output'
mutation_started=0
cleanup_ran=0
cleanup_state='nothing-started'
remaining_objects='none'
remaining_port='closed'
remaining_paths='none'

fail() {
  local message="$1"
  local next_action="$2"
  printf 'FAIL: %s\n' "$message" >&2
  printf 'NEXT ACTION: %s\n' "$next_action" >&2
  exit 1
}

secure_directory() {
  local path="$1"
  local mode
  if [[ ! -d "$path" || -L "$path" ]]; then return 1; fi
  if [[ "$(/usr/bin/stat -c '%U:%G' "$path")" != root:root ]]; then return 1; fi
  mode="$(/usr/bin/stat -c '%a' "$path")"
  if (( (8#$mode & 0022) != 0 )); then return 1; fi
}

ensure_secure_child() {
  local parent="$1"
  local child="$2"
  if ! secure_directory "$parent"; then return 1; fi
  if [[ -L "$child" ]]; then return 1; fi
  if [[ ! -e "$child" ]]; then /usr/bin/install -d -o root -g root -m 0755 "$child"; fi
  secure_directory "$child"
}

secure_source_chain() {
  local path="$1"
  local owner mode
  while :; do
    if [[ ! -d "$path" || -L "$path" ]]; then return 1; fi
    owner="$(/usr/bin/stat -c '%U' "$path")"
    if [[ "$owner" != root && "$owner" != "$SUDO_USER" ]]; then return 1; fi
    mode="$(/usr/bin/stat -c '%a' "$path")"
    if (( (8#$mode & 0022) != 0 )); then return 1; fi
    if [[ "$path" == / ]]; then return 0; fi
    path="${path%/*}"
    if [[ -z "$path" ]]; then path=/; fi
  done
}

compose_at() {
  local directory="$1"
  local file="$2"
  shift 2
  /usr/bin/docker --host "$DOCKER_HOST" compose \
    --project-name "$PROJECT" \
    --project-directory "$directory" \
    --env-file /dev/null \
    -f "$file" "$@"
}

compose_reviewed() {
  compose_at "$reviewed_directory" "$reviewed_directory/compose.yaml" "$@"
}

append_project_objects() {
  local kind="$1"
  local output
  local list_args=(ls -q)
  if [[ "$kind" == container ]]; then list_args=(ls --all -q); fi
  if ! output="$(/usr/bin/docker --host "$DOCKER_HOST" "$kind" "${list_args[@]}" \
      --filter "label=com.docker.compose.project=$PROJECT" 2>&1)"; then
    remaining_objects="unverified ($kind ls failed: $output)"
    return 1
  fi
  if [[ -n "$output" ]]; then
    if [[ "$remaining_objects" == none ]]; then remaining_objects=''; fi
    remaining_objects+="${kind}:${output//$'\n'/,};"
  fi
}

inspect_project_objects() {
  remaining_objects='none'
  if ! append_project_objects container; then return 1; fi
  if ! append_project_objects network; then return 1; fi
  if ! append_project_objects volume; then return 1; fi
}

probe_port() {
  local output
  local status
  if output="$(/usr/bin/ss -H -ltn "sport = :$PORT" 2>&1)"; then
    status=0
  else
    status=$?
  fi
  remaining_port="$(port_state_from "$status" "$output")"
  if [[ "$remaining_port" == error ]]; then
    remaining_port="unverified (ss failed: $output)"
    return 1
  fi
}

known_disposable_path() {
  local path="$1"
  local entries
  if [[ ! -d "$path" || -L "$path" ]]; then return 1; fi
  if ! entries="$(/usr/bin/find "$path" -mindepth 1 -maxdepth 1 -print)"; then return 1; fi
  if [[ "$entries" != "$path/compose.yaml" ]]; then return 1; fi
  if [[ ! -f "$path/compose.yaml" || -L "$path/compose.yaml" ]]; then return 1; fi
  if ! printf '%s  %s\n' "$LESSON_SHA" "$path/compose.yaml" | /usr/bin/sha256sum --check --strict >/dev/null; then
    return 1
  fi
  if [[ "$(/usr/bin/stat -c '%U:%G:%a' "$path")" != 'root:root:755' ]]; then return 1; fi
  if [[ "$(/usr/bin/stat -c '%U:%G:%a' "$path/compose.yaml")" != 'root:root:644' ]]; then return 1; fi
}

remove_disposable_path() {
  local path="$1"
  if [[ ! -e "$path" ]]; then return 0; fi
  if ! known_disposable_path "$path"; then return 1; fi
  /usr/bin/rm -f -- "$path/compose.yaml"
  /usr/bin/rmdir -- "$path"
}

capture_diagnostics() {
  printf 'PRE-CLEANUP COMPOSE PS\n'
  if ! compose_reviewed ps --all; then printf 'compose ps failed\n'; fi
  printf 'PRE-CLEANUP COMPOSE LOGS\n'
  if ! compose_reviewed logs --no-color --tail 200; then printf 'compose logs failed\n'; fi
}

cleanup_transaction() {
  local capture="$1"
  local cleanup_rc=0
  cleanup_ran=1
  if (( capture == 1 )); then capture_diagnostics; fi
  if ! compose_reviewed down --remove-orphans; then cleanup_rc=1; fi
  if ! remove_disposable_path "$STAGE"; then cleanup_rc=1; fi
  if ! remove_disposable_path "$RELEASE"; then cleanup_rc=1; fi
  if ! inspect_project_objects; then cleanup_rc=1; fi
  if ! probe_port; then cleanup_rc=1; fi
  remaining_paths='none'
  if [[ -e "$STAGE" || -e "$RELEASE" ]]; then
    local stage_state='absent'
    local release_state='absent'
    if [[ -e "$STAGE" ]]; then stage_state='present'; fi
    if [[ -e "$RELEASE" ]]; then release_state='present'; fi
    remaining_paths="stage=$stage_state,release=$release_state"
    cleanup_rc=1
  fi
  cleanup_state="$(cleanup_result_from "$cleanup_rc" "$remaining_objects" "$remaining_port" "$remaining_paths")"
  if [[ "$cleanup_state" == clean ]]; then
    mutation_started=0
  else
    cleanup_rc=1
  fi
  return "$cleanup_rc"
}

on_exit() {
  local rc=$?
  trap - EXIT INT TERM
  if (( rc != 0 )); then
    set +e
    if (( mutation_started == 1 && cleanup_ran == 0 )); then cleanup_transaction 1; fi
    if [[ "$cleanup_state" == nothing-started ]]; then
      printf 'FAILED STATE: no lesson object or release/staging leaf was started; prepared parent directories or Docker cache may have changed.\n' >&2
    elif [[ "$cleanup_state" == clean ]]; then
      printf 'FAILED STATE: preauthorized cleanup completed; no lesson state remains.\n' >&2
    else
      printf 'FAILED STATE: cleanup incomplete; objects=%s; port=%s; paths=%s.\n' \
        "$remaining_objects" "$remaining_port" "$remaining_paths" >&2
    fi
    printf 'EVIDENCE: %s\n' "$evidence" >&2
    printf 'NEXT ACTION: Owner on this Linux host: stop; give the named evidence log to the reviewer. Do not run a cleanup command.\n' >&2
  fi
  exit "$rc"
}
trap on_exit EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

if (( EUID != 0 )); then fail 'run this script through sudo as shown in the guide' 'Owner: use the chapter-reviewed absolute sudo invocation.'; fi
if [[ -z "${SUDO_USER:-}" || "$SUDO_USER" == root ]]; then fail 'no named human sudo caller' 'Owner: stop and review the invocation from a named account.'; fi
if (( $# != 1 )); then fail 'expected one denied agent-account name' 'Owner: use the one reviewed invocation from the chapter.'; fi
readonly DENIED_USER="$1"
for bootstrap in /usr/bin/sha256sum /usr/bin/stat /usr/bin/tr; do
  if [[ ! -x "$bootstrap" ]]; then fail "missing bootstrap prerequisite: $bootstrap" 'Owner: stop and ask the reviewer to update the dependency closure.'; fi
done
if [[ "${BASH_SOURCE[0]}" != /* ]]; then fail 'script path is not absolute' 'Owner: use the reviewed absolute invocation.'; fi
readonly SCRIPT_SOURCE="${BASH_SOURCE[0]}"
readonly SOURCE_DIR="${SCRIPT_SOURCE%/*}"
readonly COMPOSE_SOURCE="$SOURCE_DIR/compose.yaml"
readonly CONTRACT_SOURCE="$SOURCE_DIR/compose-lesson-contract.sh"
if ! secure_source_chain "$SOURCE_DIR"; then fail 'unsafe source directory chain' 'Owner: stop and inspect download provenance and parent permissions.'; fi
for source in "$SCRIPT_SOURCE" "$COMPOSE_SOURCE" "$CONTRACT_SOURCE"; do
  if [[ ! -f "$source" || -L "$source" ]]; then fail "$source is not a regular reviewed file" 'Owner: stop and restore the reviewed download set.'; fi
  if [[ "$(/usr/bin/stat -c '%U' "$source")" != "$SUDO_USER" ]]; then fail "$source is not owned by the human caller" 'Owner: stop and inspect download provenance; do not chown blindly.'; fi
  mode="$(/usr/bin/stat -c '%a' "$source")"
  if (( (8#$mode & 0022) != 0 )); then fail "$source is writable by group or others" 'Owner: stop and inspect the file mode.'; fi
done
exec {CONTRACT_FD}<"$CONTRACT_SOURCE"
exec {COMPOSE_FD}<"$COMPOSE_SOURCE"
if ! printf '%s  %s\n' "$CONTRACT_SHA" "/proc/self/fd/$CONTRACT_FD" | /usr/bin/sha256sum --check --strict; then fail 'helper digest differs from review' 'Owner: stop and restore the reviewed download set.'; fi
if ! printf '%s  %s\n' "$LESSON_SHA" "/proc/self/fd/$COMPOSE_FD" | /usr/bin/sha256sum --check --strict; then fail 'Compose digest differs from review' 'Owner: stop and restore the reviewed download set.'; fi
source "/dev/fd/$CONTRACT_FD"
compose_contract_self_test
exec {CONTRACT_FD}<&-

for binary in /usr/bin/date /usr/bin/install /usr/bin/mktemp /usr/bin/stat /usr/bin/tee; do
  if [[ ! -x "$binary" ]]; then fail "missing evidence prerequisite: $binary" 'Owner: stop and ask the reviewer to update the dependency closure.'; fi
done
if ! secure_directory /var; then fail 'unsafe /var parent' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
if ! secure_directory /var/log; then fail 'unsafe /var/log parent' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
if ! ensure_secure_child /var/log /var/log/homelab; then fail 'unsafe evidence directory' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
evidence="$(/usr/bin/mktemp /var/log/homelab/compose-lesson.XXXXXXXX.log)"
readonly evidence
exec > >(/usr/bin/tee "$evidence") 2>&1
timestamp="$(/usr/bin/date -u +%Y%m%dT%H%M%SZ)"
readonly timestamp
if ! valid_utc_timestamp "$timestamp"; then fail 'date returned a malformed UTC timestamp' 'Owner: stop and have the reviewer inspect the evidence log and system clock tooling.'; fi
printf 'RUN: %s\n' "$timestamp"

readonly REQUIRED=(
  /bin/bash /usr/bin/curl /usr/bin/docker /usr/bin/find /usr/bin/id /usr/bin/mv
  /usr/bin/rm /usr/bin/rmdir /usr/bin/sha256sum /usr/bin/ss /usr/bin/tr
  /usr/sbin/runuser
)
for binary in "${REQUIRED[@]}"; do
  if [[ ! -x "$binary" ]]; then fail "missing required binary: $binary" 'Owner: stop and ask the reviewer to update the dependency closure.'; fi
done

if ! valid_local_user 'operator-observer'; then fail 'internal positive username fixture failed' 'Owner: stop; the script review failed.'; fi
if valid_local_user '-bad'; then fail 'internal negative username fixture failed' 'Owner: stop; the script review failed.'; fi
if valid_local_user 'bad space'; then fail 'internal malformed username fixture failed' 'Owner: stop; the script review failed.'; fi
if ! valid_local_user "$DENIED_USER"; then fail 'agent account is not a valid local username' 'Owner: stop and ask the reviewer for the exact existing denied account.'; fi
if [[ "$DENIED_USER" == root || "$DENIED_USER" == "$SUDO_USER" ]]; then fail 'denied account overlaps a privileged actor' 'Owner: stop and choose no account; ask the reviewer to correct the contract.'; fi

/bin/bash -n "$SCRIPT_SOURCE"
if ! /usr/bin/id "$DENIED_USER" >/dev/null 2>&1; then fail "agent account does not exist: $DENIED_USER" 'Owner: stop and ask the reviewer for the existing denied account; do not create one here.'; fi
if [[ ! -S /var/run/docker.sock ]]; then fail 'reviewed Docker socket is absent' 'Owner: stop and have the reviewer inspect the target Docker endpoint.'; fi
if ! /usr/bin/docker --host "$DOCKER_HOST" compose version; then fail 'Docker Compose is unavailable' 'Owner: stop and have the reviewer inspect Docker installation.'; fi
if ! /usr/bin/docker --host "$DOCKER_HOST" info >/dev/null; then fail 'Docker daemon is not ready' 'Owner: stop and have the reviewer inspect Docker service health.'; fi
if ! secure_directory /srv; then fail 'unsafe /srv parent' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
if ! ensure_secure_child /srv /srv/homelab; then fail 'unsafe homelab root' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
if ! ensure_secure_child /srv/homelab /srv/homelab/releases; then fail 'unsafe release root' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
if ! ensure_secure_child /srv/homelab/releases "${RELEASE%/*}"; then fail 'unsafe lesson root' 'Owner: stop and have the reviewer inspect the directory chain.'; fi
had_prior_path=0
if [[ -e "$RELEASE" || -e "$STAGE" ]]; then had_prior_path=1; fi
if [[ -e "$RELEASE" ]]; then
  if ! known_disposable_path "$RELEASE"; then fail 'prior release is not the reviewed disposable lesson' 'Owner: stop and give this evidence log to the reviewer.'; fi
fi
if [[ -e "$STAGE" ]]; then
  if ! known_disposable_path "$STAGE"; then fail 'prior staging path is not the reviewed disposable lesson' 'Owner: stop and give this evidence log to the reviewer.'; fi
fi
reviewed_directory="$RELEASE"
if [[ ! -e "$RELEASE" ]]; then reviewed_directory="$STAGE"; fi
if [[ ! -e "$reviewed_directory" ]]; then
  mutation_started=1
  /usr/bin/install -d -o root -g root -m 0755 "$STAGE"
  /usr/bin/install -o root -g root -m 0644 "/proc/self/fd/$COMPOSE_FD" "$STAGE/compose.yaml"
  if ! known_disposable_path "$STAGE"; then fail 'initial sealed staging copy failed its checks' 'Owner: stop and give this evidence log to the reviewer.'; fi
fi

if ! inspect_project_objects; then fail 'Docker project inspection failed' 'Owner: stop and give this evidence log to the reviewer.'; fi
if ! probe_port; then fail 'TCP listener inspection failed' 'Owner: stop and give this evidence log to the reviewer.'; fi
if [[ "$remaining_port" == open && "$remaining_objects" == none ]]; then fail "TCP $PORT belongs to an unknown listener" 'Owner: stop and have the reviewer identify the listener; this script will not remove it.'; fi
if [[ "$remaining_objects" != none || "$had_prior_path" == 1 ]]; then
  mutation_started=1
  if ! cleanup_transaction 1; then fail 'bounded prior lesson residue could not be reconciled' 'Owner: stop and give this evidence log to the reviewer; do not run a second cleanup command.'; fi
  cleanup_ran=0
  cleanup_state='nothing-started'
fi
if ! probe_port; then fail 'TCP listener recheck failed' 'Owner: stop and give this evidence log to the reviewer.'; fi
if [[ "$remaining_port" != closed ]]; then fail "TCP $PORT is already in use" 'Owner: stop and have the reviewer identify the listener.'; fi

reviewed_directory="$STAGE"
mutation_started=1
/usr/bin/install -d -o root -g root -m 0755 "$STAGE"
/usr/bin/install -o root -g root -m 0644 "/proc/self/fd/$COMPOSE_FD" "$STAGE/compose.yaml"
exec {COMPOSE_FD}<&-
if ! known_disposable_path "$STAGE"; then fail 'staged release failed ownership, mode, type, or digest checks' 'Owner: stop and give this evidence log to the reviewer.'; fi
/usr/bin/mv -T -- "$STAGE" "$RELEASE"
reviewed_directory="$RELEASE"
if ! known_disposable_path "$RELEASE"; then fail 'atomic release failed its postcondition' 'Owner: stop and give this evidence log to the reviewer.'; fi
if ! compose_reviewed config --quiet; then fail 'sealed Compose model is invalid' 'Owner: stop and give this evidence log to the reviewer.'; fi
if ! compose_reviewed pull; then fail 'image pull failed' 'Owner: stop and give this evidence log to the reviewer.'; fi

compose_reviewed up --detach --wait --wait-timeout 60
/usr/bin/curl --fail --silent --show-error "http://127.0.0.1:$PORT/" >/dev/null
compose_reviewed restart
compose_reviewed up --detach --wait --wait-timeout 60
/usr/bin/curl --fail --silent --show-error "http://127.0.0.1:$PORT/" >/dev/null
expected_uid="$(/usr/bin/id -u "$DENIED_USER")"
observed_uid="$(/usr/sbin/runuser -u "$DENIED_USER" -- /usr/bin/id -u)"
/usr/sbin/runuser -u "$DENIED_USER" -- /usr/bin/id
if [[ "$observed_uid" != "$expected_uid" ]]; then fail 'runuser did not execute as the named account' 'Owner: stop and give this evidence log to the reviewer.'; fi
if denial_output="$(/usr/sbin/runuser -u "$DENIED_USER" -- /usr/bin/docker --host "$DOCKER_HOST" ps 2>&1)"; then denial_rc=0; else denial_rc=$?; fi
printf 'DENIAL OUTPUT: %s\n' "$denial_output"
denial_state="$(denial_state_from "$denial_rc" "$denial_output")"
if [[ "$denial_state" == allowed ]]; then fail "$DENIED_USER unexpectedly has Docker access" 'Owner: stop; remove no access here and give this evidence log to the security reviewer.'; fi
if [[ "$denial_state" != denied ]]; then fail 'Docker denial was ambiguous rather than a socket permission denial' 'Owner: stop and give this evidence log to the reviewer.'; fi

if ! cleanup_transaction 0; then fail 'preauthorized cleanup did not restore the recorded clean state' 'Owner: stop and give this evidence log to the reviewer; do not run a second cleanup command.'; fi
trap - EXIT INT TERM
printf 'SUCCESS: deployment, restart, consumer check, classified Docker denial, cleanup, and final-state checks passed. Evidence: %s\n' "$evidence"
