Publish built artifacts into a Maven repository layout — driven entirely by a configuration file.
Repoman reads a .conf file that lists the modules to publish (their Maven
coordinates and where the built artifacts live), copies them into a Maven
repository layout, generates a POM and checksums, and updates
maven-metadata.xml. Publishing is immutable: an existing version is
never overwritten.
The repository target can be a local directory or a remote rsync destination
(rsync:// daemon URL or scp-style [user@]host:path).
- Config-driven — one
.conffile per publish run; no build-system detection, no guessing. Coordinates and artifact locations are declared explicitly. - Indentation-tolerant conf format —
----delimitedkey : valueblocks; lines are trimmed, empty lines and#comments are ignored. - Quoting — values/keys may be quoted (
"...",'...') with\"escaping, or use Rust-style raw strings (r"...",r#"..."#). - Immutable publishing — existing versions are skipped, never overwritten; cleanup is the user's responsibility.
- Complete Maven layout —
groupPath/artifact/version/, generated POM,.sha256checksums, andmaven-metadata.xml. - Local or remote — publish into a local directory, or into a remote
repository over rsync (daemon or ssh; never
--delete): the remote's directory layout andmaven-metadata.xmlfiles are pulled into./staging/(binaries stay on the remote), the new artifacts are patched in, and the combined tree is sy B6B6 nced back. - Clean output — concise standard output, or detailed step-by-step output
with
--verbose.
- Go 1.25+ to build.
rsynconPATHwhen publishing to a remote target.upx(optional) to compress the binary duringbuild.sh.
./build.shThis produces ./repoman. Set VERSION to inject a version string (defaults
to dev):
VERSION=0.3.0 ./build.shOr build manually:
go build -trimpath -ldflags="-s -w -X main.version=0.3.0" -o repoman .repoman [--verbose] <conf-file> <target>
| Argument | Description |
|---|---|
conf-file |
Required conf file listing the modules to publish |
target |
Required local directory, or a remote rsync destination: an rsync:// daemon URL or [user@]host:path |
| Option | Description |
|---|---|
--verbose, -v |
Print detailed step-by-step output |
-h, --help |
Show help |
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Success (possibly with skipped modules) |
| 1 | A module failed, or a fatal error occurred |
| 2 | Usage / argument error |
A conf file is a flat list of modules. Each module is a ----delimited block
of key : value lines. Lines are trimmed; empty lines and lines starting
with # are ignored, so indentation is harmless. Leading, trailing, and
doubled --- separators are tolerated.
Each module requires:
group-id— Maven group IDartifact-id— Maven artifact IDversion— version to publish- exactly one of:
jar— a pinned artifact file to publishoutput-dir— a directory to scan for artifacts (picks up pom, sources, javadoc, executables, etc.)
Relative jar / output-dir paths are resolved against the conf file's
directory.
Example:
# org.example.core.conf
---
group-id : org.example
artifact-id : core
version : 1.2.3
jar : ../core/target/core-1.2.3.jar
---
group-id : org.example
artifact-id : api
version : 1.2.3
output-dir : ../api/target
---
Keys and values may be quoted with " or ' to include spaces or special
characters:
jar: "path with spaces.jar"
Inside a quoted value, \" escapes a double quote and \\ escapes a
backslash. Rust-style raw strings are also accepted and skip escaping
altogether:
jar: r#"path with "quotes".jar"#
For each module, repoman:
- Checks that the version does not already exist in the target repository.
An existing version is skipped (never overwritten) and repoman prints
a
SKIPPEDnotice with the directory to remove to republish. - Copies the artifact(s) into
<target>/<group-path>/<artifact>/<version>/. Pinned jars are copied as<artifact>-<version>.jar; output directories are scanned for matching artifacts, executables (which get a platform tag), and supporting files. - Generates a POM (
<artifact>-<version>.pom) if one is not already present. - Generates a SHA-256 checksum file (
<artifact>-<version>.sha256). - Updates
maven-metadata.xmlwith the new version.
For a local target, maven-metadata.xml is updated in place: the
metadata directory is scanned for existing version directories and the new
version is merged in (deduplicated, sorted, <release>/<latest> updated),
so already-published versions are preserved. For a remote target, the
remote's directory layout and maven-metadata.xml files are first pulled
into ./staging/, then the same in-place merge runs against that staged
tree, and the combined result is synced back — see Remote
Publishing.
Standard output (default):
Repoman 0.3.0
Configuration: altcontainers.conf
Repository: /tmp/repo
Modules: 2
Options: none
[1/2] org.altcontainers:core:0.3.0-POST
✓ Published
• jar: core-0.3.0-POST.jar
• repo: /tmp/repo/org/altcontainers/core/0.3.0-POST
• metadata: updated
[2/2] org.altcontainers:reaper:0.3.0-POST
✓ Published
• jar: reaper-0.3.0-POST.jar
• repo: /tmp/repo/org/altcontainers/reaper/0.3.0-POST
• metadata: updated
Summary
Published: 2
Skipped: 0
Failed: 0
Published artifacts
• org.altcontainers:core:0.3.0-POST
-> /tmp/repo/org/altcontainers/core/0.3.0-POST
• org.altcontainers:reaper:0.3.0-POST
-> /tmp/repo/org/altcontainers/reaper/0.3.0-POST
Repository: /tmp/repo
A version that already exists is skipped, with the directory to remove to republish:
[1/1] org.altcontainers:core:0.3.0-POST
SKIPPED Version already exists
Remove REPOSITORY/org/altcontainers/core/0.3.0-POST to republish
Summary
Published: 0
Skipped: 1
Failed: 0
Repository: ./REPOSITORY
Verbose output (--verbose):
Repoman 0.3.0
Configuration: altcontainers.conf
Repository: /tmp/repo
Modules: 2
Options: --verbose
[1/2] org.altcontainers:core:0.3.0-POST
• source jar: ../../altcontainers/altcontainers/core/target/core-0.3.0-POST.jar
• target dir: /tmp/repo/org/altcontainers/core/0.3.0-POST
✓ Repository directory ready
✓ Version is unique
✓ Copied artifact: core-0.3.0-POST.jar
✓ Generated POM: core-0.3.0-POST.pom
✓ Updated metadata: /tmp/repo/org/altcontainers/core/maven-metadata.xml
✓ Verified repository integrity
[2/2] org.altcontainers:reaper:0.3.0-POST
• source jar: ../../altcontainers/altcontainers/reaper/target/reaper-0.3.0-POST.jar
• target dir: /tmp/repo/org/altcontainers/reaper/0.3.0-POST
✓ Version is unique
✓ Copied artifact: reaper-0.3.0-POST.jar
✓ Generated POM: reaper-0.3.0-POST.pom
✓ Updated metadata: /tmp/repo/org/altcontainers/reaper/maven-metadata.xml
✓ Verified repository integrity
Summary
Published: 2
Skipped: 0
Failed: 0
Repository: /tmp/repo
Passing a remote URL as the target publishes in three phases: the remote's
directory layout and maven-metadata.xml files are first pulled into
./staging/ — binaries and other artifact files stay on the remote, so the
pull stays cheap even for large repositories — the new artifacts and the
regenerated maven-metadata.xml are patched on top of it, then the
combined tree is synced back to the remote. Pulling first matters:
existing remote versions are preserved and their metadata is merged instead
of being replaced by a fresh local file, so maven-metadata.xml keeps
listing every published version. A remote that does not exist yet (or is
empty) is treated as a fresh repository and the first publish creates it; a
missing source is not an error.
The staging directory is created if missing and emptied at the start of each run before the pull, but it is never deleted, so it can be inspected between runs.
Local targets do not use the pull/patch/push flow: repoDir is the target
directory itself, so existing versions are seen and maven-metadata.xml is
merged in place.
Two transports are supported:
- rsync daemon —
rsync://[user@]host/modulesyncs to a module defined in the remotersyncd.conf. Requires an rsync daemon on the remote. - ssh — scp-style
[user@]host:pathsyncs to a real directory over ssh. Uses your normal ssh authentication (keys, agent,~/.ssh/config); no daemon needed.
repoman altcontainers.conf rsync://repo.example.com/maven
repoman altcontainers.conf user@repo.example.com:/var/mavenRemote targets never delete files (no --delete): the remote is cumulative,
and cleanup is the user's responsibility. If an rsync:// daemon cannot be
reached at all (no rsyncd running, firewall, ...), repoman retries the same
URL path over ssh — for both the pull and the push — so
rsync://host/path works on hosts without a daemon by syncing to
host:/path instead.
Authentication uses environment variables:
| Variable | Purpose |
|---|---|
REPOMAN_USERNAME |
Added to the URL (rsync://user@host/..., user@host:...) when the URL has none |
REPOMAN_PASSWORD |
Mapped to rsync's RSYNC_PASSWORD for rsync:// daemon syncs (not used for ssh) |
example.conf— annotated templatealtcontainers.conf— real-world example (single module, pinned jar)
go build . # build
go vet ./... # static checks
gofmt -l . # formatting check (should print nothing)
./build.sh # full build with optional UPX compressionApache License 2.0. See LICENSE.
Copyright (c) 2026-present Douglas Hoard