Document catalog host moves and add per-module host-deps installer.
Some checks are pending
offline / test (push) Waiting to run

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.
This commit is contained in:
George Lambert 2026-09-11 23:12:25 -04:00
parent 20155de96b
commit 2d51d7a0dd
17 changed files with 629 additions and 9 deletions

View file

@ -0,0 +1,348 @@
#!/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