Version-drift tracking for the stuff overlay via
nvchecker, designed to run
from a local cron.
A weekly (or however-you-like) cron runs run.sh, which queries each
tracked package's upstream (PyPI, GitHub releases, SourceForge, CPAN),
diffs against the last run, and prints a drift report to stdout. When
invoked by cron, non-empty output is delivered via local mail — so
quiet runs stay quiet, and only genuine upstream movement generates
notifications.
State lives under $XDG_STATE_HOME/stuff-nvchecker/ (default
~/.local/state/stuff-nvchecker/), outside this repo.
dev-util/nvchecker(installsnvcheckerandnvcmpinto$PATH)- A working
crondelivery (for the notification channel)
| File | Purpose |
|---|---|
generate.py |
Walks the overlay, emits nvchecker.toml by classifying each package's upstream source |
nvchecker.toml |
Generated config: one entry per trackable package, skip comments for untrackable ones |
run.sh |
Cron-friendly runner: executes nvchecker, diffs against last run via nvcmp, prints drift |
The GitHub API rate-limits unauthenticated requests to 60/hour per IP,
which is trivially exceeded by this config. Create a personal access
token (public-read scope is enough — a classic PAT with no scopes
works, or a fine-grained token with no extra permissions) and put it
in ~/.config/nvchecker/keyfile.toml:
[keys]
github = "ghp_..."run.sh looks for that file automatically and passes it via
nvchecker -k when present. Without it, most GitHub entries will
fail with "rate limited" after the first ~60 requests and the tool
is effectively useless for GitHub tracking.
./scripts/nvchecker/run.shThe first run fetches current upstream versions for every tracked
package and exits after printing baseline established. No drift is
reported because there's nothing to diff against yet.
Add an entry. Weekly is fine:
0 6 * * 0 /path/to/stuff/scripts/nvchecker/run.shAfter adding, dropping, or retargeting packages in the overlay, regenerate the tracker config:
./scripts/nvchecker/generate.pyThe generator classifies each package's newest ebuild by source type:
inherit pypiorpypi.ioSRC_URI → PyPI entrygithub.com/.../archive/in SRC_URI,EGIT_REPO_URIpointing at GitHub, or a plain GitHubHOMEPAGE→ GitHub entry (use_max_tag = true, which works for both Release-curated repos and plain-tagged ones)bitbucket.org/<owner>/<repo>→ Bitbucket entrygitlab.*/<owner>/<repo>→ GitLab entry (withhost = "<instance>"set for self-hosted installs like gitlab.freedesktop.org, gitlab.gnome.org, gitlab.kde.org)inherit perl-module→ CPAN entry (distribution name =${PN})sourceforge.net/project/<slug>/ordownloads.sourceforge.net/<slug>/→ skip comment with a SourceForge hint (nvchecker 2.x has no built-insourceforgesource; aregexentry against the RSS feed works if tracking is wanted)- Live-only (
-9999) ebuilds → skip comment; no release number to track - Python-2-pinned packages (ebuild name ends
-python2or inherits a*_py2eclass) → skip comment; upstream has moved past py2 inherit qt5-build→ skip comment; tracked via ::gentoo's Qt 5 snapshot rather than upstream Qt release cadence- Anything else → skip comment of the form
custom upstream at <host>; hand-add an nvchecker regex/htmlparser entry if tracking is wanted, where<host>is the HOMEPAGE domain (falling back to SRC_URI)
Hand-edits to nvchecker.toml are overwritten on the next
generate.py run. To pin a non-obvious upstream, add the detection
rule to generate.py so the classification survives regeneration.
Clean run (nothing changed upstream): silent.
Drift run: stdout looks roughly like
stuff overlay — nvchecker drift report
run: 2026-05-01T06:00:00+02:00
dev-python/hyperspy 2.3.0 -> 2.4.0
dev-python/bokeh 3.4.1 -> 3.5.0
sci-physics/mantid 6.15.0.3 -> 6.16.0
Each line is <category/pkg> <old> -> <new>. Use it as a worklist
for the next round of bumps; run emerge --ask =<atom> locally to
test before committing.
The same nvchecker.toml is also consumed by a weekly GitHub Actions
job at .github/workflows/nvchecker.yml, firing on Mondays at 06:00
UTC. The CI variant builds its baseline from current ebuild PVs (via
tree_baseline.py) rather than persisting last week's upstream
snapshot, so it answers a different question:
| Runner | Baseline | Question answered |
|---|---|---|
local cron (run.sh) |
last run's upstream snapshot | "what changed upstream since last week?" |
CI (nvchecker.yml) |
current ebuild PVs (tree_baseline.py) |
"what does upstream have that we don't ship?" |
The two can coexist — the local runner's state lives in
$XDG_STATE_HOME outside the repo and doesn't touch CI in any way.
CI emits the drift list as a 30-day workflow artifact and (in later
phases) will gate auto-PR creation on a conservative
auto-bumpability predicate.
- No state in the repo:
old_ver.json/new_ver.jsonfor the local runner live in$XDG_STATE_HOME; CI runs are entirely stateless against the worktree. The only thing version-controlled is the generated config and the scripts. - Silent on success (local): cron mails on non-empty stdout, so a clean week doesn't fill your inbox.
- Transient network failures don't mail:
run.shdoes not pass--failurestonvchecker, so a flaky PyPI response during one run doesn't turn into a false drift report. Same convention in CI.