fix: bug: bin/disinto init rotates all bot tokens and passwords on every run, invalidating existing cloned repos with embedded credentials (#584)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline was successful

This commit is contained in:
Claude 2026-04-10 14:18:27 +00:00
parent 6b858c9c43
commit cecfb3374d
2 changed files with 133 additions and 35 deletions

View file

@ -69,6 +69,7 @@ Init options:
--forge-url <url> Forge base URL (default: http://localhost:3000)
--bare Skip compose generation (bare-metal setup)
--yes Skip confirmation prompts
--rotate-tokens Force regeneration of all bot tokens/passwords (idempotent by default)
Hire an agent options:
--formula <path> Path to role formula TOML (default: formulas/<role>.toml)
@ -551,15 +552,16 @@ disinto_init() {
shift
# Parse flags
local branch="" repo_root="" ci_id="0" auto_yes=false forge_url_flag="" bare=false
local branch="" repo_root="" ci_id="0" auto_yes=false forge_url_flag="" bare=false rotate_tokens=false
while [ $# -gt 0 ]; do
case "$1" in
--branch) branch="$2"; shift 2 ;;
--repo-root) repo_root="$2"; shift 2 ;;
--ci-id) ci_id="$2"; shift 2 ;;
--forge-url) forge_url_flag="$2"; shift 2 ;;
--bare) bare=true; shift ;;
--yes) auto_yes=true; shift ;;
--branch) branch="$2"; shift 2 ;;
--repo-root) repo_root="$2"; shift 2 ;;
--ci-id) ci_id="$2"; shift 2 ;;
--forge-url) forge_url_flag="$2"; shift 2 ;;
--bare) bare=true; shift ;;
--yes) auto_yes=true; shift ;;
--rotate-tokens) rotate_tokens=true; shift ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
@ -651,7 +653,12 @@ p.write_text(text)
fi
# Set up local Forgejo instance (provision if needed, create users/tokens/repo)
setup_forge "$forge_url" "$forge_repo"
if [ "$rotate_tokens" = true ]; then
echo "Note: Forcing token rotation (tokens/passwords will be regenerated)"
setup_forge --rotate-tokens "$forge_url" "$forge_repo"
else
setup_forge "$forge_url" "$forge_repo"
fi
# Preflight: verify factory requirements
preflight_check "$forge_repo" "$forge_url"

View file

@ -38,10 +38,33 @@ _forgejo_exec() {
fi
}
# Check if a token already exists in .env (for idempotency)
# Returns 0 if token exists, 1 if it doesn't
_token_exists_in_env() {
local token_var="$1"
local env_file="$2"
grep -q "^${token_var}=" "$env_file" 2>/dev/null
}
# Check if a password already exists in .env (for idempotency)
# Returns 0 if password exists, 1 if it doesn't
_pass_exists_in_env() {
local pass_var="$1"
local env_file="$2"
grep -q "^${pass_var}=" "$env_file" 2>/dev/null
}
# Provision or connect to a local Forgejo instance.
# Creates admin + bot users, generates API tokens, stores in .env.
# When $DISINTO_BARE is set, uses standalone docker run; otherwise uses compose.
# Usage: setup_forge [--rotate-tokens] <forge_url> <repo_slug>
setup_forge() {
local rotate_tokens=false
# Parse optional --rotate-tokens flag
if [ "$1" = "--rotate-tokens" ]; then
rotate_tokens=true
shift
fi
local forge_url="$1"
local repo_slug="$2"
local use_bare="${DISINTO_BARE:-false}"
@ -319,10 +342,22 @@ setup_forge() {
local bot_user bot_pass token token_var pass_var
for bot_user in dev-bot review-bot planner-bot gardener-bot vault-bot supervisor-bot predictor-bot architect-bot; do
bot_pass="bot-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)"
token_var="${bot_token_vars[$bot_user]}"
pass_var="${bot_pass_vars[$bot_user]}"
# Check if bot user exists
# Check if token already exists in .env
local token_exists=false
if _token_exists_in_env "$token_var" "$env_file"; then
token_exists=true
fi
# Check if password already exists in .env
local pass_exists=false
if _pass_exists_in_env "$pass_var" "$env_file"; then
pass_exists=true
fi
# Check if bot user exists on Forgejo
local user_exists=false
if curl -sf --max-time 5 \
-H "Authorization: token ${admin_token}" \
@ -330,7 +365,25 @@ setup_forge() {
user_exists=true
fi
# Skip token/password regeneration if both exist in .env and not forcing rotation
if [ "$token_exists" = true ] && [ "$pass_exists" = true ] && [ "$rotate_tokens" = false ]; then
echo " ${bot_user} token and password preserved (use --rotate-tokens to force)"
# Still export the existing token for use within this run
local existing_token existing_pass
existing_token=$(grep "^${token_var}=" "$env_file" | head -1 | cut -d= -f2-)
existing_pass=$(grep "^${pass_var}=" "$env_file" | head -1 | cut -d= -f2-)
export "${token_var}=${existing_token}"
export "${pass_var}=${existing_pass}"
continue
fi
# Generate new credentials if:
# - Token doesn't exist (first run)
# - Password doesn't exist (first run)
# - --rotate-tokens flag is set (explicit rotation)
if [ "$user_exists" = false ]; then
# User doesn't exist - create it
bot_pass="bot-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)"
echo "Creating bot user: ${bot_user}"
local create_output
if ! create_output=$(_forgejo_exec forgejo admin user create \
@ -358,16 +411,22 @@ setup_forge() {
fi
echo " ${bot_user} user created"
else
echo " ${bot_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.
_forgejo_exec forgejo admin user change-password \
--username "${bot_user}" \
--password "${bot_pass}" \
--must-change-password=false || {
echo "Error: failed to reset password for existing bot user '${bot_user}'" >&2
exit 1
}
# User exists - reset password if needed
echo " ${bot_user} user exists"
if [ "$rotate_tokens" = true ] || [ "$pass_exists" = false ]; then
bot_pass="bot-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)"
_forgejo_exec forgejo admin user change-password \
--username "${bot_user}" \
--password "${bot_pass}" \
--must-change-password=false || {
echo "Error: failed to reset password for existing bot user '${bot_user}'" >&2
exit 1
}
echo " ${bot_user} password reset for token generation"
else
# Password exists, get it from .env
bot_pass=$(grep "^${pass_var}=" "$env_file" | head -1 | cut -d= -f2-)
fi
fi
# Generate token via API (basic auth as the bot user — Forgejo requires
@ -412,7 +471,6 @@ setup_forge() {
# Store password in .env for git HTTP push (#361)
# Forgejo 11.x API tokens don't work for git push; password auth does.
pass_var="${bot_pass_vars[$bot_user]}"
if grep -q "^${pass_var}=" "$env_file" 2>/dev/null; then
sed -i "s|^${pass_var}=.*|${pass_var}=${bot_pass}|" "$env_file"
else
@ -439,7 +497,19 @@ setup_forge() {
llama_token_var="${llama_token_vars[$llama_user]}"
llama_pass_var="${llama_pass_vars[$llama_user]}"
# Check if llama bot user exists
# Check if token already exists in .env
local token_exists=false
if _token_exists_in_env "$llama_token_var" "$env_file"; then
token_exists=true
fi
# Check if password already exists in .env
local pass_exists=false
if _pass_exists_in_env "$llama_pass_var" "$env_file"; then
pass_exists=true
fi
# Check if llama bot user exists on Forgejo
local llama_user_exists=false
if curl -sf --max-time 5 \
-H "Authorization: token ${admin_token}" \
@ -447,10 +517,26 @@ setup_forge() {
llama_user_exists=true
fi
# Skip token/password regeneration if both exist in .env and not forcing rotation
if [ "$token_exists" = true ] && [ "$pass_exists" = true ] && [ "$rotate_tokens" = false ]; then
echo " ${llama_user} token and password preserved (use --rotate-tokens to force)"
# Still export the existing token for use within this run
local existing_token existing_pass
existing_token=$(grep "^${llama_token_var}=" "$env_file" | head -1 | cut -d= -f2-)
existing_pass=$(grep "^${llama_pass_var}=" "$env_file" | head -1 | cut -d= -f2-)
export "${llama_token_var}=${existing_token}"
export "${llama_pass_var}=${existing_pass}"
continue
fi
# Generate new credentials if:
# - Token doesn't exist (first run)
# - Password doesn't exist (first run)
# - --rotate-tokens flag is set (explicit rotation)
if [ "$llama_user_exists" = false ]; then
echo "Creating llama bot user: ${llama_user}"
# Generate a unique password for this user
# User doesn't exist - create it
llama_pass="llama-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)"
echo "Creating llama bot user: ${llama_user}"
local create_output
if ! create_output=$(_forgejo_exec forgejo admin user create \
--username "${llama_user}" \
@ -477,17 +563,22 @@ setup_forge() {
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
}
# User exists - reset password if needed
echo " ${llama_user} user exists"
if [ "$rotate_tokens" = true ] || [ "$pass_exists" = false ]; then
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
}
echo " ${llama_user} password reset for token generation"
else
# Password exists, get it from .env
llama_pass=$(grep "^${llama_pass_var}=" "$env_file" | head -1 | cut -d= -f2-)
fi
fi
# Generate token via API (basic auth as the llama user)