#!/bin/sh
# script/bootstrap: install all dependencies needed to build and develop
# this Hugo site, idempotently. Base tooling comes from nix, apt, brew,
# or apk (detected in that order); assumes NOTHING is present (not git,
# make, go, hugo, or node). Installs hugo (the site build, at the exact
# version pinned below) and node/npm (prettier, used to format the
# repo's own markdown docs). Every install is guarded by a check so
# already-installed tools are skipped.
set -eu

ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"

PKGMGR=""
SUDO=""

# --- Hugo -------------------------------------------------------------
#
# Hugo produces the published artifact, so its version is a property of
# the site's output, not of the build environment. It is therefore
# pinned here rather than taken from whatever the distro serves: before
# this, alpine 3.21's apk supplied hugo 0.139.0 -- a version chosen by
# nobody, roughly two years behind upstream, and liable to change
# silently whenever the base image digest moves.
#
# `go install` is the hash-verified mechanism: Go checks the module
# against the sum.golang.org checksum database. That is the mechanism
# REPO_POLICIES.md already names for Go, and it needs no hand-maintained
# sha256. The other tools this script installs stay on the package
# manager, which #19 settled is fine for build-time conveniences.
#
# hugo v0.164.0, 2026-07-06
HUGO_VERSION="v0.164.0"

# hugo v0.164.0's go.mod requires go >= 1.26.0, and alpine 3.21's `go`
# package is 1.23.9 built with GOTOOLCHAIN=local, so a bare `go install`
# refuses to run at all. Naming the toolchain explicitly makes Go fetch
# it through the module proxy and verify it against sum.golang.org like
# any other module, so the chain stays hash-verified end to end -- and
# the Go version that compiles hugo becomes deliberate too, instead of
# being inherited from whatever the base image happens to ship.
# go1.26.5, 2026-08-09
HUGO_GOTOOLCHAIN="go1.26.5"

# Standard hugo, not hugo extended: CGO_ENABLED=0 is deliberate.
# Verified that this site uses nothing extended provides -- there are no
# .scss/.sass files, no resources.ToCSS, no PostCSS, and no image
# processing (.Resize/.Fill/.Fit/images.* are all absent). The CSS is
# plain and inlined by `readFile` in baseof.html. The `+extended` on the
# apk build this replaces was incidental, not a requirement, so do not
# assume a future change needs it without rechecking the above.
HUGO_CGO_ENABLED="0"

# Where the hugo binary lands. It has to be on the default PATH of a
# *fresh* shell, not just of this script: the Dockerfile's `RUN make
# check` and deploy.yml's `script/test` step each start their own shell
# and would never see a GOPATH bin directory. Overridable so an
# unprivileged install can point somewhere writable.
HUGO_BIN_DIR="${HUGO_BIN_DIR:-/usr/local/bin}"

detect_pkgmgr() {
    [ -n "$PKGMGR" ] && return 0
    if command -v nix-env >/dev/null 2>&1; then
        PKGMGR="nix"
    elif command -v apt-get >/dev/null 2>&1; then
        PKGMGR="apt"
    elif command -v brew >/dev/null 2>&1; then
        PKGMGR="brew"
    elif command -v apk >/dev/null 2>&1; then
        PKGMGR="apk"
    else
        echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&2
        exit 1
    fi
    if [ "$PKGMGR" = "apt" ]; then
        export DEBIAN_FRONTEND=noninteractive
        detect_sudo
        $SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
    fi
}

detect_sudo() {
    if [ -z "$SUDO" ] && [ "$(id -u)" != "0" ]; then
        SUDO="sudo"
    fi
}

# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
pkg_install() {
    detect_pkgmgr
    case "$PKGMGR" in
        nix) nix-env -iA "nixpkgs.$1" ;;
        apt) $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2" ;;
        brew) brew install "$3" ;;
        apk) apk add --no-cache "$4" ;;
    esac
}

missing() {
    ! command -v "$1" >/dev/null 2>&1
}

# True when the hugo already on PATH is the pinned version. Unlike the
# other tools, mere presence is not good enough here: an older hugo has
# to be replaced, not accepted, or the pin means nothing.
hugo_pinned() {
    command -v hugo >/dev/null 2>&1 || return 1
    # `hugo version` prints e.g. "hugo v0.164.0 linux/amd64 ..." for a
    # `go install` build, or "hugo v0.164.0-ce2470e+extended ..." for an
    # official release binary. Compare only the vX.Y.Z part: a build of
    # the same version that happens to be extended renders this site
    # identically (see HUGO_CGO_ENABLED above), so there is no reason to
    # overwrite a developer's existing matching install.
    have="$(hugo version 2>/dev/null | awk '{print $2}' | sed 's/[-+].*//')"
    [ "$have" = "$HUGO_VERSION" ]
}

install_hugo() {
    detect_sudo

    # The Go toolchain is a build-time convenience like git and make, so
    # it comes from the package manager; the thing that must be
    # deliberate is what it builds, which HUGO_VERSION and
    # HUGO_GOTOOLCHAIN pin.
    if missing go; then pkg_install go golang-go go go; fi

    # Build as the invoking user into a scratch GOBIN, then place the
    # binary with `install`. Running the whole `go install` under sudo
    # would work but would populate root's module cache instead of the
    # user's, which is needlessly slow and surprising on a workstation.
    gobin="$(mktemp -d)"
    CGO_ENABLED="$HUGO_CGO_ENABLED" \
        GOTOOLCHAIN="$HUGO_GOTOOLCHAIN" \
        GOBIN="$gobin" \
        go install "github.com/gohugoio/hugo@${HUGO_VERSION}"
    $SUDO install -d "$HUGO_BIN_DIR"
    $SUDO install -m 0755 "$gobin/hugo" "$HUGO_BIN_DIR/hugo"
    rm -rf "$gobin"

    # Drop any cached PATH lookup of the hugo we just replaced, so the
    # check below tests the new binary and not the old one.
    hash -r 2>/dev/null || true

    # Fail loudly rather than let a later build run on a shadowing hugo
    # from somewhere earlier in PATH.
    if ! hugo_pinned; then
        echo "bootstrap: installed $HUGO_VERSION into $HUGO_BIN_DIR but" \
            "'hugo' on PATH is still $(hugo version 2>/dev/null || echo absent)" >&2
        exit 1
    fi
}

main() {
    cd "$ROOT"

    # Base tooling.
    if missing git; then pkg_install git git git git; fi
    if missing make; then pkg_install gnumake make make make; fi

    # The theme is vendored in-repo, but initialise submodules if any
    # are ever added so a fresh clone is buildable.
    if [ -f .gitmodules ]; then
        git submodule update --init --recursive
    fi

    # Site build. Pinned and hash-verified -- see the HUGO_* constants.
    if ! hugo_pinned; then install_hugo; fi

    # node/npm provide prettier (via npx) for formatting the docs.
    if missing node; then pkg_install nodejs nodejs node nodejs; fi
    if missing npx; then pkg_install nodejs npm npm npm; fi

    echo "bootstrap complete"
}

main "$@"
