Compare commits
4 commits
0ccecf6ae5
...
e43300662c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e43300662c | ||
|
|
57725e2c4b | ||
|
|
f590111a8e | ||
|
|
8f3b999150 |
6 changed files with 14 additions and 176 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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/
|
||||
|
|
|
|||
101
bin/disinto
101
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 <NAME>" >&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 <NAME>
|
||||
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 <<EOF >&2
|
||||
Usage: disinto secrets <subcommand>
|
||||
|
||||
Individual secrets (secrets/<NAME>.enc):
|
||||
add <NAME> Prompt for value, encrypt, store in secrets/<NAME>.enc
|
||||
show <NAME> 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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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" \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue