Forgejo 11.x rejects API tokens for git HTTP push while accepting them for all other operations. Store bot passwords alongside tokens during init and use password auth for git operations consistently. - forge-setup.sh: persist bot passwords to .env (FORGE_PASS, etc.) - forge-push.sh: use FORGE_PASS instead of FORGE_TOKEN for git remote URL - entrypoint.sh: configure git credential helper with password auth - entrypoint-llama.sh: use FORGE_PASS for git clone (fallback to FORGE_TOKEN) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
155 lines
5.9 KiB
Bash
155 lines
5.9 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# entrypoint.sh — Start agent container with cron in foreground
|
|
#
|
|
# Runs as root inside the container. Installs crontab entries for the
|
|
# agent user from project TOMLs, then starts cron in the foreground.
|
|
# All cron jobs execute as the agent user (UID 1000).
|
|
|
|
DISINTO_DIR="/home/agent/disinto"
|
|
LOGFILE="/home/agent/data/agent-entrypoint.log"
|
|
mkdir -p /home/agent/data
|
|
chown agent:agent /home/agent/data
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" | tee -a "$LOGFILE"
|
|
}
|
|
|
|
# Build crontab from project TOMLs and install for the agent user.
|
|
install_project_crons() {
|
|
local cron_lines="DISINTO_CONTAINER=1
|
|
USER=agent
|
|
FORGE_URL=http://forgejo:3000"
|
|
|
|
# Parse DISINTO_AGENTS env var (default: all agents)
|
|
# Expected format: comma-separated list like "review,gardener" or "dev"
|
|
# Note: supervisor is NOT installed here — it runs on the host, not in container.
|
|
# Supervisor requires host-level Docker access and pgrep, which the container lacks.
|
|
local agents_to_run="review,dev,gardener"
|
|
if [ -n "${DISINTO_AGENTS:-}" ]; then
|
|
agents_to_run="$DISINTO_AGENTS"
|
|
fi
|
|
|
|
for toml in "${DISINTO_DIR}"/projects/*.toml; do
|
|
[ -f "$toml" ] || continue
|
|
local pname
|
|
pname=$(python3 -c "
|
|
import sys, tomllib
|
|
with open(sys.argv[1], 'rb') as f:
|
|
print(tomllib.load(f)['name'])
|
|
" "$toml" 2>/dev/null) || continue
|
|
|
|
cron_lines="${cron_lines}
|
|
PROJECT_REPO_ROOT=/home/agent/repos/${pname}
|
|
# disinto: ${pname}"
|
|
|
|
# Add review-poll only if review agent is configured
|
|
if echo "$agents_to_run" | grep -qw "review"; then
|
|
cron_lines="${cron_lines}
|
|
2,7,12,17,22,27,32,37,42,47,52,57 * * * * ${DISINTO_DIR}/review/review-poll.sh ${toml} >>/home/agent/data/logs/cron.log 2>&1"
|
|
fi
|
|
|
|
# Add dev-poll only if dev agent is configured
|
|
if echo "$agents_to_run" | grep -qw "dev"; then
|
|
cron_lines="${cron_lines}
|
|
4,9,14,19,24,29,34,39,44,49,54,59 * * * * ${DISINTO_DIR}/dev/dev-poll.sh ${toml} >>/home/agent/data/logs/cron.log 2>&1"
|
|
fi
|
|
|
|
# Add gardener-run only if gardener agent is configured
|
|
if echo "$agents_to_run" | grep -qw "gardener"; then
|
|
cron_lines="${cron_lines}
|
|
0 0,6,12,18 * * * cd ${DISINTO_DIR} && bash gardener/gardener-run.sh ${toml} >>/home/agent/data/logs/cron.log 2>&1"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$cron_lines" ]; then
|
|
printf '%s\n' "$cron_lines" | crontab -u agent -
|
|
log "Installed crontab for agent user (agents: ${agents_to_run})"
|
|
else
|
|
log "No project TOMLs found — crontab empty"
|
|
fi
|
|
}
|
|
|
|
log "Agent container starting"
|
|
|
|
# Set USER for scripts that source lib/env.sh (e.g., OPS_REPO_ROOT default)
|
|
export USER=agent
|
|
|
|
# Verify Claude CLI is available (expected via volume mount from host).
|
|
if ! command -v claude &>/dev/null; then
|
|
log "FATAL: claude CLI not found in PATH."
|
|
log "Mount the host binary into the container, e.g.:"
|
|
log " volumes:"
|
|
log " - /usr/local/bin/claude:/usr/local/bin/claude:ro"
|
|
exit 1
|
|
fi
|
|
log "Claude CLI: $(claude --version 2>&1 || true)"
|
|
|
|
# ANTHROPIC_API_KEY fallback: when set, Claude uses the API key directly
|
|
# and OAuth token refresh is not needed (no rotation race). Log which
|
|
# auth method is active so operators can debug 401s.
|
|
if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
|
|
log "Auth: ANTHROPIC_API_KEY is set — using API key (no OAuth rotation)"
|
|
elif [ -f /home/agent/.claude/credentials.json ]; then
|
|
log "Auth: OAuth credentials mounted from host (~/.claude)"
|
|
else
|
|
log "WARNING: No ANTHROPIC_API_KEY and no OAuth credentials found."
|
|
log "Run 'claude auth login' on the host, or set ANTHROPIC_API_KEY in .env"
|
|
fi
|
|
|
|
install_project_crons
|
|
|
|
# Configure git credential helper for password-based HTTP auth.
|
|
# Forgejo 11.x rejects API tokens for git push (#361); password auth works.
|
|
# This ensures all git operations (clone, fetch, push) from worktrees use
|
|
# password auth without needing tokens embedded in remote URLs.
|
|
if [ -n "${FORGE_PASS:-}" ] && [ -n "${FORGE_URL:-}" ]; then
|
|
_forge_host=$(printf '%s' "$FORGE_URL" | sed 's|https\?://||; s|/.*||')
|
|
_forge_proto=$(printf '%s' "$FORGE_URL" | sed 's|://.*||')
|
|
# Determine the bot username from FORGE_TOKEN identity (or default to dev-bot)
|
|
_bot_user=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
"${FORGE_URL}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || _bot_user=""
|
|
_bot_user="${_bot_user:-dev-bot}"
|
|
|
|
# Write a static credential helper script (git credential protocol)
|
|
cat > /home/agent/.git-credentials-helper <<CREDEOF
|
|
#!/bin/sh
|
|
# Auto-generated git credential helper for Forgejo password auth (#361)
|
|
# Only respond to "get" action; ignore "store" and "erase".
|
|
[ "\$1" = "get" ] || exit 0
|
|
# Read and discard stdin (git sends protocol/host info)
|
|
cat >/dev/null
|
|
echo "protocol=${_forge_proto}"
|
|
echo "host=${_forge_host}"
|
|
echo "username=${_bot_user}"
|
|
echo "password=${FORGE_PASS}"
|
|
CREDEOF
|
|
chmod 755 /home/agent/.git-credentials-helper
|
|
chown agent:agent /home/agent/.git-credentials-helper
|
|
|
|
su -s /bin/bash agent -c "git config --global credential.helper '/home/agent/.git-credentials-helper'"
|
|
log "Git credential helper configured for ${_bot_user}@${_forge_host} (password auth)"
|
|
fi
|
|
|
|
# Configure tea CLI login for forge operations (runs as agent user).
|
|
# tea stores config in ~/.config/tea/ — persistent across container restarts
|
|
# only if that directory is on a mounted volume.
|
|
if command -v tea &>/dev/null && [ -n "${FORGE_TOKEN:-}" ] && [ -n "${FORGE_URL:-}" ]; then
|
|
local_tea_login="forgejo"
|
|
case "$FORGE_URL" in
|
|
*codeberg.org*) local_tea_login="codeberg" ;;
|
|
esac
|
|
su -s /bin/bash agent -c "tea login add \
|
|
--name '${local_tea_login}' \
|
|
--url '${FORGE_URL}' \
|
|
--token '${FORGE_TOKEN}' \
|
|
--no-version-check 2>/dev/null || true"
|
|
log "tea login configured: ${local_tea_login} → ${FORGE_URL}"
|
|
else
|
|
log "tea login: skipped (tea not found or FORGE_TOKEN/FORGE_URL not set)"
|
|
fi
|
|
|
|
# Run cron in the foreground. Cron jobs execute as the agent user.
|
|
log "Starting cron daemon"
|
|
exec cron -f
|