#!/usr/bin/env bash

# More details: https://kitten.small-web.org/reference/technical-manual/#installer

# Because Apple wants to take from the commons but not give back to it,
# we have to limit the features we use in this Bash script to Bash
# version 3.2 (specifically 3.2.57), the archaic version from the 2000s
# that the multi-trillion-dollar corporation includes in its OS.
#
# (Otherwise, we’d have to ask people on Macs to upgrade their Bash
# versions, which is easily done using Brew, but Brew only runs on the
# latest three versions of macOS, thereby locking out people with
# older computers.)
#
# (If you’re on Linux, you don’t have to worry about any of this.)

set -e

# Start timer, except on macOS because trillion-dollar company’s date
# command cannot support sub-second precision ¯\_(ツ)_/¯
os="$(uname | tr '[:upper:]' '[:lower:]')"

if [ "${os}" != 'darwin' ]; then
  T="$(date +%s%N)"
fi

echo '🐱 Installing Kitten…'
echo ''

# Ensure we’re running on a supported operating system.
# For this bash-based installer, that’s Linux and macOS.
if [ "${os}" != 'linux' ] && [ "${os}" != 'darwin' ]; then
  printf '❌ Sorry, Kitten is only supported on Linux and macOS.\n\n   If you want to take on supporting Kitten on an unlisted platform, please maintain a soft fork – thank you and apologies for the inconvenience. Kitten is currently being developed by one person and there are only so many platforms I can test on myself.\n'
  echo "   (Your operating system is detected as ${os}.)"
  exit 1
fi

if [ "${BASH_VERSINFO[0]}" -lt 3 ]; then
  printf '❌ Kitten install requires Bash version >= 3.2, you have: %s\n' "$BASH_VERSION"
  exit 1
fi

architecture=$(uname -m)

# Map architecture to runtime architecture string
mapArchitecture() {
  case "$1" in
    x86_64)  echo 'x64' ;;
    aarch64) echo 'arm64' ;;
    arm64)   echo 'arm64' ;;
    *)       echo '' ;;
  esac
}

################################################################################
#
# Node 24 (LTS, maintained until April 2028).
# 
# Node 25 and later need libatomic. Most distributions do not install it by default (nodejs/node#60790).
# 
# When upgrading, do not forget to also update:
# 
# • src/lib/typeScriptTransformOptions.js: The target for the esbuild TypeScript configuration.
# • package.json: The engines field.
# • .nvmrc
# • build-templates/runtime-checksums: The lines for this version from https://nodejs.org/dist/<version>/SHASUMS256.txt.
#
################################################################################

runtimeVersion='v24.22.1'
runtimePrefix='https://nodejs.org/dist'

if [ -z "$(mapArchitecture "$architecture")" ]; then
  echo "❌ Sorry, Kitten does not run on ${architecture}: Node.js ${runtimeVersion} has no build for it." >&2
  exit 1
fi

# If the API version is specified (as a positional argument, integer),
# then set it so we tack it to the download URL of the package.
apiVersion=''
if [ $# -eq 1 ]; then
  apiVersion="${1}"
fi

distributionHost='kittens.small-web.org'
LATEST_KITTEN_DISTRIBUTION_DOWNLOAD_URL="https://${distributionHost}/download/${apiVersion}"

# Paths

# Binary and configuration path usage adheres to 
# freedesktop.org XDG Base Directory Specification
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
# HOME is unset under some systemd units and in some containers.
: "${HOME:=$(eval echo "~$(id -un)")}"
BINARY_HOME="${HOME}/.local/bin"
KITTEN_BINARY_PATH="${BINARY_HOME}/kitten"
KITTEN_NPM_BINARY_PATH="${BINARY_HOME}/kitten-npm"
KITTEN_NODE_BINARY_PATH="${BINARY_HOME}/kitten-node"

case "${XDG_DATA_HOME:-}" in
  /*) DATA_HOME="$XDG_DATA_HOME" ;;
  *)  DATA_HOME="$HOME/.local/share" ;;
esac
SMALL_TECH_DATA_HOME="${DATA_HOME}/small-tech.org"

KITTEN_DATA_HOME="${SMALL_TECH_DATA_HOME}/kitten"
KITTEN_DATA_DIRECTORY="${KITTEN_DATA_HOME}/data"
KITTEN_APP_DIRECTORY_PATH="${KITTEN_DATA_HOME}/app"
KITTEN_VERSIONS_DIRECTORY="${KITTEN_DATA_HOME}/versions"
KITTEN_RUNTIMES_DIRECTORY="${KITTEN_DATA_HOME}/runtimes"
KITTEN_INCOMING_DIRECTORY="${KITTEN_VERSIONS_DIRECTORY}/.incoming"

KITTEN_TEMP_DIRECTORY="$(mktemp -d)"
trap 'rm -rf "${KITTEN_TEMP_DIRECTORY}"' EXIT
KITTEN_TEMP_RUNTIME_ARCHIVE_PATH="${KITTEN_TEMP_DIRECTORY}/runtime.tar.xz"
KITTEN_TEMP_LATEST_KITTEN_DISTRIBUTION_PATH="${KITTEN_TEMP_DIRECTORY}/kitten.tar.xz"

KITTEN_RUNTIME_DIRECTORY="${KITTEN_DATA_HOME}/runtime"
KITTEN_RUNTIME_NODE_BINARY_PATH="${KITTEN_RUNTIME_DIRECTORY}/bin/node"
KITTEN_RUNTIME_NPM_BINARY_PATH="${KITTEN_RUNTIME_DIRECTORY}/bin/npm"

case "${XDG_CONFIG_HOME:-}" in
  /*) CONFIG_HOME="$XDG_CONFIG_HOME" ;;
  *)  CONFIG_HOME="$HOME/.config" ;;
esac
KITTEN_SYSTEMD_UNIT_PATH="${CONFIG_HOME}/systemd/user/kitten.service"

mkdir -p "${KITTEN_VERSIONS_DIRECTORY}" "${KITTEN_RUNTIMES_DIRECTORY}"
chmod 700 "${KITTEN_DATA_HOME}"

# Ensure unprivileged binary folder exists (as we will be symlinking
# to the Kitten binary from there.)
mkdir -p "${BINARY_HOME}"

if ! command -v 'curl' > /dev/null 2>&1 && ! command -v 'wget' > /dev/null 2>&1; then
  echo '❌ Neither curl nor wget found on system.'
  echo "   (Please install one or the other and run the installer again.)"
  exit 1
fi

# A download should run silently and exit the installer with a message on any failure.
download () {
  if command -v 'curl' > /dev/null 2>&1; then
    curl --fail --silent --show-error --location --output "$2" "$1" || { echo "❌ Could not download ${1}." >&2; exit 1; }
  else
    wget --no-verbose --output-document "$2" "$1" || { echo "❌ Could not download ${1}." >&2; exit 1; }
  fi
}

removeDirectory () {
  case "$1" in
    */../*|*/..) echo "❌ Refusing to remove ${1}, which contains a .. segment." >&2; exit 1 ;;
    "${KITTEN_DATA_HOME}"/?*) ;;
    *) echo "❌ Refusing to remove ${1}, which is outside ${KITTEN_DATA_HOME}." >&2; exit 1 ;;
  esac
  [ -e "$1" ] || [ -L "$1" ] || return 0
  # An interrupted removal leaves a partial directory only at .removing.
  removingPath="$(dirname "$1")/.removing"
  rm -rf "${removingPath}" || { echo "❌ Could not remove ${removingPath}. Remove it and run the installer again." >&2; exit 1; }
  mv "$1" "${removingPath}"
  rm -rf "${removingPath}" || { echo "❌ Could not remove ${removingPath}. Remove it and run the installer again." >&2; exit 1; }
}

# Plain mv would move the new link into the directory the old one points to.
pointSymlink () {
  rm -f "$1.new"
  ln -s "$2" "$1.new"
  if mv --version > /dev/null 2>&1; then
    mv -T "$1.new" "$1"
  else
    mv -h "$1.new" "$1"
  fi
}

linkBinary () {
  if [ "$(readlink "$2")" != "$1" ]; then
    rm -f "$2"
    ln -s "$1" "$2"
  fi
}

pointRuntime () {
  pointSymlink "${KITTEN_RUNTIME_DIRECTORY}" "runtimes/$1"
  linkBinary "${KITTEN_RUNTIME_NPM_BINARY_PATH}" "${KITTEN_NPM_BINARY_PATH}"
  linkBinary "${KITTEN_RUNTIME_NODE_BINARY_PATH}" "${KITTEN_NODE_BINARY_PATH}"
}

versionField () {
  sed -n "s/^ *\"$2\": *\"*\([^\",]*\)\"*,*\$/\1/p" "$1/version.json" 2>/dev/null
}

# A version directory’s name is its exactVersion with every character outside [A-Za-z0-9._-] replaced by an underscore.
# A version.json with an empty field has no name.
versionNameOf () {
  exactVersion="$(versionField "$1" apiVersion)-$(versionField "$1" gitHash)-$(versionField "$1" nodeVersion)-$(versionField "$1" versionStamp)"
  case "${exactVersion}" in
    -*|*--*|*-) ;;
    *) printf '%s' "${exactVersion}" | tr -c 'A-Za-z0-9._-' '_' ;;
  esac
}

migrateToSymlink () {
  name="${3:-unknown}"
  if [ -d "$1" ] && [ ! -L "$1" ]; then
    if [ "$2" = runtimes ] && [ -d "${KITTEN_DATA_HOME}/$2/${name}" ]; then
      echo "  • Replacing ${1} with a link to ${KITTEN_DATA_HOME}/$2/${name}."
      removeDirectory "$1"
    else
      removeDirectory "${KITTEN_DATA_HOME}/$2/${name}"
      echo "  • Moving ${1} to ${KITTEN_DATA_HOME}/$2/${name}."
      mv "$1" "${KITTEN_DATA_HOME}/$2/${name}"
    fi
    pointSymlink "$1" "$2/${name}"
  fi
}

linkTargetName () {
  if [ -L "$1" ]; then basename "$(readlink "$1")"; fi
}

installRuntimeIfNecessary () {
  # Kitten always runs from its own runtime, even during development.
  # A runtime that crashes is treated as the wrong version and reinstalled.
  actualRuntimeVersion=$("${KITTEN_RUNTIMES_DIRECTORY}/$1/bin/node" --version 2>/dev/null || true)
  if [ "${actualRuntimeVersion}" = "v$1" ]; then
    echo "  • Runtime ${actualRuntimeVersion} already exists, not updating."
  else
    installRuntime "$1" "$2"
  fi
}

sha256Of () {
  if command -v 'sha256sum' > /dev/null 2>&1; then sha256sum "$1"; else shasum -a 256 "$1"; fi | cut -d ' ' -f 1
}

verifyRuntimeArchive () {
  checksumsPath="$2/runtime-checksums"
  [ -f "${checksumsPath}" ] || { echo "❌ ${checksumsPath} is missing." >&2; exit 1; }
  expectedChecksum="$(awk -v name="$1" '$2 == name { print $1 }' "${checksumsPath}" 2>/dev/null || true)"
  if [ -z "${expectedChecksum}" ]; then
    echo "❌ ${checksumsPath} has no checksum for ${1}." >&2
    exit 1
  fi
  if [ "$(sha256Of "${KITTEN_TEMP_RUNTIME_ARCHIVE_PATH}")" != "${expectedChecksum}" ]; then
    echo "❌ ${1} does not match its checksum in ${checksumsPath}." >&2
    exit 1
  fi
}

installRuntime () {
  runtimeDirectory="${KITTEN_RUNTIMES_DIRECTORY}/$1"
  runtimeArchiveName="node-v$1-${os}-$(mapArchitecture "$architecture").tar.xz"
  echo "  • Installing runtime v$1."
  download "${runtimePrefix}/v$1/${runtimeArchiveName}" "${KITTEN_TEMP_RUNTIME_ARCHIVE_PATH}"
  verifyRuntimeArchive "${runtimeArchiveName}" "$2"

  removeDirectory "${runtimeDirectory}.staging"
  mkdir -p "${runtimeDirectory}.staging"
  tar -xf "${KITTEN_TEMP_RUNTIME_ARCHIVE_PATH}" --directory="${runtimeDirectory}.staging" --strip-components=1

  # The npm launcher must use Kitten’s runtime, not any Node.js installed on the system.
  printf "#!/usr/bin/env %s\nrequire('../lib/cli.js')(process)\n" "${runtimeDirectory}/bin/node" > "${runtimeDirectory}.staging/bin/npm"

  removeDirectory "${runtimeDirectory}"
  mv "${runtimeDirectory}.staging" "${runtimeDirectory}"
}

checkDistribution () {
  newRuntimeVersion="$(cat "$1/node-version" 2>/dev/null || true)"
  case "${newRuntimeVersion}" in ''|*[!0-9.]*|.*|*..*|*.) echo "❌ $1/node-version must contain a Node.js version such as 24.21.0. A package without one is older than this installer." >&2; exit 1 ;; esac

  newVersion="$(versionNameOf "$1")"
  if [ -z "${newVersion}" ]; then
    echo "❌ $1/version.json must have apiVersion, gitHash, nodeVersion, and versionStamp." >&2
    exit 1
  fi
}

installDependencies () {
  previousVersionDirectory="${KITTEN_VERSIONS_DIRECTORY}/${previousVersion}"
  if [ -n "${previousVersion}" ] && [ -d "${previousVersionDirectory}/node_modules" ] && cmp -s "${previousVersionDirectory}/package.json" "$1/package.json"; then
    cp -R "${previousVersionDirectory}/node_modules" "$1/"
  fi

  # esbuild needs its binary for this platform at runtime.
  (
    cd "$1" || {
      echo "❌ Could not change to app directory: $1" >&2
      exit 1
    }
    PATH="${KITTEN_RUNTIMES_DIRECTORY}/${newRuntimeVersion}/bin:${PATH}" "${KITTEN_RUNTIMES_DIRECTORY}/${newRuntimeVersion}/bin/npm" install --loglevel=error > /dev/null || {
      echo "❌ Installing esbuild runtime dependency via npm install failed" >&2
      exit 1
    }
  )
}

if [ "${1:-}" = '--stage' ]; then
  stageDirectory="${2:-}"
  [ -d "${stageDirectory}" ] || { echo "❌ install --stage needs the path of an extracted distribution, and ${stageDirectory} is not a directory." >&2; exit 1; }
  checkDistribution "${stageDirectory}"
  installRuntimeIfNecessary "${newRuntimeVersion}" "${stageDirectory}"
  previousVersion="$(linkTargetName "${KITTEN_APP_DIRECTORY_PATH}")"
  installDependencies "${stageDirectory}"
  echo "  • Staged ${newVersion} in ${stageDirectory}."
  exit 0
fi

migrateToSymlink "${KITTEN_APP_DIRECTORY_PATH}" versions "$(versionNameOf "${KITTEN_APP_DIRECTORY_PATH}")"
migratedRuntimeVersion="$("${KITTEN_RUNTIME_NODE_BINARY_PATH}" --version 2>/dev/null || true)"
case "${migratedRuntimeVersion}" in
  ''|v|[!v]*|v*[!0-9.]*|v.*|*..*|*.) migratedRuntimeVersion='' ;;
esac
migrateToSymlink "${KITTEN_RUNTIME_DIRECTORY}" runtimes "${migratedRuntimeVersion#v}"

previousVersion="$(linkTargetName "${KITTEN_APP_DIRECTORY_PATH}")"

removeDirectory "${KITTEN_INCOMING_DIRECTORY}"
mkdir -p "${KITTEN_INCOMING_DIRECTORY}"

# BASH_SOURCE[0] is empty when the script is piped in.
checkoutDirectory=''
if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
  checkoutDirectory="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
fi

# Detect whether the script is being run from a checkout
# (i.e., during development) or being piped to bash
# via the online installation instructions. If it’s the latter,
# we’ll skip the build process and deploy as quickly as
# possible using the pre-built distribution directory.
if [ -n "${checkoutDirectory}" ] && [ -x "${checkoutDirectory}/build" ]; then
  ############################################################
  # Development install (local).
  ############################################################
  echo '  • Development install detected.'

  # originalWorkingDirectory resolves a relative kittenPackageFile after the cd.
  originalWorkingDirectory="$(pwd)"
  # shellcheck disable=SC1007
  CDPATH= cd "${checkoutDirectory}"

  installRuntimeIfNecessary "${runtimeVersion#v}" "${checkoutDirectory}/build-templates"
  pointRuntime "${runtimeVersion#v}"

  # Install npm dependencies if asked to, if node modules have not been
  # installed yet, or if the package-lock file has changed.
  # (npm refreshes node_modules/.package-lock.json on every install.)
  if [ "$1" = '--npm' ] || [ ! -d ./node_modules ] || [ package-lock.json -nt node_modules/.package-lock.json ]; then
    # Ensure npm dependencies are up to date.
    echo '  • Installing dependencies via npm.'
    PATH="${KITTEN_RUNTIMES_DIRECTORY}/${runtimeVersion#v}/bin:${PATH}" "${KITTEN_RUNTIMES_DIRECTORY}/${runtimeVersion#v}/bin/npm" install
  else
    echo '  • Skipping npm install step (pass --npm to NOT skip)'
  fi

  # If a path to a tar.xz package is provided as the first
  # positional argument in a development install, we will
  # attempt to install that Kitten package instead of the
  # current latest source.
  if [ $# -eq 1 ] && [ "$1" != '--npm' ]; then
    kittenPackageFile="${1}"
    case "${kittenPackageFile}" in
      /*) ;;
      *) kittenPackageFile="${originalWorkingDirectory}/${kittenPackageFile}" ;;
    esac
    echo "  • Installing from local package file at ${kittenPackageFile}"

    # Extract specified Kitten distribution.
    tar -xf "${kittenPackageFile}" --directory="${KITTEN_INCOMING_DIRECTORY}" --strip-components=1
  else
    # Create distribution build.
    echo '  • Creating distribution build.'
    ./build

    # Copy distribution folder to installation location.
    echo '  • Copying app directory.'
    cp -R dist/. "${KITTEN_INCOMING_DIRECTORY}"
  fi
else
  ############################################################
  # Web install (we’re being piped into bash).
  ############################################################
  echo '  • Web install detected.'
  webInstall='true'

  # Download latest Kitten distribution (or the latest for the
  # requested API version, if any).
  if [ $# -eq 1 ]; then
    echo "  • Installing latest Kitten distribution for API version ${apiVersion}."
  else
    echo '  • Installing latest Kitten distribution.'
  fi
  download "${LATEST_KITTEN_DISTRIBUTION_DOWNLOAD_URL}" "${KITTEN_TEMP_LATEST_KITTEN_DISTRIBUTION_PATH}"

  # Extract latest Kitten distribution.
  tar -xf "${KITTEN_TEMP_LATEST_KITTEN_DISTRIBUTION_PATH}" --directory="${KITTEN_INCOMING_DIRECTORY}" --strip-components=1
fi

checkDistribution "${KITTEN_INCOMING_DIRECTORY}"
installRuntimeIfNecessary "${newRuntimeVersion}" "${KITTEN_INCOMING_DIRECTORY}"

newVersionDirectory="${KITTEN_VERSIONS_DIRECTORY}/${newVersion}"

# A running server may be using an existing version directory.
if [ -d "${newVersionDirectory}" ] && [ "$(versionNameOf "${newVersionDirectory}")" = "${newVersion}" ]; then
  echo "  • Version ${newVersion} is already installed, using it."
  removeDirectory "${KITTEN_INCOMING_DIRECTORY}"
else
  installDependencies "${KITTEN_INCOMING_DIRECTORY}"
  removeDirectory "${newVersionDirectory}"
  mv "${KITTEN_INCOMING_DIRECTORY}" "${newVersionDirectory}"
fi

if [ "${webInstall:-}" = 'true' ] && [ -f "${KITTEN_SYSTEMD_UNIT_PATH}" ]; then
  echo "  • A Kitten service is deployed. Updating it with kitten update."
  status=0; "${newVersionDirectory}/kitten" update < /dev/null || status=$?
  exit "${status}"
fi

pointSymlink "${KITTEN_APP_DIRECTORY_PATH}" "versions/${newVersion}"
pointRuntime "${newRuntimeVersion}"
linkBinary "${KITTEN_APP_DIRECTORY_PATH}/kitten" "${KITTEN_BINARY_PATH}"

runtimeVersionOf () {
  cat "${KITTEN_VERSIONS_DIRECTORY}/$1/node-version" 2>/dev/null || true
}

pruneVersionsAndRuntimes () {
  [ -n "${previousVersion}" ] || return 0
  [ "${newVersion}" != "${previousVersion}" ] || return 0
  previousVersionRuntimeVersion="$(runtimeVersionOf "${previousVersion}")"
  keptRuntimeVersions=" ${newRuntimeVersion} ${previousVersionRuntimeVersion} "
  for directory in "${KITTEN_VERSIONS_DIRECTORY}"/*/ "${KITTEN_RUNTIMES_DIRECTORY}"/*/; do
    [ -d "${directory}" ] || continue
    name="$(basename "${directory}")"
    case "${name}" in *.staging) continue ;; esac
    case "${directory}" in
      "${KITTEN_VERSIONS_DIRECTORY}"/*) kept=" ${newVersion} ${previousVersion} " ;;
      *)
        # The installer prunes runtimes only when the previous version has a node-version file.
        [ -n "${previousVersionRuntimeVersion}" ] || continue
        kept="${keptRuntimeVersions}"
        ;;
    esac
    case "${kept}" in
      *" ${name} "*) ;;
      *) removeDirectory "$(dirname "${directory}")/${name}" ;;
    esac
  done
}

pruneVersionsAndRuntimes

# Create Kitten data directory.
# (We do this here so the data migration script can correctly determine if
# this is a new version install or not.)
mkdir -p "${KITTEN_DATA_DIRECTORY}"

# Check if privileged ports are disabled and, if not, disable them.

styleBold='\e[1m'
styleGreen="\e[0;32m"
styleBoldGreen="\e[1;32m"
styleBlueItalic="\e[3;34m"
styleBoldBlue="\e[1;34m"
styleYellowItalic="\e[3;33m"
styleBoldBlack="\e[1;30m"
styleReset="\e[0m"

if [ "${os}" = 'linux' ]; then
  unprivilegedPortStart=$(/sbin/sysctl -n net.ipv4.ip_unprivileged_port_start)
  if [ "$unprivilegedPortStart" -le 80 ]; then
    echo "  • Unprivileged ports start at ${unprivilegedPortStart}, no action necessary."
    printf "    %sWe can bind to ports 80 and 443 without requiring elevated privileges.%s\n" "$styleBlueItalic" "$styleReset"
  else
    printf "  • Unprivileged ports start at %s, %swill attempt to reduce it to 80%s\n" "$unprivilegedPortStart" "$styleYellowItalic" "$styleReset"
    printf "    %sso we can bind to ports 80 and 443 without elevated privileges.%s\n" "$styleYellowItalic" "$styleReset"
    echo ''
    printf "    %sThis may ask you for your system password.%s\n" "$styleBoldBlack" "$styleReset"

    # Reduce unprivileged ports for this session…
    # (Silence warning: suppress all messages; redirect stderr to stdout and stdout to /dev/null)
    # shellcheck disable=SC2069
    sudo /sbin/sysctl -w net.ipv4.ip_unprivileged_port_start=80 2>&1 1>/dev/null

    # …and also persist the change by writing a sysctl configuration file.
    sysctlConfigurationFilePath='/etc/sysctl.d/99-reduce-unprivileged-port-start-to-80.conf'
    # (Silence warning: suppress all messages; redirect stderr to stdout and stdout to /dev/null)
    # shellcheck disable=SC2069
    echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee "${sysctlConfigurationFilePath}" 2>&1 1>/dev/null
    sudo chmod 0777 "${sysctlConfigurationFilePath}"

    echo ''
    printf "    %sDone. Unprivileged ports now start at 80.%s\n" "$styleGreen" "$styleReset"
    printf "    %sChange also persisted in /etc/sysctl.d/99-reduce-unprivileged-port-start-to-80.conf\n" "$styleGreen"
    printf "    %sWe can bind to ports 80 and 443 without requiring elevated privileges.%s\n" "$styleBlueItalic" "$styleReset"
  fi
fi

if [ "${os}" != 'darwin' ]; then
  # Timing code courtesy of https://stackoverflow.com/a/3684051/92548
  T="$(($(date +%s%N)-T))"
  S="$((T/1000000000))"
  M="$((T%1000000000/1000000))"

  printf "\nInstalled in %ds %03dms!\n\n" "$((S%60))" "${M}"
else
  printf "\nInstalled.\n\n"
fi

if [ "${os}" = 'darwin' ]; then
  # Tell the person to add ~/.local/bin to their path.
  echo "╭───────────────────────────────╮"
  printf "│ %b │\n" "${styleBoldBlue}macOS Additional Instructions${styleReset}"
  echo "╰───────────────────────────────╯"
  echo ""
  printf "  %b\n" "${styleBold}1. Update your system’s path to add the location of the kitten command.${styleReset}"
  echo ""
  printf "     %b\n" "${styleBoldBlue}zsh (default shell on macOS):${styleReset}"
  echo ""
  printf "     %b\n" "${styleBoldGreen}echo \"export PATH=\$PATH:~/.local/bin\" >> ~/.zshrc${styleReset}"
  printf "     %b\n" "${styleBoldGreen}source ~/.zshrc${styleReset}"
  echo ""
  printf "     %b\n" "${styleBoldBlue}Fish shell${styleReset}"
  echo ""
  printf "     %b\n" "${styleBoldGreen}echo 'set -gx PATH \"~/.local/bin\" \"\$PATH\"' >> ~/.config/fish/config.fish${styleReset}"
  printf "     %b\n" "${styleBoldGreen}source ~/.config/fish/config.fish${styleReset}"
  echo ""
  printf "  %b\n" "${styleBold}2. Add localhost aliases to your hosts file.${styleReset}"
  echo ""
  printf "     If you want to test peer-to-peer apps locally, edit your %b file\n" "${styleGreen}/etc/hosts${styleReset}"
  printf "     to ensure your %b line resembles the one below:\n" "${styleGreen}localhost${styleReset}"
  echo ""
  printf "     %b\n" "${styleBoldGreen}127.0.0.1  localhost place1.localhost place2.localhost place3.localhost place4.localhost${styleReset}"
  echo ""

  # (Silence warning: suppress all messages; redirect stderr to stdout and stdout to /dev/null)
  # shellcheck disable=SC2069
  export PATH=$PATH:~/.local/bin 2>&1 1>/dev/null
fi

if [ "${os}" = 'linux' ]; then
  if ! echo ":$PATH:" | grep -q ":$HOME/.local/bin:"; then
    # Tell the person to add ~/.local/bin to their path.
    echo "╭───────────────────────────────────────────────────────╮"
    echo "│                                                       │"
    printf "│ %b                               │\n" "${styleBoldBlue}Additional instructions${styleReset}"
    echo "│                                                       │"
    echo "│ Before you can use the kitten command, you must       │"
    echo "│ update your system’s path to add the location of      │"
    echo "│ the kitten command.                                   │"
    echo "│                                                       │"
    echo "│ Please add the following directory to the end of the  │"
    echo "│ PATH environment variable for your shell of choice:   │"
    echo "│                                                       │"
    printf "│ %b                                      │\n" "${styleBoldGreen}\$HOME/.local/bin${styleReset}"
    echo "│                                                       │"
    echo "│ Please note that on some distributions simply         │"
    echo "│ logging out and logging back in will add that folder  │"
    echo "│ to your path automatically.                           │"
    echo "│                                                       │"
    printf "│ %b  │\n" "${styleBlueItalic}For shell-specific instructions and code, please see${styleReset}"
    printf "│ %b │\n" "${styleBlueItalic}https://codeberg.org/aral/gists/src/local-bin-path.md${styleReset}"
    echo "│                                                       │"
    echo "╰───────────────────────────────────────────────────────╯"
    echo ''
  fi
fi

echo 'Run using: kitten [path to serve]'
