diff --git a/.env.example b/.env.example index 4b54f39..08deb9c 100644 --- a/.env.example +++ b/.env.example @@ -22,6 +22,28 @@ CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-vervang-mij # Als deze ge-revoked wordt: rebuild + redeploy (zie README). 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// 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/. 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 ------------------------------------ # Beide URLs uit het Neon-dashboard. DATABASE_URL is pooled, # DIRECT_URL is unpooled — scrum4me-mcp gebruikt DATABASE_URL diff --git a/Dockerfile b/Dockerfile index 49bd7ff..44e171b 100644 --- a/Dockerfile +++ b/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 \ build-essential python3 \ tzdata logrotate \ + gnupg \ && ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \ && dpkg-reconfigure --frontend=noninteractive tzdata \ && 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 ------------------------------------------------------- # Voor zowel Claude Code (de native installer heeft geen node nodig, maar # scrum4me-mcp draait op tsx) als de health-server. @@ -56,6 +70,7 @@ ARG AGENT_GID=1000 RUN groupadd -g ${AGENT_GID} 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 /home/agent/Projects /home/agent/.scrum4me-agent-worktrees \ && chown -R agent:agent /var/cache /var/log/agent /var/run/agent /home/agent # ----- runner files ------------------------------------------------------ diff --git a/README.md b/README.md index cd79ff3..0e64016 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,13 @@ fouten. # b. SCRUM4ME_TOKEN → log in als de dedicated agent-user in # Scrum4Me, /settings/tokens, label "NAS-runner" # 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 ssh admin@nas @@ -126,10 +133,30 @@ laatste heartbeat ouder is dan 5 minuten. ## Filesystem-grenzen -De agent-user heeft geen SSH-keys, geen `~/.gitconfig` met push-credentials, -en geen toegang tot andere shares dan `/share/Agent/*`. Commits worden -lokaal in de per-job clone gemaakt; pushen gebeurt door jou op je -werkstation na review (CLAUDE.md regel: *"`git push` is altijd expliciet"*). +De agent-user heeft geen SSH-keys en geen toegang tot andere shares dan +`/share/Agent/*`. Wel een `~/.git-credentials` met de `GH_TOKEN` voor +HTTPS-clone/push (zie volgende sectie) — die token is scoped tot de twee +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//.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//.git`. Worktrees +voor jobs landen vervolgens onder `~/.scrum4me-agent-worktrees//` +zodat de hoofd-clone niet wordt aangeraakt. + +Push gaat over dezelfde token: `git push -u origin feat/story-` +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 diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index b2f89f3..8b90ec7 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -61,6 +61,15 @@ gosu agent /bin/bash -c 'cat > "${AGENT_STATE_DIR}/state.json"' < — +# 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 diff --git a/bin/repo-bootstrap.sh b/bin/repo-bootstrap.sh new file mode 100644 index 0000000..a646d8a --- /dev/null +++ b/bin/repo-bootstrap.sh @@ -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//.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/ 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"