#!/bin/sh
# The Hatchery installer. https://hatchery.world/install/
#
# Puts one Hatchery pet into Codex. It is short on purpose, so you can read all of it before you run it.
#
#   curl -fsSL https://hatchery.world/install.sh | sh -s -- bookworm
#   sh install.sh bookworm            (after downloading this file and reading it)
#   sh install.sh bookworm --force    (replace a copy that is already installed)
#
# What it does, and nothing else:
#   1. Downloads the pet's checksum list, then its two files (pet.json and its sprite sheet) from
#      hatchery.world over HTTPS, into a temporary folder.
#   2. Checks every file against its SHA-256 checksum, and stops before installing anything if one differs.
#   3. Moves the pet into Codex's pets folder: ${CODEX_HOME:-$HOME/.codex}/pets/<pet>/
#
# It never asks for a password, never uses sudo, never runs anything it downloads, and never touches
# anything outside that one pet's folder. A pet that is already installed is left alone unless you add --force.

set -eu

SITE="${HATCHERY_URL:-https://hatchery.world}"   # change only to test against a local copy of the site
USAGE="usage: sh install.sh <pet> [--force]   for example: sh install.sh bookworm"

say() { printf '%s\n' "$*"; }
fail() { printf 'hatchery: %s\n' "$*" >&2; exit 1; }

PET=""
FORCE=0
for arg in "$@"; do
  case "$arg" in
    --force) FORCE=1 ;;
    -h | --help) say "$USAGE"; exit 0 ;;
    -*) fail "unknown option $arg. $USAGE" ;;
    *) [ -z "$PET" ] || fail "one pet at a time. $USAGE"; PET="$arg" ;;
  esac
done

# A pet's name is lower-case letters, digits and hyphens, as in its address on hatchery.world.
case "$PET" in
  "" | -* | *[!a-z0-9-]*) fail "give the pet's name as it appears in its hatchery.world address. $USAGE" ;;
esac

[ "$(id -u)" -ne 0 ] || fail "run this as yourself, not with sudo or as root: Codex's pets live in your own folder"

command -v curl >/dev/null 2>&1 || fail "curl is needed to download the pet, and it isn't installed"
if command -v sha256sum >/dev/null 2>&1; then
  digest() { sha256sum "$1" | cut -d ' ' -f 1; }
elif command -v shasum >/dev/null 2>&1; then
  digest() { shasum -a 256 "$1" | cut -d ' ' -f 1; }
elif command -v openssl >/dev/null 2>&1; then
  digest() { openssl dgst -sha256 -r "$1" | cut -d ' ' -f 1; }
else
  fail "no SHA-256 tool found (sha256sum, shasum or openssl), so the download can't be checked"
fi

case "$SITE" in
  https://*) SECURE="--proto =https --tlsv1.2" ;;   # HTTPS only, including redirects
  *) SECURE="" ;;
esac
fetch() {   # fetch <path on the site> <file>
  # shellcheck disable=SC2086  # $SECURE is two options, or none
  curl -fsSL --retry 2 $SECURE -o "$2" "$SITE/$1"
}

PETS="${CODEX_HOME:-$HOME/.codex}/pets"
DEST="$PETS/$PET"
if [ -e "$DEST" ] && [ "$FORCE" -ne 1 ]; then
  say "$PET is already installed, in $DEST. Left as it is."
  say "To replace it with the copy on hatchery.world, run the same command with --force at the end."
  exit 0
fi

TMP=$(mktemp -d "${TMPDIR:-/tmp}/hatchery.XXXXXX") || fail "could not make a temporary folder"
STAGE=""
trap 'rm -rf "$TMP" ${STAGE:+"$STAGE"}' EXIT
trap 'exit 1' INT TERM HUP

say "Installing $PET from $SITE"
fetch "$PET/install.sha256" "$TMP/install.sha256" || fail "there is no pet called $PET on $SITE (or the site can't be reached)"

mkdir "$TMP/$PET"
COUNT=0
while read -r WANT NAME; do
  case "$NAME" in "" | .* | *[!A-Za-z0-9._-]*) fail "the checksum list names an unexpected file; nothing was installed" ;; esac
  case "$WANT" in *[!0-9a-f]*) fail "the checksum list is not well formed; nothing was installed" ;; esac
  [ "${#WANT}" -eq 64 ] || fail "the checksum list is not well formed; nothing was installed"
  fetch "$PET/$NAME" "$TMP/$PET/$NAME" || fail "could not download $NAME; nothing was installed"
  [ "$(digest "$TMP/$PET/$NAME")" = "$WANT" ] || fail "$NAME does not match its checksum; nothing was installed"
  say "  $NAME  sha256 $WANT  checked"
  COUNT=$((COUNT + 1))
done < "$TMP/install.sha256"

[ "$COUNT" -ge 2 ] && [ -f "$TMP/$PET/pet.json" ] || fail "the download is incomplete; nothing was installed"
grep -q "\"id\"[[:space:]]*:[[:space:]]*\"$PET\"" "$TMP/$PET/pet.json" || fail "pet.json is for a different pet; nothing was installed"

mkdir -p "$PETS"
STAGE="$PETS/.hatchery-$PET-$$"   # assembled beside its final place, then renamed in one step
cp -R "$TMP/$PET" "$STAGE"
if [ -e "$DEST" ]; then rm -rf "$DEST"; fi   # reached only with --force
mv "$STAGE" "$DEST"
STAGE=""

say "Installed $PET in $DEST"
say "In Codex: Settings > Appearance > Pets. If $PET isn't listed yet, quit Codex and open it again."
