diff --git a/.gitignore b/.gitignore index bcc5231..dd9365d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,3 @@ metrics/supervisor-metrics.jsonl .DS_Store dev/ci-fixes-*.json gardener/dust.jsonl - -# Individual encrypted secrets (managed by disinto secrets add) -secrets/ diff --git a/bin/disinto b/bin/disinto index d6b8c6e..7a0714e 100755 --- a/bin/disinto +++ b/bin/disinto @@ -232,7 +232,6 @@ services: - ${HOME}/.claude.json:/home/agent/.claude.json:ro - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro - \${HOME}/.ssh:/home/agent/.ssh:ro - - \${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro environment: FORGE_URL: http://forgejo:3000 WOODPECKER_SERVER: http://woodpecker:8000 @@ -2023,88 +2022,7 @@ disinto_secrets() { fi } - local secrets_dir="${FACTORY_ROOT}/secrets" - local age_key_file="${HOME}/.config/sops/age/keys.txt" - - # Shared helper: ensure age key exists and export AGE_PUBLIC_KEY - _secrets_ensure_age_key() { - if ! command -v age &>/dev/null; then - echo "Error: age is required." >&2 - echo " Install age: apt install age / brew install age" >&2 - exit 1 - fi - if [ ! -f "$age_key_file" ]; then - echo "Error: age key not found at ${age_key_file}" >&2 - echo " Run 'disinto init' to generate one, or create manually with:" >&2 - echo " mkdir -p ~/.config/sops/age && age-keygen -o ${age_key_file}" >&2 - exit 1 - fi - AGE_PUBLIC_KEY="$(age-keygen -y "$age_key_file" 2>/dev/null)" - if [ -z "$AGE_PUBLIC_KEY" ]; then - echo "Error: failed to read public key from ${age_key_file}" >&2 - exit 1 - fi - export AGE_PUBLIC_KEY - } - case "$subcmd" in - add) - local name="${2:-}" - if [ -z "$name" ]; then - echo "Usage: disinto secrets add " >&2 - exit 1 - fi - _secrets_ensure_age_key - mkdir -p "$secrets_dir" - - printf 'Enter value for %s: ' "$name" >&2 - local value - IFS= read -rs value - echo >&2 - if [ -z "$value" ]; then - echo "Error: empty value" >&2 - exit 1 - fi - - local enc_path="${secrets_dir}/${name}.enc" - if [ -f "$enc_path" ]; then - printf 'Secret %s already exists. Overwrite? [y/N] ' "$name" >&2 - local confirm - read -r confirm - if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then - echo "Aborted." >&2 - exit 1 - fi - fi - if ! printf '%s' "$value" | age -r "$AGE_PUBLIC_KEY" -o "$enc_path"; then - echo "Error: encryption failed" >&2 - exit 1 - fi - echo "Stored: ${enc_path}" - ;; - show) - local name="${2:-}" - if [ -n "$name" ]; then - # Show individual secret: disinto secrets show - local enc_path="${secrets_dir}/${name}.enc" - if [ ! -f "$enc_path" ]; then - echo "Error: ${enc_path} not found" >&2 - exit 1 - fi - if [ ! -f "$age_key_file" ]; then - echo "Error: age key not found at ${age_key_file}" >&2 - exit 1 - fi - age -d -i "$age_key_file" "$enc_path" - else - # Show all agent secrets: disinto secrets show - if [ ! -f "$enc_file" ]; then - echo "Error: ${enc_file} not found." >&2 - exit 1 - fi - sops -d "$enc_file" - fi - ;; edit) if [ ! -f "$enc_file" ]; then echo "Error: ${enc_file} not found. Run 'disinto secrets migrate' first." >&2 @@ -2112,6 +2030,13 @@ disinto_secrets() { fi sops "$enc_file" ;; + show) + if [ ! -f "$enc_file" ]; then + echo "Error: ${enc_file} not found." >&2 + exit 1 + fi + sops -d "$enc_file" + ;; migrate) if [ ! -f "$env_file" ]; then echo "Error: ${env_file} not found — nothing to migrate." >&2 @@ -2119,12 +2044,6 @@ disinto_secrets() { fi _secrets_ensure_sops encrypt_env_file "$env_file" "$enc_file" - # Verify decryption works - if ! sops -d "$enc_file" >/dev/null 2>&1; then - echo "Error: failed to verify .env.enc decryption" >&2 - rm -f "$enc_file" - exit 1 - fi rm -f "$env_file" echo "Migrated: .env -> .env.enc (plaintext removed)" ;; @@ -2157,13 +2076,9 @@ disinto_secrets() { cat <&2 Usage: disinto secrets -Individual secrets (secrets/.enc): - add Prompt for value, encrypt, store in secrets/.enc - show Decrypt and print an individual secret - Agent secrets (.env.enc): edit Edit agent secrets (FORGE_TOKEN, CLAUDE_API_KEY, etc.) - show Show decrypted agent secrets (no argument) + show Show decrypted agent secrets migrate Encrypt .env -> .env.enc Vault secrets (.env.vault.enc): diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index bdbdb70..3a78f53 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -185,11 +185,7 @@ log "preflight passed" # ============================================================================= # CLAIM ISSUE # ============================================================================= -if ! issue_claim "$ISSUE"; then - log "SKIP: failed to claim issue #${ISSUE} (already assigned to another agent)" - echo '{"status":"already_done","reason":"issue was claimed by another agent"}' > "$PREFLIGHT_RESULT" - exit 0 -fi +issue_claim "$ISSUE" CLAIMED=true # ============================================================================= diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index 22ba929..98b8b7d 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -307,11 +307,6 @@ memory_guard 2000 # PRIORITY 1: orphaned in-progress issues # ============================================================================= log "checking for in-progress issues" - -# Get current bot identity for assignee checks -BOT_USER=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ - "${API%%/repos*}/user" | jq -r '.login') || BOT_USER="" - ORPHANS_JSON=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ "${API}/issues?state=open&labels=in-progress&limit=10&type=issues") @@ -392,20 +387,7 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then log "issue #${ISSUE_NUM} has open PR #${HAS_PR} (CI: ${CI_STATE}, waiting)" fi else - # Check assignee before adopting orphaned issue - ISSUE_JSON=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ - "${API}/issues/${ISSUE_NUM}") || true - ASSIGNEE=$(echo "$ISSUE_JSON" | jq -r '.assignee.login // ""') || true - - if [ -n "$ASSIGNEE" ] && [ "$ASSIGNEE" != "$BOT_USER" ]; then - log "issue #${ISSUE_NUM} assigned to ${ASSIGNEE} — skipping (not orphaned)" - # Remove in-progress label since this agent isn't working on it - curl -sf -X DELETE -H "Authorization: token ${FORGE_TOKEN}" \ - "${API}/issues/${ISSUE_NUM}/labels/in-progress" >/dev/null 2>&1 || true - exit 0 - fi - - log "recovering orphaned issue #${ISSUE_NUM} (no PR found, assigned to ${BOT_USER:-unassigned})" + log "recovering orphaned issue #${ISSUE_NUM} (no PR found)" nohup "${SCRIPT_DIR}/dev-agent.sh" "$ISSUE_NUM" >> "$LOGFILE" 2>&1 & log "started dev-agent PID $! for issue #${ISSUE_NUM} (recovery)" exit 0 diff --git a/docker/agents/Dockerfile b/docker/agents/Dockerfile index 947af02..927b076 100644 --- a/docker/agents/Dockerfile +++ b/docker/agents/Dockerfile @@ -4,20 +4,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ bash curl git jq tmux cron python3 python3-pip openssh-client ca-certificates age \ && pip3 install --break-system-packages networkx \ && curl -sL https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64 \ - -o /usr/local/bin/sops \ - && curl -sL https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.checksums.txt \ - -o /tmp/sops-checksums.txt \ - && sha256sum -c --ignore-missing /tmp/sops-checksums.txt \ - && rm -f /tmp/sops-checksums.txt \ - && chmod +x /usr/local/bin/sops \ + -o /usr/local/bin/sops && chmod +x /usr/local/bin/sops \ && rm -rf /var/lib/apt/lists/* -# tea CLI — official Gitea/Forgejo CLI for issue/label/comment operations -# Checksum from https://dl.gitea.com/tea/0.9.2/tea-0.9.2-linux-amd64.sha256 -RUN curl -sL https://dl.gitea.com/tea/0.9.2/tea-0.9.2-linux-amd64 -o /usr/local/bin/tea \ - && echo "be10cdf9a619e3c0f121df874960ed19b53e62d1c7036cf60313a28b5227d54d /usr/local/bin/tea" | sha256sum -c - \ - && chmod +x /usr/local/bin/tea - # Claude CLI is mounted from the host via docker-compose volume. # No internet access to cli.anthropic.com required at build time. diff --git a/lib/issue-lifecycle.sh b/lib/issue-lifecycle.sh index 19c422d..df6a0ae 100644 --- a/lib/issue-lifecycle.sh +++ b/lib/issue-lifecycle.sh @@ -81,35 +81,11 @@ _ilc_in_progress_id() { _ilc_ensure_label_id _ILC_IN_PROGRESS_ID "in-progress" _ilc_blocked_id() { _ilc_ensure_label_id _ILC_BLOCKED_ID "blocked" "#e11d48"; } # --------------------------------------------------------------------------- -# issue_claim — assign issue to bot, add "in-progress" label, remove "backlog". +# issue_claim — add "in-progress" label, remove "backlog" label. # Args: issue_number -# Returns: 0 on success, 1 if already assigned to another agent # --------------------------------------------------------------------------- issue_claim() { local issue="$1" - - # Get current bot identity - local me - me=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ - "${FORGE_URL}/api/v1/user" | jq -r '.login') || return 1 - - # Check current assignee - local current - current=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ - "${FORGE_API}/issues/${issue}" | jq -r '.assignee.login // ""') || return 1 - - if [ -n "$current" ] && [ "$current" != "$me" ]; then - _ilc_log "issue #${issue} already assigned to ${current} — skipping" - return 1 - fi - - # Assign to self (Forgejo rejects if already assigned differently) - curl -sf -X PATCH \ - -H "Authorization: token ${FORGE_TOKEN}" \ - -H "Content-Type: application/json" \ - "${FORGE_API}/issues/${issue}" \ - -d "{\"assignees\":[\"${me}\"]}" >/dev/null 2>&1 || return 1 - local ip_id bl_id ip_id=$(_ilc_in_progress_id) bl_id=$(_ilc_backlog_id) @@ -126,23 +102,14 @@ issue_claim() { "${FORGE_API}/issues/${issue}/labels/${bl_id}" >/dev/null 2>&1 || true fi _ilc_log "claimed issue #${issue}" - return 0 } # --------------------------------------------------------------------------- -# issue_release — remove "in-progress" label, add "backlog" label, clear assignee. +# issue_release — remove "in-progress" label, add "backlog" label. # Args: issue_number # --------------------------------------------------------------------------- issue_release() { local issue="$1" - - # Clear assignee - curl -sf -X PATCH \ - -H "Authorization: token ${FORGE_TOKEN}" \ - -H "Content-Type: application/json" \ - "${FORGE_API}/issues/${issue}" \ - -d '{"assignees":[]}' >/dev/null 2>&1 || true - local ip_id bl_id ip_id=$(_ilc_in_progress_id) bl_id=$(_ilc_backlog_id) @@ -217,19 +184,11 @@ issue_block() { } # --------------------------------------------------------------------------- -# issue_close — clear assignee, PATCH state to closed. +# issue_close — PATCH state to closed. # Args: issue_number # --------------------------------------------------------------------------- issue_close() { local issue="$1" - - # Clear assignee before closing - curl -sf -X PATCH \ - -H "Authorization: token ${FORGE_TOKEN}" \ - -H "Content-Type: application/json" \ - "${FORGE_API}/issues/${issue}" \ - -d '{"assignees":[]}' >/dev/null 2>&1 || true - curl -sf -X PATCH \ -H "Authorization: token ${FORGE_TOKEN}" \ -H "Content-Type: application/json" \