- Go 100%
|
All checks were successful
nagonag (Push) / nagonag (push) Successful in 3m21s
|
||
|---|---|---|
| .agents | ||
| .claude | ||
| .forgejo/workflows | ||
| .gemini | ||
| internal | ||
| .gitignore | ||
| .gogogo.conf | ||
| .golangci.yml | ||
| DESIGN.md | ||
| go-list-raw.json | ||
| go.mod | ||
| go.sum | ||
| LICENSE.txt | ||
| main.go | ||
| minor_patch_updates.json | ||
| README.md | ||
go · go · go
Reproducible release builder for Go modules on Forgejo / Gitea.
Publish what others can build. Verify what your users will get.
Contents
- What is it
- Why
- Quick start
- How it works
- Configuration
- Commands
- Module proxy verification
- Authentication & private repositories
- Major-version subdirectory modules
- Release lifecycle
- Native packages DEB RPM
- Verbosity
- Roadmap
- License
What is it
gogogo clones a Go module straight from your VCS host, verifies the clone byte-for-byte against the public module proxy, builds across every OS/arch you list, and uploads the artifacts as a release on Forgejo or Gitea — all from a single config file next to your go.mod.
The point is reproducibility: the binary you publish is built from the same bytes that any user gets via go install.
┌────────────────────────────────────────────────────────────────────┐
│ $ gogogo publish │
│ ✓ cloned go.example.com/foo @ v1.2.3 │
│ ✓ verified clone hash matches module proxy │
│ ✓ built foo-linux-amd64 foo-linux-arm64 foo-darwin-arm64 │
│ ✓ uploaded release v1.2.3 to forgejo.example.com │
│ ✓ cleaned up superseded pre-releases │
└────────────────────────────────────────────────────────────────────┘
Why
| Bit-for-bit reproducible | GOWORK=off, fresh go mod init, go build -trimpath. Nothing from your local checkout leaks in. |
| Hash-verified against the proxy | The clone is checked against golang.org/x/mod/zip hashes from the Go module proxy before any binary is built. |
| Cross-arch in parallel | Every OS/arch target builds concurrently. |
| VCS-stamped binaries | Builds run inside a real git checkout, so vcs.revision and vcs.time end up in the binary. |
| Private-repo aware | Token is auto-injected into git/go mod download via GIT_CONFIG_* — including across redirect chains. |
| Self-cleaning | Pre-releases superseded by a full release are removed automatically. |
Quick start
go install go.schlittermann.de/heiko/gogogo@latest
cd /path/to/your/module # the directory with go.mod
gogogo config default > .gogogo.conf
$EDITOR .gogogo.conf # set build.targets and any non-detected repo fields
# at HEAD on a signed semver tag:
gogogo publish
Ahead of a real run, gogogo publish --dry-run shows what would happen without building or uploading.
How it works
flowchart LR
L[Local checkout]:::soft -. ref check .-> CL
R[(Remote / Forgejo)] -->|clone @ ref| CL[Verified clone]
P[(Go module proxy)] <-->|hash compare| CL
CL -->|go build -trimpath| B[Cross-arch binaries]
B -->|package linux targets| PKG[Deb and RPM packages]
B -->|publish.blob| PB[publish.blob]
PKG -->|publish.deb| PD[publish.deb]
PKG -->|publish.rpm| PR[publish.rpm]
PB -->|default| RR[releases]
PB -->|empty list| NONE[no destinations]
PB -->|explicit names| DESTS[publish.destinations]
PD -->|default| RP[packages]
PD -->|empty list| NONE
PD -->|explicit names| DESTS
PR -->|default| RP
PR -->|empty list| NONE
PR -->|explicit names| DESTS
RR -->|built-in| TREL[type: forgejo-release]
RP -->|built-in| TPKG[type: forgejo-packages]
TREL --> REL[(Forgejo release)]
DESTS -->|named destination| XREL[type: forgejo-release]
DESTS -->|named destination| XPKG[type: forgejo-packages]
DESTS -->|named destination| XDUP[type: dupload]
CL -->|stamps vcs.revision / vcs.time| B
classDef soft fill:#f4f4f4,stroke:#bbb,color:#666;
- Clone — shallow-clone at the requested ref. Stable releases must point to the same commit locally and remotely.
- Verify — compute the module zip hash (
golang.org/x/mod/zip) and compare with whatgo mod download -jsonreports. Mismatch = abort. - Build —
go build -trimpathruns inside the clone, so the Go toolchain stamps real VCS info into every binary. - Package & publish — route blobs, Debian artifacts, and RPM artifacts through
publish.blob,publish.deb, andpublish.rpm, using built-in defaults or explicit destinations frompublish.destinations, then run retention cleanup.
Each publish.* route accepts default, [], or an explicit destination list. Built-in destination names are releases and packages; explicit names resolve through publish.destinations to a type such as forgejo-release, forgejo-packages, or dupload.
If repo config is missing or --local is set, gogogo falls back to a temp-dir build via the module cache (go get module@version). That path can't stamp VCS metadata and isn't bit-for-bit reproducible across runs.
Configuration
.gogogo.conf lives next to your go.mod. Generate the annotated default with gogogo config default.
| Field | Description |
|---|---|
version |
Config schema version; current new-style config uses 2 |
build.commands |
Subdirectories of commands to build (empty = module root) |
build.targets |
OS/arch pairs, e.g. linux/amd64 (empty = current platform) |
build.strip |
Linker strip level: all (default, -s -w), symbols, dwarf, none |
build.test.skip |
Skip tests before building |
repo.remote |
Git remote used to derive repo settings (default: origin) |
repo.apiurl |
Forgejo/Gitea API URL; derived from repo.remote when unset |
repo.owner |
Repository owner / organization; derived from repo.remote when unset |
repo.name |
Repository name; derived from repo.remote when unset |
repo.token |
API token — defaults to netrc:<remote-host> when unset |
security.trusted_redirects |
Permitted redirect-target prefixes that may receive forwarded request headers |
artifacts.packages |
Multi-package split: absent = one package, '*' = one per cmd/, or explicit list (see below) |
artifacts.blob.extra |
Extra files to publish as blobs (list of {src, name}) |
artifacts.common.name |
Override the source/package name derived from the module path's last component |
artifacts.common.maintainer |
Package maintainer name and email; auto-detects from git committer when unset |
artifacts.deb.lintian |
Run lintian on generated .deb packages (default: true) |
artifacts.deb.systemd |
Auto-generate maintainer scripts for systemd/sysusers/tmpfiles assets (default: true) |
artifacts.deb.scripts |
Explicit maintainer scripts (preinst, postinst, prerm, postrm); disables auto-generation |
artifacts.deb.conffiles |
Paths registered as dpkg conffiles |
artifacts.rpm.vendor |
RPM Vendor tag |
artifacts.rpm.repository |
RPM Repository tag |
publish.* |
Artifact destination routing |
release.branch |
Branch where release tags may be created (empty = repository default branch) |
env |
Extra environment variables passed to the build |
Tip
If
repo.tokenis unset, gogogo falls back tonetrc:<remote-host>. Rungo tool dist listfor the full target catalog.
Minimal real-world example
A typical project needs only a handful of fields — everything else is auto-detected from go.mod and the origin remote:
version: 2
build:
targets:
- linux/amd64
- linux/arm64
- darwin/arm64
- windows/amd64
artifacts:
common:
maintainer: "Jane Doe <jane@example.com>"
description: "My Go tool — short one-liner"
homepage: "https://example.com/mytool"
license: "Apache-2.0"
deb:
enabled: true # .deb packages for Linux targets
rpm:
enabled: true # .rpm packages for Linux targets
repo.* fields are omitted here: gogogo derives the Forgejo API URL, owner, and repo name from git remote get-url origin, and picks up the token from ~/.netrc. Add them explicitly only if auto-detection fails or you use a non-standard remote.
Commands
gogogo [flags] <command> [flags]
-v, --verbose progress and status messages
--debug LEVEL 1=HTTP requests/status, 2=full bodies (implies --verbose)
--expose-secrets show credentials in cleartext in debug output (dangerous)
--no-network disable network access globally
--version print version and exit
--help help for any command
Commands:
config show configuration (subcommands: default, parsed, migrate, packages, auto)
doctor run pre-flight checks
build run build stage only
pack run build + package stages (subcommand: populate)
publish run build + package + publish stages
tag create the next annotated release tag
status list existing releases
completion generate shell completion script
gogogo config — inspect and migrate configuration
gogogo config default # built-in default config
gogogo config parsed # effective config (defaults + .gogogo.conf)
gogogo config migrate # print migrated new-style config to stdout
gogogo config packages # scaffold artifacts.packages: block from cmd/* discovery
gogogo config auto # generate a pre-filled .gogogo.conf from project state
migrate reads the existing .gogogo.conf and prints a new rich commented config. Values matching defaults or auto-detected settings are commented out; non-default user values stay active.
packages generates an artifacts.packages: block. Use --shorthand to emit the packages: '*' one-liner, or --in-place to splice the block directly into .gogogo.conf.
auto analyses the current project (module path, git remote, README, LICENSE, systemd units) and emits a pre-filled .gogogo.conf. By default writes to stdout; use --write to update .gogogo.conf in place (a .bak backup is kept by default).
gogogo tag — create the next release tag
--approve approve a guessed or major release for creation
--bump LEVEL create the next release tag with LEVEL (patch, minor, or major)
--dry-run print the changelog without creating the tag
--force replace an existing tag
--push push the tag to the tracking remote after creation
--gpg-sign MODE GPG-sign the tag: auto (default), true, or false
If you omit --bump, gogogo guesses from commits since the latest stable release tag. The heuristics are deliberately simple:
feat:orfeat(scope):means minorfeat!:/feat(scope)!:orBREAKING CHANGEin the body means major- everything else falls back to patch
Pre-release and pseudo-version tags are ignored when looking for the baseline release. --bump patch and --bump minor imply --approve; --bump major and guessed releases require --approve before anything is created.
Tag creation must run on release.branch. If unset, gogogo uses the repository's default branch, usually main or master. --dry-run can be used from any branch; --force overrides the branch guard.
gogogo build, gogogo pack, gogogo publish
build build
pack build + package (subcommand: pack populate — scaffold debian/* and rpm/ stubs)
publish build + package + publish
Shared stage flags:
--commit COMMITISH what to release (default: auto-detect from HEAD)
(empty) use tag at HEAD if present, else
pseudo-version from current branch
@ current local branch
+ default branch HEAD
latest latest tag on default branch (via proxy)
--out DIR output directory; artifacts written to DIR/{blob,deb,rpm}/
required for build and pack; optional for publish
--sign GPG-sign release binaries (default: auto from git user.signingkey)
--no-lintian skip lintian check on generated .deb packages
--skip-doctor skip release pre-flight checks
--skip-verify skip module-proxy verification for stable releases
(env: GOGOGO_RELEASE_SKIP_VERIFY)
--timeout DURATION total timeout for staged operation (default 2m)
(env: GOGOGO_RELEASE_TIMEOUT)
--allow-unclean allow --no-network builds from a dirty worktree
Build/pack-only flags:
--keep-tmp keep temporary build directories instead of cleaning up
Publish-only flags:
--dry-run show what would happen, no builds, no uploads
--replace delete and recreate an existing Forgejo release;
for Debian, the pool cannot replace so the revision
auto-bumps and a warning is emitted (see Re-publishing
and Debian revisions below)
--local build only, do not upload
--verify during --dry-run, actually run the verification
When --commit is omitted, gogogo inspects HEAD:
- Tagged with a semver tag → that tag is used (after a remote-reachability check).
- Untagged → the current branch name is used, producing a pseudo-version pre-release.
- In both cases, HEAD must be reachable from a remote tracking branch — push first.
gogogo build, gogogo pack, and gogogo publish all run the same pre-flight checks as gogogo doctor unless you pass --skip-doctor.
--no-network builds from the local checkout, uses a tag at HEAD or derives a local pseudo-version, disables clone/proxy/upload operations, and for publish must be combined with --local. By default it requires a clean worktree; pass --allow-unclean to build uncommitted changes anyway.
gogogo pack populate scaffolds editable debian/ and rpm/ stub files (with text/template placeholders) so you can fully control package layout. Use --force to overwrite existing stubs.
gogogo status — list releases
gogogo status # tabular
gogogo status --json # machine-readable
gogogo status --limit 10 # cap the count (0 = all)
Shows tag, name, asset count, creation time, and draft/pre flags.
gogogo completion — shell completion
# bash
source <(gogogo completion bash)
# zsh
gogogo completion zsh > "${fpath[1]}/_gogogo"
# fish
gogogo completion fish | source
The --commit flag completes to git tags, branches, and the special tokens @, +, latest.
Module proxy verification
For stable releases (semver-tagged, e.g. v1.2.0 or v2.0.0-rc.1), gogogo runs go mod download -json module@version in a temp dir and compares the proxy-reported hash with the clone's content hash. This guarantees the release matches what users will get via go get.
For branch builds (pseudo-versions), proxy verification is skipped automatically — the commit SHA already anchors the build, and remote reachability was confirmed during cloning.
Note
Verification is skipped only when
--skip-verifyis passed orGOGOGO_RELEASE_SKIP_VERIFYis set. In that case the version is derived from the highest semver tag pointing at HEAD.
Authentication & private repositories
repo.token is used for cloning (https://token:<TOKEN>@host/…) and Forgejo API calls. For module-proxy verification the Go toolchain runs separately; gogogo wires the token via GIT_CONFIG_* automatically.
Vanity domain + redirect chain
A common Go-vanity setup chains a vanity domain → a legacy git host → the actual Forgejo:
sequenceDiagram
participant U as gogogo
participant V as go.example.com<br/>(vanity)
participant G as git.example.com<br/>(legacy)
participant F as forgejo.example.com<br/>(real)
U->>V: GET /org/repo?go-import=1
V-->>U: meta refresh → git.example.com/org/repo
U->>G: HEAD /org/repo
G-->>U: 301 forgejo.example.com/org/repo
U->>F: clone with credentials for forgejo.example.com
Note over U,F: Token is sent only to the final host —<br/>git does not forward credentials across redirects.
gogogo resolves the chain before injecting the token, so the credential always lands on the final host — both for cloning and for the GIT_CONFIG_* URL rewriting passed to go mod download.
Minimal private-repo config
# .gogogo.conf
env:
GOPRIVATE: "go.example.com"
GOPRIVATE tells Go to skip the public proxy and checksum DB. gogogo handles token injection and redirect resolution automatically.
Advanced: explicit GIT_CONFIG_* for unusual setups
# .gogogo.conf
env:
GOPRIVATE: "go.example.com"
GOPROXY: "direct"
GIT_CONFIG_COUNT: "1"
GIT_CONFIG_KEY_0: "url.https://token:${FORGEJO_TOKEN}@forgejo.example.com/.insteadOf"
GIT_CONFIG_VALUE_0: "https://go.example.com/"
| Variable | Purpose |
|---|---|
GOPRIVATE |
Bypass the proxy for matching modules |
GONOSUMCHECK |
Skip checksum verification for matching modules |
GOPROXY |
Override the module proxy (e.g. direct) |
GIT_CONFIG_COUNT |
Number of process-scoped Git config entries |
GIT_CONFIG_KEY_N |
Git config key (e.g. url....insteadOf) |
GIT_CONFIG_VALUE_N |
Git config value |
User-provided entries are preserved; gogogo's auto-injected entry is appended after them.
Important
--skip-verifyis for the rare case where verification is fundamentally impossible (air-gapped, broken vanity-domain DNS). For everyday private-repo work, just setGOPRIVATE.
Major-version subdirectory modules
gogogo supports the Go major-version subdirectory layout (e.g. v2/ with its own go.mod). When the configured module ends with /vN (N ≥ 2) and the clone has vN/go.mod, gogogo automatically:
- Hashes only the subdirectory (matching what the proxy serves)
- Runs
go listinside the subdirectory - Treats
build.commandsas relative to the subdirectory
Release lifecycle
Releasing a tag
- Create and push a signed semver tag on your default branch.
- Run
gogogo publish— the tag at HEAD is auto-detected.
Releasing a branch (pre-release)
gogogo publish # uses current branch
gogogo publish --commit my-feature # explicit
Untagged HEAD produces a Go pseudo-version pre-release automatically.
Retention policy
flowchart TD
NEW[New release published]
NEW --> KIND{kind?}
KIND -->|pre-release| P1["Strip assets from older pre-releases<br/>(keep the immediate previous one)"]
KIND -->|pre-release| P2[Full releases untouched]
KIND -->|full release| F1[Delete all older pre-releases entirely<br/>release object + git tag]
KIND -->|full release| F2["Strip assets from older full releases<br/>(keep the immediate previous one)"]
"Previous" is always defined by semver order, not upload time.
Re-publishing and Debian revisions
A semver tag (e.g. v0.0.12) corresponds to a single upstream release, but packaging fixes (wrong dependency, broken postinst, etc.) often need to be shipped without touching the source. gogogo handles this with a separate tag namespace and an automatic revision counter:
- Receipt tag
deb/v<X>-<N>— after every successful Debian publish, gogogo writes a GPG-signed annotated tag likedeb/v0.0.12-3at the same commit, with messagegogogo: debian packaging revision N for v<X>. The next publish reads this and bumps to0.0.12-4. The tag namespace is non-semver, so it never participates in release-tag selection. Requiresuser.signingkeyto be set in the local git config (same key gogogo uses for signing binaries). - Auto-bump — running
gogogo publishafter a packaging-fix commit produces0.0.12-(N+1)without any flag. A verbose log line reports the bump:# deb revision 4 for v0.0.12 (previous: 3). - No collision in the Debian pool — the Debian archive format (reprepro, dak, aptly, mini-dak) enforces "one checksum per filename" in
pool/. Because each revision has a unique filename, gogogo's revisions land cleanly without conflict.
Re-publishing to an existing Forgejo release
When a Forgejo release for the upstream tag already exists from a prior publish, gogogo publish (without --replace) merges the new artifacts into the existing release:
warning: release for tag v0.0.12 already exists; adding new artifacts (use --replace to recreate)
warning: release asset xr-invoiced-linux-amd64 already exists; skipping (use --replace to overwrite)
- Assets whose names don't already exist in the release are uploaded normally.
- Assets whose names match an existing one are skipped with a per-asset warning. This preserves the prior binary and only adds the new revision-suffixed
.deb/.dsc/.debian.tar.gz. - The release's notes, description, and metadata are preserved.
--replace semantics
--replace is the explicit override. Per transport:
- Forgejo release — the existing release and its tag are deleted, then a fresh release is created with the new artifacts. Use this when you want to overwrite the binary blobs on the release page or reset the release notes.
- Debian pool — cannot be replaced (archive-format invariant, not a tool quirk). The revision auto-bumps and a warning is emitted:
warning: debian pool rejects same-revision re-uploads; bumping v0.0.12-3 → v0.0.12-4 and writing deb/v0.0.12-4. - Had no effect — if
--replacewas given but neither side actually replaced anything:warning: --replace had no effect; nothing to replace at v0.0.12.
Auto-pruning old revision artifacts
When new revision-N artifacts are uploaded to an existing Forgejo release that still contains revision-(N-1) .deb/.dsc/.debian.tar.gz assets, those obsolete files are auto-pruned so the release shows only the latest revision. The binary blob and .orig.tar.gz (no revision suffix) are not touched. This only applies on the default publish path — --replace already starts from a clean slate.
Native packages (DEB, RPM)
For Linux targets, gogogo generates .deb (Debian/Ubuntu) and .rpm (Fedora/RHEL) packages alongside the raw binaries. Binary packages install to /usr/bin/<name> with metadata from .gogogo.conf; source packages (.dsc/.src.rpm) include vendored dependencies for distribution rebuilds.
Configuration
Enable packaging in .gogogo.conf:
artifacts:
common:
maintainer: "Your Name <you@example.com>"
description: "My tool does X"
homepage: "https://example.com"
license: "Apache-2.0"
deb:
enabled: true # Generate .deb packages for Linux targets
source: false # Generate Debian source artifacts
source_vendor: true # Run go mod vendor in source artifacts
section: "utils" # Debian section (default: misc)
priority: "optional" # Debian priority (default: optional)
depends: ["libc6 (>= 2.31)"] # Additional runtime dependencies
files: # Extra package files; src supports globs and rsync -r directory semantics
- src: packaging/hooks/ # trailing slash installs directory contents
dst: /usr/lib/mytool/hooks/
- src: packaging/*.service # dst inferred as /lib/systemd/system/<name>
- src: packaging/mytool.tmpfiles # installs as /usr/lib/tmpfiles.d/mytool.conf
rpm:
enabled: true # Generate RPM packages for Linux targets
source: false # Generate source RPM artifacts
source_vendor: true # Run go mod vendor in source artifacts
group: "Development/Tools" # RPM group (default: empty)
requires: ["glibc"] # Additional runtime dependencies
publish:
blob: default # default: Forgejo/Gitea release assets
deb:
- packages # Forgejo/Gitea Debian package registry
- debian_upload # extra dupload destination
rpm: default # default: Forgejo/Gitea RPM package registry
destinations:
debian_upload:
type: dupload
host: myrepo # nickname from ~/.dupload.conf
keyid: ABC123 # optional debsign key
configfile: ~/.dupload.conf.local # optional alternate dupload config
dir: ~/.dupload-uploads/mytool # optional staging dir outside the module root
Note
Packaging gracefully skips source-package generation if required tools are absent. Debian source artifacts require
dpkg-source; source RPMs requirerpmbuild. Binary.deband.rpmartifacts are still built without those source tools.
Note
Debian source package routing: Debian source artifacts (
.dsc,.orig.tar.*,.debian.tar.*,.diff.gz) are automatically classified and routed alongside binary.debfiles. With--outdir, they go tooutdir/deb/(notoutdir/blobs/). Both binary and source packages are sent to dupload destinations. Forgejo's Debian package registry receives only binary.debfiles, not source sidecars.
Warning
Debian source packages are currently uploaded as release artifacts, not to a
deb-srcrepository. Source-package generation is disabled by default until Forgejo supports Debian source repositories in the package registry; see issue #45.
Multi-binary packages
artifacts.packages controls how cmd/* binaries are split across .deb/.rpm packages.
| Value | Behaviour |
|---|---|
| absent (default) | all binaries in one package named after the module |
'*' |
one package per cmd/* binary, named after the binary |
| explicit list | full control: name, binary assignment, per-package deb/rpm overrides |
artifacts:
packages:
- name: myapp-server
binaries: [myapp-server]
deb:
depends: ["libsystemd0"]
- name: myapp-client
binaries: [myapp-client]
Entries in binaries may be bare names (myapp-server) or full Go package paths (cmd/myapp-server) — both are accepted.
Use gogogo config packages to generate this block from cmd/* discovery, or gogogo config packages --shorthand for the packages: '*' one-liner.
Use gogogo pack populate to scaffold editable debian/ and rpm/ template files when you need full control over the package layout.
Using dupload
type: dupload adds a Debian publishing path that stages packages and uploads them via the dupload tool — for example, to an incoming queue or a self-hosted APT repository front-end. It works alongside or instead of Forgejo's built-in package registry.
Prerequisites
apt install devscripts dupload # debsign comes from devscripts
gogogo doctor checks for both tools and fails with an actionable hint if either is missing. You do not need to run doctor manually; gogogo publish runs it automatically.
How the upload works
For each release that produces .deb files, gogogo:
- Creates a staging directory — default
$XDG_CACHE_HOME/gogogo/dupload-uploads/<package-name>(typically~/.cache/gogogo/dupload-uploads/<package-name>), or the path indir. The directory is created if it does not exist and must be outside the module root. - Copies artifacts — each
.deband any Debian source-package sidecars (.dsc,.tar.gz, etc.) are copied in flat; the original build directory is left untouched. - Writes a
.changesfile —<name>_<version>.changesis generated from the staged files plus metadata taken from.gogogo.conf:Maintainer/Changed-Byfromartifacts.common.maintainerDescriptionfromartifacts.common.descriptionDistributionis the space-joined list ofartifacts.deb.distributions(defaultstable) so reprepro can land one.changesin multiple suites atomicallySectionandPriorityfromartifacts.deb.section/artifacts.deb.priorityChecksums-Sha1,Checksums-Sha256, andFilescomputed from the staged files
- Signs with
debsign— runsdebsign [-k<keyid>] <changes>in the staging directory. The key must be available in the local GPG keyring. Signing happens during the pack phase; the dupload step is upload-only. - Uploads with
dupload— before calling dupload, gogogo checks a SHA256 sidecar (.changes.gogogo-sha256) left by the last successful upload of this.changesfile. If the signed content is identical, the upload is skipped. Otherwise any stale dupload upload record (.upload) is removed so dupload re-uploads cleanly. On success, the sidecar is updated and payload files (.deb,.dsc,.changes, etc.) are purged from the staging directory; the sidecar itself is kept for future dedup checks.
~/.dupload.conf examples
dupload supports several transport methods. Choose the one that matches your repository's incoming queue:
# ~/.dupload.conf
# scp — most common for private repos and Debian Mentors work
$cfg{'myrepo'} = {
fqdn => 'repo.example.com',
method => 'scp',
incoming => '/srv/incoming/debian',
login => 'upload',
};
# ftp — legacy, but still used by some self-hosted reprepro setups
$cfg{'myftp'} = {
fqdn => 'repo.example.com',
method => 'ftp',
incoming => '/queue/incoming',
login => 'anonymous',
};
# local — useful for testing; copies to a local directory
$cfg{'local'} = {
fqdn => 'localhost',
method => 'local',
incoming => '/var/spool/incoming/debian',
};
Refer to man dupload.conf for the full option set, including dinstall_runs, post_upload_command, and preupload.
Full configuration example
# .gogogo.conf
artifacts:
common:
maintainer: "Jane Doe <jane@example.com>"
description: "My tool does X"
deb:
enabled: true
distributions: ["stable"] # written into the .changes Distribution field
section: "utils"
priority: "optional"
publish:
deb:
- packages # Forgejo Debian package registry
- debian_upload # additionally upload via dupload
destinations:
debian_upload:
type: dupload
host: myrepo # nickname from ~/.dupload.conf
keyid: ABC123 # GPG key for debsign (optional; omit to use default key)
configfile: ~/.dupload.conf.local # alternate config file (optional)
dir: ~/.dupload-uploads/mytool # staging dir outside the module root (optional)
To upload to both the Forgejo package registry and an external repository via dupload, list both destinations:
publish:
deb:
- packages # built-in: Forgejo/Gitea Debian package registry
- debian_upload # custom destination defined in publish.destinations
destinations:
debian_upload:
type: dupload
host: myrepo # nickname from ~/.dupload.conf
keyid: ABC123 # optional GPG key for debsign
packages is the built-in alias for type: forgejo-packages; releases is the built-in alias for type: forgejo-release. Neither needs to be defined under destinations.
To skip Forgejo and upload only via dupload:
publish:
deb:
- debian_upload # [] would disable all deb publishing
Reference
| Field | Required | Default | Description |
|---|---|---|---|
type |
yes | — | Must be dupload |
host |
yes | — | Nickname matching a stanza in ~/.dupload.conf |
keyid |
no | GPG default key | Key passed as -k<keyid> to debsign |
configfile |
no | ~/.dupload.conf |
Alternate dupload config file |
dir |
no | $XDG_CACHE_HOME/gogogo/dupload-uploads/<package-name> |
Staging directory; ~/ is expanded; must be outside module root |
Verbosity
gogogo is quiet by default — only errors are printed.
| Flag | Effect |
|---|---|
--verbose (-v) |
One-line status per action; progress bars on TTYs; richer error hints. |
--debug 1 |
Above + HTTP request lines and status codes. |
--debug 2 |
Above + full request and response bodies (verbose; secrets are redacted). |
Roadmap
- APT/YUM repository publishing (
gogogo reposubcommand) — host your packages on a Debian or RPM repository server. - Winget + Scoop manifests for Windows releases — auto-publish installers to package registries.
License
Apache-2.0 — see LICENSE.txt.