#!/usr/bin/env bash
# check.sh: a backup that looks fresh while its content is old, and the check that catches it.
# From the field note "Green is not fresh", https://infinimind-creations.com/notes/green-is-not-fresh/
#
# Runs entirely in a temporary directory and deletes it afterwards. Needs bash, cp, ln, find.
# Usage: bash check.sh        Exit code 0 when both checks behave as the note says.

set -euo pipefail

work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
src="$work/source"
dst="$work/backup"

# The source writes a marker into its own data every time it changes. In a real system that is
# a row the database updates every hour, or a file the service rewrites; here it is the night.
change_source() {
  printf 'records of night %s\n' "$1" > "$src/data.txt"
  printf '%s\n' "$1" > "$src/marker"
}

# One nightly backup run. Every run ends the way the lab's runs ended: a new dated folder,
# a "current" link pointing at it, exit code 0.
#   ok      copies the source
#   frozen  carries last night's snapshot forward under a new name (the lab, late August)
#   empty   copies nothing at all (the lab, July: 16 kilobytes instead of 1.2 gigabytes)
#   skipped the job does not run. No time passes in this simulation, so the link is set back
#           to 2020, which is what it looks like the morning after a night without a run.
backup_run() {
  local snap="$dst/night-$1"
  if [ "$2" = skipped ]; then touch -h -t 202001010000 "$dst/current"; return; fi
  mkdir -p "$snap"
  case $2 in
    ok)     cp -a "$src/." "$snap/" ;;
    frozen) cp -a "$dst/current/." "$snap/" ;;
    empty)  : ;;
  esac
  ln -sfn "$snap" "$dst/current"
}

# Check 1, the one that stayed green: is the "current" link younger than 36 hours?
link_check() {
  if [ -n "$(find "$dst/current" -prune -mmin -2160)" ]; then echo green; else echo red; fi
}

# Check 2: read the marker from inside the copy and compare it with what the source wrote.
content_check() {
  local want got
  want=$(cat "$src/marker")
  got=$(cat "$dst/current/marker" 2>/dev/null || true)
  if [ "$got" = "$want" ]; then echo green
  elif [ -z "$got" ]; then echo "red: no marker in the copy"
  else echo "red: copy holds night $got, source is at night $want"
  fi
}

link_green=0 link_red=0 content_red=0
simulate() {
  local title=$1 night=0 mode l c
  shift
  rm -rf "$src" "$dst"; mkdir -p "$src" "$dst"
  link_green=0 link_red=0 content_red=0
  printf '\n%s\n  %-6s %-7s %-6s %s\n' "$title" night run link content
  for mode in "$@"; do
    night=$((night + 1))
    change_source "$night"
    backup_run "$night" "$mode"
    l=$(link_check); c=$(content_check)
    if [ "$l" = green ]; then link_green=$((link_green + 1)); else link_red=$((link_red + 1)); fi
    [ "$c" != green ] && content_red=$((content_red + 1))
    printf '  %-6s %-7s %-6s %s\n' "$night" "$mode" "$l" "$c"
  done
}

simulate "Five nights; the copy breaks on night 3 and stays broken:" ok ok frozen frozen empty
broken_link=$link_green broken_red=$content_red
simulate "Negative control, five healthy nights:" ok ok ok ok ok
healthy_link=$link_green healthy_red=$content_red
simulate "The job skips night 3:" ok ok skipped
skipped_link_red=$link_red skipped_red=$content_red

printf '\nBroken run: the link check was green on %s of 5 nights, the content check red on %s.\n' "$broken_link" "$broken_red"
printf 'Healthy run: the link check was green on %s of 5 nights, the content check red on %s.\n' "$healthy_link" "$healthy_red"
printf 'Skipped night: the link check was red on %s night, the content check red on %s.\n' "$skipped_link_red" "$skipped_red"

if [ "$broken_link" -eq 5 ] && [ "$broken_red" -eq 3 ] && [ "$healthy_red" -eq 0 ] && [ "$skipped_link_red" -eq 1 ] && [ "$skipped_red" -eq 1 ]; then
  echo "As described: the link cannot see a broken copy, the marker can, and it stays quiet on a healthy one."
else
  echo "Unexpected result; please tell us: office@infinimind-creations.com" >&2
  exit 1
fi
