#!/bin/bash
# =============================================================================
# Officer — install
# =============================================================================
#
# One command, blank machine to running platform. It runs the two halves in
# order and does nothing else itself:
#
#   setup/machine-setup/machine-setup.sh   a usable machine — packages, tailnet,
#                                          runtimes, docker, shell
#   setup/officer-setup.sh                 the platform on top of it — repo,
#                                          dependencies, postgres, .env, secret
#                                          store, schema, build, pm2
#
# They stay two scripts because they answer two different questions and are worth
# running separately: a machine you already trust needs only the second, and a
# machine you are rebuilding needs only the first. This is the wrapper for the
# case where you want both, which is most first runs.
#
# Both are re-runnable. Each remembers the steps it finished and skips them, so
# stopping halfway and coming back costs nothing.
#
# Run it as yourself — it asks for administrator rights when it needs them.
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MACHINE="$SCRIPT_DIR/setup/machine-setup/machine-setup.sh"
OFFICER="$SCRIPT_DIR/setup/officer-setup.sh"

BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

say() { echo -e "$*"; }
die() {
  echo -e "${YELLOW}error:${NC} $*" >&2
  exit 1
}

[[ -r "$MACHINE" ]] || die "missing $MACHINE"
[[ -r "$OFFICER" ]] || die "missing $OFFICER"

# Which halves to run. Both by default.
RUN_MACHINE=true
RUN_OFFICER=true

# Kept before the loop below eats them: this script re-executes itself through sudo
# further down, and `shift` would otherwise leave it re-running with no arguments —
# silently dropping --officer-only and turning a platform-only run into a full one.
#
# The `${x[@]+"${x[@]}"}` form is for `set -u`: expanding an empty array unquoted-safe
# is an error on bash before 4.4, and this runs on whatever the machine came with.
ORIGINAL_ARGS=(${@+"$@"})

# A `while`/`shift` loop rather than `for arg in "$@"`, because --repo takes a value
# and a for-loop cannot consume the argument after it.
while [[ $# -gt 0 ]]; do
  case "$1" in
    --machine-only) RUN_OFFICER=false ;;
    --officer-only) RUN_MACHINE=false ;;
    --repo)
      [[ -n "${2:-}" ]] || die "--repo needs a URL"
      OFFICER_REPO="$2"
      shift
      ;;
    --repo=*) OFFICER_REPO="${1#--repo=}" ;;
    # Every question that HAS a default answers itself. The ones with no possible
    # default still ask — see the note above the run below.
    --unattended | -y)
      export UNATTENDED=1 ASSUME_YES=1
      ;;
    -h | --help)
      say "usage: install.sh [--machine-only | --officer-only] [--repo <url>]"
      say ""
      say "  no flags        both halves, machine first"
      say "  --machine-only  stop after the machine is provisioned"
      say "  --officer-only  the platform only, on a machine you already trust"
      say "  --repo <url>    clone the platform from here instead of the default"
      say "  --unattended    take the default for every question that has one (-y)"
      say ""
      say "  The default is a private Gitea over SSH, which only authenticates on a"
      say "  machine whose key it already knows. Pass an https URL on a fresh box."
      say ""
      say "  --unattended still asks the questions that have no possible default:"
      say "    the username, the Tailscale control plane / login server / auth key,"
      say "    an SSH public key when the account has none, and the git identity."
      say "  Answer those ahead of time with SETUP_USERNAME, TS_LOGIN_SERVER,"
      say "  TS_AUTHKEY and TIMEZONE to reduce it further."
      exit 0
      ;;
    *) die "unknown option: $1" ;;
  esac
  shift
done

# Exported so `officer-setup.sh` reads it from the environment and this script does
# not have to forward arguments it does not own. `lib/repo.sh` takes it as
# `${OFFICER_REPO:-<default>}`, so unset here still means the default there.
[[ -n "${OFFICER_REPO:-}" ]] && export OFFICER_REPO

KERNEL="$(uname -s)"
case "$KERNEL" in
  Darwin)
    [[ "$EUID" -eq 0 ]] && die "do not run this with sudo on macOS — Homebrew refuses to run as root. Run it as yourself."
    ;;
  Linux) ;;
  *) die "unsupported system: $KERNEL. Officer installs on Linux and macOS." ;;
esac

SELF="$SCRIPT_DIR/install.sh"

# One report for the whole run, not one per half. Both scripts append to this
# file, so the person reviewing it sees a single account of what happened rather
# than two they have to stitch together and hope are complete.
#
# Exported before either half starts, and timestamped once here — if each script
# made its own name they would differ by however long the first one took.
export REPORT_FILE="${REPORT_FILE:-${HOME}/officer-install-report-$(date '+%Y%m%d-%H%M%S').md}"

# ── Privileges: asked for, not demanded ──
#
# Run this as YOURSELF. On Linux it needs root for apt, systemd units, useradd,
# netplan, ufw and for creating directories owned by the service account — so it
# asks, once, through sudo, and re-executes itself. Typing `sudo` yourself works
# too and changes nothing, but it should not be the price of starting.
#
# Variables are passed to sudo explicitly rather than with -E. `env_reset` is the
# sudoers default and strips the environment, which is how DATA_PATH was lost
# once already; naming them on the command line survives it.
#
# macOS never escalates. Homebrew refuses to run as root, and nothing in the
# macOS path needs it — the account running this IS the owner, so there is
# nothing to chown and nothing to drop privileges to.
if [[ "$KERNEL" != "Darwin" && "$EUID" -ne 0 ]]; then
  command -v sudo >/dev/null 2>&1 || die "this needs root and sudo is not installed — run it as root"
  say ""
  say "  This needs administrator rights. You will be asked for your password."
  say ""
  exec sudo \
    OFFICER_ROOT="${OFFICER_ROOT:-}" \
    SETUP_USERNAME="${SETUP_USERNAME:-}" \
    MACHINE_ROLE="${MACHINE_ROLE:-}" \
    REPORT_FILE="${REPORT_FILE:-}" \
    UNATTENDED="${UNATTENDED:-}" \
    ASSUME_YES="${ASSUME_YES:-}" \
    OFFICER_REPO="${OFFICER_REPO:-}" \
    bash "$SELF" ${ORIGINAL_ARGS[@]+"${ORIGINAL_ARGS[@]}"}
fi


say ""
say "${BOLD}Officer install${NC}"
say "  system:   $KERNEL"
$RUN_MACHINE && say "  1/2       machine setup"
$RUN_OFFICER && say "  $($RUN_MACHINE && echo 2/2 || echo 1/1)       officer setup"
say ""
say "  Either half can be run on its own later:"
say "    scripts/setup/machine-setup/machine-setup.sh"
say "    scripts/setup/officer-setup.sh"
say ""

# Not `set -e`'s job: a half that exits non-zero should say which half, and stop
# before the next one starts on a machine that is not ready for it.
# ── Who says "you are still root" ──
#
# Both halves end as root and both need to say so, but only the LAST one to run
# should — otherwise a full install says it twice, once in the middle where it is
# wrong, because officer-setup is about to run and still needs the privilege.
#
# So the rule is "say it if nothing follows you", and this is the only place that
# knows whether anything does.
if $RUN_MACHINE; then
  $RUN_OFFICER && export OFFICER_SETUP_FOLLOWS=1
  bash "$MACHINE" || die "machine setup did not finish — fix what it reported, then run this again"
  unset OFFICER_SETUP_FOLLOWS
fi

if $RUN_OFFICER; then
  bash "$OFFICER" || die "officer setup did not finish — fix what it reported, then run this again"
fi

say ""
say "${GREEN}Done.${NC}"
