feat(bootstrap): GH_TOKEN-based clone of Scrum4Me + scrum4me-mcp
Fixes the 'no GitHub credentials' deadlock observed in the first
NAS-Docker batch run (2 May 2026): scrum4me-mcp's `wait_for_job`
expects a local clone at `~/Projects/<repo-name>` (convention-fallback
in resolveRepoRoot) but the container had no credentials and no clone.
Agent asked the user how to proceed; turn closed without claim.
Changes:
- `.env.example`: GH_TOKEN (fine-grained PAT, repo+PR scope) and
GH_PRECLONE_REPOS (comma-separated owner/name list, default covers
Scrum4Me + scrum4me-mcp).
- `bin/repo-bootstrap.sh` (new): runs as agent-user; configures git
credential-helper with HTTPS oauth2 token, then clones-or-fetches
each entry in GH_PRECLONE_REPOS into ~/Projects/<name>. Idempotent.
- `bin/entrypoint.sh`: hooks repo-bootstrap before run-agent.sh.
- `Dockerfile`:
- installs `gh` CLI (used for auto_pr `gh pr create`; reads GH_TOKEN
from env directly).
- pre-creates `~agent/Projects` and `~agent/.scrum4me-agent-worktrees`
so directory-ownership is right from the first boot.
- `README.md`: 'Repo bootstrap (clone-on-start)' section + GH_TOKEN
step in the deploy checklist; corrects the obsolete 'no push
credentials' note (agent now pushes feature-branches, gh creates PRs).
Same token covers clone, push and PR-creation — one secret to rotate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
47b1de93db
commit
c090e6c349
5 changed files with 147 additions and 6 deletions
22
.env.example
22
.env.example
|
|
@ -22,6 +22,28 @@ CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-vervang-mij
|
||||||
# Als deze ge-revoked wordt: rebuild + redeploy (zie README).
|
# Als deze ge-revoked wordt: rebuild + redeploy (zie README).
|
||||||
SCRUM4ME_TOKEN=vervang-mij
|
SCRUM4ME_TOKEN=vervang-mij
|
||||||
|
|
||||||
|
# ----- GitHub credentials -----------------------------------
|
||||||
|
# Personal Access Token (fine-grained) met:
|
||||||
|
# - Repository access op madhura68/Scrum4Me + madhura68/scrum4me-mcp
|
||||||
|
# - Permissions: Contents (read/write), Pull requests (read/write),
|
||||||
|
# Metadata (read)
|
||||||
|
#
|
||||||
|
# Gebruikt voor:
|
||||||
|
# 1. Pre-clone van de repos in /home/agent/Projects/<name>/ bij
|
||||||
|
# container-start (entrypoint.sh)
|
||||||
|
# 2. `git push` van agent feature-branches via HTTPS
|
||||||
|
# 3. `gh pr create` (auto_pr=true) — gh CLI leest GH_TOKEN uit env
|
||||||
|
#
|
||||||
|
# Genereer op github.com → Settings → Developer settings →
|
||||||
|
# Personal access tokens → Fine-grained tokens.
|
||||||
|
GH_TOKEN=ghp_vervang-mij
|
||||||
|
|
||||||
|
# Lijst (komma-gescheiden) van repos om vooraf te clonen naar
|
||||||
|
# ~agent/Projects/<name>. resolveRepoRoot in scrum4me-mcp valt
|
||||||
|
# automatisch terug op die conventie. Voeg meer toe als je nieuwe
|
||||||
|
# producten/repos toevoegt aan Scrum4Me.
|
||||||
|
GH_PRECLONE_REPOS=madhura68/Scrum4Me,madhura68/scrum4me-mcp
|
||||||
|
|
||||||
# ----- Scrum4Me database ------------------------------------
|
# ----- Scrum4Me database ------------------------------------
|
||||||
# Beide URLs uit het Neon-dashboard. DATABASE_URL is pooled,
|
# Beide URLs uit het Neon-dashboard. DATABASE_URL is pooled,
|
||||||
# DIRECT_URL is unpooled — scrum4me-mcp gebruikt DATABASE_URL
|
# DIRECT_URL is unpooled — scrum4me-mcp gebruikt DATABASE_URL
|
||||||
|
|
|
||||||
15
Dockerfile
15
Dockerfile
|
|
@ -12,10 +12,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates curl git tini gosu jq xz-utils \
|
ca-certificates curl git tini gosu jq xz-utils \
|
||||||
build-essential python3 \
|
build-essential python3 \
|
||||||
tzdata logrotate \
|
tzdata logrotate \
|
||||||
|
gnupg \
|
||||||
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
|
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||||
&& dpkg-reconfigure --frontend=noninteractive tzdata \
|
&& dpkg-reconfigure --frontend=noninteractive tzdata \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ----- gh CLI ------------------------------------------------------------
|
||||||
|
# Required for auto_pr (`gh pr create`) and authenticates via the GH_TOKEN
|
||||||
|
# env-var that is also used by the git credential helper for HTTPS.
|
||||||
|
RUN install -m 0755 -d /etc/apt/keyrings \
|
||||||
|
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
||||||
|
| gpg --dearmor -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||||
|
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||||
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
||||||
|
> /etc/apt/sources.list.d/github-cli.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends gh \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# ----- node 22 LTS -------------------------------------------------------
|
# ----- node 22 LTS -------------------------------------------------------
|
||||||
# Voor zowel Claude Code (de native installer heeft geen node nodig, maar
|
# Voor zowel Claude Code (de native installer heeft geen node nodig, maar
|
||||||
# scrum4me-mcp draait op tsx) als de health-server.
|
# scrum4me-mcp draait op tsx) als de health-server.
|
||||||
|
|
@ -56,6 +70,7 @@ ARG AGENT_GID=1000
|
||||||
RUN groupadd -g ${AGENT_GID} agent \
|
RUN groupadd -g ${AGENT_GID} agent \
|
||||||
&& useradd -u ${AGENT_UID} -g ${AGENT_GID} -m -s /bin/bash agent \
|
&& useradd -u ${AGENT_UID} -g ${AGENT_GID} -m -s /bin/bash agent \
|
||||||
&& mkdir -p /var/cache/repos /var/cache/npm /var/log/agent /var/run/agent \
|
&& mkdir -p /var/cache/repos /var/cache/npm /var/log/agent /var/run/agent \
|
||||||
|
&& mkdir -p /home/agent/Projects /home/agent/.scrum4me-agent-worktrees \
|
||||||
&& chown -R agent:agent /var/cache /var/log/agent /var/run/agent /home/agent
|
&& chown -R agent:agent /var/cache /var/log/agent /var/run/agent /home/agent
|
||||||
|
|
||||||
# ----- runner files ------------------------------------------------------
|
# ----- runner files ------------------------------------------------------
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -71,6 +71,13 @@ fouten.
|
||||||
# b. SCRUM4ME_TOKEN → log in als de dedicated agent-user in
|
# b. SCRUM4ME_TOKEN → log in als de dedicated agent-user in
|
||||||
# Scrum4Me, /settings/tokens, label "NAS-runner"
|
# Scrum4Me, /settings/tokens, label "NAS-runner"
|
||||||
# c. DATABASE_URL/DIRECT_URL → Neon dashboard
|
# c. DATABASE_URL/DIRECT_URL → Neon dashboard
|
||||||
|
# d. GH_TOKEN → github.com → Settings → Developer settings →
|
||||||
|
# Personal access tokens → Fine-grained.
|
||||||
|
# Repository access op madhura68/Scrum4Me +
|
||||||
|
# madhura68/scrum4me-mcp; Permissions:
|
||||||
|
# Contents (RW), Pull requests (RW),
|
||||||
|
# Metadata (R). Wordt gebruikt voor clone,
|
||||||
|
# push en `gh pr create` (auto_pr).
|
||||||
|
|
||||||
# 2. Repo op de NAS plaatsen
|
# 2. Repo op de NAS plaatsen
|
||||||
ssh admin@nas
|
ssh admin@nas
|
||||||
|
|
@ -126,10 +133,30 @@ laatste heartbeat ouder is dan 5 minuten.
|
||||||
|
|
||||||
## Filesystem-grenzen
|
## Filesystem-grenzen
|
||||||
|
|
||||||
De agent-user heeft geen SSH-keys, geen `~/.gitconfig` met push-credentials,
|
De agent-user heeft geen SSH-keys en geen toegang tot andere shares dan
|
||||||
en geen toegang tot andere shares dan `/share/Agent/*`. Commits worden
|
`/share/Agent/*`. Wel een `~/.git-credentials` met de `GH_TOKEN` voor
|
||||||
lokaal in de per-job clone gemaakt; pushen gebeurt door jou op je
|
HTTPS-clone/push (zie volgende sectie) — die token is scoped tot de twee
|
||||||
werkstation na review (CLAUDE.md regel: *"`git push` is altijd expliciet"*).
|
configured repos en mag worden gerouleerd door rebuild + redeploy.
|
||||||
|
|
||||||
|
## Repo bootstrap (clone-on-start)
|
||||||
|
|
||||||
|
Bij elke container-start runt `bin/repo-bootstrap.sh` (als de
|
||||||
|
`agent`-user, ná drop-privileges) en zet zo'n setup neer:
|
||||||
|
|
||||||
|
1. Configureert git's credential-helper met `GH_TOKEN` zodat
|
||||||
|
`git clone`/`push` naar `https://github.com/...` zonder prompt werkt.
|
||||||
|
2. Voor elke repo in `GH_PRECLONE_REPOS` (komma-gescheiden owner/name):
|
||||||
|
- Bestaat `~/Projects/<name>/.git` al? → `git fetch origin --prune`
|
||||||
|
- Anders → fresh `git clone`
|
||||||
|
|
||||||
|
Daarna vindt scrum4me-mcp's `resolveRepoRoot` (in `wait_for_job`) de
|
||||||
|
clone via z'n convention-fallback `~/Projects/<name>/.git`. Worktrees
|
||||||
|
voor jobs landen vervolgens onder `~/.scrum4me-agent-worktrees/<jobId>/`
|
||||||
|
zodat de hoofd-clone niet wordt aangeraakt.
|
||||||
|
|
||||||
|
Push gaat over dezelfde token: `git push -u origin feat/story-<id>`
|
||||||
|
slaagt zonder prompt. `gh pr create` (voor producten met `auto_pr=true`)
|
||||||
|
gebruikt dezelfde `GH_TOKEN` via de `gh` CLI's standaard env-detect.
|
||||||
|
|
||||||
## Bekende grenzen
|
## Bekende grenzen
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,15 @@ gosu agent /bin/bash -c 'cat > "${AGENT_STATE_DIR}/state.json"' <<EOF
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# ----- 3. drop privileges en start daemon-loop --------------------------
|
# ----- 3. drop privileges, bootstrap repos, start daemon-loop -----------
|
||||||
log "dropping to agent user and starting run-agent.sh"
|
# repo-bootstrap.sh runs as agent (NOT root) so that the cloned repos
|
||||||
|
# are owned by the agent user and live under ~agent/Projects/<name> —
|
||||||
|
# that is exactly where scrum4me-mcp's resolveRepoRoot looks via its
|
||||||
|
# convention fallback.
|
||||||
|
log "dropping to agent user and bootstrapping repos"
|
||||||
|
gosu agent /opt/agent/bin/repo-bootstrap.sh \
|
||||||
|
>> "${AGENT_LOG_DIR}/repo-bootstrap.log" 2>&1 \
|
||||||
|
|| log "WARN: repo-bootstrap returned non-zero (continuing)"
|
||||||
|
|
||||||
|
log "starting run-agent.sh"
|
||||||
exec gosu agent /opt/agent/bin/run-agent.sh
|
exec gosu agent /opt/agent/bin/run-agent.sh
|
||||||
|
|
|
||||||
68
bin/repo-bootstrap.sh
Normal file
68
bin/repo-bootstrap.sh
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# repo-bootstrap.sh — pre-clone repos into ~agent/Projects so that
|
||||||
|
# scrum4me-mcp's `wait_for_job` finds a working repoRoot via the
|
||||||
|
# convention-fallback `~/Projects/<name>/.git`.
|
||||||
|
#
|
||||||
|
# Idempotent:
|
||||||
|
# - Sets up git credential helper using GH_TOKEN (HTTPS auth)
|
||||||
|
# - For each entry in GH_PRECLONE_REPOS (comma-separated owner/name list):
|
||||||
|
# * If ~/Projects/<name> exists → `git fetch origin --prune`
|
||||||
|
# * Otherwise → fresh `git clone`
|
||||||
|
#
|
||||||
|
# Runs as the agent user (called from entrypoint.sh after `gosu agent …`).
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
source /opt/agent/bin/_lib.sh
|
||||||
|
|
||||||
|
: "${GH_TOKEN:=}"
|
||||||
|
: "${GH_PRECLONE_REPOS:=}"
|
||||||
|
|
||||||
|
if [[ -z "$GH_TOKEN" ]]; then
|
||||||
|
log "GH_TOKEN not set — skipping clone bootstrap. wait_for_job will fail until repos exist."
|
||||||
|
return 0 2>/dev/null || exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$GH_PRECLONE_REPOS" ]]; then
|
||||||
|
log "GH_PRECLONE_REPOS empty — nothing to clone."
|
||||||
|
return 0 2>/dev/null || exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----- 1. configure git credential helper for HTTPS clone/push -----------
|
||||||
|
mkdir -p "$HOME"
|
||||||
|
git config --global credential.helper store
|
||||||
|
CREDS_FILE="$HOME/.git-credentials"
|
||||||
|
if [[ ! -f "$CREDS_FILE" ]] || ! grep -q "oauth2:${GH_TOKEN}@github.com" "$CREDS_FILE" 2>/dev/null; then
|
||||||
|
printf 'https://oauth2:%s@github.com\n' "$GH_TOKEN" > "$CREDS_FILE"
|
||||||
|
chmod 600 "$CREDS_FILE"
|
||||||
|
log "git credentials helper configured at ${CREDS_FILE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prevent the token from leaking into commit-author of automated commits.
|
||||||
|
git config --global user.name "${GIT_AUTHOR_NAME:-Scrum4Me Agent}"
|
||||||
|
git config --global user.email "${GIT_AUTHOR_EMAIL:-agent@scrum4me.local}"
|
||||||
|
|
||||||
|
# ----- 2. clone-or-fetch each repo --------------------------------------
|
||||||
|
mkdir -p "$HOME/Projects"
|
||||||
|
|
||||||
|
IFS=',' read -ra REPOS <<< "$GH_PRECLONE_REPOS"
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
repo=$(echo "$repo" | tr -d '[:space:]')
|
||||||
|
[[ -z "$repo" ]] && continue
|
||||||
|
|
||||||
|
name=$(basename "$repo")
|
||||||
|
target="$HOME/Projects/$name"
|
||||||
|
|
||||||
|
if [[ -d "$target/.git" ]]; then
|
||||||
|
log "fetching ${repo} into ${target}"
|
||||||
|
git -C "$target" fetch origin --prune --quiet \
|
||||||
|
|| log "WARN: fetch failed for ${repo} (continuing)"
|
||||||
|
else
|
||||||
|
log "cloning ${repo} into ${target}"
|
||||||
|
rm -rf "$target"
|
||||||
|
git clone --quiet "https://github.com/${repo}.git" "$target" \
|
||||||
|
|| { log "ERROR: clone failed for ${repo}"; continue; }
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log "repo-bootstrap done"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue