Resolve your first artefact¶
By the end of this you will have a verified file on disk, fetched from the live channel, and you will have watched the verification refuse a tampered copy.
It takes about ten minutes. Everything here runs against the public channel, so no credentials are needed.
Before you start¶
You need Go 1.26.6 or newer:
Step 1 — a module to work in¶
mkdir artefact-tutorial && cd artefact-tutorial
go mod init example.com/artefact-tutorial
GOPRIVATE='gitlab.com/phpboyscout/*' go get gitlab.com/phpboyscout/go/artifacts
Step 2 — get the signing key¶
Verification needs a key compiled into your program. Download the channel's public key and keep it beside your source:
curl -fsSL -o release.asc \
https://gitlab.com/phpboyscout/artifacts/-/raw/main/keys/artifacts-release-signing.asc
Check you got the key you expected before you trust it:
The fingerprint must read 544E64F3 B87561D5 67393336 34A711C4 B9EAA99A. If it
does not, stop — you have someone else's key, and everything below would happily
verify against it.
Step 3 — resolve something¶
Create main.go:
package main
import (
"context"
"fmt"
"log"
_ "embed"
"gitlab.com/phpboyscout/go/artifacts"
"gitlab.com/phpboyscout/go/artifacts/trust"
)
//go:embed release.asc
var releaseKey []byte
func main() {
verifier, err := trust.Estate(releaseKey)
if err != nil {
log.Fatal(err)
}
dir, err := artifacts.UserCacheDir()
if err != nil {
log.Fatal(err)
}
client, err := artifacts.New(verifier,
artifacts.WithCache(artifacts.NewDirCache(dir)))
if err != nil {
log.Fatal(err)
}
ref := artifacts.Ref{Name: "onnxruntime", Version: "1.28.0"}
// Ask what this version contains before downloading any of it.
files, err := client.Manifest(context.Background(), ref)
if err != nil {
log.Fatal(err)
}
for name := range files {
fmt.Println(name)
}
}
Run it:
You should see the files published for that version — one archive per platform, plus the manifest itself. Nothing has been downloaded yet beyond the manifest and its signature.
If this fails with a verification error
The most likely cause is the WKD lookup. trust.Estate requires the embedded
key and the key published at openpgpkey.phpboyscout.uk to agree, and it
fails closed when it cannot reach the second one. On a network that
intercepts TLS or blocks the host, that is a refusal by design — see
The trust model.
Step 4 — fetch a file¶
Pick the archive matching your platform and resolve it. Replace the Println
loop with:
path, err := client.Resolve(context.Background(), ref,
"onnxruntime-linux-x64-1.28.0.tgz")
if err != nil {
log.Fatal(err)
}
fmt.Println("verified and cached at:", path)
Run it again. The first run downloads; the second returns immediately from the cache.
Step 5 — ask for something that was never published¶
Change the filename to a platform the channel does not carry:
That answer comes from the signed manifest, not from the server's response code. The manifest is the authoritative list of what a version contains, so a file missing from it does not exist however the URL behaves — and a channel that served a 200 with a plausible-looking archive at that path would still get this answer.
The example is real rather than contrived: upstream dropped macOS Intel builds between ONNX Runtime 1.23.0 and 1.26.0, so 1.28.0 genuinely has no such file.
Match it with errors.Is:
if errors.Is(err, artifacts.ErrNotFound) {
// no build for this platform, or a version nobody approved
}
A note on the cache¶
If you edit a file in the cache, the next resolve detects it and fetches the artefact again. Entries are re-hashed against a digest from a freshly verified manifest, so an edited copy is replaced rather than returned.
That means a cache hit still talks to the network — it saves the artefact download, not the round trip. See Choose a cache location for what that means in a firewalled environment.
What you built¶
A program that fetches an artefact only after establishing who published it, stores it somewhere reusable, and refuses bytes that do not match what was signed.
Next¶
- Pin a version — why the version is your decision, not the channel's
- The trust model — what the signature proves and what it does not