#!/bin/sh
#
# packages.modpagespeed.com install bootstrap
#
# Detects the local distro family and configures the apt or yum repository
# pointing at packages.modpagespeed.com (prod). Run as root.
#
#   curl -fsSL https://packages.modpagespeed.com/install.sh | sudo sh
#
# Or to target the auth-gated staging endpoint, set credentials via env vars
# BEFORE piping (so they don't show up on the curl command-line / ps output):
#
#   export PACKAGES_URL=https://packages.modpagespeed.com/staging
#   export PACKAGES_USER=operator
#   export PACKAGES_PASS=...
#   curl -fsSL https://packages.modpagespeed.com/install.sh | sudo -E sh
#
# Subscriber suite (ADR-140): credentials issued by We-Amp select the
# /subscriber/ channel with one variable:
#
#   export PACKAGES_CHANNEL=subscriber
#   export PACKAGES_USER=sub0042
#   export PACKAGES_PASS=...
#   curl -fsSL https://packages.modpagespeed.com/install.sh | sudo -E sh
#
# PACKAGES_CHANNEL=subscriber sets PACKAGES_URL to .../subscriber unless
# PACKAGES_URL is given. The default channel is public.
#
# Credentials are written to a 0600 file (apt: /etc/apt/auth.conf.d/modpagespeed.conf;
# yum: /etc/yum.repos.d/modpagespeed.repo with username/password directives) so
# they DO NOT appear in /etc/apt/sources.list.d/ nor in shell history.

set -eu

PACKAGES_CHANNEL="${PACKAGES_CHANNEL:-public}"
case "$PACKAGES_CHANNEL" in
  public)     PACKAGES_URL="${PACKAGES_URL:-https://packages.modpagespeed.com}" ;;
  subscriber) PACKAGES_URL="${PACKAGES_URL:-https://packages.modpagespeed.com/subscriber}" ;;
  *) echo "modpagespeed-install: unknown PACKAGES_CHANNEL '$PACKAGES_CHANNEL' (expected: public, subscriber)" >&2; exit 1 ;;
esac
# Normalise trailing slashes so every derived URL is well formed.
while :; do
  case "$PACKAGES_URL" in */) PACKAGES_URL="${PACKAGES_URL%/}" ;; *) break ;; esac
done
PACKAGES_USER="${PACKAGES_USER:-}"
PACKAGES_PASS="${PACKAGES_PASS:-}"
KEYRING_PATH="/usr/share/keyrings/modpagespeed-archive-keyring.gpg"

die() { echo "modpagespeed-install: $*" >&2; exit 1; }

if [ "$PACKAGES_CHANNEL" = subscriber ] && { [ -z "$PACKAGES_USER" ] || [ -z "$PACKAGES_PASS" ]; }; then
  die "PACKAGES_CHANNEL=subscriber needs PACKAGES_USER and PACKAGES_PASS (the credential issued by We-Amp)"
fi

# Strip any embedded basic-auth from a URL for safe logging.
sanitize_url() {
  # Removes "user:pass@" from "scheme://user:pass@host/..." for log lines.
  echo "$1" | sed -E 's,(://)[^@/]+@,\1,'
}

# The signing key is public and lives at the site root, never under a channel
# prefix (/staging, /subscriber). Print scheme://host[:port] of PACKAGES_URL,
# with any embedded user:pass@ removed and the scheme matched case-insensitively.
site_root() {
  echo "$PACKAGES_URL" | sed -E 's,^([A-Za-z][A-Za-z0-9+.-]*://)([^/]*@)?([^/]+).*,\1\3,'
}

# fetch_key <url> <outfile>: public key download, never with credentials; for
# https, refuse to be downgraded (also across redirects).
fetch_key() {
  case "$1" in
    [Hh][Tt][Tt][Pp][Ss]://*) curl -fsSL --proto '=https' --proto-redir '=https' "$1" -o "$2" ;;
    *) curl -fsSL "$1" -o "$2" ;;
  esac
}

# Tests source this file with INSTALL_SH_LIB_ONLY=1 to exercise the helpers above.
if [ "${INSTALL_SH_LIB_ONLY:-}" = 1 ]; then return 0 2>/dev/null || exit 0; fi

ensure_pkg() {
  pkg="$1"
  if command -v "$pkg" >/dev/null 2>&1; then return 0; fi
  if command -v apt-get >/dev/null 2>&1; then
    apt-get update -qq && apt-get install -y "$pkg"
  elif command -v dnf >/dev/null 2>&1; then
    dnf install -y "$pkg"
  else
    die "no package manager found to install $pkg"
  fi
}

# ------------------------------------------------------------------------------
# Detect distro family
# ------------------------------------------------------------------------------

if [ -r /etc/os-release ]; then
  # shellcheck disable=SC1091
  . /etc/os-release
else
  die "/etc/os-release not found; cannot detect distro"
fi

family=
for id in ${ID:-} ${ID_LIKE:-}; do
  case "$id" in
    debian|ubuntu)                family=apt; break ;;
    rhel|centos|almalinux|rocky|fedora)  family=yum; break ;;
  esac
done
[ -n "$family" ] || die "unsupported distro: ID=${ID:-} ID_LIKE=${ID_LIKE:-}"

# ------------------------------------------------------------------------------
# Install the apt source + signing key (Ubuntu 24.04 noble at launch)
# ------------------------------------------------------------------------------

install_apt() {
  # Supported apt suites map 1:1 to the distro codename (the apt suite name ==
  # the codename). Each suite carries the nginx-module-pagespeed .deb built and
  # exact-version-pinned against that distro's stock nginx (ADR-070 P3):
  #   bullseye->1.18.0  bookworm->1.22.1  trixie->1.26.3  jammy->1.18.0  noble->1.24.0
  case "${VERSION_CODENAME:-}" in
    noble)    distro=noble ;;
    jammy)    distro=jammy ;;
    bookworm) distro=bookworm ;;
    bullseye) distro=bullseye ;;
    trixie)   distro=trixie ;;
    *) die "unsupported distro; supported: Ubuntu 22.04 jammy / 24.04 noble, Debian 11 bullseye / 12 bookworm / 13 trixie; found VERSION_CODENAME=${VERSION_CODENAME:-unknown}" ;;
  esac

  ensure_pkg curl
  # gpg is needed for the --dearmor below; minimal images (e.g. a bare noble
  # container) lack it. The yum path uses rpm --import and needs no gpg.
  ensure_pkg gpg

  echo "==> fetching signing key from $(sanitize_url "$(site_root)")/pubkey.gpg"
  # The key is public at the site root: no credentials are sent for it (and
  # none ever appear on a curl command line).
  pub_url="$(site_root)"
  tmpkey="$(mktemp)"
  fetch_key "$pub_url/pubkey.gpg" "$tmpkey"
  [ -s "$tmpkey" ] || die "pubkey.gpg fetch produced an empty file"
  gpg --dearmor < "$tmpkey" > "$KEYRING_PATH.tmp"
  [ -s "$KEYRING_PATH.tmp" ] || die "gpg --dearmor produced an empty keyring"
  mv "$KEYRING_PATH.tmp" "$KEYRING_PATH"
  chmod 0644 "$KEYRING_PATH"
  rm -f "$tmpkey"

  echo "==> writing /etc/apt/sources.list.d/modpagespeed.list"
  # Pin the apt source to the host's native dpkg architecture so a multi-arch
  # host (e.g. amd64 with arm64 foreign-arch enabled) fetches only its own
  # Packages index. The repo publishes amd64 + arm64 per suite (ADR-070 P3);
  # without the arch= qualifier apt would try every enabled arch.
  dpkg_arch="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
  # NOTE: the source file contains NO credentials — apt reads them from
  # /etc/apt/auth.conf.d/* (mode 0600) when needed. This keeps the sources
  # file world-readable without leaking the staging password.
  cat > /etc/apt/sources.list.d/modpagespeed.list <<EOF
deb [arch=$dpkg_arch signed-by=$KEYRING_PATH] $PACKAGES_URL/apt $distro main
EOF

  if [ -n "$PACKAGES_USER" ] && [ -n "$PACKAGES_PASS" ]; then
    echo "==> writing /etc/apt/auth.conf.d/modpagespeed.conf (0600)"
    install -d -m 0755 /etc/apt/auth.conf.d
    auth_host="$(site_root | sed -E 's,^[^:]+://,,')"
    auth_path="$(echo "$PACKAGES_URL" | sed -E 's,^[A-Za-z][A-Za-z0-9+.-]*://[^/]*(.*),\1,')"
    # Atomically create with 0600 before any bytes are written. Avoids the
    # umask race where `: > file` lands a world-readable file for the few
    # microseconds before chmod runs.
    install -m 0600 /dev/null /etc/apt/auth.conf.d/modpagespeed.conf
    chown root:root /etc/apt/auth.conf.d/modpagespeed.conf
    cat > /etc/apt/auth.conf.d/modpagespeed.conf <<EOF
machine $auth_host${auth_path:+$auth_path/} login $PACKAGES_USER password $PACKAGES_PASS
EOF
  fi

  echo "==> apt-get update"
  apt-get update -qq

  cat <<EOF

mod_pagespeed apt repository configured.

Next:
  Apache (1.15): sudo apt-get install mod-pagespeed
  nginx (1.15):  sudo apt-get install nginx-module-pagespeed
  nginx (2.0):   sudo apt-get install modpagespeed-2-nginx        # post-2.0 GA

EOF
}

# ------------------------------------------------------------------------------
# Install the yum source + signing key (AlmaLinux/RHEL 9 at launch)
# ------------------------------------------------------------------------------

install_yum() {
  # ADR-101: one el10 x86_64 build serves RHEL/Rocky/AlmaLinux/CloudLinux 10.
  case "${VERSION_ID:-}" in
    9*)  distro=el9 ;;
    10*) distro=el10 ;;
    *) die "only EL9 + EL10 (AlmaLinux/RHEL/Rocky/CloudLinux 9 or 10) are supported; found VERSION_ID=${VERSION_ID:-unknown}" ;;
  esac

  # el10 publishes x86_64 only; arm64 el10 is deferred (ADR-101 D7) and
  # yum/el10/aarch64 404s. Refuse it with a clear message rather than writing a
  # repo that breaks dnf. (el9 ships both arches, so this gate is el10-only.)
  if [ "$distro" = el10 ] && [ "$(uname -m)" = aarch64 ]; then
    die "EL10 arm64 is not yet available; only EL10 x86_64 is published. (EL9 ships both x86_64 and arm64.)"
  fi

  ensure_pkg curl

  # cPanel / EasyApache 4 detection. On a cPanel host the operator wants the EA4
  # module (ea-apache24-mod_pagespeed) built against EA4's bundled Apache, not
  # the stock mod_pagespeed RPM. We always WRITE the EA4 repo stanza (so the
  # package is one `--enablerepo=ea4` away on any host) but only ENABLE it by
  # default on a cPanel server, so EA4 RPMs never leak onto non-cPanel EL9 boxes.
  if [ -d /usr/local/cpanel ] || [ -f /usr/local/cpanel/version ] || \
     command -v whmapi1 >/dev/null 2>&1; then
    is_cpanel=1
    # EA4 trees are live for el9 + el10 (ea4/el10 shipped with v1.15.0+r14;
    # ADR-101), so a cPanel host on either gets the [ea4] stanza ENABLED. el8 is
    # build-only (no public tree). Non-cPanel hosts keep it written-but-disabled.
    ea4_enabled=1
  else
    is_cpanel=0
    ea4_enabled=0
  fi

  echo "==> importing signing key"
  pub_url="$(site_root)"   # public key at the site root; sent without credentials
  fetch_key "$pub_url/pubkey.gpg" /etc/pki/rpm-gpg/RPM-GPG-KEY-modpagespeed
  [ -s /etc/pki/rpm-gpg/RPM-GPG-KEY-modpagespeed ] || \
    die "pubkey.gpg fetch produced an empty file"
  rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-modpagespeed

  echo "==> writing /etc/yum.repos.d/modpagespeed.repo"
  # yum/dnf has no equivalent to apt auth.conf.d. When staging credentials
  # are passed, they're embedded in this repo file — we create it 0600
  # atomically (avoiding the umask race where the password lands in a
  # world-readable file for a few microseconds). Prod (no creds) is
  # world-readable as expected.
  repo_path=/etc/yum.repos.d/modpagespeed.repo
  if [ -n "$PACKAGES_USER" ] && [ -n "$PACKAGES_PASS" ]; then
    install -m 0600 /dev/null "$repo_path"
    chown root:root "$repo_path"
  else
    install -m 0644 /dev/null "$repo_path"
  fi
  {
    echo "[modpagespeed]"
    echo "name=mod_pagespeed repository"
    # $basearch is emitted LITERALLY (\$) so dnf expands it per-host: x86_64 on
    # Intel (byte-identical to the pre-arm64 URL), aarch64 on arm64 (ADR-070 P2
    # el9 arm64). The served generic tree names (yum/el9/x86_64, yum/el9/aarch64)
    # match dnf $basearch exactly.
    echo "baseurl=$PACKAGES_URL/yum/$distro/\$basearch"
    echo "enabled=1"
    # Verify both the signed repodata AND each .rpm's GPG signature. The
    # publish pipeline signs individual RPMs via rpmsign before createrepo_c
    # builds the metadata, and detach-signs repomd.xml at the end — both flags
    # below get a real signature to validate. Apt gets the equivalent for free
    # via InRelease; yum/dnf needs the two-flag dance.
    echo "repo_gpgcheck=1"
    echo "gpgcheck=1"
    echo "gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-modpagespeed"
    if [ -n "$PACKAGES_USER" ] && [ -n "$PACKAGES_PASS" ]; then
      echo "username=$PACKAGES_USER"
      echo "password=$PACKAGES_PASS"
    fi
    # EA4 (cPanel EasyApache 4) tree — ea-apache24-mod_pagespeed. Always
    # written; enabled=0 on non-cPanel hosts so the EA4 RPM (a different package
    # name + Apache ABI surface) never mixes with the stock mod_pagespeed RPM.
    # Opt in per-command elsewhere with `dnf install --enablerepo=ea4 ...`.
    # The EA4 el9 + el10 trees are both live (ea4/el10 shipped with v1.15.0+r14;
    # ADR-101). el8 is build-only (no public tree). The stanza is written on any
    # host but only ENABLED when cPanel is detected (el9 + el10 cPanel = enabled).
    # cPanel el10 = AlmaLinux 10 + CloudLinux 10 only (cPanel dropped Rocky at
    # v134; ADR-101) — a Rocky-10 host has no cPanel anyway.
    # EA4 is x86_64-only (cPanel/EA4 has no aarch64 build or tree) — so the arch
    # stays the literal x86_64 (NOT $basearch).
    echo ""
    echo "[ea4]"
    echo "name=mod_pagespeed for EasyApache 4"
    echo "baseurl=$PACKAGES_URL/yum/ea4/$distro/x86_64"
    echo "enabled=$ea4_enabled"
    echo "repo_gpgcheck=1"
    echo "gpgcheck=1"
    echo "gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-modpagespeed"
    if [ -n "$PACKAGES_USER" ] && [ -n "$PACKAGES_PASS" ]; then
      echo "username=$PACKAGES_USER"
      echo "password=$PACKAGES_PASS"
    fi
  } >> "$repo_path"

  if [ "$is_cpanel" = 1 ]; then
    cat <<EOF

mod_pagespeed yum repository configured — cPanel / EasyApache 4 detected.

The EA4 module repository [ea4] is enabled. Install the EasyApache 4 module and
turn it on in WHM:

  1. sudo dnf install ea-apache24-mod_pagespeed
  2. WHM > Software > EasyApache 4 > Customize > Apache Modules:
     check "ea-apache24-mod_pagespeed", then Review + Provision.
  3. Activate your license at /pagespeed_global_admin on the server.

cPanel guide: https://modpagespeed.com/1.1/cpanel/

EOF
  else
    cat <<EOF

mod_pagespeed yum repository configured.

Next:
  Apache (1.15): sudo dnf install mod-pagespeed
  nginx (1.15):  sudo dnf install nginx-module-pagespeed
  nginx (2.0):   sudo dnf install modpagespeed-2-nginx        # post-2.0 GA

On cPanel / EasyApache 4 hosts, install the EA4 module instead:
  sudo dnf install --enablerepo=ea4 ea-apache24-mod_pagespeed
  cPanel guide: https://modpagespeed.com/1.1/cpanel/

EOF
  fi
}

# ------------------------------------------------------------------------------
# Dispatch
# ------------------------------------------------------------------------------

case "$family" in
  apt) install_apt ;;
  yum) install_yum ;;
esac
