Configure Self-Updating¶
GTB's UpdateCmd feature lets your tool check for newer releases and replace its own binary. This guide covers how to wire it up with a release backend. Six built-in source types are available out of the box: github, gitlab, bitbucket, gitea, codeberg, and direct. For anything else, see Add a Custom Release Source.
The update system has two parts:
1. props.Tool.ReleaseSource: tells the framework where to find releases at compile time
2. Config (provider-specific subtree): provides the API token and endpoint at runtime
Step 1: Populate props.Tool in main.go¶
The Tool struct is constructed once at startup and injected into Props. Fill in the ReleaseSource field:
import (
"gitlab.com/phpboyscout/go-tool-base/pkg/props"
)
tool := props.Tool{
Name: "mytool",
Summary: "My developer tool",
ReleaseSource: props.ReleaseSource{
Type: "github", // see supported types below
Owner: "my-org",
Repo: "mytool",
},
}
For private repositories, set Private: true. The framework will require a token and error early if none is found:
ReleaseSource: props.ReleaseSource{
Type: "github",
Owner: "my-org",
Repo: "mytool",
Private: true,
},
Supported source types¶
Type |
Platform | Notes |
|---|---|---|
"github" |
GitHub / GitHub Enterprise | Set Host for GHE instances |
"gitlab" |
GitLab / self-managed | Set Host for self-managed GitLab |
"bitbucket" |
Bitbucket Cloud Downloads | Version inferred from asset filenames |
"gitea" |
Gitea / Forgejo | Host is required |
"codeberg" |
Codeberg (Forgejo) | Host defaults to https://codeberg.org |
"static" |
The static release channel: any https location the release publishes a pointer and per-tag manifests under, no forge involved | BaseURL is the one setting; see below |
"direct" |
Arbitrary HTTP / S3 / CDN through go/forge's direct provider | URL template required in configuration |
For a self-managed GitLab instance, also set Host:
ReleaseSource: props.ReleaseSource{
Type: "gitlab",
Host: "gitlab.example.com",
Owner: "my-group",
Repo: "mytool",
},
For a Gitea instance:
ReleaseSource: props.ReleaseSource{
Type: "gitea",
Host: "https://git.example.com",
Owner: "my-org",
Repo: "mytool",
},
For a direct download server, the release source names only the connection:
Its URL templates are configuration, under the direct subtree:
direct:
url_template: https://dl.example.com/{tool}/{version}/{tool}_{os}_{arch}.{ext}
version_url: https://dl.example.com/latest.json
They live in configuration rather than in a struct field so that two sources of
one type can coexist, each reading its own subtree. These were previously a
ReleaseSource.Params map; see the
migration note.
See the Release Provider component for the configuration keys each provider reads.
The static release channel¶
A tool need not read a forge at all. On the static channel (spec 0203) the release publishes two small JSON documents beside the binaries: <base>/latest.json, a pointer naming the current tag and its manifest, and <base>/<tag>/release.json, a manifest listing that release's archives with their platform, size and SHA-256, its checksums.txt and signature, and the tag before it. The tool reads the pointer, follows the manifest, picks its platform and verifies as it does on a forge. Nothing else is configured, and no credential is read: a public location is the design.
ReleaseSource: props.ReleaseSource{
Type: props.ReleaseSourceStatic,
BaseURL: "https://pkg.example.org/acme/mytool",
},
The base URL must be https, carry no userinfo and no query, and not be a placeholder host such as example.com. gtb itself updates this way from https://pkg.phpboyscout.uk/go-tool-base.
A generated project chooses the channel on the wizard's self-update page ("A static location", then the location on the page after) or with --release-channel static --release-base-url <url>; a project that is not hosted on a forge (--no-forge) can self-update this way and no other. The release configuration then publishes the two documents for you; Secure releases says what that needs in CI, and the layout reference is the contract any other publisher can meet.
On this channel update is also exempt from two gates that would stop a broken install helping itself: the unlinked-forge check and the missing-config gate. A tool whose forge adapters or configuration are broken can still pull its own fix.
To check a location by hand:
curl -fsS https://pkg.example.org/acme/mytool/latest.json # the pointer: tag and manifest URL
curl -fsS https://pkg.example.org/acme/mytool/v1.2.0/release.json # the manifest it names
curl -fsS https://pkg.example.org/acme/mytool/v1.2.0/checksums.txt # the digests the manifest repeats
mytool doctor # the Release source check reads the pointer and names the current tag
Step 2: Ensure UpdateCmd is Enabled¶
UpdateCmd is enabled by default. If you previously disabled it, re-enable it via SetFeatures:
To disable it (e.g. for internal tools distributed another way):
Step 3: Configure the Token¶
The framework reads token configuration from the relevant subtree of your config file. Add defaults to your embedded config asset (e.g. assets/config/defaults.yaml):
For GitHub:
github:
url:
api: "" # leave empty for github.com; set for GitHub Enterprise
upload: ""
auth:
env: GITHUB_TOKEN # environment variable to read
value: "" # or set a literal token here (not recommended for public repos)
For GitLab:
gitlab:
url:
api: "" # leave empty for gitlab.com; set for self-managed
auth:
env: GITLAB_TOKEN
value: ""
Users then set the environment variable before running the update command:
Step 4: Build with ldflags Version Info¶
The update command compares the running version against the latest release tag. It needs Version to be set at build time via ldflags. In your goreleaser.yaml or Makefile:
In main.go, wire the version into Props:
var (
version = "dev"
commit = "none"
)
func main() {
p := &props.Props{
Tool: tool,
// Props.Version is a version.Info; NewInfo normalises the v prefix.
Version: version.NewInfo(version, commit, ""),
}
// ...
}
For a development build (a non-semver version, or one containing -dev/-dirty, version.IsDevelopment()), the update check is automatically skipped, so no API calls are made during local development.
Step 5: Verify¶
Build a release binary and run:
mytool update # check and apply if a newer version is found
mytool update --version 1.2.3 # update to a specific version
Expected output when up to date:
Expected output when an update is available:
INFO Update available current=v1.2.3 latest=v1.3.0
INFO Downloading update...
INFO Update applied successfully. Restart to use the new version.
Roll Back and Pin¶
Your users can step back from a bad release and stay there, with nothing for you to configure:
mytool update --rollback # the release before this one, after a confirmation
mytool update --rollback=v1.1.0 # a named older release
mytool update --pin # stay on the running version
mytool update --pin=v1.1.0 # install v1.1.0 and stay on it
mytool update --unpin # resume updates
- The rollback is verified against the release's signed checksums, as a forward update is.
- It asks first at a terminal. An unattended
--rollback(CI, or no terminal) needs--force. - A successful rollback pins its target. The update check then stays quiet, even under the
enabledpolicy, and plainupdatedoes nothing untilupdate --unpin.
On the static release channel, a release published before your tool moved to the channel has no manifest, so it cannot
be named. Install it from its archive with update --from-file. The flags and the full behaviour are in the
command reference.
Throttling¶
By default, the update check runs at most once per 24 hours (stored in the tool's config directory). This prevents hammering the API on every command invocation. The throttle interval is configurable:
Services and Other Unattended Runs¶
The update check only runs for a person at a terminal. A service under systemd, a pod, a cron job or a script has no terminal on stdin, so it makes no check, is never blocked by the enabled policy and never self-updates. Under enabled it logs one warning that the policy was not enforced.
Keep such an install current from the deployment instead:
- redeploy the service with the new release (a package upgrade, an image rollout), or
- run
your-tool updateas a deployment step.
If the service's stdin is a terminal (tty: true in Compose or Kubernetes, docker run -t), it is treated as attended. Drop the tty, or pass --ci.
Air-Gapped / Offline Environments¶
For environments without network access, the update command supports installing from a local release archive. This workflow has three steps:
1. Download on a connected machine¶
Download the release archive and checksum from the GitHub/GitLab releases page:
# On a machine with internet access
curl -LO https://github.com/my-org/mytool/releases/download/v1.3.0/mytool_Linux_x86_64.tar.gz
curl -LO https://github.com/my-org/mytool/releases/download/v1.3.0/mytool_Linux_x86_64.tar.gz.sha256
2. Transfer to the target machine¶
Copy both files to the air-gapped machine via USB, SCP, or your internal artifact pipeline.
3. Install from file¶
The checksum sidecar is automatically detected and verified. No API token or network access is required.
The --from-file flag is mutually exclusive with --version. The --force flag has no effect on offline updates since no version comparison is performed.
Interaction with require_* flags
The offline path enforces the same verification contract as the online path:
update.require_checksum: true: the install fails if the.sha256sidecar is missing. Always transfer both files.update.require_signature: true: the offline path cannot verify an OpenPGP signature (there is no manifest+signature flow for a local archive), so it refuses the update. Use the online path for signature-verified updates, or disablerequire_signaturefor air-gapped installs.
Related Documentation¶
- Auto-Update Lifecycle: how the update loop works
- Release Provider component: all built-in providers, registry API, and per-provider configuration keys
- Add a Custom Release Source: register your own provider for any backend
- GitHub provider: GitHub release provider and token resolution (external
go/forge-githubmodule) - GitLab component:
NewReleaseProviderfor GitLab - Configuring Built-in Features: enabling and disabling UpdateCmd