Skip to content

Terminal quickstart

Install the nxr binary and drive a Nexus raw repository from the shell, in about five minutes. nxr is curl for a raw repository: URL in argv, credentials from -u or the environment, no config file, nothing to install on the server.

The same operations as a Rust library: the Rust quickstart. From Node: the Node quickstart.

Install

Prebuilt archives ship on every GitHub release, one per platform: nxr-linux-x64, nxr-linux-aarch64, nxr-darwin-x64, nxr-darwin-arm64, nxr-windows-x64. The linux build is a musl static binary, so it runs on any glibc too. Every archive unpacks nxr at its root, with README.md and LICENSE next to it, and the release carries one SHA256SUMS for all of them:

curl -fsSLO https://github.com/Lipen/nexus-raw/releases/latest/download/nxr-linux-x64.tar.gz
curl -fsSLO https://github.com/Lipen/nexus-raw/releases/latest/download/SHA256SUMS
grep ' nxr-linux-x64.tar.gz$' SHA256SUMS | sha256sum -c -
sudo tar -xzf nxr-linux-x64.tar.gz -C /usr/local/bin ./nxr
nxr --version
nxr-linux-x64.tar.gz: OK
nxr <version>

The transcript hides the version the same way it hides the host: your binary prints its own.

On macOS the archive is nxr-darwin-x64.tar.gz, on Apple Silicon nxr-darwin-arm64.tar.gz, on Windows nxr-windows-x64.tar.gz with nxr.exe inside. If you already have a Rust toolchain, cargo install nexus-raw builds the same binary, and cargo binstall nexus-raw fetches the release archive through cargo.

Credentials

Three sources, in precedence order: -u user:pass per call, NXR_AUTH (base64 of user:pass), NXR_USERNAME + NXR_PASSWORD. doctor reports which source resolved, without printing the value, and probes the server:

export NXR_USERNAME=deployer
export NXR_PASSWORD="$SECRET_TOKEN"
nxr doctor https://nexus.example.com/repository/raw-main/1.4.0/
doctor:
  [  ok  ] credentials: resolved from NXR_USERNAME + NXR_PASSWORD
  [  ok  ] tls: verification is ON
  [  ok  ] settings: workers 8, retry 4, stall 30s, connect 15s
  [  ok  ] proxy: no proxy in the environment
  [  ok  ] probe: HEAD https://nexus.example.com/repository/raw-main/1.4.0/ → HTTP 404
  all checks passed

Keep -u for interactive one-offs: the value is visible in ps and shell history. The credential forms and their precedence: the CLI reference.

The one-liners

Point BASE at your repository and V at the version:

BASE=https://nexus.example.com/repository/raw-main
V=1.4.0

No server handy? From the repository checkout, cargo run -p mock-nexus -- atomic --port 8080 plays one; set BASE=http://127.0.0.1:8080/repository/raw-main. The transcripts below ran against that mock, with the host shown as nexus.example.com.

One object, up and down:

printf 'release notes\n' > notes.txt
nxr put "$BASE/scratch/notes.txt" -f notes.txt --sha
nxr get "$BASE/scratch/notes.txt" -o notes.back
$ nxr put "$BASE/scratch/notes.txt" -f notes.txt --sha
put: 14 bytes + marker 48b1a29e44eeff814abc6250e43395bf8ac81827f5791261378cb13b6699e37f → https://nexus.example.com/repository/raw-main/scratch/notes.txt
$ nxr get "$BASE/scratch/notes.txt" -o notes.back
get: 14 bytes → notes.back

A version directory, prepared once. Two artifacts and nothing reserved: the meaning of a name belongs to the publisher.

mkdir -p "dist/$V/bom"
printf 'payload\n' > "dist/$V/app.zip"
printf '{"dep":"x"}\n' > "dist/$V/bom/sbom.json"

Keep the name list next to the artifacts, then compare, publish and pull it back on another machine:

printf 'app.zip\nbom/sbom.json\n' > "dist/$V/list.txt"
nxr diff "dist/$V/" "$BASE/$V/" --name app.zip --name bom/sbom.json   # both sides, nothing moves
nxr up "dist/$V/" "$BASE/$V/" --plan                                 # the upload plan only
nxr up "dist/$V/" "$BASE/$V/"                                        # the transfer
nxr down "$BASE/$V/" "vendor/$V/" --names-from "dist/$V/list.txt"    # the same names, verified, resumable
nxr verify "vendor/$V/"                                              # digests again, offline
$ nxr diff "dist/$V/" "$BASE/$V/" --names-from "dist/$V/list.txt"
missing-remote app.zip
missing-remote bom/sbom.json
$ echo $?
1
$ nxr up "dist/$V/" "$BASE/$V/" --plan
upload app.zip
upload bom/sbom.json
$ nxr up "dist/$V/" "$BASE/$V/"
plan: 2 to upload, 0 to download, 0 up to date
↑ app.zip ok
↑ bom/sbom.json ok
uploaded 2, downloaded 0, skipped 0
$ nxr down "$BASE/$V/" "vendor/$V/" --names-from "dist/$V/list.txt"
plan: 0 to upload, 2 to download, 0 up to date
↓ app.zip ok
↓ bom/sbom.json ok
uploaded 0, downloaded 2, skipped 0
$ nxr verify "vendor/$V/"
verify: 2 ok, FAILED: none
uploaded 0, downloaded 0, skipped 2

The diff verdict rides the exit code: 0 when every name is same, 1 when the report holds a difference. On an empty server the report is missing-remote across the board, and the up after it converges the two sides.

An interrupted up or down finishes by repeating the same command: finished names are skipped, part files resume through Range requests.

Name the version, read the name back:

nxr channel set "$BASE/latest" "$V" --if-forward
nxr channel get "$BASE/latest"
$ nxr channel set "$BASE/latest" "$V" --if-forward
channel: set https://nexus.example.com/repository/raw-main/latest → 1.4.0
$ nxr channel get "$BASE/latest"
1.4.0

--if-forward compares tokens in dotted-numeric order, so an older pipeline cannot roll latest back.

In a pipeline

The machine-readable stream and the exit codes are the API:

nxr up "dist/$V/" "$BASE/$V/" --json \
  | jq -e 'select(.event=="summary") | .failed == []'

The exit code is the verdict: 0 continue, 1 data, 2 misuse, 3 transport. The full table: errors and exit codes.

Where to go next