Update Checker
A strictly notify-only update checker, reached through sn.updates(). Point it at a GitHub repository and it tells you (and your admins) when there is a release newer than the installed version. It never downloads a jar, never touches the running plugin, and never auto-updates anything. The only outputs are one INFO line in the console and a join-notice to players holding a permission.
This guarantee is permanent and by design. There is no flag, no config key, and no code path that makes this module fetch or swap a jar. If you want the newer version installed, a human installs it. Nothing here ever mutates the running server.
Fully opt-in
Declaring nothing generates zero traffic and zero state. A consumer that never declares updates(...) in its spec and never calls sn.updates() produces no HTTP request, arms no timer, and registers no per-plugin state. You pay for the module only when you ask for it.
Two ways to activate it
Declarative (recommended)
Declare the repo in your SnSpec. This auto-arms a recurring watch when the plugin enables.
@Override
protected SnSpec buildSpec() {
return SnSpec.builder()
.config("config.yml")
.updates("owner/repo") // arms a recurring watch on enable
.build();
}Imperative
If you would rather drive it from code, sn.updates() gives you two entry points:
Sn sn = sn();
// One immediate check, no recurring timer:
sn.updates().checkNow("owner/repo");
// Arm (or re-arm) a recurring timer for this repo:
sn.updates().watch("owner/repo");checkNow runs a single check off the main thread and arms nothing. watch arms a recurring timer, and re-watching the same repo replaces (and cancels) the previous timer for that repo. In both cases an invalid owner/repo format WARNs and does nothing; the accepted format is a single owner/repo matching ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$.
Shared releases repo (one public repo for many plugins)
Both updates(...) and watch/checkNow also accept a tag prefix. Without it, the repo is assumed dedicated to this one plugin and is polled against releases/latest. With it, the repo is treated as shared by several plugins: the checker instead lists the repo's releases and keeps only the tags starting with your prefix, then picks the highest matching version.
.updates("owner/Sn-Releases", "myplugin-") // only tags like myplugin-v1.4.0 are consideredsn.updates().checkNow("owner/Sn-Releases", "myplugin-");
sn.updates().watch("owner/Sn-Releases", "myplugin-");This exists so a whole family of plugins can publish to one public repo instead of maintaining a dedicated public releases repo per plugin. The convention is to tag each release <pluginId>-vX.Y.Z (for example myplugin-v1.4.0) on the shared repo, so the prefix (myplugin-) unambiguously picks out this plugin's releases among everyone else's tags. Passing null (or using the single-argument overloads) keeps the old dedicated-repo behavior.
Timing
- First check: 60 seconds after enable (1200 ticks).
- Then: every 6 hours (432000 ticks).
- Always off the main thread, through the JDK
HttpClientwith a 5-second connect timeout and a 10-second request timeout.
The watch lives for the enable. A consumer reload neither re-arms nor duplicates it, and the timer is cancelled cleanly on disable.
What it checks and how it compares
Each watched repo is polled against the GitHub releases/latest endpoint:
GET https://api.github.com/repos/<owner>/<repo>/releases/latestThe response tag_name is read, a leading v/V is stripped only when a digit follows (so v1.4.0 becomes 1.4.0, while a tag like vanilla stays intact), and the result is compared against the installed plugin version with SemverComparator. This means a tag like v1.4.0 is correctly compared against an installed 1.3.2. When the latest is strictly greater, it is recorded as a finding and an INFO line is logged once per new version:
[MyPlugin] Version 1.4.0 available, installed 1.3.2.Neither the console line nor the admin chat notice carries the release URL; both report the versions only.
If the latest release is not newer, any prior finding for that repo is cleared.
What admins see
When a NEW finding lands, admins already online holding the permission <plugin>.admin.update receive an immediate chat notice, and while the finding exists, players who join with that permission receive the same notice (sent a short moment after join). The notice names the new and installed versions.
To make this permission default to op, declare it in your own plugin.yml:
permissions:
myplugin.admin.update:
description: Receive update notices for MyPlugin
default: opIf you do not declare it, only players who have been explicitly granted myplugin.admin.update are notified. The permission name is always your plugin's (lowercased) name plus .admin.update.
For the full receiving-end view - what the console line and the join notice look like from an admin's chair, and how to grant the permission - see Updates in the admin guide.
Failure handling
A non-200 response, a network error, or a response missing tag_name triggers exactly one WARN per repo per enable, then stays silent for the rest of that run. This keeps a repo that has no releases yet, a rate-limited API, or a flaky network from spamming the console.
[MyPlugin] update check of 'owner/repo' failed: HTTP 404In shared-repo mode, a repo with no release tag matching your prefix WARNs the same way once and stays silent:
[MyPlugin] update check of 'owner/Sn-Releases' failed: no release tag matching prefix 'myplugin-'Private repositories
To watch a private repo, provide a read-only token under update-check.token in your own config:
# config.yml
update-check:
token: "ghp_your_read_only_token"The token is read from your config on every single check (not cached at enable), so you can rotate it without a restart. It is sent as a Bearer header and is never logged. Leave the key empty or absent for public repos.
Real-world example: SnLib watches itself
Now that the SnLib repository is public, SnLib uses this exact module on itself for the notification side. Its internal buildSelfSpec() wires in the same declarative call every consumer uses:
private static SnSpec buildSelfSpec() {
return SnSpec.builder()
.config("config.yml")
.debugCommand()
.updates("ValentinTarnovsky/SnLib")
.build();
}So the library dogfoods the very module it hands to every consumer: when a newer SnLib release is published, the console logs it and admins holding snlib.admin.update are notified on join, exactly as they would be for any other plugin. Through this module, and therefore for you, nothing else happens.
The one thing that does install a jar, and why it is not here
SnLib additionally keeps its own jar current: it can download a newer SnLib.jar, verify it and replace the file on disk. That capability deliberately lives outside this module, in com.sn.lib.update.internal.SelfUpdater, and it is worth understanding why the split exists rather than a config flag on sn.updates().
- It is not reachable from a consumer context. It is not on
Sn, it is not inSnSpec, and its package is internal. There is no call you can make from a plugin that reaches it. - It cannot be pointed at anything else. The repository and the allowed asset URL prefix are compile-time constants for SnLib's own releases; there is no configuration that redirects it at a consumer jar or a third-party host.
- It therefore cannot weaken the guarantee above. The notify-only contract of this module is a property of the module, and the module has no download code in it at all.
The practical consequence for you as a developer: sn.updates() behaves exactly as documented on this page and will keep doing so, and you never have to reason about your plugin's jar being touched. Only the shared library maintains itself, only within a major version by default, and even then the new SnLib is merely placed on disk - it becomes active on the next full server restart, never by hot-swap. The admin-facing description of that behavior, including its auto-update config block, is in Updates.
See also
- Updates - the admin-facing side of the join notice.
- Bossbars, Holograms, Cron, Leaderboards, Discord - the Discord module shares the same
HttpClientand warn-once discipline. - Back to the developer guide.