Skip to content

Point your tool at its releases

Everything self-update needs lives on ToolMetadata. The download, verification and swap are rtb-update's job; this page covers the declaration.

Declare where the releases are

use rtb_app::metadata::{ReleaseSource, ToolMetadata, UpdatePolicy};

let metadata = ToolMetadata::builder()
    .name("mytool")
    .summary("does the thing")
    .release_source(ReleaseSource::Gitlab {
        project: "acme/tools/mytool".into(),
        host: "gitlab.com".into(),
    })
    .update_public_keys(vec![
        "RWRWR1qnVGNHTAOhB7/zzhC+HXDdGOdLwJln5NYwm6UNXx3chmQSVTG4".to_string(),
    ])
    .build();

Six hosting shapes are available — Github, Gitlab, Bitbucket, Gitea, Codeberg and Direct. Their required fields and host defaults are tabulated in Tool metadata. Two to watch:

  • Gitea has no host default. Omitting host fails with missing field "host".
  • Codeberg has no host field at all — use it instead of Gitea with host: codeberg.org.

Pin the signing keys

update_public_keys holds minisign public keys in the encoded form a minisign.pub file carries — the RWR… string, not raw key bytes. Use the same string you pin as pubkey in [package.metadata.binstall.signing] and that sigillum keys minisign prints, so cargo-binstall and rtb-update cannot end up trusting different things and an operator can compare the two by eye.

An empty list means rtb-update refuses to run. It is empty by default, so this is the field most likely to be the reason self-update does nothing.

List more than one to rotate keys without breaking already-deployed binaries — any one verifying is accepted, so a binary shipped trusting {old, new} spans the rotation:

.update_public_keys(vec![old_key.to_string(), new_key.to_string()])

Name the assets, if yours are named differently

update_asset_pattern is None by default, and rtb-update then falls back to {name}-{version}-{target}{ext}:

Placeholder Resolves to
{name} ToolMetadata::name
{version} the release tag with any leading v stripped
{target} the Rust host triple
{ext} .tar.gz on Unix, .zip on Windows

Set it explicitly when your release job names assets some other way.

update_checksums_asset names an asset listing SHA-256 checksums. When set, rtb-update downloads it alongside the binary and cross-checks the hash before the swap. When None, signature verification is the only integrity gate — which is a real gate, but it is one gate rather than two.

Turn automatic checking on

.update_policy(UpdatePolicy::Prompt)
.update_check_interval(Duration::from_secs(6 * 60 * 60))
Policy Behaviour
Disabled (default) No automatic check. The update subcommand still works.
Prompt Checks, throttled; prompts when a newer version exists.
Enabled Checks, throttled; updates before running.

The default is Disabled so a tool makes no unsolicited network call and pays no pre-run cost unless its author asked for it. The update subcommand is available regardless — the policy governs only the automatic pre-run check.

Four things that silently disable self-update

Work down this list when nothing happens:

  1. Feature::Update is runtime-disabled. It is on by default, but a tool that built its Features with FeaturesBuilder::none() — or with FeaturesBuilder::default(), which is also empty — has turned it off.
  2. release_source is None. Every update field is inert without it.
  3. update_public_keys is empty. rtb-update refuses rather than trusting an unsigned asset.
  4. The tool still uses VersionInfo::from_env(). It reports rtb-app's version instead of the tool's, and the post-download self-check compares the staged binary's reported version against the release tag. See Report your tool's own version.

Setting these from a config file

Only some of these fields can come from a config file. update_public_keys, update_checksums_asset and update_asset_pattern all carry #[serde(skip)] and are compile-time-only — and because ToolMetadata is also deny_unknown_fields, putting one of them in a YAML document fails the whole parse rather than being ignored.

release_source, update_policy and update_check_interval do deserialise. update_check_interval needs serde's Duration shape:

update_check_interval:
  secs: 21600
  nanos: 0