#!/usr/bin/env bash # gitea-doctor — pre-flight validation for Gitea npm registry connectivity. # # Run this before any deploy, before any `pnpm install` that needs @bytelyst/* # from Gitea, and after any token rotation. Exits non-zero if anything is wrong. # # Usage: # bash scripts/gitea/doctor.sh # full check # bash scripts/gitea/doctor.sh --quiet # only print failures # bash scripts/gitea/doctor.sh --probe PKG # also try resolving PKG@latest # # Checks: # 1. NETWORK env var sane # 2. GITEA_NPM_HOST set + DNS resolves # 3. GITEA_NPM_OWNER set # 4. GITEA_NPM_TOKEN in env matches the on-disk file (catches stale shells) # 5. Registry HTTP 200 with token # 6. Token has write scope (best-effort detection) # 7. (Optional) given package@version resolves # # Exit codes: # 0 all checks pass # 1 one or more checks fail set -uo pipefail QUIET=false PROBE_PKG="" while [ $# -gt 0 ]; do case "$1" in --quiet) QUIET=true ;; --probe) PROBE_PKG="${2:-}"; shift ;; -h|--help) sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "Unknown arg: $1" >&2; exit 2 ;; esac shift done fail=0 say() { $QUIET || echo "$@"; } ok() { say " ✓ $1"; } warn() { echo " ⚠ $1"; } err() { echo " ✗ $1"; fail=1; } say "🔍 gitea-doctor — Gitea npm registry pre-flight" say "" # ── 1. NETWORK ─────────────────────────────────────────────────── NETWORK="${NETWORK:-home}" case "$NETWORK" in corp|home) ok "NETWORK=$NETWORK" ;; *) err "NETWORK='$NETWORK' (must be 'corp' or 'home')" ;; esac # ── 2. GITEA_NPM_HOST ──────────────────────────────────────────── if [ -z "${GITEA_NPM_HOST:-}" ]; then err "GITEA_NPM_HOST not set. Source switch-network.sh." else ok "GITEA_NPM_HOST=$GITEA_NPM_HOST" # DNS / reachability — only matters for non-localhost if [ "$GITEA_NPM_HOST" != "localhost" ] && [ "$GITEA_NPM_HOST" != "127.0.0.1" ]; then if ! getent hosts "$GITEA_NPM_HOST" >/dev/null 2>&1 && ! host "$GITEA_NPM_HOST" >/dev/null 2>&1; then err "DNS does not resolve $GITEA_NPM_HOST" else ok "DNS resolves $GITEA_NPM_HOST" fi fi fi # ── 3. GITEA_NPM_OWNER ─────────────────────────────────────────── if [ -z "${GITEA_NPM_OWNER:-}" ]; then err "GITEA_NPM_OWNER not set. Source switch-network.sh (or export manually)." else ok "GITEA_NPM_OWNER=$GITEA_NPM_OWNER" fi # ── 4. Token consistency (env vs file) ─────────────────────────── env_token="${GITEA_NPM_TOKEN:-}" file_token="" token_file="" for candidate in "$HOME/.gitea_npm_token_${NETWORK}" "$HOME/.gitea_npm_token"; do if [ -f "$candidate" ]; then file_token="$(tr -d '\n\r ' < "$candidate")" token_file="$candidate" break fi done if [ -z "$file_token" ]; then err "No token file found (~/.gitea_npm_token or ~/.gitea_npm_token_$NETWORK)" elif [ -z "$env_token" ]; then warn "GITEA_NPM_TOKEN not in env, but $token_file exists (re-source ~/.zshrc)" env_token="$file_token" # use file for further checks elif [ "$env_token" != "$file_token" ]; then err "STALE TOKEN: env GITEA_NPM_TOKEN ≠ $token_file" err " env starts: ${env_token:0:8}…" err " file starts: ${file_token:0:8}…" err " Fix: source ~/.zshrc (or open a new terminal)" env_token="$file_token" # still try registry check with fresh value else ok "Token consistent (env matches $token_file, ${#env_token} chars)" fi # ── 5. Registry connectivity + auth ────────────────────────────── if [ -n "${GITEA_NPM_HOST:-}" ] && [ -n "${GITEA_NPM_OWNER:-}" ] && [ -n "$env_token" ]; then url="http://${GITEA_NPM_HOST}:3300/api/packages/${GITEA_NPM_OWNER}/npm/@bytelyst%2Ferrors" code=$(curl -fsS -o /dev/null -w "%{http_code}" "$url" \ -H "Authorization: token $env_token" --noproxy '*' --max-time 5 2>/dev/null || echo "000") case "$code" in 200) ok "Registry HTTP 200 on @bytelyst/errors" ;; 401|403) err "Registry HTTP $code — token rejected. Rotate token." ;; 404) err "Registry HTTP 404 — owner '$GITEA_NPM_OWNER' may be wrong" ;; 000) err "Registry unreachable at $url (Gitea down? SSH tunnel? VPN?)" ;; *) err "Registry HTTP $code (unexpected)" ;; esac else warn "Skipping registry probe (host/owner/token incomplete)" fi # ── 6. Token scope (best-effort) ───────────────────────────────── # Gitea exposes /api/v1/users//tokens for the authenticated user; # we can't enumerate scopes for the current token directly, so probe a # write endpoint with a HEAD request that won't actually publish. if [ -n "$env_token" ] && [ -n "${GITEA_NPM_HOST:-}" ]; then url="http://${GITEA_NPM_HOST}:3300/api/v1/repos/search?limit=1" code=$(curl -fsS -o /dev/null -w "%{http_code}" "$url" \ -H "Authorization: token $env_token" --noproxy '*' --max-time 5 2>/dev/null || echo "000") if [ "$code" = "200" ]; then ok "Token authenticates against Gitea API" else warn "Could not verify token via Gitea API (HTTP $code)" fi fi # ── 7. Optional package probe ──────────────────────────────────── if [ -n "$PROBE_PKG" ] && [ -n "$env_token" ]; then encoded="${PROBE_PKG//\//%2F}" url="http://${GITEA_NPM_HOST}:3300/api/packages/${GITEA_NPM_OWNER}/npm/${encoded}" code=$(curl -fsS -o /dev/null -w "%{http_code}" "$url" \ -H "Authorization: token $env_token" --noproxy '*' --max-time 5 2>/dev/null || echo "000") if [ "$code" = "200" ]; then versions=$(curl -fsS "$url" -H "Authorization: token $env_token" --noproxy '*' --max-time 5 \ 2>/dev/null | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -V | tail -3 | tr '\n' ' ') ok "$PROBE_PKG resolvable (latest versions: $versions)" else err "$PROBE_PKG not found in registry (HTTP $code)" fi fi say "" if [ "$fail" -eq 0 ]; then say "✅ All Gitea pre-flight checks passed" else echo "❌ Gitea pre-flight failed — fix issues above before deploying" fi exit "$fail"