167 lines
6.0 KiB
Bash
Executable File
167 lines
6.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build and serve eco's Sphinx documentation on this host.
|
|
#
|
|
# Kept as its own small job rather than a route inside the status server: the
|
|
# docs must stay up while the status server restarts (minutes of init_all()),
|
|
# and the status server should not grow code for something unrelated to it.
|
|
#
|
|
# Layout under $ECO_DOCS_ROOT (default ~/.eco/docs_site):
|
|
# src/ own clone of the repository - never a shared checkout,
|
|
# since every build does `git reset --hard` on it
|
|
# builds/<stamp>/ one finished build each, the last few kept
|
|
# current -> builds/<stamp> what is served; swapped atomically, so a
|
|
# failing build never replaces a working site
|
|
#
|
|
# `serve` follows the `current` symlink on every request, so a new build is
|
|
# live the moment `build` swaps it - no restart needed.
|
|
set -euo pipefail
|
|
|
|
SELF_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
ROOT="${ECO_DOCS_ROOT:-$HOME/.eco/docs_site}"
|
|
REPO="${ECO_DOCS_REPO:-$(git -C "$SELF_DIR/.." remote get-url origin 2>/dev/null || true)}"
|
|
BRANCH="${ECO_DOCS_BRANCH:-master}"
|
|
PYTHON="${ECO_DOCS_PYTHON:-/sf/bernina/applications/python/.pixi/envs/bpy312/bin/python}"
|
|
PORT="${ECO_DOCS_PORT:-8095}"
|
|
BIND="${ECO_DOCS_BIND:-0.0.0.0}"
|
|
KEEP="${ECO_DOCS_KEEP:-3}"
|
|
|
|
SRC="$ROOT/src"
|
|
BUILDS="$ROOT/builds"
|
|
CURRENT="$ROOT/current"
|
|
|
|
# A local checkout as the source (e.g. the shared gac-bernina one) is usually
|
|
# owned by another account, which git refuses ("dubious ownership") unless
|
|
# told otherwise. Allow exactly that one directory, for these commands only,
|
|
# and use the real transport (--no-local) rather than hardlinking its files.
|
|
GIT=(git)
|
|
CLONE_OPTS=()
|
|
if [ -d "$REPO" ]; then
|
|
# Both spellings: a clone checks the .git directory itself.
|
|
GIT=(git -c "safe.directory=$(readlink -f "$REPO")"
|
|
-c "safe.directory=$(readlink -f "$REPO")/.git")
|
|
CLONE_OPTS=(--no-local)
|
|
fi
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
usage: $(basename "$0") <command> [options]
|
|
|
|
build [-f] fetch $BRANCH, rebuild if it changed (-f: always), and
|
|
make the new build current only if sphinx-build succeeded
|
|
serve serve the current build on http://$BIND:$PORT (foreground)
|
|
check verify the python has everything sphinx-build needs
|
|
status show source revision, current build and server state
|
|
config print the resolved configuration
|
|
|
|
Settings come from the environment (see eco-docs-install-user-service):
|
|
ECO_DOCS_ROOT=$ROOT
|
|
ECO_DOCS_REPO=$REPO
|
|
ECO_DOCS_BRANCH=$BRANCH
|
|
ECO_DOCS_PYTHON=$PYTHON
|
|
ECO_DOCS_PORT=$PORT ECO_DOCS_BIND=$BIND ECO_DOCS_KEEP=$KEEP
|
|
EOF
|
|
}
|
|
|
|
log() { echo "[eco-docs $(date '+%F %T')] $*"; }
|
|
|
|
cmd_check() {
|
|
"$PYTHON" - <<'EOF'
|
|
import importlib, sys
|
|
missing = []
|
|
for mod in ("sphinx", "furo", "myst_parser", "sphinx_copybutton", "sphinx_design"):
|
|
try:
|
|
importlib.import_module(mod)
|
|
except ImportError:
|
|
missing.append(mod)
|
|
print(sys.executable, sys.version.split()[0])
|
|
if missing:
|
|
print("missing:", ", ".join(missing))
|
|
print("install them with: <python> -m pip install -r docs/requirements.txt")
|
|
sys.exit(1)
|
|
print("ok: everything sphinx-build needs is importable")
|
|
EOF
|
|
}
|
|
|
|
update_source() {
|
|
if [ ! -d "$SRC/.git" ]; then
|
|
[ -n "$REPO" ] || { echo "no repository: set ECO_DOCS_REPO" >&2; exit 1; }
|
|
log "cloning $REPO ($BRANCH)"
|
|
mkdir -p "$ROOT"
|
|
"${GIT[@]}" clone --quiet "${CLONE_OPTS[@]}" --branch "$BRANCH" "$REPO" "$SRC"
|
|
else
|
|
"${GIT[@]}" -C "$SRC" fetch --quiet origin "$BRANCH"
|
|
git -C "$SRC" reset --quiet --hard "origin/$BRANCH"
|
|
fi
|
|
}
|
|
|
|
cmd_build() {
|
|
local force=0
|
|
[ "${1:-}" = "-f" ] && force=1
|
|
update_source
|
|
local rev built=""
|
|
rev="$(git -C "$SRC" rev-parse --short HEAD)"
|
|
[ -e "$CURRENT/.revision" ] && built="$(cat "$CURRENT/.revision")"
|
|
if [ "$force" -eq 0 ] && [ "$rev" = "$built" ]; then
|
|
log "already built $rev, nothing to do"
|
|
return 0
|
|
fi
|
|
|
|
mkdir -p "$BUILDS"
|
|
local out="$BUILDS/$(date '+%Y%m%d-%H%M%S')-$rev"
|
|
log "building $rev into $out"
|
|
# -E: fresh environment each time, so a removed page does not linger.
|
|
if ! "$PYTHON" -m sphinx -b html -E -q "$SRC/docs" "$out" > "$out.log" 2>&1; then
|
|
log "sphinx-build FAILED, keeping the current site - see $out.log"
|
|
tail -n 20 "$out.log" >&2
|
|
rm -rf "$out"
|
|
exit 1
|
|
fi
|
|
echo "$rev" > "$out/.revision"
|
|
mv "$out.log" "$out/.build.log"
|
|
|
|
# Atomic swap: rename a fresh symlink over the old one.
|
|
ln -sfn "$out" "$ROOT/current.new"
|
|
mv -T "$ROOT/current.new" "$CURRENT"
|
|
log "now serving $rev"
|
|
|
|
# Keep the newest $KEEP builds (the current one is always among them).
|
|
ls -1d "$BUILDS"/*/ 2>/dev/null | sort -r | tail -n +"$((KEEP + 1))" | xargs -r rm -rf
|
|
rm -f "$BUILDS"/*.log
|
|
}
|
|
|
|
cmd_serve() {
|
|
[ -e "$CURRENT" ] || { echo "nothing built yet - run: $(basename "$0") build" >&2; exit 1; }
|
|
log "serving $CURRENT on http://$BIND:$PORT"
|
|
exec "$PYTHON" -m http.server "$PORT" --bind "$BIND" --directory "$CURRENT"
|
|
}
|
|
|
|
cmd_status() {
|
|
echo "source : $( [ -d "$SRC/.git" ] && git -C "$SRC" log -1 --format='%h %ad %s' --date=short || echo 'not cloned yet')"
|
|
echo "current: $( [ -e "$CURRENT" ] && echo "$(readlink "$CURRENT") ($(cat "$CURRENT/.revision" 2>/dev/null))" || echo 'nothing built yet')"
|
|
if command -v curl > /dev/null; then
|
|
local code
|
|
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "http://localhost:$PORT/" || true)"
|
|
echo "server : http://$(hostname -f):$PORT/ -> HTTP ${code:-no answer}"
|
|
fi
|
|
}
|
|
|
|
cmd_config() {
|
|
echo "ECO_DOCS_ROOT=$ROOT"
|
|
echo "ECO_DOCS_REPO=$REPO"
|
|
echo "ECO_DOCS_BRANCH=$BRANCH"
|
|
echo "ECO_DOCS_PYTHON=$PYTHON"
|
|
echo "ECO_DOCS_PORT=$PORT"
|
|
echo "ECO_DOCS_BIND=$BIND"
|
|
echo "ECO_DOCS_KEEP=$KEEP"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
build) shift; cmd_build "$@" ;;
|
|
serve) cmd_serve ;;
|
|
check) cmd_check ;;
|
|
status) cmd_status ;;
|
|
config) cmd_config ;;
|
|
-h|--help|"") usage ;;
|
|
*) echo "unknown command: $1" >&2; usage; exit 2 ;;
|
|
esac
|