#!/usr/bin/env bash # Shared helpers for scripts/ci/*.sh. Everything here is plain bash so the same script # runs identically on a developer's machine and on a bare CI runner. set -euo pipefail log() { echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $*" } fail() { echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] ERROR: $*" >&2 exit 1 } # Repo root, regardless of the caller's cwd. CI_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # The .NET channel to install when no SDK is found. This has to match the TargetFramework # the projects declare — a git hook runs with a stripped PATH, so "whatever channel is # current" quietly installed .NET 9 for a net10.0 repo and failed the whole build. DOTNET_CHANNEL="${DOTNET_CHANNEL:-10.0}" # Puts a .NET SDK on PATH. Prefers one that is already installed — including the common # per-user and system locations a git hook's PATH does not include — and only downloads # one as a last resort, so a bare runner behaves the same as a dev machine. ensure_dotnet() { if command -v dotnet > /dev/null 2>&1; then # DOTNET_ROOT is unset by default even when dotnet is already on PATH (e.g. a # per-user install at ~/.dotnet) — apphost binaries like reportgenerator's fail to # find the runtime without it. export DOTNET_ROOT="${DOTNET_ROOT:-$(dirname "$(command -v dotnet)")}" return 0 fi local candidate for candidate in "${DOTNET_ROOT:-}" "$CI_ROOT/.dotnet" "$HOME/.dotnet" /usr/share/dotnet /usr/lib/dotnet; do if [[ -n "$candidate" && -x "$candidate/dotnet" ]]; then log "Using the .NET SDK at $candidate" export PATH="$candidate:$PATH" export DOTNET_ROOT="$candidate" return 0 fi done local install_dir="$CI_ROOT/.dotnet" log "dotnet not found; installing the .NET $DOTNET_CHANNEL SDK into $install_dir" curl -fsSL https://dot.net/v1/dotnet-install.sh -o "/tmp/dotnet-install.sh" bash "/tmp/dotnet-install.sh" --channel "$DOTNET_CHANNEL" --install-dir "$install_dir" rm -f "/tmp/dotnet-install.sh" export PATH="$install_dir:$PATH" export DOTNET_ROOT="$install_dir" } ensure_node() { command -v npm > /dev/null 2>&1 || fail "npm not found on PATH; install Node.js to build the web client" } ensure_reportgenerator() { if command -v reportgenerator > /dev/null 2>&1; then return 0 fi local tool_dir="$CI_ROOT/.dotnet-tools" if [[ ! -x "$tool_dir/reportgenerator" ]]; then log "reportgenerator not found on PATH; installing dotnet-reportgenerator-globaltool" dotnet tool install dotnet-reportgenerator-globaltool --tool-path "$tool_dir" fi export PATH="$tool_dir:$PATH" } # Registry configuration. All values come from the environment (CI secrets or a # developer's shell) — nothing is hardcoded, per project convention. REGISTRY="${REGISTRY:-}" REGISTRY_OWNER="${REGISTRY_OWNER:-}" REGISTRY_USER="${REGISTRY_USER:-}" REGISTRY_TOKEN="${REGISTRY_TOKEN:-}" GIT_SHA="$(git -C "$CI_ROOT" rev-parse --short HEAD)" require_registry_vars() { [[ -n "$REGISTRY" ]] || fail "REGISTRY env var is required (e.g. git.wampler.us)" [[ -n "$REGISTRY_OWNER" ]] || fail "REGISTRY_OWNER env var is required (e.g. your gitea org/user)" } # Populates API_IMAGE / WEB_IMAGE, e.g. git.wampler.us/wamplerj/novelly-api image_names() { require_registry_vars API_IMAGE="$REGISTRY/$REGISTRY_OWNER/novelly-api" WEB_IMAGE="$REGISTRY/$REGISTRY_OWNER/novelly-web" } registry_login() { require_registry_vars [[ -n "$REGISTRY_USER" ]] || fail "REGISTRY_USER env var is required to push images" [[ -n "$REGISTRY_TOKEN" ]] || fail "REGISTRY_TOKEN env var is required to push images" log "Logging in to $REGISTRY as $REGISTRY_USER" echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin }