disinto/docker/agents/entrypoint.sh
Claude daf9151b9a
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
fix: fix: Forgejo API tokens rejected for git HTTP push — agents must use password auth (#361)
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>
2026-04-07 16:48:43 +00:00

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