mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(cron_vm): publish from agent-shin fork + harden systemd unit
The cron host has no write access to BerriAI/litellm-docs by design. PRs now open from a long-lived fork at agent-shin/litellm-docs: - run_daily.sh validates AGENT_SHIN_GITHUB_TOKEN up front (failing 30 min into a run because the env file is missing one line is wasted spend). - The pre-commit shim adds a transient `fork` remote with the token embedded in the URL, force-pushes the branch, then removes the remote so the token never lives on disk. - `gh pr create --head agent-shin:<branch>` opens the cross-repo PR with GH_TOKEN scoped to AGENT_SHIN_GITHUB_TOKEN. A second `gh pr edit --add-reviewer` runs under GITHUB_TOKEN (mateo-berri's PAT) because agent-shin's PAT lacks RequestReviewsByLogin permission on the upstream repo. - PR_REVIEWERS env var (default `mateo-berri`) controls who gets auto-tagged; empty disables. Also bring litellm-compat-matrix.service to working state: - Hardcode `/home/mateo` paths everywhere %h was used. systemd expands %h against the *manager's* home (/root for PID 1) in *system* units, not against the User= directive. The mismatch made ReadWritePaths point at /root/.cache and the namespace setup failed with status=226/NAMESPACE before run_daily.sh ever started. - Explicit Environment=PATH so `uv` and `claude` under ~/.local/bin are visible to the up-front command-presence check; systemd's default PATH excludes them. - Expand ReadWritePaths to include ~/.claude (CLI per-session state) and ~/.config/gh (gh host config fallback); both are written under ProtectHome=read-only. env.example refreshed: drop AWS_ACCESS_KEY_ID/SECRET + GOOGLE_APPLICATION_CREDENTIALS in favor of AWS_BEARER_TOKEN_BEDROCK and ADC via the VM's metadata server; document AGENT_SHIN_GITHUB_TOKEN, FORK_OWNER/FORK_REPO overrides, and VERTEXAI_LOCATION=global.
This commit is contained in:
parent
1b30abf4a8
commit
891da2372d
3 changed files with 139 additions and 21 deletions
|
|
@ -8,22 +8,36 @@
|
|||
# Anthropic
|
||||
ANTHROPIC_API_KEY=
|
||||
|
||||
# Bedrock (invoke + converse columns)
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
# Bedrock (invoke + converse columns).
|
||||
# Use Anthropic's Bedrock API-key passthrough (long-lived bearer token).
|
||||
# No AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY required for the matrix --
|
||||
# both the LiteLLM invoke and converse routes pick up
|
||||
# AWS_BEARER_TOKEN_BEDROCK when present.
|
||||
AWS_BEARER_TOKEN_BEDROCK=
|
||||
AWS_REGION_NAME=us-east-1
|
||||
|
||||
# Vertex AI
|
||||
# Vertex AI.
|
||||
# On the GCP VM, the default service-account ADC from the metadata server
|
||||
# is used -- no JSON key file is needed. If you ever need to run outside
|
||||
# GCP, also export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json.
|
||||
VERTEXAI_PROJECT=
|
||||
VERTEXAI_LOCATION=us-east5
|
||||
GOOGLE_APPLICATION_CREDENTIALS=/etc/litellm-compat-matrix.gcp.json
|
||||
VERTEXAI_LOCATION=global
|
||||
|
||||
# Microsoft Foundry (Azure column)
|
||||
AZURE_FOUNDRY_API_KEY=
|
||||
AZURE_FOUNDRY_API_BASE=
|
||||
|
||||
# REQUIRED for publishing: PAT for the `agent-shin` user, used to push
|
||||
# the daily compat-matrix branch to its fork (agent-shin/litellm-docs)
|
||||
# and open the cross-repo PR against BerriAI/litellm-docs. Scopes:
|
||||
# classic `repo` + `workflow`, or fine-grained on agent-shin/litellm-docs
|
||||
# with Contents:RW + Pull requests:RW + Workflows:RW.
|
||||
# Skip by setting SKIP_PUBLISH=1 (publishes nothing; only writes the
|
||||
# matrix JSON locally).
|
||||
AGENT_SHIN_GITHUB_TOKEN=
|
||||
|
||||
# Optional: lifts the unauthenticated rate limit on the GitHub Releases
|
||||
# API used by `resolver.py`. Not required.
|
||||
# API used by `resolver.py`. Any token works (read-only). Not required.
|
||||
# GITHUB_TOKEN=
|
||||
|
||||
# Optional overrides; defaults are sensible for the cron VM.
|
||||
|
|
@ -32,3 +46,5 @@ AZURE_FOUNDRY_API_BASE=
|
|||
# DOCS_REPO=BerriAI/litellm-docs
|
||||
# DOCS_BRANCH=main
|
||||
# DOCS_TARGET_PATH=src/data/compatibility-matrix.json
|
||||
# FORK_OWNER=agent-shin
|
||||
# FORK_REPO=agent-shin/litellm-docs
|
||||
|
|
|
|||
|
|
@ -13,9 +13,13 @@
|
|||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable --now litellm-compat-matrix.timer
|
||||
#
|
||||
# The %h specifier expands to the runtime user's home directory, which
|
||||
# avoids hard-coding /home/mateo into a unit that should be portable
|
||||
# across operator handoffs. The runtime user (`User=mateo`) must:
|
||||
# Paths are hard-coded to /home/mateo rather than using systemd's %h
|
||||
# specifier. Why: in *system* units (this one), %h is expanded at
|
||||
# parse time against the *manager's* home -- which is /root for PID 1
|
||||
# -- and *not* against the User= directive. That mismatch makes
|
||||
# ReadWritePaths point at /root/.cache (which doesn't exist), causing
|
||||
# the namespace setup to fail with status=226/NAMESPACE before the
|
||||
# script ever runs. The runtime user (`User=mateo`) must:
|
||||
#
|
||||
# * have a checkout of `BerriAI/litellm` at `~/litellm/litellm` so the
|
||||
# publisher module is importable;
|
||||
|
|
@ -28,7 +32,7 @@
|
|||
|
||||
[Unit]
|
||||
Description=Claude Code compatibility-matrix populator (oneshot)
|
||||
Documentation=file://%h/litellm/litellm/tests/claude_code/cron_vm/README.md
|
||||
Documentation=file:///home/mateo/litellm/litellm/tests/claude_code/cron_vm/README.md
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
|
|
@ -41,9 +45,21 @@ Group=mateo
|
|||
# is the standard `KEY=value` one line per env var.
|
||||
EnvironmentFile=-/etc/litellm-compat-matrix.env
|
||||
|
||||
WorkingDirectory=%h/litellm/litellm
|
||||
# systemd starts with a minimal PATH (~/usr/local/bin:/usr/bin:/bin).
|
||||
# `uv` and `claude` are installed under the runtime user's `~/.local/bin`
|
||||
# so we have to prepend it explicitly; otherwise run_daily.sh fails at
|
||||
# the up-front command-presence check.
|
||||
Environment=PATH=/home/mateo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
ExecStart=%h/litellm/litellm/tests/claude_code/cron_vm/run_daily.sh
|
||||
# `HOME` is auto-set to /home/mateo when User=mateo is honored, but be
|
||||
# explicit so anything that reads $HOME (e.g. uv's cache lookup, the
|
||||
# claude CLI's per-session dir) sees the right value even if a future
|
||||
# refactor flips DynamicUser= or PrivateUsers= on.
|
||||
Environment=HOME=/home/mateo
|
||||
|
||||
WorkingDirectory=/home/mateo/litellm/litellm
|
||||
|
||||
ExecStart=/home/mateo/litellm/litellm/tests/claude_code/cron_vm/run_daily.sh
|
||||
|
||||
# 90 minutes is generous: cold runs do `git clone` + `uv sync` of a new
|
||||
# tag's lockfile, which can take a couple of minutes on a 2-vCPU VM,
|
||||
|
|
@ -57,10 +73,27 @@ Restart=no
|
|||
# Security hardening: the populator only reads the litellm checkout and
|
||||
# the env-file; everything else it writes lives in either the worktree
|
||||
# (managed) or `/tmp` (cleaned up by tempfile).
|
||||
#
|
||||
# ReadWritePaths whitelist:
|
||||
# * litellm-cron-worktree - the long-lived stable-tag checkout +
|
||||
# its `.venv` (`uv sync` rewrites every
|
||||
# run) + `.uv-bin` (pinned `uv` binary
|
||||
# cache).
|
||||
# * .cache - uv's wheel cache (~/.cache/uv) so we
|
||||
# don't redownload pinned deps each run.
|
||||
# * .claude - `claude` CLI's per-session state under
|
||||
# `~/.claude/projects/<sha>/`; created
|
||||
# on every `claude --print` invocation.
|
||||
# * .config/gh - `gh` CLI host config; technically not
|
||||
# needed when we pass GH_TOKEN inline,
|
||||
# but cheap to whitelist and prevents
|
||||
# future regressions if a code path
|
||||
# ever falls back to the host config.
|
||||
# * /tmp - mktemp -d workdir + proxy logs.
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths=%h/litellm-cron-worktree %h/.cache /tmp
|
||||
ReadWritePaths=/home/mateo/litellm-cron-worktree /home/mateo/.cache /home/mateo/.claude /home/mateo/.config/gh /tmp
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ DOCS_BRANCH="${DOCS_BRANCH:-main}"
|
|||
DOCS_TARGET_PATH="${DOCS_TARGET_PATH:-src/data/compatibility-matrix.json}"
|
||||
SKIP_PUBLISH="${SKIP_PUBLISH:-0}"
|
||||
PYTEST_K="${PYTEST_K:-}"
|
||||
# Comma-separated GitHub usernames to request a review from on every PR.
|
||||
# Reviewers must have at least read access to ${DOCS_REPO}. PR-author
|
||||
# (agent-shin) has implicit rights to request reviews from anyone with
|
||||
# read access, so no extra token scope is needed. Set to empty to skip.
|
||||
PR_REVIEWERS="${PR_REVIEWERS:-mateo-berri}"
|
||||
|
||||
POPULATOR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORKDIR="$(mktemp -d -t litellm-compat-matrix.XXXXXX)"
|
||||
|
|
@ -89,6 +94,15 @@ for cmd in git uv gh jq curl claude; do
|
|||
command -v "${cmd}" >/dev/null 2>&1 || die "missing required command: ${cmd}"
|
||||
done
|
||||
|
||||
# Publishing is from a fork (agent-shin/litellm-docs) so neither the cron
|
||||
# host nor the bot identity needs write access to BerriAI/litellm-docs. We
|
||||
# require the fork token up front -- failing 30 minutes into a run because
|
||||
# the env file is missing one line is a waste of CI quota.
|
||||
if [[ "${SKIP_PUBLISH}" != "1" ]]; then
|
||||
[[ -n "${AGENT_SHIN_GITHUB_TOKEN:-}" ]] \
|
||||
|| die "AGENT_SHIN_GITHUB_TOKEN required to open PRs from agent-shin/litellm-docs (or set SKIP_PUBLISH=1)"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Resolve versions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -340,6 +354,8 @@ fi
|
|||
DATE_UTC="$(date -u +%Y-%m-%d)"
|
||||
BRANCH_NAME="compat-matrix/${LITELLM_VERSION}-${CLAUDE_CODE_VERSION}-${DATE_UTC}"
|
||||
DOCS_CLONE="${WORKDIR}/litellm-docs"
|
||||
FORK_OWNER="${FORK_OWNER:-agent-shin}"
|
||||
FORK_REPO="${FORK_REPO:-${FORK_OWNER}/litellm-docs}"
|
||||
|
||||
log "cloning ${DOCS_REPO}@${DOCS_BRANCH}"
|
||||
gh repo clone "${DOCS_REPO}" "${DOCS_CLONE}" -- --depth 1 --branch "${DOCS_BRANCH}"
|
||||
|
|
@ -369,9 +385,24 @@ EOF
|
|||
)"
|
||||
git commit -m "${COMMIT_MSG}"
|
||||
|
||||
# --force-with-lease so a same-day rerun fast-forwards (or rebases) the
|
||||
# existing branch without clobbering a maintainer's manual fixup.
|
||||
git push --force-with-lease --set-upstream origin "${BRANCH_NAME}"
|
||||
# Push to the fork (agent-shin/litellm-docs), not to BerriAI/litellm-docs.
|
||||
# The cron host has no write access to BerriAI/litellm-docs by design --
|
||||
# only agent-shin's PAT does, and only over its own fork. The temp remote
|
||||
# carries the token in its URL, so we add it, push, then immediately
|
||||
# remove it so the token never lingers in ${DOCS_CLONE}/.git/config.
|
||||
# (${DOCS_CLONE} is also rm -rf'd by the cleanup trap on exit.)
|
||||
#
|
||||
# Plain --force (not --force-with-lease) is acceptable here: the fork
|
||||
# branch is bot-owned, only this script ever writes to it, and runs are
|
||||
# serialized by the systemd timer. --force-with-lease would require a
|
||||
# fetch to populate the remote-tracking ref before each push and adds
|
||||
# no safety in this single-writer setup.
|
||||
FORK_PUSH_URL="https://x-access-token:${AGENT_SHIN_GITHUB_TOKEN}@github.com/${FORK_REPO}.git"
|
||||
git remote remove fork 2>/dev/null || true
|
||||
git remote add fork "${FORK_PUSH_URL}"
|
||||
git push --force --set-upstream fork "${BRANCH_NAME}"
|
||||
git remote remove fork
|
||||
unset FORK_PUSH_URL
|
||||
|
||||
# Per-feature status table for the PR body. Reviewers triage from this.
|
||||
PR_FEATURE_TABLE="$(jq -r '
|
||||
|
|
@ -402,13 +433,26 @@ Generated by \`tests/claude_code/cron_vm/run_daily.sh\`. Close without merging i
|
|||
EOF
|
||||
)"
|
||||
|
||||
log "opening PR"
|
||||
log "opening PR from ${FORK_OWNER}:${BRANCH_NAME} -> ${DOCS_REPO}:${DOCS_BRANCH}"
|
||||
# GH_TOKEN here is scoped to this single subshell so we don't bleed the
|
||||
# fork token into the rest of the script (release-listing earlier uses
|
||||
# ${GITHUB_TOKEN}, which may be a different identity). gh's --head accepts
|
||||
# `OWNER:BRANCH` for cross-repo PRs from a fork.
|
||||
#
|
||||
# Reviewer assignment is done in a *separate* call below: as the PR
|
||||
# author from a fork, agent-shin has no write/triage access on
|
||||
# ${DOCS_REPO} and the `RequestReviewsByLogin` GraphQL mutation
|
||||
# (which backs `gh pr create --reviewer` and `gh pr edit --add-reviewer`)
|
||||
# rejects with "does not have the correct permissions". We use the
|
||||
# collaborator-scoped ${GITHUB_TOKEN} for that instead. Don't fold
|
||||
# --reviewer into `gh pr create` here -- it would fail the whole
|
||||
# create on the very first cron run.
|
||||
set +e
|
||||
PR_OUT="$(
|
||||
gh pr create \
|
||||
GH_TOKEN="${AGENT_SHIN_GITHUB_TOKEN}" gh pr create \
|
||||
--repo "${DOCS_REPO}" \
|
||||
--base "${DOCS_BRANCH}" \
|
||||
--head "${BRANCH_NAME}" \
|
||||
--head "${FORK_OWNER}:${BRANCH_NAME}" \
|
||||
--title "${PR_TITLE}" \
|
||||
--body "${PR_BODY}" 2>&1
|
||||
)"
|
||||
|
|
@ -418,10 +462,35 @@ echo "${PR_OUT}"
|
|||
|
||||
if [[ ${PR_EXIT} -ne 0 ]]; then
|
||||
if grep -q "a pull request for branch.*already exists" <<<"${PR_OUT}"; then
|
||||
log "PR already exists for ${BRANCH_NAME}; updated branch in place"
|
||||
log "PR already exists for ${FORK_OWNER}:${BRANCH_NAME}; updated branch in place"
|
||||
else
|
||||
die "gh pr create failed (exit ${PR_EXIT})"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Request reviews from PR_REVIEWERS using the collaborator-scoped
|
||||
# ${GITHUB_TOKEN} (mateo-berri's token, already provisioned for release
|
||||
# listing). This is idempotent: `gh pr edit --add-reviewer` is a no-op
|
||||
# on a user who's already in reviewRequests, and silently re-adds
|
||||
# anyone whose prior review was dismissed -- so same-day reruns stay
|
||||
# clean. Reviewer-add failures are non-fatal: the matrix JSON has
|
||||
# already landed on the PR; the worst case is a manual ping.
|
||||
if [[ -n "${PR_REVIEWERS}" ]]; then
|
||||
if [[ -z "${GITHUB_TOKEN:-}" ]]; then
|
||||
log "WARN: PR_REVIEWERS set but GITHUB_TOKEN missing -- cannot request reviews; skipping"
|
||||
else
|
||||
log "requesting reviews from: ${PR_REVIEWERS}"
|
||||
set +e
|
||||
GH_TOKEN="${GITHUB_TOKEN}" gh pr edit \
|
||||
"${FORK_OWNER}:${BRANCH_NAME}" \
|
||||
--repo "${DOCS_REPO}" \
|
||||
--add-reviewer "${PR_REVIEWERS}" 2>&1 | sed 's/^/ /'
|
||||
REVIEWER_EXIT=${PIPESTATUS[0]}
|
||||
set -e
|
||||
if [[ ${REVIEWER_EXIT} -ne 0 ]]; then
|
||||
log "WARN: gh pr edit --add-reviewer exited ${REVIEWER_EXIT} (non-fatal)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
log "done"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue