# systemd service for the Claude Code compatibility-matrix populator. # # Triggered by `litellm-compat-matrix.timer`; not started directly. The # unit is a `Type=oneshot` so the timer's `OnCalendar=` semantics # describe "run once per day" cleanly — there's no long-lived daemon to # supervise; each invocation runs the populator end-to-end and exits. # # Install # ------- # # sudo cp tests/e2e/claude_code/cron_vm/litellm-compat-matrix.service /etc/systemd/system/ # sudo cp tests/e2e/claude_code/cron_vm/litellm-compat-matrix.timer /etc/systemd/system/ # sudo systemctl daemon-reload # sudo systemctl enable --now litellm-compat-matrix.timer # # 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; # * have a uv venv at `~/litellm/litellm/.venv` (created by # `uv sync --frozen` inside that checkout once); # * have `gh` already authenticated against an account with # `pull-requests: write` on `BerriAI/litellm-docs`; # * have provider credentials exported in `/etc/litellm-compat-matrix.env` # (see `litellm-compat-matrix.env.example` in this directory); # * have the mateo-berri publish PAT at # `/etc/litellm-compat-matrix-github-token` (chmod 0600, single # line), delivered via `LoadCredential=` below. [Unit] Description=Claude Code compatibility-matrix populator (oneshot) Documentation=file:///home/mateo/litellm/litellm/tests/e2e/claude_code/cron_vm/README.md Wants=network-online.target After=network-online.target [Service] Type=oneshot User=mateo Group=mateo # Provider credentials + any gh/PROXY_PORT overrides live here. Format # is the standard `KEY=value` one line per env var. EnvironmentFile=-/etc/litellm-compat-matrix.env # The mateo-berri publish PAT is mapped in via the credential store, NOT # the EnvironmentFile, so it never lands in the process environment that # pytest, the proxy, and the model-driven claude CLI inherit (any # same-UID process can read /proc//environ). run_daily.sh reads # ${CREDENTIALS_DIRECTORY}/github-token and hands it to gh per call. # Unlike EnvironmentFile= above, this is deliberately NOT optional: a # missing token file fails the unit at start instead of 30 minutes in. LoadCredential=github-token:/etc/litellm-compat-matrix-github-token # 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 # `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/e2e/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, # plus the full feature x provider grid of pytest cells hitting several # cloud providers. TimeoutStartSec=90min # A failed run shouldn't restart automatically — the next timer fire is # the right retry. Reruns of the same day's matrix are idempotent. 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//`; 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=/home/mateo/litellm-cron-worktree /home/mateo/.cache /home/mateo/.claude /home/mateo/.config/gh /tmp PrivateTmp=true [Install] WantedBy=multi-user.target