disinto/lib/env.sh

166 lines
6 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# env.sh — Load environment and shared utilities
# Source this at the top of every script: source "$(dirname "$0")/lib/env.sh"
set -euo pipefail
# Resolve script root (parent of lib/)
FACTORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Container detection: when running inside the agent container, DISINTO_CONTAINER
# is set by docker-compose.yml. Adjust paths so phase files, logs, and thread
# maps land on the persistent volume instead of /tmp (which is ephemeral).
if [ "${DISINTO_CONTAINER:-}" = "1" ]; then
DISINTO_DATA_DIR="${HOME}/data"
mkdir -p "${DISINTO_DATA_DIR}"
fi
# Load secrets: prefer .env.enc (SOPS-encrypted), fall back to plaintext .env.
# Inside the container, compose already injects env vars via env_file + environment
# overrides (e.g. FORGE_URL=http://forgejo:3000). Re-sourcing .env would clobber
# those compose-level values, so we skip it when DISINTO_CONTAINER=1.
if [ "${DISINTO_CONTAINER:-}" != "1" ]; then
if [ -f "$FACTORY_ROOT/.env.enc" ] && command -v sops &>/dev/null; then
set -a
eval "$(sops -d --output-type dotenv "$FACTORY_ROOT/.env.enc" 2>/dev/null)" \
|| echo "Warning: failed to decrypt .env.enc — secrets not loaded" >&2
set +a
elif [ -f "$FACTORY_ROOT/.env" ]; then
set -a
# shellcheck source=/dev/null
source "$FACTORY_ROOT/.env"
set +a
fi
fi
# PATH: foundry, node, system
export PATH="${HOME}/.local/bin:${HOME}/.foundry/bin:${HOME}/.nvm/versions/node/v22.20.0/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
export HOME="${HOME:-/home/debian}"
# Load project TOML if PROJECT_TOML is set (by poll scripts that accept project arg)
if [ -n "${PROJECT_TOML:-}" ] && [ -f "$PROJECT_TOML" ]; then
source "${FACTORY_ROOT}/lib/load-project.sh" "$PROJECT_TOML"
fi
# Forge token: new FORGE_TOKEN > legacy CODEBERG_TOKEN
if [ -z "${FORGE_TOKEN:-}" ]; then
FORGE_TOKEN="${CODEBERG_TOKEN:-}"
fi
export FORGE_TOKEN
export CODEBERG_TOKEN="${FORGE_TOKEN}" # backwards compat
# Review bot token: FORGE_REVIEW_TOKEN > legacy REVIEW_BOT_TOKEN
export FORGE_REVIEW_TOKEN="${FORGE_REVIEW_TOKEN:-${REVIEW_BOT_TOKEN:-}}"
export REVIEW_BOT_TOKEN="${FORGE_REVIEW_TOKEN}" # backwards compat
# Bot usernames filter: FORGE_BOT_USERNAMES > legacy CODEBERG_BOT_USERNAMES
export FORGE_BOT_USERNAMES="${FORGE_BOT_USERNAMES:-${CODEBERG_BOT_USERNAMES:-}}"
export CODEBERG_BOT_USERNAMES="${FORGE_BOT_USERNAMES}" # backwards compat
# Project config (FORGE_* preferred, CODEBERG_* fallback)
export FORGE_REPO="${FORGE_REPO:-${CODEBERG_REPO:-}}"
export CODEBERG_REPO="${FORGE_REPO}" # backwards compat
export FORGE_URL="${FORGE_URL:-http://localhost:3000}"
export FORGE_API="${FORGE_API:-${FORGE_URL}/api/v1/repos/${FORGE_REPO}}"
export FORGE_WEB="${FORGE_WEB:-${FORGE_URL}/${FORGE_REPO}}"
export CODEBERG_API="${FORGE_API}" # backwards compat
export CODEBERG_WEB="${FORGE_WEB}" # backwards compat
# tea CLI login name: derived from FORGE_URL (codeberg vs local forgejo)
if [ -z "${TEA_LOGIN:-}" ]; then
case "${FORGE_URL}" in
*codeberg.org*) TEA_LOGIN="codeberg" ;;
*) TEA_LOGIN="forgejo" ;;
esac
fi
export TEA_LOGIN
export PROJECT_NAME="${PROJECT_NAME:-${FORGE_REPO##*/}}"
export PROJECT_REPO_ROOT="${PROJECT_REPO_ROOT:-/home/${USER}/${PROJECT_NAME}}"
export PRIMARY_BRANCH="${PRIMARY_BRANCH:-master}"
export WOODPECKER_REPO_ID="${WOODPECKER_REPO_ID:-}"
export WOODPECKER_SERVER="${WOODPECKER_SERVER:-http://localhost:8000}"
export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}"
# Disable Claude Code auto-updater, telemetry, error reporting in factory sessions.
# Factory processes must never phone home or auto-update mid-session (#725).
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
# Shared log helper
log() {
printf '[%s] %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*"
}
# Forge API helper — usage: forge_api GET /issues?state=open
forge_api() {
local method="$1" path="$2"
shift 2
curl -sf -X "$method" \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE_API}${path}" "$@"
}
# Backwards-compat alias
codeberg_api() { forge_api "$@"; }
# Paginate a Forge API GET endpoint and return all items as a merged JSON array.
# Usage: forge_api_all /path (no existing query params)
# forge_api_all /path?a=b (with existing params — appends &limit=50&page=N)
# forge_api_all /path TOKEN (optional second arg: token; defaults to $FORGE_TOKEN)
forge_api_all() {
local path_prefix="$1"
local FORGE_TOKEN="${2:-${FORGE_TOKEN}}"
local sep page page_items count all_items="[]"
case "$path_prefix" in
*"?"*) sep="&" ;;
*) sep="?" ;;
esac
page=1
while true; do
page_items=$(forge_api GET "${path_prefix}${sep}limit=50&page=${page}")
count=$(printf '%s' "$page_items" | jq 'length')
[ "$count" -eq 0 ] && break
all_items=$(printf '%s\n%s' "$all_items" "$page_items" | jq -s 'add')
[ "$count" -lt 50 ] && break
page=$((page + 1))
done
printf '%s' "$all_items"
}
# Backwards-compat alias
codeberg_api_all() { forge_api_all "$@"; }
# Woodpecker API helper
woodpecker_api() {
local path="$1"
shift
curl -sfL \
-H "Authorization: Bearer ${WOODPECKER_TOKEN}" \
"${WOODPECKER_SERVER}/api${path}" "$@"
}
# Woodpecker DB query helper
wpdb() {
PGPASSWORD="${WOODPECKER_DB_PASSWORD}" psql \
-U "${WOODPECKER_DB_USER:-woodpecker}" \
-h "${WOODPECKER_DB_HOST:-127.0.0.1}" \
-d "${WOODPECKER_DB_NAME:-woodpecker}" \
-t "$@" 2>/dev/null
}
# Memory guard — exit 0 (skip) if available RAM is below MIN_MB.
# Usage: memory_guard [MIN_MB] (default 2000)
memory_guard() {
local min_mb="${1:-2000}"
local avail_mb
avail_mb=$(awk '/MemAvailable/{printf "%d", $2/1024}' /proc/meminfo)
if [ "${avail_mb:-0}" -lt "$min_mb" ]; then
log "SKIP: only ${avail_mb}MB available (need ${min_mb}MB)"
exit 0
fi
}
# Source tea helpers (available when tea binary is installed)
if command -v tea &>/dev/null; then
# shellcheck source=tea-helpers.sh
source "$(dirname "${BASH_SOURCE[0]}")/tea-helpers.sh"
fi