Skip to content

Go Hosted Repository Workflow ​

This document explains how Pkgly manages hosted Go module repositories end to end: upload, storage layout, metadata updates, and client download behaviour.

Repository URL layout ​

A hosted repository is exposed under two equivalent URL prefixes:

  • Canonical: https://<host>/repositories/<storage-name>/<repository-name>/…
  • Legacy alias (Artipie-style, same repository resolution rules): https://<host>/api/<repository-name>/…

All examples below use the canonical form. Substitute <storage-name> and <repository-name> with your environment values.

Upload workflow ​

Hosted repositories accept uploads via a multipart form request that closely mirrors the Athens API (POST /upload). Pkgly requires authentication with write permission for the repository.

bash
curl -X POST \
     -H "Authorization: Bearer <token>" \
     -F "[email protected]" \
     -F "version=v1.2.3" \
     -F "module_name=github.com/acme/widget" \
     -F "[email protected]" \        # optional
     -F "[email protected]" \                  # optional
     https://<host>/repositories/<storage-name>/<repository-name>/upload

Multipart fields ​

FieldRequiredDescription
moduleYesThe Go module archive (.zip). Pkgly validates paths and rebuilds the zip.
versionYesModule version (e.g. v1.2.3).
module_nameYesFull module path (e.g. github.com/acme/widget).
infoOptionalContents of <version>.info. Pkgly will generate one if omitted.
gomodOptionalContents of go.mod. Pkgly extracts it from the zip if omitted.

Validation steps ​

  1. Authentication: requester must have RepositoryActions::Write.
  2. Version consistency: when an .info file is supplied it must contain the same version.
  3. Archive integrity:
    • No absolute paths or ../ segments.
    • Pkgly rebuilds the archive with the canonical structure <module>@<version>/… and inserts the resolved go.mod.
  4. Metadata synthesis: if info is absent Pkgly generates:
    json
    {
      "Version": "v1.2.3",
      "Time": "<current-timestamp-UTC>"
    }

Storage layout ​

Uploaded files are stored under:

<module>/@v/<version>.info
<module>/@v/<version>.mod
<module>/@v/<version>.zip
<module>/@latest          (latest version metadata, refreshed automatically)
<module>/go.mod           (latest go.mod alias)

Pkgly also maintains @v/list, a newline-delimited list of published versions for the module. This file is regenerated whenever a new version is published.

Metadata refresh ​

After a successful upload Pkgly performs the following updates:

  1. Save artefacts: .info, .mod, .zip.
  2. Update @v/list: append the version (sorted lexicographically; duplicates ignored).
  3. Refresh aliases:
    • @latest: points to the latest .info JSON.
    • go.mod: latest go.mod. These aliases are only updated when both .info and .zip exist for a version.

Download workflow ​

Go toolchain interacts with a hosted repository through the standard proxy endpoints:

OperationHTTPExample
List versionsGET /@v/listhttps://<host>/repositories/<storage>/<repo>/<module>/@v/list
Version infoGET /@v/{version}.info…/@v/v1.2.3.info
go.modGET /@v/{version}.mod…/@v/v1.2.3.mod
Module archiveGET /@v/{version}.zip…/@v/v1.2.3.zip
Latest metadataGET /@latest…/@latest
Latest go.modGET /go.mod…/go.mod

A typical client flow (go get github.com/acme/[email protected]) will issue:

  1. GET /@v/list
  2. GET /@v/v1.2.3.info
  3. GET /@v/v1.2.3.mod
  4. GET /@v/v1.2.3.zip
  5. Optional sumdb lookups if GOSUMDB is enabled (Pkgly currently returns 501 for hosted sumdb).

Since Pkgly normalizes the uploaded zip, the archive content always matches Go’s expected layout and passes the standard checksum validation.

Example download with go get:

bash
GOSUMDB=off GOPROXY=https://<host>/repositories/<storage-name>/<repo-name>,https://proxy.golang.org,direct go get -x github.com/example/hello-world

SumDB behaviour ​

Hosted repositories return:

  • GET /sumdb/sum.golang.org/supported → false
  • GET /sumdb/sum.golang.org/lookup/... and tile/... → 501 Not Implemented

Clients relying on sumdb should keep the default upstream (sum.golang.org) in GOSUMDB or GONOSUMDB settings for private modules.

Error handling ​

ErrorHTTP codeDescription
Missing or malformed multipart payload400Missing fields, invalid boundary, etc.
Version mismatch in .info400Info Version differs from the supplied version field.
Invalid archive structure400Absolute paths, ../, or missing go.mod (if extraction fails).
Unauthorized401Missing/invalid credentials.
Forbidden403Repository disabled or user lacks write permission.

Legacy PUT endpoints ​

For backwards compatibility the repository still accepts direct PUT uploads to each artefact:

  • PUT /<module>/@v/<version>.mod
  • PUT /<module>/@v/<version>.info
  • PUT /<module>/@v/<version>.zip

All validations and metadata refresh logic are shared with the multipart handler, so mixed workflows (e.g. first PUT .mod, then multipart for the remaining fields) remain supported.

Admin tooling ​

The Admin UI exposes the following controls for hosted repositories:

  • Packages tab: displays hosted module versions and allows deletion. Selecting a .info, .mod, or .zip entry removes all three artefacts for that version, its catalog records, and its entry in @v/list. The @latest and go.mod aliases are refreshed from the remaining versions or removed when none remain. Other versions remain available.
  • Repository configuration: Hosted mode selection under “Go” config panel.

The CLI and MCP package_delete tool use the same deletion API. Passing a module directory, such as github.com/acme/widget, removes that directory and its catalog entries within the selected repository. Directory matching respects path boundaries, so github.com/acme/widget-other is preserved.

If files were already removed but a package still appears in listings or search, repeat deletion with its module directory or exact artefact path. Pkgly also clears stale catalog rows when storage reports the requested files as missing. The response's deleted count describes storage removals; missing can contain paths whose stale metadata was cleaned up.