master-zapier-plan-draft/packages/verae-bootstrap/scripts/host-deps.sh
George Lambert 2d51d7a0dd
Some checks are pending
offline / test (push) Waiting to run
Document catalog host moves and add per-module host-deps installer.
zapier-docs-master now has CATALOG-HOST.md (relative PDFs / rewrite-docs-host.py)
and HOST-DEPS.md. verae-bootstrap/scripts/host-deps.sh checks and installs
git, Node, native build tools, and nats-server on Debian or Alpine from
host-deps.tsv (every Forgejo module). fetch.sh runs it before clone/npm.
2026-09-11 23:12:25 -04:00

348 lines
10 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Check (and optionally install) OS + Node + NATS packages needed to run
# Verae Time × Zapier modules on this host.
#
# Debian/Ubuntu and Alpine are first-class. Other Linux: print the plan.
# macOS: Homebrew when present.
#
# Usage:
# bash scripts/host-deps.sh # detect modules, install missing
# bash scripts/host-deps.sh --check # report only
# bash scripts/host-deps.sh --type ns1-all-in-one
# bash scripts/host-deps.sh --module zappier-edge
# bash scripts/host-deps.sh --nats # also install nats-server
# bash scripts/host-deps.sh --docs # pandoc / weasyprint / pypdf
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MANIFEST="$ROOT/host-deps.tsv"
NODE_VER="${NODE_VER:-22.14.0}"
NATS_VER="${NATS_VER:-2.10.26}"
PREFIX="${PREFIX:-}"
CHECK_ONLY=0
WANT_NATS=0
WANT_DOCS=0
WANT_CADDY=0
TYPE=""
MODULES=()
usage() { sed -n '2,16p' "$0" | tr -d '#'; exit "${1:-0}"; }
while [[ $# -gt 0 ]]; do
case "$1" in
--check) CHECK_ONLY=1 ;;
--install) CHECK_ONLY=0 ;;
--nats) WANT_NATS=1 ;;
--docs) WANT_DOCS=1 ;;
--caddy) WANT_CADDY=1 ;;
--type) TYPE="${2:-}"; shift ;;
--module) MODULES+=("${2:-}"); shift ;;
--prefix) PREFIX="${2:-}"; shift ;;
-h|--help) usage 0 ;;
*) echo "unknown arg $1" >&2; usage 1 ;;
esac
shift
done
# --- OS / arch ---
OS_ID="unknown"
OS_FAMILY="unknown"
if [[ "$(uname -s)" == Darwin ]]; then
OS_ID=macos
OS_FAMILY=macos
elif [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
OS_ID="${ID:-unknown}"
case "$OS_ID" in
debian|ubuntu|raspbian|linuxmint) OS_FAMILY=debian ;;
alpine) OS_FAMILY=alpine ;;
*) OS_FAMILY=linux ;;
esac
fi
ARCH="$(uname -m)"
case "$ARCH" in
x86_64|amd64) NODE_ARCH=x64; NATS_ARCH=amd64; DEB_ARCH=amd64 ;;
aarch64|arm64) NODE_ARCH=arm64; NATS_ARCH=arm64; DEB_ARCH=arm64 ;;
armv7l) NODE_ARCH=armv7l; NATS_ARCH=arm7; DEB_ARCH=armhf ;;
*) NODE_ARCH=$ARCH; NATS_ARCH=$ARCH; DEB_ARCH=$ARCH ;;
esac
MUSL=0
if [[ "$OS_FAMILY" == alpine ]]; then MUSL=1; fi
is_root() { [[ "$(id -u)" -eq 0 ]]; }
run_root() {
if is_root; then
"$@"
elif command -v sudo >/dev/null; then
sudo "$@"
else
echo "need root (or sudo) to run: $*" >&2
return 1
fi
}
if [[ -z "$PREFIX" ]]; then
if is_root; then PREFIX=/usr/local; else PREFIX="${HOME}/.local"; fi
fi
mkdir -p "$PREFIX/bin"
export PATH="$PREFIX/bin:$PATH"
# --- module list ---
PKG_TO_REPO="$(cat <<'EOF'
zappier zappier-edge
verae-zapier-middleware verae-middleware
verae-zapier verae-zapier-app
docs-master zapier-docs-master
ui-docs UI-Docs
EOF
)"
map_name() {
local n="$1"
local line
while read -r from to; do
[[ "$n" == "$from" ]] && { echo "$to"; return; }
done <<<"$PKG_TO_REPO"
echo "$n"
}
collect_from_list() {
local f="$1"
while read -r name; do
[[ -z "$name" || "$name" == \#* ]] && continue
MODULES+=("$name")
done < "$f"
}
if [[ ${#MODULES[@]} -eq 0 ]]; then
if [[ -n "$TYPE" ]]; then
collect_from_list "$ROOT/types/$TYPE/repos.txt"
elif [[ -n "${VERAE_SRC:-}" && -d "${VERAE_SRC}" ]]; then
for d in "$VERAE_SRC"/*/; do
[[ -d "$d" ]] || continue
MODULES+=("$(basename "$d")")
done
elif [[ -d "$PWD/packages" ]]; then
for d in "$PWD"/packages/*/; do
MODULES+=("$(map_name "$(basename "$d")")")
done
MODULES+=(master-zapier-plan-draft)
elif [[ -f "$PWD/package.json" ]]; then
n="$(basename "$PWD")"
MODULES+=("$(map_name "$n")")
else
collect_from_list "$ROOT/repos.txt"
fi
fi
# types that run nats-server on this box
case "$TYPE" in
ns1-all-in-one|ns1-archive) WANT_NATS=1 ;;
esac
# --- union flags ---
MIN_NODE=0
NEED_NPM=0
NEED_NATIVE=0
NEED_NATS_CLIENT=0
NEED_SSH=0
NEED_DOCS=$WANT_DOCS
while IFS='|' read -r name min flags; do
[[ -z "$name" || "$name" == \#* ]] && continue
wanted=0
for m in "${MODULES[@]}"; do
[[ "$m" == "$name" ]] && wanted=1
done
[[ $wanted -eq 1 ]] || continue
if [[ "$min" != 0 && "$min" -gt "$MIN_NODE" ]]; then MIN_NODE=$min; fi
[[ ",$flags," == *",npm,"* ]] && NEED_NPM=1
[[ ",$flags," == *",native,"* ]] && NEED_NATIVE=1
[[ ",$flags," == *",nats,"* ]] && NEED_NATS_CLIENT=1
[[ ",$flags," == *",nats_server,"* ]] && WANT_NATS=1
[[ ",$flags," == *",ssh,"* ]] && NEED_SSH=1
[[ ",$flags," == *",docs,"* ]] && NEED_DOCS=1
done < "$MANIFEST"
echo "os=$OS_ID family=$OS_FAMILY arch=$ARCH musl=$MUSL prefix=$PREFIX"
echo "modules: ${MODULES[*]}"
echo "need: node>=${MIN_NODE:-0} npm=$NEED_NPM native=$NEED_NATIVE nats_client=$NEED_NATS_CLIENT nats_server=$WANT_NATS ssh=$NEED_SSH docs=$NEED_DOCS"
MISSING=()
have() { command -v "$1" >/dev/null 2>&1; }
need_cmd() {
local c="$1"
if have "$c"; then
echo "ok $c $($c --version 2>/dev/null | head -1 || true)"
else
echo "NO $c"
MISSING+=("$c")
fi
}
node_ok() {
local want="$1"
have node || return 1
node -e "process.exit(parseInt(process.versions.node,10)>=$want?0:1)" 2>/dev/null
}
# --- package installers ---
apt_install() {
run_root apt-get update -qq
run_root env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@"
}
apk_install() { run_root apk add --no-cache "$@"; }
brew_install() { brew install "$@"; }
install_pkgs() {
local pkgs=("$@")
[[ ${#pkgs[@]} -eq 0 ]] && return 0
echo "install OS packages: ${pkgs[*]}"
[[ $CHECK_ONLY -eq 1 ]] && return 0
case "$OS_FAMILY" in
debian) apt_install "${pkgs[@]}" ;;
alpine) apk_install "${pkgs[@]}" ;;
macos) brew_install "${pkgs[@]}" ;;
*) echo "install these packages yourself: ${pkgs[*]}" >&2; return 1 ;;
esac
}
install_node() {
local want="${1:-20}"
if node_ok "$want"; then
echo "ok node $(node -v) (>=$want)"
return 0
fi
echo "NO node (>=$want) have=$(node -v 2>/dev/null || echo none)"
MISSING+=("node>=$want")
[[ $CHECK_ONLY -eq 1 ]] && return 0
if [[ "$OS_FAMILY" == alpine && $MUSL -eq 1 ]]; then
apk_install nodejs npm python3 make g++ linux-headers || true
if node_ok "$want"; then echo "ok node $(node -v) (apk)"; return 0; fi
fi
if [[ "$OS_FAMILY" == debian ]]; then
apt_install ca-certificates curl xz-utils
elif [[ "$OS_FAMILY" == alpine ]]; then
apk_install curl tar xz
fi
local dist="linux-${NODE_ARCH}"
[[ $MUSL -eq 1 ]] && dist="linux-${NODE_ARCH}-musl"
local base="https://nodejs.org/dist/v${NODE_VER}"
[[ $MUSL -eq 1 ]] && base="https://unofficial-builds.nodejs.org/download/release/v${NODE_VER}"
local tar="node-v${NODE_VER}-${dist}.tar.xz"
local tmp
tmp="$(mktemp -d)"
echo "download $base/$tar"
curl -fsSL "$base/$tar" -o "$tmp/$tar"
tar -xJf "$tmp/$tar" -C "$tmp"
mkdir -p "$PREFIX"
run_root cp -a "$tmp/node-v${NODE_VER}-${dist}/." "$PREFIX/"
rm -rf "$tmp"
hash -r
node_ok "$want" || { echo "node install failed" >&2; return 1; }
echo "ok node $(node -v)$PREFIX"
}
install_nats() {
if have nats-server; then
echo "ok nats-server $(nats-server -v 2>/dev/null | head -1 || true)"
return 0
fi
echo "NO nats-server"
MISSING+=("nats-server")
[[ $CHECK_ONLY -eq 1 ]] && return 0
if [[ "$OS_FAMILY" == alpine ]]; then
apk_install nats-server 2>/dev/null && { echo "ok nats-server (apk)"; return 0; } || true
fi
local tar="nats-server-v${NATS_VER}-linux-${NATS_ARCH}.tar.gz"
local url="https://github.com/nats-io/nats-server/releases/download/v${NATS_VER}/${tar}"
local tmp
tmp="$(mktemp -d)"
echo "download $url"
curl -fsSL "$url" -o "$tmp/$tar"
tar -xzf "$tmp/$tar" -C "$tmp"
run_root install -m 0755 "$tmp/nats-server-v${NATS_VER}-linux-${NATS_ARCH}/nats-server" "$PREFIX/bin/nats-server"
rm -rf "$tmp"
have nats-server || { echo "nats-server install failed" >&2; return 1; }
echo "ok nats-server → $PREFIX/bin"
}
# --- plan OS packages ---
PKGS=()
add_pkg() { PKGS+=("$1"); }
case "$OS_FAMILY" in
debian)
add_pkg ca-certificates; add_pkg curl; add_pkg git; add_pkg bash; add_pkg tar; add_pkg xz-utils
[[ $NEED_NATIVE -eq 1 || $NEED_NPM -eq 1 ]] && { add_pkg python3; add_pkg make; add_pkg g++; add_pkg python3-dev; }
[[ $NEED_SSH -eq 1 ]] && add_pkg openssh-client
[[ $NEED_DOCS -eq 1 ]] && { add_pkg pandoc; add_pkg weasyprint; add_pkg python3-pip; }
[[ $WANT_CADDY -eq 1 ]] && add_pkg debian-keyring
;;
alpine)
add_pkg ca-certificates; add_pkg curl; add_pkg git; add_pkg bash; add_pkg tar; add_pkg xz
[[ $NEED_NATIVE -eq 1 || $NEED_NPM -eq 1 ]] && { add_pkg python3; add_pkg make; add_pkg g++; add_pkg linux-headers; add_pkg libstdc++; }
[[ $NEED_SSH -eq 1 ]] && add_pkg openssh-client
[[ $NEED_DOCS -eq 1 ]] && { add_pkg pandoc; add_pkg py3-pip; add_pkg weasyprint; }
;;
macos)
add_pkg git; add_pkg curl
[[ $NEED_DOCS -eq 1 ]] && { add_pkg pandoc; add_pkg weasyprint; }
;;
esac
echo "--- check ---"
need_cmd git
need_cmd curl
[[ $NEED_SSH -eq 1 ]] && need_cmd ssh
if [[ "$MIN_NODE" != 0 ]]; then
if node_ok "$MIN_NODE"; then echo "ok node $(node -v)"; else echo "NO node (>=$MIN_NODE)"; MISSING+=("node>=$MIN_NODE"); fi
need_cmd npm
fi
[[ $WANT_NATS -eq 1 ]] && { have nats-server && echo "ok nats-server" || { echo "NO nats-server"; MISSING+=("nats-server"); }; }
[[ $NEED_DOCS -eq 1 ]] && { need_cmd pandoc; have weasyprint || have weasyprint3 || echo "NO weasyprint"; }
if [[ $CHECK_ONLY -eq 1 ]]; then
if [[ ${#MISSING[@]} -eq 0 ]]; then
echo "host deps OK"
exit 0
fi
echo "missing: ${MISSING[*]}"
exit 2
fi
echo "--- install ---"
if [[ ${#PKGS[@]} -gt 0 ]]; then
UNIQ=()
while IFS= read -r p; do
[[ -n "$p" ]] && UNIQ+=("$p")
done < <(printf '%s\n' "${PKGS[@]}" | sort -u)
install_pkgs "${UNIQ[@]}"
fi
if [[ "$MIN_NODE" != 0 ]]; then
install_node "$MIN_NODE"
fi
if [[ $WANT_NATS -eq 1 ]]; then
install_nats
fi
if [[ $NEED_DOCS -eq 1 ]] && have python3; then
python3 -m pip install --user pypdf >/dev/null 2>&1 || true
fi
MISSING=()
need_cmd git
need_cmd curl
if [[ "$MIN_NODE" != 0 ]]; then
node_ok "$MIN_NODE" || MISSING+=("node>=$MIN_NODE")
have npm || MISSING+=("npm")
fi
[[ $WANT_NATS -eq 1 ]] && { have nats-server || MISSING+=("nats-server"); }
if [[ ${#MISSING[@]} -eq 0 ]]; then
echo "host deps OK (PATH should include $PREFIX/bin)"
exit 0
fi
echo "still missing: ${MISSING[*]}" >&2
exit 1