diff --git a/.env.example b/.env.example index 037abe1..95d49e2 100644 --- a/.env.example +++ b/.env.example @@ -19,15 +19,32 @@ FORGE_URL=http://localhost:3000 # [CONFIG] local Forgejo instance # ── Auth tokens ─────────────────────────────────────────────────────────── # Each agent has its own Forgejo account and API token (#747). # Per-agent tokens fall back to FORGE_TOKEN if not set. +# +# Tokens and passwords are auto-generated by `disinto init` and stored in .env. +# Each bot user gets: +# - FORGE_TOKEN_ = API token for REST calls (user identity via /api/v1/user) +# - FORGE_PASS_ = password for git HTTP push (#361, Forgejo 11.x limitation) +# +# Local-model agents (agents-llama) use FORGE_TOKEN_LLAMA / FORGE_PASS_LLAMA +# with FORGE_BOT_USER_LLAMA=dev-qwen to ensure correct attribution (#563). FORGE_TOKEN= # [SECRET] dev-bot API token (default for all agents) -FORGE_TOKEN_DEVQWEN= # [SECRET] dev-qwen API token (for agents-llama) +FORGE_PASS= # [SECRET] dev-bot password for git HTTP push (#361) +FORGE_TOKEN_LLAMA= # [SECRET] dev-qwen API token (for agents-llama) +FORGE_PASS_LLAMA= # [SECRET] dev-qwen password for git HTTP push FORGE_REVIEW_TOKEN= # [SECRET] review-bot API token +FORGE_REVIEW_PASS= # [SECRET] review-bot password for git HTTP push FORGE_PLANNER_TOKEN= # [SECRET] planner-bot API token +FORGE_PLANNER_PASS= # [SECRET] planner-bot password for git HTTP push FORGE_GARDENER_TOKEN= # [SECRET] gardener-bot API token +FORGE_GARDENER_PASS= # [SECRET] gardener-bot password for git HTTP push FORGE_VAULT_TOKEN= # [SECRET] vault-bot API token +FORGE_VAULT_PASS= # [SECRET] vault-bot password for git HTTP push FORGE_SUPERVISOR_TOKEN= # [SECRET] supervisor-bot API token +FORGE_SUPERVISOR_PASS= # [SECRET] supervisor-bot password for git HTTP push FORGE_PREDICTOR_TOKEN= # [SECRET] predictor-bot API token +FORGE_PREDICTOR_PASS= # [SECRET] predictor-bot password for git HTTP push FORGE_ARCHITECT_TOKEN= # [SECRET] architect-bot API token +FORGE_ARCHITECT_PASS= # [SECRET] architect-bot password for git HTTP push FORGE_BOT_USERNAMES=dev-bot,review-bot,planner-bot,gardener-bot,vault-bot,supervisor-bot,predictor-bot,architect-bot # ── Backwards compatibility ─────────────────────────────────────────────── @@ -35,6 +52,10 @@ FORGE_BOT_USERNAMES=dev-bot,review-bot,planner-bot,gardener-bot,vault-bot,superv # CODEBERG_TOKEN automatically (same for REVIEW_BOT_TOKEN, CODEBERG_REPO, # CODEBERG_BOT_USERNAMES). No action needed for existing deployments. # Per-agent tokens default to FORGE_TOKEN when unset (single-token setups). +# +# Note: `disinto init` auto-generates all bot tokens/passwords when you +# configure [agents.llama] in a project TOML. The credentials are stored +# in .env.enc (encrypted) or .env (plaintext fallback). # ── Woodpecker CI ───────────────────────────────────────────────────────── WOODPECKER_TOKEN= # [SECRET] Woodpecker API token diff --git a/docker-compose.yml b/docker-compose.yml index 5985159..0740679 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,8 @@ services: - /usr/local/bin/claude:/usr/local/bin/claude:ro environment: - FORGE_URL=http://forgejo:3000 - - FORGE_TOKEN=${FORGE_TOKEN_DEVQWEN:-} + - FORGE_TOKEN=${FORGE_TOKEN_LLAMA:-} + - FORGE_PASS=${FORGE_PASS_LLAMA:-} - FORGE_SUPERVISOR_TOKEN=${FORGE_SUPERVISOR_TOKEN:-} - FORGE_PREDICTOR_TOKEN=${FORGE_PREDICTOR_TOKEN:-} - FORGE_ARCHITECT_TOKEN=${FORGE_ARCHITECT_TOKEN:-} diff --git a/lib/forge-setup.sh b/lib/forge-setup.sh index d640755..08cb7d1 100644 --- a/lib/forge-setup.sh +++ b/lib/forge-setup.sh @@ -307,6 +307,16 @@ setup_forge() { [predictor-bot]="FORGE_PREDICTOR_PASS" [architect-bot]="FORGE_ARCHITECT_PASS" ) + # Llama bot users (local-model agents) — separate from main agents + # Each llama agent gets its own Forgejo user, token, and password + local -A llama_token_vars=( + [dev-qwen]="FORGE_TOKEN_LLAMA" + [dev-qwen-nightly]="FORGE_TOKEN_LLAMA_NIGHTLY" + ) + local -A llama_pass_vars=( + [dev-qwen]="FORGE_PASS_LLAMA" + [dev-qwen-nightly]="FORGE_PASS_LLAMA_NIGHTLY" + ) local bot_user bot_pass token token_var pass_var @@ -421,12 +431,126 @@ setup_forge() { fi done + # Create llama bot users and tokens (local-model agents) + # These are separate from the main agents and get their own credentials + echo "" + echo "── Setting up llama bot users ────────────────────────────" + + local llama_user llama_pass llama_token llama_token_var llama_pass_var + for llama_user in "${!llama_token_vars[@]}"; do + llama_token_var="${llama_token_vars[$llama_user]}" + llama_pass_var="${llama_pass_vars[$llama_user]}" + + # Check if llama bot user exists + local llama_user_exists=false + if curl -sf --max-time 5 \ + -H "Authorization: token ${admin_token}" \ + "${forge_url}/api/v1/users/${llama_user}" >/dev/null 2>&1; then + llama_user_exists=true + fi + + if [ "$llama_user_exists" = false ]; then + echo "Creating llama bot user: ${llama_user}" + # Generate a unique password for this user + llama_pass="llama-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)" + local create_output + if ! create_output=$(_forgejo_exec forgejo admin user create \ + --username "${llama_user}" \ + --password "${llama_pass}" \ + --email "${llama_user}@disinto.local" \ + --must-change-password=false 2>&1); then + echo "Error: failed to create llama bot user '${llama_user}':" >&2 + echo " ${create_output}" >&2 + exit 1 + fi + # Forgejo 11.x ignores --must-change-password=false on create; + # explicitly clear the flag so basic-auth token creation works. + _forgejo_exec forgejo admin user change-password \ + --username "${llama_user}" \ + --password "${llama_pass}" \ + --must-change-password=false + + # Verify llama bot user was actually created + if ! curl -sf --max-time 5 \ + -H "Authorization: token ${admin_token}" \ + "${forge_url}/api/v1/users/${llama_user}" >/dev/null 2>&1; then + echo "Error: llama bot user '${llama_user}' not found after creation" >&2 + exit 1 + fi + echo " ${llama_user} user created" + else + echo " ${llama_user} user exists (resetting password for token generation)" + # User exists but may not have a known password. + # Use admin API to reset the password so we can generate a new token. + llama_pass="llama-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)" + _forgejo_exec forgejo admin user change-password \ + --username "${llama_user}" \ + --password "${llama_pass}" \ + --must-change-password=false || { + echo "Error: failed to reset password for existing llama bot user '${llama_user}'" >&2 + exit 1 + } + fi + + # Generate token via API (basic auth as the llama user) + # First, delete any existing tokens to avoid name collision + local existing_llama_token_ids + existing_llama_token_ids=$(curl -sf \ + -u "${llama_user}:${llama_pass}" \ + "${forge_url}/api/v1/users/${llama_user}/tokens" 2>/dev/null \ + | jq -r '.[].id // empty' 2>/dev/null) || existing_llama_token_ids="" + + # Delete any existing tokens for this user + if [ -n "$existing_llama_token_ids" ]; then + while IFS= read -r tid; do + [ -n "$tid" ] && curl -sf -X DELETE \ + -u "${llama_user}:${llama_pass}" \ + "${forge_url}/api/v1/users/${llama_user}/tokens/${tid}" >/dev/null 2>&1 || true + done <<< "$existing_llama_token_ids" + fi + + llama_token=$(curl -sf -X POST \ + -u "${llama_user}:${llama_pass}" \ + -H "Content-Type: application/json" \ + "${forge_url}/api/v1/users/${llama_user}/tokens" \ + -d "{\"name\":\"disinto-${llama_user}-token\",\"scopes\":[\"all\"]}" 2>/dev/null \ + | jq -r '.sha1 // empty') || llama_token="" + + if [ -z "$llama_token" ]; then + echo "Error: failed to create API token for '${llama_user}'" >&2 + exit 1 + fi + + # Store token in .env under the llama-specific variable name + if grep -q "^${llama_token_var}=" "$env_file" 2>/dev/null; then + sed -i "s|^${llama_token_var}=.*|${llama_token_var}=${llama_token}|" "$env_file" + else + printf '%s=%s\n' "$llama_token_var" "$llama_token" >> "$env_file" + fi + export "${llama_token_var}=${llama_token}" + echo " ${llama_user} token generated and saved (${llama_token_var})" + + # Store password in .env for git HTTP push (#361) + # Forgejo 11.x API tokens don't work for git push; password auth does. + if grep -q "^${llama_pass_var}=" "$env_file" 2>/dev/null; then + sed -i "s|^${llama_pass_var}=.*|${llama_pass_var}=${llama_pass}|" "$env_file" + else + printf '%s=%s\n' "$llama_pass_var" "$llama_pass" >> "$env_file" + fi + export "${llama_pass_var}=${llama_pass}" + echo " ${llama_user} password saved (${llama_pass_var})" + done + # Create .profile repos for all bot users (if they don't already exist) # This runs the same logic as hire-an-agent Step 2-3 for idempotent setup echo "" echo "── Setting up .profile repos ────────────────────────────" local -a bot_users=(dev-bot review-bot planner-bot gardener-bot vault-bot supervisor-bot predictor-bot architect-bot) + # Add llama bot users to .profile repo creation + for llama_user in "${!llama_token_vars[@]}"; do + bot_users+=("$llama_user") + done local bot_user for bot_user in "${bot_users[@]}"; do @@ -534,6 +658,15 @@ setup_forge() { -d "{\"permission\":\"${bot_perm}\"}" >/dev/null 2>&1 || true done + # Add llama bot users as write collaborators for local-model agents + for llama_user in "${!llama_token_vars[@]}"; do + curl -sf -X PUT \ + -H "Authorization: token ${admin_token:-${FORGE_TOKEN}}" \ + -H "Content-Type: application/json" \ + "${forge_url}/api/v1/repos/${repo_slug}/collaborators/${llama_user}" \ + -d '{"permission":"write"}' >/dev/null 2>&1 || true + done + # Add disinto-admin as admin collaborator curl -sf -X PUT \ -H "Authorization: token ${admin_token:-${FORGE_TOKEN}}" \ diff --git a/lib/generators.sh b/lib/generators.sh index cabdf71..be487f8 100644 --- a/lib/generators.sh +++ b/lib/generators.sh @@ -72,7 +72,9 @@ _generate_local_model_services() { - \${HOME}/.ssh:/home/agent/.ssh:ro environment: FORGE_URL: http://forgejo:3000 - FORGE_TOKEN: \${FORGE_TOKEN:-} + # Use llama-specific credentials if available, otherwise fall back to main FORGE_TOKEN + FORGE_TOKEN: \${FORGE_TOKEN_LLAMA:-\${FORGE_TOKEN:-}} + FORGE_PASS: \${FORGE_PASS_LLAMA:-\${FORGE_PASS:-}} FORGE_REVIEW_TOKEN: \${FORGE_REVIEW_TOKEN:-} FORGE_BOT_USERNAMES: \${FORGE_BOT_USERNAMES:-} AGENT_ROLES: "${roles}" diff --git a/projects/disinto.toml.example b/projects/disinto.toml.example index 04e99e5..d7c2058 100644 --- a/projects/disinto.toml.example +++ b/projects/disinto.toml.example @@ -27,6 +27,11 @@ check_pipeline_stall = false # for local LLM inference. Each agent gets its own container with isolated # credentials and configuration. # +# When enabled, `disinto init` automatically: +# 1. Creates a Forgejo bot user matching agents.llama.forge_user +# 2. Generates FORGE_TOKEN_ and FORGE_PASS_ (stored in .env.enc) +# 3. Adds the bot user as a write collaborator on the project repo +# # [agents.llama] # base_url = "http://10.10.10.1:8081" # model = "unsloth/Qwen3.5-35B-A3B"