A single GitHub repository that monitors upstream versions of all 80+ packages
in your openSUSE OBS project and automatically fires an OBS service run
(obs_scm re-fetch) the moment a new version is detected.
Upstream (GitHub / KDE invent / freedesktopβ¦)
β new tag / release
βΌ
nvchecker checks versions every 6 h (GitHub Actions cron)
β version changed?
βΌ
trigger_obs.py β POST /trigger/runservice β OBS re-fetches sources
β
βΌ
versions.json updated & committed back to this repo
ββββββββββββββββββββββββββββ
β GitHub "control repo" β
β (your single repo) β
ββββββββββββββ¬ββββββββββββββ
β
ββββββββββββββββββββββΌβββββββββββββββββββββ
β β β
Upstream APIs Version tracking Scheduler (cron)
(GitHub releases) (JSON/YAML DB) (GitHub Actions)
β β β
ββββββββββββββββ¬ββββββ΄ββββββββββββββββββββββ
β β
Detect new versions β
β β
βββββββββ¬ββββββββββββ
β
Trigger OBS API
β
ββββββββββββββββΌβββββββββββββββ
β Open Build Service (OBS) β
β osc service rr / rebuild β
βββββββββββββββββββββββββββββββ
- How it works
- One-time setup
- Day-to-day usage
- Adding a new package
- Supported upstream sources
- Troubleshooting
- File reference
| Component | Role |
|---|---|
nvchecker.toml |
Declares where to find the upstream version of every package |
versions.json |
Stores the last-known version of each package (committed to git) |
.github/workflows/check-updates.yml |
Cron job: runs nvchecker, diffs versions, fires OBS triggers |
scripts/trigger_obs.py |
Calls POST /trigger/runservice on OBS for every updated package |
scripts/bootstrap_packages.py |
One-time helper to generate a starter nvchecker.toml from your OBS package list |
nvchecker reads nvchecker.toml, checks each upstream, and writes
versions_new.json. trigger_obs.py compares versions.json (old) with
versions_new.json (new), and for every package whose version changed it calls:
POST https://build.opensuse.org/trigger/runservice
?project=home:itachi_re
&package=<PACKAGE_NAME>
Authorization: Token <OBS_TOKEN>
This triggers the _service file in that OBS package to run β which for
obs_scm means OBS fetches the latest commit/tag from the upstream SCM,
creates a new tarball, updates the version in the .spec file, and queues
a rebuild.
The token must have operation=runservice scope.
Create it once with osc:
# Install osc if not already installed
zypper install osc # on openSUSE
# or: pip install osc
# Configure osc (stores credentials in ~/.config/osc/oscrc)
osc # just run it once and follow prompts
# Create a project-wide runservice token
osc api -X POST \
"/person/itachi_re/token?operation=runservice"The response looks like:
<status code="ok">
<summary>Ok</summary>
<data name="token">abc123xyz789...LONG_TOKEN_STRING...</data>
<data name="id">42</data>
</status>Copy the token string β you will not see it again. You can also create it from the OBS Web UI:
Profile β Manage Your Tokens β Create Token β Operation: "Run services"
β οΈ Do NOT create a token bound to a specific package β you need one token that works for the whole project.
In your GitHub repository go to Settings β Secrets and variables β Actions.
| Name | Value |
|---|---|
OBS_TOKEN |
The long token string from Step 1 |
| Name | Value | Default |
|---|---|---|
OBS_PROJECT |
home:itachi_re |
home:itachi_re |
You can either:
Option A β Use the included nvchecker.toml as a starting point.
It already has entries for the standard KDE Plasma 6 and KDE Frameworks
packages. Delete/add entries to match your actual package list.
Option B β Auto-generate from your OBS package list using the bootstrap script:
# Install dependencies
pip install requests lxml
# Run the bootstrapper (reads your _service files to detect upstream URLs)
python scripts/bootstrap_packages.py \
--project home:itachi_re \
--obs-user itachi_re \
--obs-password YOUR_OBS_PASSWORD \
--output nvchecker.tomlThis will produce a nvchecker.toml with one section per package.
Packages whose upstream it couldn't detect are marked with # TODO β
search for those and fill them in manually.
Each package entry looks like this:
# KDE invent.kde.org (GitLab)
[plasma-desktop]
source = "gitlab"
host = "https://invent.kde.org"
gitlab = "plasma/plasma-desktop"
use_max_tag = true
# GitHub with releases
[some-app]
source = "github"
github = "owner/repo"
use_latest_release = true
# PyPI
[python-foo]
source = "pypi"
pypi = "foo"The section name (e.g.
[plasma-desktop]) must exactly match the package name in OBS. If your OBS package is namedplasma5-desktop, the section must be[plasma5-desktop].
Before the first real run, seed versions.json so the system knows what's
already built and doesn't re-trigger everything at once.
# Install nvchecker locally
pip install 'nvchecker[all]'
# Export your GitHub token so nvchecker can use the GitHub API
export NVCHECKER_GITHUB_TOKEN=ghp_your_token_here
# Run nvchecker β this writes versions_new.json
nvchecker -c nvchecker.toml
# Use the freshly-checked versions as the baseline
cp versions_new.json versions.json
# Commit both files
git add nvchecker.toml versions.json .gitignore
git commit -m "feat: initial nvchecker setup"
git pushAfter pushing, go to Actions in your GitHub repo and either:
- Wait for the next scheduled run (every 6 hours), or
- Click Run workflow β Run workflow to trigger it manually.
Check the workflow log for output like:
Packages tracked : 83
Updates detected : 2
π¦ plasma-desktop: 6.1.4 β 6.1.5
β
Triggered
π¦ kwin: 6.1.4 β 6.1.5
β
Triggered
Triggered : 2
Failed : 0
Everything runs automatically. The workflow runs every 6 hours.
Manual force-trigger β you can trigger a specific package right now from the GitHub Actions UI without waiting for a version change:
- Go to Actions β Check Upstream Versions & Trigger OBS
- Click Run workflow
- Fill in Force-trigger a specific package name (e.g.
plasma-desktop) - Click Run workflow
Dry run β check what would be triggered without actually calling OBS:
- Run workflow β set Dry run to
true
-
Add a new entry to
nvchecker.toml:[my-new-package] source = "github" github = "owner/repo" use_latest_release = true
-
Run nvchecker locally or trigger the workflow with dry run first to verify the version is detected correctly.
-
Commit
nvchecker.toml. The next scheduled run will pick it up. On first run, the new package version will appear as "new" (old version = unknown) and an OBS trigger will fire. This is expected and desired.
| Source | Key | Notes |
|---|---|---|
| KDE invent.kde.org | source = "gitlab" + host = "https://invent.kde.org" |
Most KDE/Plasma packages |
| freedesktop GitLab | source = "gitlab" + host = "https://gitlab.freedesktop.org" |
wayland, mesa, etc. |
| GNOME GitLab | source = "gitlab" + host = "https://gitlab.gnome.org" |
|
| GitHub | source = "github" |
Use use_latest_release or use_max_tag |
| PyPI | source = "pypi" |
Python packages |
| AUR | source = "aur" |
Useful for cross-checking |
| Generic git tags | source = "git" |
Any git repo |
| HTML regex | source = "regex" |
Scrape a download page |
| Shell command | source = "cmd" |
Ultimate escape hatch |
Full documentation: https://nvchecker.readthedocs.io/en/latest/usage.html
Tag prefix stripping β many repos use tags like v6.1.5 while the spec
uses 6.1.5. Add prefix = "v" to strip the prefix:
[libfoo]
source = "github"
github = "owner/libfoo"
use_max_tag = true
prefix = "v"Check nvchecker_errors.log in the workflow artifacts. Common issues:
- GitHub rate limit β
GITHUB_TOKENis provided automatically but has a 1000 req/hr limit for workflow tokens. For heavy use, create a personal access token and store it asMY_GITHUB_PATsecret, then change the workflow to use it. - Tag pattern wrong β use
nvchecker --logger json -c nvchecker.tomllocally to debug. Addinclude_patternorexclude_patternif needed. use_latest_tagrequires a PAT β this uses the GitHub GraphQL API. Switch touse_max_tag(REST API) which works withGITHUB_TOKEN.
The package name in nvchecker.toml doesn't match the OBS package name.
Check your OBS project at:
https://build.opensuse.org/project/show/home:itachi_re
Your OBS_TOKEN secret is wrong or expired. Re-create the token:
osc api -X POST "/person/itachi_re/token?operation=runservice"Then update the OBS_TOKEN secret in GitHub.
The version string nvchecker returns doesn't match what's in versions.json.
Check what nvchecker produces:
nvchecker -c nvchecker.toml
cat versions_new.jsonIf the format differs (e.g. v6.1.5 vs 6.1.5), add prefix = "v" to
strip the prefix, then re-seed versions.json.
.
βββ .github/
β βββ workflows/
β βββ check-updates.yml β GitHub Actions cron + trigger logic
βββ scripts/
β βββ trigger_obs.py β Fires OBS API calls for updated packages
β βββ bootstrap_packages.py β One-time: generates nvchecker.toml from OBS
βββ nvchecker.toml β WHERE to find the upstream version of each package
βββ versions.json β LAST KNOWN version of each package (git-tracked)
βββ .nvchecker_keyfile.toml β Local API keys (git-IGNORED, never commit)
βββ .gitignore
versions_new.json is written by nvchecker at runtime and is not committed
to git (listed in .gitignore). After each successful run, its contents are
merged into versions.json and that file is committed.