Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

artifactx-rs/artifactx

ArtifactX — publish apt/yum repos and Linux packages from one binary

ArtifactX logo

CI Release Latest release crates.io

Import first. Cut over when ready. Pull packages from the repos you already have, package new release artifacts when you need them, regenerate apt/yum metadata under your key, and serve the result from one static Rust binary.

ArtifactX (arx) is a small Rust tool for teams that ship Linux packages but do not want to operate Nexus, aptly, Pulp, S3 glue scripts, custom signing jobs, and a web server just to let users run apt install or dnf install.

Start with the documentation map if you want the import tutorial, Docker/systemd guides, signing notes, or CLI/config reference.

# Path 1: migrate a slice of an existing repo, then serve it
arx init ./repo
arx import https://packages.example.com --apt --dist stable --component main --match-name myapp
arx publish --root ./repo
arx serve --root ./repo
# Path 2: start a new repo from packages you already built
arx init ./repo
arx add dist --root ./repo
arx publish --root ./repo
arx serve --root ./repo
# Path 3: build Linux packages, then publish the .deb/.rpm outputs
cargo build --release
arx pack ./Cargo.toml --out dist
arx add dist --root ./repo
arx publish --root ./repo

What users get:

sudo apt-get update && sudo apt-get install myapp
# or
sudo dnf install myapp

How ArtifactX works

ArtifactX import, sign, publish, and static hosting architecture

Why ArtifactX

Most package repo setups become a pile of special cases: one path for .deb, another for .rpm, a signing key somewhere else, a CI upload script, a separate server, and no easy rollback.

ArtifactX keeps the package repository as a directory you can inspect, back up, move, and rebuild:

  • Import first — pull packages from an existing apt or yum/dnf repository into your own signed repo.
  • One binary — pack, add, import, publish, publish-dir, serve, push, promote, search, GC, rollback.
  • Native package output — build .deb, .rpm, .apk, and Arch .pkg.tar.zst from an ArtifactX manifest or Rust Cargo.toml.
  • Metadata-signed repos — apt InRelease / Release.gpg, yum repomd.xml.asc. ArtifactX does not re-sign individual packages.
  • Atomic publish — build metadata in staging, then flip the live state. publish-dir turns a package drop directory into add + publish + optional live cutover with fast no-op detection, first-class optional RPM payload signing, and optional external sync.
  • Incremental publish — unchanged apt/yum packages are reused from a file manifest; production dogfood reduced small-add publish from ~18s to ~1s after the one-time yum fragment backfill.
  • Rollbackable — keep published states and flip back when a bad release escapes.
  • CI-friendlyarx push uploads to arx serve; token or GitHub OIDC auth.
  • No daemon required — static binary, Docker image, or GitHub Pages-hosted repo. Public Pages repos should use a stable imported signing key.

The migration path: import, publish, serve

Use import when you already have packages somewhere and want a cleaner repo in front of them. Start with a bounded slice, verify clients, then cut over when the repo is boring.

arx import copies package payloads into the local pool. The new repository becomes client-consumable after arx publish, which regenerates and signs fresh apt/yum metadata for the ArtifactX repo. Existing upstream package files are not rewritten or re-signed; upstream repository metadata signatures cannot be reused because paths, checksums, expiry, and trust root now belong to the new repo.

Import from apt

arx init ./repo
arx import https://packages.example.com \
 --root ./repo \
 --apt \
 --dist stable \
 --component main \
 --arch amd64 \
 --match-name myapp \
 --limit 20
arx publish --root ./repo
arx serve --root ./repo

ArtifactX reads Packages.gz or Packages and downloads matching .deb files into the pool. arx publish then regenerates signed apt metadata under apt/dists/<dist>.

Import from yum/dnf

arx init ./repo
arx import https://packages.example.com/yum/x86_64 \
 --root ./repo \
 --yum \
 --component myrepo \
 --limit 20
arx publish --root ./repo
arx serve --root ./repo

ArtifactX reads repodata/repomd.xml, follows the primary metadata stream, and downloads .rpm files. arx publish then regenerates signed yum repodata. Invalid upstream entries with bad size/checksum are warned and skipped so one damaged historical package does not block the rest of a migration.

Signing keys: the simple model

ArtifactX signs repository metadata, not individual packages. apt clients verify InRelease / Release.gpg; dnf/yum clients verify repomd.xml.asc. The generated keys are OpenPGP v4 RSA keys so old and new distro tooling can verify them.

There are three supported paths:

1. Local/dev: let ArtifactX generate the key

arx init ./repo
arx publish --root ./repo

This creates keys/private.asc and keys/public.asc. If no passphrase is provided, the private key is stored unencrypted and ArtifactX warns you. That is acceptable for throwaway local repos, not for public or production repos.

2. Production: encrypt the repo signing key

printf '%s\n' 'use-a-real-secret-here' > passphrase.txt
arx init ./repo --passphrase-file passphrase.txt
arx publish --root ./repo --passphrase-file passphrase.txt

CI can use the environment variable instead of a file:

export ARX_KEY_PASSPHRASE='use-a-real-secret-here'
arx publish --root ./repo

3. Company key: import your existing armored private key

arx init ./repo --no-key
arx key import company-private.asc --root ./repo --passphrase-file passphrase.txt
arx key export --root ./repo > public.asc
arx publish --root ./repo --passphrase-file passphrase.txt

Clients trust the exported public key. If you rotate the key, clients must trust the new public key before the next cutover:

arx key rotate --root ./repo --passphrase-file passphrase.txt
arx key export --root ./repo > public.asc

What is intentionally not configurable yet: RSA bit size, key algorithm, and signature policy knobs. ArtifactX currently uses a conservative RSA/OpenPGP profile because compatibility with stock apt/dnf matters more than exposing crypto tuning in the happy path.

Expiry policy: what ArtifactX owns

ArtifactX has one built-in expiry default: new apt repositories get valid_days = 7 in arx.toml, so each arx publish writes a signed Valid-Until roughly seven days in the future. Republish refreshes the window.

[apt]
valid_days = 7

Set it to 0 only if you intentionally want to omit Valid-Until:

[apt]
valid_days = 0

Everything else is an operator policy, not something ArtifactX should guess:

  • yum/dnf metadata does not get an ArtifactX-specific expiry field.
  • ArtifactX-generated OpenPGP keys do not expire automatically.
  • Organizations with key expiry, HSM/KMS, audit, or rotation requirements should import an organization-managed OpenPGP key and run their own trust-rollout process.

That boundary is deliberate: ArtifactX keeps the default repo safe against stale apt metadata, but it does not pretend to be your key-governance system.

What import does — and does not do

ArtifactX import is a migration path, not a magic mirror.

  • It imports package files from existing apt Packages metadata or yum/dnf repodata.
  • It regenerates repository metadata during publish under your ArtifactX signing key, so clients trust your repo boundary.
  • It is intentionally sliceable with filters like --match-name, --arch, and --limit so the first migration is small and observable.
  • It does not reuse upstream repository signatures. InRelease, Release.gpg, and repomd.xml.asc must be created for the new repository metadata, ideally with your existing organization repo key.
  • It is not a bit-for-bit mirror of the upstream repository metadata. Use mirroring when you need continuous upstream sync.
  • It does not re-sign individual packages. Keep package signing in your build pipeline if your clients enforce package-level signatures.

Current focus: keep the shipped paths boring

ArtifactX now has both repository publishing and package building paths. The current work is adoption polish: keep import/add/pack -> publish -> serve/Pages -> client install -> rollback reliable, documented, and easy to verify before adding broader package ecosystems, storage backends, or UI surface.

Build your own packages too

ArtifactX is not only a repo importer. It can build packages from a standalone manifest or directly from a Rust Cargo.toml. Manifests can list individual [[files]] and recursively expanded [[dirs]] payloads:

arx pack ./Cargo.toml --out dist
arx add dist --root ./repo
arx publish --root ./repo

From zero to a signed repo:

arx init # create repo + signing key
arx pack ./Cargo.toml --out dist # build .deb .rpm .apk .pkg.tar.zst
arx add dist # add the built .deb/.rpm packages
arx publish # sign + index
arx serve # local server on 127.0.0.1:8080

arx pack emits all supported package artifacts by default. arx add currently indexes the apt/yum repository formats (.deb/.rpm) and leaves .apk / .pkg.tar.zst artifacts in the output directory for downstream handling.

See the pack reference for manifest fields, Cargo.toml mode, format-specific behavior, and current limits.

For repeated package drop directories, publish-dir wraps discovery, no-op detection, publish, optional live cutover, optional RPM payload signing, and optional downstream sync:

arx publish-dir ./dist --root ./repo \
 --apt-live ./public/deb \
 --yum-flat-live ./public/repo

That repository-ingestion workflow is separate from pack manifest [[dirs]], which installs a directory tree inside packages built by arx pack.

Install arx

Cargo

cargo install artifactx
arx --version

Download static binary

curl -fsSLO https://github.com/artifactx-rs/artifactx/releases/latest/download/arx-latest-amd64
sudo install -m 755 arx-latest-amd64 /usr/local/bin/arx
arx --version

Nix

nix run github:artifactx-rs/artifactx -- --version

Docker

docker run --rm -v $(pwd)/repo:/repo -p 8080:8080 \
 ghcr.io/artifactx-rs/arx:latest serve --root /repo --addr 0.0.0.0:8080

Docker Compose

Generate the files instead of hand-copying YAML:

arx compose --root ./repo --out ./deploy
cd deploy
docker compose up -d

The generated compose file deliberately binds the container to 0.0.0.0:8080 because Docker port publishing needs a non-localhost listener inside the container.

systemd service

arx serve defaults to 127.0.0.1:8080. That is intentional: put Caddy, nginx, or another TLS/reverse proxy in front when exposing a repo publicly.

# /etc/systemd/system/arx.service
[Unit]
Description=ArtifactX package repository
Documentation=https://github.com/artifactx-rs/artifactx
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=arx
Group=arx
EnvironmentFile=-/etc/arx/arx.env
ExecStart=/usr/local/bin/arx serve --root /var/lib/arx/repo
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/arx/repo
[Install]
WantedBy=multi-user.target
id -u arx >/dev/null 2>&1 || sudo useradd --system --home /var/lib/arx --shell /usr/sbin/nologin arx
sudo install -d -o arx -g arx /var/lib/arx/repo
sudo install -d -o root -g root -m 0750 /etc/arx
sudo systemctl daemon-reload
sudo systemctl enable --now arx
curl -fsS http://127.0.0.1:8080/api/v1/health
journalctl -u arx -f

Use /etc/arx/arx.env for secrets only when the server needs write APIs:

ARX_SERVE_TOKEN=change-me
ARX_KEY_PASSPHRASE=optional-if-your-repo-key-is-encrypted

If you need the same process to expose legacy public paths, mount exported live layouts explicitly. Writes and API state still use --root; /deb/* and /repo/* are read-only static mounts:

arx serve --root /data/arx/prod --apt-live /srv/deb --yum-flat-live /srv/repo

If you really want direct LAN exposure without a reverse proxy, be explicit:

arx serve --root /var/lib/arx/repo --addr 0.0.0.0:8080

Build from source

git clone https://github.com/artifactx-rs/artifactx.git && cd artifactx
cargo build --release
./target/release/arx --version

Commands

Command Why you use it
arx init Create repository layout, config, and signing key
arx import Migrate packages from an existing apt/yum repo into ArtifactX
arx pack Build .deb, .rpm, .apk, .pkg.tar.zst from a manifest or Cargo.toml
arx add Put existing .deb / .rpm files, or directories containing them, into the pool
arx publish Generate and sign apt + yum metadata; optionally export + cut over live public symlinks
arx serve Serve apt/dnf-compatible repo + REST API + /metrics
arx push Upload packages to a remote arx serve from CI
arx promote Move packages between staging/prod components or repos
arx rm Yank a package from the pool
arx gc Prune old versions with version-aware retention
arx rollback Restore a previous published state
arx history Inspect retained published states
arx publish-dir Ingest a drop directory, publish, cut over live symlinks, and optionally trigger external sync
arx watch Watch a directory and auto-add + publish new packages
arx key Generate, import, rotate, revoke, or export signing keys

Client setup

apt

sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL http://REPO_HOST:8080/keys/public.asc \
 | sudo tee /etc/apt/keyrings/arx.asc >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/arx.asc] http://REPO_HOST:8080/apt stable main" \
 | sudo tee /etc/apt/sources.list.d/arx.list
sudo apt-get update
sudo apt-get install myapp

dnf/yum

ArtifactX signs yum repository metadata (repomd.xml.asc). It does not re-sign individual .rpm packages; keep package signing in your build pipeline if you require gpgcheck=1.

# /etc/yum.repos.d/arx.repo
[arx]
name=ArtifactX
baseurl=http://REPO_HOST:8080/yum/myrepo/$basearch
enabled=1
gpgcheck=0
repo_gpgcheck=1
gpgkey=http://REPO_HOST:8080/keys/public.asc
sudo dnf install myapp

Repository layout

repo/
 arx.toml
 keys/
 private.asc
 public.asc
 apt/
 pool/<component>/
 dists/<dist>/
 yum/
 <repo>/<arch>/
 repodata/

Back it up with tar. Restore by extracting. Metadata is deterministic; if generated files are lost, run arx publish again.

The arx.toml defaults are intentionally boring and branded:

[apt.release]
origin = "ArtifactX"
label = "ArtifactX"
description = "Signed package repository managed by ArtifactX"
[signing]
enabled = true
keys_dir = "keys"
private_key = "keys/private.asc"
public_key = "keys/public.asc"
user_id = "ArtifactX Repository Signing <signing@artifactx.local>"
[server]
addr = "127.0.0.1:8080"

Change [apt.release] origin, label, description, and signing.user_id before generating or importing a production key if your apt repo should present your company identity instead of the ArtifactX default.

Verified

  • Workspace tests pass with cargo test --workspace.
  • cargo clippy --workspace --all-targets -- -D warnings is clean.
  • Real apt/dnf flows are covered by integration tests where host tools are available.
  • Package output is deterministic with SOURCE_DATE_EPOCH support.

Project links

License

  • crates/arx CLI: GPL-2.0-or-later
  • crates/arx-debrepo and crates/arx-pack: MIT OR Apache-2.0

About

Import or create signed apt/yum repositories, then serve them from one static binary.

Topics

Resources

License

GPL-2.0 and 2 other licenses found

Licenses found

GPL-2.0
LICENSE
Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

Contributors

AltStyle によって変換されたページ (->オリジナル) /