A CyberPC Angel, LLC project.
Cross-platform, language-agnostic package manager and binary archive for the scientific computing community.
cvcpkg resolves a set of component requirements against a package
catalog, downloads the matching prebuilt bundles, verifies their integrity,
and materializes a single CMAKE_PREFIX_PATH-compatible install prefix —
or builds any component from source via versioned recipes when no prebuilt
bundle fits. It also runs as a self-hostable archive server with its own
build orchestration, multi-platform builders, and publishing pipeline.
Downstream projects should adopt cvcpkg instead of manually managing
dependency archives by hand. Per-component bundles are smaller,
cacheable, and version-locked — you only pull what you need.
# Install from PyPI (once published):
pipx install cvcpkg
# Or install from source:
pip install -e '.[progress]'
# List available components:
cvcpkg list --available
# Install specific components into a prefix:
cvcpkg install --prefix ./deps boost hdf5 fftw3
# Install from a requirements file:
cvcpkg install --from cvc-requirements.yaml --prefix ./deps
# Verify an existing prefix:
cvcpkg verify --prefix ./depsThis repository's recipes/ set is the shared dependency ecosystem
(Boost, Qt6, VTK, CGAL, the CUDA-math libs, the Python interpreters, …).
A project owns the recipe for its own package, in its own repo — libcvc,
volrover, and grl-snam each keep their recipe under cvcpkg/recipes/ in
their own repository and publish under the cvc org. Don't add project
packages here. See docs/recipe-authoring.md.
cvcpkg supports two primary modes: server mode (default) and local mode. The mode determines where recipes and prebuilt packages come from.
By default, cvcpkg connects to a package server to fetch prebuilt
binaries and the latest recipe definitions. The server is specified
by the CVCPKG_SERVER_URL environment variable or --server flag.
When neither is set, the official server at https://cvcpkg.org is
used.
# Install prebuilt binaries from the official server:
cvcpkg install zlib boost --prefix ./deps
# Build from recipes pulled from the server:
cvcpkg build zlib --prefix ./prefix
# Build all recipes (fetches latest from server):
cvcpkg build-all --prefix ./prefix
# Use a custom server:
export CVCPKG_SERVER_URL=https://pkg.mycompany.com
cvcpkg install --from cvc-requirements.yaml --prefix ./depsPass --local (or set CVCPKG_LOCAL=1) to skip all server
communication and use only bundled/local recipes. This is useful for
air-gapped environments, offline development, or when you want to
build against a specific set of recipes without pulling updates.
# Build a recipe from local/bundled recipes:
cvcpkg build zlib --local --prefix ./prefix
# Build all recipes from local sources:
cvcpkg build-all --local --prefix ./prefix
# Install from source using local recipes (no catalog):
cvcpkg install --local zlib boost --prefix ./deps
# Combine with --recipes-dir to overlay custom recipes:
cvcpkg build zlib --local --recipes-dir ./my-recipes --prefix ./prefixWhen --local is used with cvcpkg install, it implies
--fallback-to-source — all components are built from source recipes
rather than downloaded as prebuilt binaries.
You can also overlay additional recipe directories with --recipes-dir
(may be specified multiple times; later directories win on name
conflicts).
Status: planned (roadmap Phase 23). This section describes a direction, not shipped behavior. It is documented here because it shapes the recipe format; the checkboxes live in
CVCPKG-ROADMAP.md.
cvcpkg is not only for publishing packages. A recipe set can be used as a general build system and configuration-management tool — closer in spirit to SaltStack/Ansible than to a plain package manager, but built into one holistic, cross-platform, content-addressed system rather than bolted on beside it:
- Installing a recipe applies state; uninstalling tears it down.
- A machine's configuration is a dependency graph of recipes — installing one triggers its dependent recipes and their state changes, in dependency order.
- Recipes can initialize proprietary or licensed software and then layer legal, first-party modifications on top, with the license and redistributability of every input declared explicitly in the recipe.
- Bring-your-own (BYO) recipes reference assets cvcpkg cannot legally
redistribute (a licensed installer, retail game data, a client's
proprietary blob): the user supplies the file, cvcpkg verifies it by
a pre-published
sha256, and never fetches or re-hosts it.
The model is deliberately declarative-with-an-escape-hatch: typed
state: resources (file, template, service, env, registry-key,
…) follow a Get/Test/Set contract so they are idempotent and
auto-reversible, while an explicit script: + teardown: pair handles
anything the built-ins do not cover. Three modes, no resident agent:
cvcpkg check (audit/report-only), cvcpkg apply, and cvcpkg uninstall;
a scheduler (cron/CI) owns any enforcement loop.
Honest limits (documented on purpose, so nothing over-promises):
- Teardown is authoritative inside the prefix, best-effort outside it.
Files cvcpkg tracks, it removes cleanly; state a recipe reaches out to
mutate (system services, the registry,
/etc) is reverted only by a declared inverse — ateardown:slot, or the captured prior value of a typed resource. Recipes with an untracked imperative effect are labeled non-revertible in status output. - Apply is on-demand and non-atomic — it corrects drift when you run it (Ansible-shaped), not continuously (Puppet-shaped). A half-failed apply leaves a half-configured host; there is no automatic rollback of arbitrary scripts.
- Idempotency is a per-recipe contract, enforced in CI (apply twice → the second run is a no-op), not a magic property of the engine.
Every state operation is recorded in a per-machine, hash-chained,
append-only transaction journal (who, from where, what changed, with
per-file before/after hashes) that cross-anchors to the server audit log —
a tamper-evident paper trail for forensics that mainstream configuration
tools do not provide. See CVCPKG-ROADMAP.md Phase 23 for the full design,
security model, and worked recipe examples.
cvcpkg install writes shell activation scripts into the prefix in
the style of Python's venv. Sourcing one of them prepends the
prefix to PATH, CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and the
platform's dynamic-linker variable (LD_LIBRARY_PATH on
Linux/BSD/WASI, DYLD_LIBRARY_PATH on macOS). A matching
cvcpkg_deactivate function restores the previous environment.
| Shell | Command |
|---|---|
| bash / zsh / dash / sh | source ./deps/bin/activate |
| fish | source ./deps/bin/activate.fish |
| csh / tcsh | source ./deps/bin/activate.csh |
| PowerShell (any OS) | . ./deps/Scripts/Activate.ps1 |
| cmd.exe (Windows) | .\deps\Scripts\activate.bat |
Deactivate:
cvcpkg_deactivate # bash / zsh / fish / csh / PowerShell
.\deps\Scripts\cvcpkg_deactivate.bat # cmd.exeEnvironment variables exported after activation:
CVCPKG_ACTIVE_PREFIX— the absolute prefix path (also used to detect a stale activation on re-source).PATH— prepended with<prefix>/bin(POSIX) or<prefix>/Scripts+<prefix>/bin(Windows).CMAKE_PREFIX_PATH— prepended with<prefix>.PKG_CONFIG_PATH— prepended with<prefix>/{lib,lib64,share}/pkgconfigfor whichever of those directories exist.LD_LIBRARY_PATH/DYLD_LIBRARY_PATH— prepended with<prefix>/liband<prefix>/lib64if present.
Set CVCPKG_ACTIVATE_NO_PROMPT=1 before sourcing to skip the
(<prefix-name>) shell-prompt annotation.
The scripts are self-contained and safe to copy along with the prefix;
they do not require cvcpkg at activation time.
A cvcpkg prefix can carry several CPython interpreters side by side —
python311, python312, python313, and the free-threaded python313t —
each installed as its own <prefix>/bin/pythonX.Y with its own
<prefix>/lib/pythonX.Y/site-packages. There is no global "the" Python; you
pick one.
Selecting an interpreter — just run the version you want:
source ./deps/bin/activate # puts <prefix>/bin on PATH
python3.12 -c "import numpy; print(numpy.__version__)"
python3.11 my_script.py
python3 # bare python3 / python -> the prefix's DEFAULT interpreter (a symlink)python3 / pip3 come from the python3 meta package and python / pip
from the python meta package — both resolve to the prefix's DEFAULT
interpreter (currently python313). Install python when you want the
conventional commands; a prefix that only installed python312 exposes
python3.12 and nothing else, exactly as its dependency graph says. (An
embedding host like volrover3 does not choose at runtime — it links
libpython3.11 at build time, so its embedded interpreter is fixed to that
version, and its recipe pins the matching columns.)
Every Python package is a per-interpreter column recipe —
<name>-cp311, -cp312, -cp313, -cp313t — one per interpreter cvcpkg
ships. A column depends on its interpreter (and on its deps' matching
columns) and installs only into that interpreter's own
lib/pythonX.Y[t]/site-packages. The dependency graph is the whole story:
installing fastapi-cp313 gives python3.13 a working fastapi and touches
nothing else; numpy-cp313t serves the free-threaded build and its import
check runs with the GIL genuinely disabled. How the wheel behind a column
is sourced varies, but the naming and import rules do not:
| Wheel kind | Columns that exist | Notes |
|---|---|---|
pure-Python (py3-none-any) — e.g. click, jinja2, sympy |
all four | same wheel in every column |
stable-ABI (abi3) — e.g. cryptography, bcrypt |
cp311/312/313 (+ cp313t only if an exact free-threaded wheel exists) |
the free-threaded build has no stable ABI |
per-version wheel — e.g. pydantic-core, markupsafe, cffi |
wherever upstream ships a wheel | markupsafe-cp313t exists; pydantic-core-cp313t does not (no wheel) |
built from source — e.g. numpy-cp311, h5py-cp311, vtk-python-cp31x, pyside6-cp311 |
the columns we have built | extend by adding a column recipe |
A column exists only if its whole dependency closure exists for that
interpreter: pydantic-core ships no cp313t wheel, so there is no
pydantic-cp313t and no fastapi-cp313t — the catalog never promises an
import that cannot work. Adding a future python314 is a new column, not a
rebuild of the existing ones.
import numpy from python3.12 therefore means: the prefix's closure must
include numpy-cp312 (install it, or depend on it). Requirements files and
recipes always name the -cpNNN column matching their interpreter.
Packages whose wheels install console scripts (pytest, black,
uvicorn, ...) declare provides: [<base>]: their columns clobber the same
bin/ entry points, so the slot makes them mutually exclusive per prefix —
and lets cvcpkg install pytest resolve a column by its bare name. Library
columns coexist freely (their payloads live in disjoint site-packages).
Recipes define how to build each component from source. They live in
recipes/ directories and contain a recipe.yaml, platform-specific
build scripts, and optional patches.
# List bundled/local recipes:
cvcpkg recipes
# List recipes on the server:
cvcpkg recipe list
# Show details of a specific recipe:
cvcpkg recipes --show grpc
# Filter by tag:
cvcpkg recipes --tag math# Download a single recipe:
cvcpkg recipe pull zlib --output-dir ./recipes
# Download the full base recipe set:
cvcpkg recipe pull-all --output-dir ./recipes
# Download an organization's recipe set:
cvcpkg recipe pull-all --org my-org --output-dir ./org-recipes# Push a single recipe (admin):
cvcpkg recipe push zlib
# Push and register as a placeholder package:
cvcpkg recipe publish zlib
# Push all recipes at once:
cvcpkg recipe push-all --recipes-dir ./recipesrecipe publish is a convenience command that pushes the recipe
bundle and registers a placeholder entry in the catalog. The
placeholder tells consumers "this recipe exists" before any binary
has been built. Remote builders or local users can then produce the
actual binaries.
cvcpkg supports a remote build system where dedicated builder agents poll the server for build jobs, execute them, and publish the results. This replaces long-running CI workflows (some builds exceed 6 hours) with persistent, uncapped build agents.
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN=cvctok_...
# Start a builder agent (platform and arch are auto-detected):
cvcpkg builder run \
--name linux-x64-builder-1 \
--max-jobs 4 \
--work-dir /mnt/scratch/builder
# Start with wasm cross-compilation support:
cvcpkg builder run \
--name linux-x64-builder-1 \
--max-jobs 4 \
--work-dir /mnt/scratch/builder \
--cross-platform wasm
# Specify a non-default cross-arch:
cvcpkg builder run \
--name linux-riscv-builder \
--max-jobs 2 \
--work-dir /mnt/scratch/builder \
--cross-platform linux --cross-arch riscv64
# Start multiple builders for parallel builds:
cvcpkg builder run --name builder-2 &
cvcpkg builder run --name builder-3 &Builders that pass --cross-platform wasm register the target in
their capabilities with a default arch of wasm32. The scheduler
dispatches jobs to any builder whose cross_platforms list includes
a matching platform/arch pair, even though the builder's native
platform is linux or windows. The builder automatically passes
--host-platform to the build so that the correct cross-compilation
scripts (e.g. build-wasm.sh) and toolchains (emsdk) are selected.
--cross-arch is paired positionally with --cross-platform. If
omitted, sane defaults are applied:
--cross-platform |
Default --cross-arch |
|---|---|
wasm |
wasm32 |
wasi |
wasm32 |
| (other) | host architecture |
Builders register with the server and receive jobs via WebSocket (with HTTP long-poll fallback). Each job downloads the recipe from the server, builds it, packages the result, and publishes the archive.
# Submit a single build job:
cvcpkg builds submit --recipe zlib --platform linux --arch x86_64
# Submit a dependency graph (DAG) of build jobs:
cvcpkg builds submit-dag \
--recipe zlib --recipe zstd --recipe hdf5 \
--platform linux --arch x86_64
# Submit wasm builds (dispatched to builders with --cross-platform wasm):
cvcpkg builds submit-dag \
--recipe zlib --recipe zstd \
--platform wasm --arch wasm32
# Wait for builds to finish (exits non-zero on failure):
cvcpkg builds submit-dag --wait \
--recipe zlib --recipe boost \
--platform linux --arch x86_64
# Monitor build progress:
cvcpkg builds list --status running
cvcpkg builds monitor # top-like live dashboardRemote builders capture full build output (cmake, make, gcc, etc.) and stream it to the server in real time. You can tail any job's log or follow an entire DAG:
# View the full log for a completed job:
cvcpkg builds log <job-id>
# Follow a single job's output in real time (SSE stream):
cvcpkg builds log <job-id> --follow
# Follow ALL jobs in a DAG — multiplexed output with [builder/recipe/platform/arch] prefixes:
cvcpkg builds follow-dag <dag-id>builds log <id> -f |
builds follow-dag <dag-id> |
|
|---|---|---|
| Scope | Single job (you supply the job ID) | All jobs in a DAG (discovered automatically) |
| Output | Raw build output, no prefix | Lines prefixed with [builder/recipe/platform/arch] |
| Job discovery | None — you must know the ID | Polls for new jobs as dependencies finish and they get dispatched |
| Concurrency | One stream | One thread per active job, interleaved |
| Exit code | 0 when stream ends | 0 if all succeed, 1 if any fail |
| Best for | Debugging a single build | CI pipelines, bulk build monitoring |
follow-dag is designed for CI pipelines where you need live output
from all builders at once. It spawns a thread per active job, prints
prefixed lines as they arrive, and exits with code 0 if all jobs
succeed or code 1 if any fail.
The populate-server.yml GitHub Actions workflow uses this pattern:
- name: Submit build DAGs
id: submit
run: |
DAG_ID="populate-$(date +%Y%m%d-%H%M%S)"
echo "dag_id=$DAG_ID" >> "$GITHUB_OUTPUT"
cvcpkg builds submit-dag --dag-id "$DAG_ID" \
--platform linux,freebsd --arch x86_64 \
zlib boost hdf5
- name: Follow build output
run: cvcpkg builds follow-dag "${{ steps.submit.outputs.dag_id }}"# List registered builders:
cvcpkg builder list
# Check a specific builder:
cvcpkg builder status --name linux-x64-builder-1
# Unregister a builder:
cvcpkg builder unregister <builder-id>The official cvcpkg server at https://cvcpkg.org hosts prebuilt
binaries for all supported platforms and the canonical recipe set.
As a consumer — install prebuilt packages:
# No configuration needed — cvcpkg.org is the default:
cvcpkg install --from cvc-requirements.yaml --prefix ./depsAs a contributor — register and publish:
# Register for an API token:
cvcpkg register --server https://cvcpkg.org \
--name alice --email alice@example.org --role publisher
# Set credentials:
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN=cvctok_...
# Publish recipes and packages:
cvcpkg recipe publish my-library
cvcpkg publish my-library --output-dir ./distYou can run your own cvcpkg server for private packages, custom recipes, or air-gapped environments.
# Install with server + database extras:
pip install 'cvcpkg[server,db]'
# Start with PostgreSQL:
export CVCPKG_DATABASE_URL="postgresql+asyncpg://user:pass@localhost/cvcpkg"
cvcpkg-server run \
--state-dir /var/lib/cvcpkg \
--host 0.0.0.0 --port 8080
# Bootstrap the first admin token:
cvcpkg-server bootstrap --name admin --email admin@example.orgexport CVCPKG_SERVER_URL=https://my-server.example.com
export CVCPKG_TOKEN=cvctok_<admin-token>
# Push all base recipes to your server:
cvcpkg recipe push-all --recipes-dir ./recipes
# Or push individual recipes:
cvcpkg recipe push zlib
cvcpkg recipe push boostRun builder agents on each target platform:
# On a Linux x86_64 build host (platform auto-detected):
cvcpkg builder run \
--server https://my-server.example.com \
--token cvctok_... \
--name linux-builder \
--max-jobs 4 --work-dir /scratch/builder
# On a macOS arm64 build host (platform auto-detected):
cvcpkg builder run \
--server https://my-server.example.com \
--token cvctok_... \
--name macos-builder \
--max-jobs 2 --work-dir ~/builder-work# Submit DAG builds for all recipes:
cvcpkg builds submit-dag \
--recipe zlib --recipe boost --recipe hdf5 ... \
--platform linux --arch x86_64
# Or build locally and publish:
cvcpkg pack-all --local --output-dir ./dist
cvcpkg publish --all --output-dir ./distPoint downstream consumers at your server:
export CVCPKG_SERVER_URL=https://my-server.example.com
cvcpkg install --from cvc-requirements.yaml --prefix ./depsOr configure it in ~/.config/cvcpkg/config.yaml:
catalog:
primary: https://my-server.example.com/v1/catalogA complete guide to bootstrapping a cvcpkg server, creating API keys, registering builders, and kicking off your first remote builds.
Using Docker Compose (recommended for production):
# from the repo root
cp .env.production.example .env.production
# Edit .env.production — set POSTGRES_PASSWORD and BACKEND_BIND_ADDR
docker compose -f docker-compose.production.yml \
--env-file .env.production up -dOr run directly for development:
pip install 'cvcpkg[server,db]'
export CVCPKG_DATABASE_URL="postgresql+asyncpg://user:pass@localhost/cvcpkg"
cvcpkg-server run --state-dir /var/lib/cvcpkg --host 127.0.0.1 --port 8420On a fresh server there are no tokens. Use the server CLI (or
docker compose exec) to create them directly against the database:
# Create an admin token (full access: manage tokens, delete packages, etc.)
docker compose -f docker-compose.production.yml \
--env-file .env.production exec -T backend \
cvcpkg-server token create --name my_admin --role admin --email you@example.org
# Create a publisher token (for builders to push packages)
docker compose -f docker-compose.production.yml \
--env-file .env.production exec -T backend \
cvcpkg-server token create --name builder_publisher --role publisher
# Without Docker — if running cvcpkg-server directly:
cvcpkg-server token create --name my_admin --role admin --email you@example.org
cvcpkg-server token create --name builder_publisher --role publisherSave the cvctok_... values that are printed — they are shown only
once and cannot be recovered.
Available roles:
| Role | Permissions |
|---|---|
admin |
Full access: create/revoke tokens, delete packages, manage orgs |
publisher |
Publish packages, push recipes, yank/unyank |
reader |
Browse catalog, download packages |
After you have an admin token, you can also create tokens via the API:
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN=cvctok_<admin-token>
cvcpkg token create --name ci_reader --role reader
cvcpkg token create --name another_publisher --role publisher --expires-in-days 90
cvcpkg token list
cvcpkg token revoke --name old_token
# Rotate a secret in place (name/role/org memberships survive); the old
# secret keeps working for an hour so CI secrets can be swapped calmly:
cvcpkg token rotate --name another_publisher --grace-minutes 60export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN=cvctok_<admin-token>
# Push all recipes at once:
cvcpkg recipe push-all --recipes-dir ./recipes
# Or push individual recipes:
cvcpkg recipe push zlib
cvcpkg recipe push boostOn each build machine, start a builder agent with the publisher token:
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN=cvctok_<publisher-token>
# Linux x86_64 builder (platform auto-detected):
cvcpkg builder run \
--name linux-builder-01 \
--max-jobs 4 \
--work-dir /scratch/builder
# macOS arm64 builder (platform auto-detected):
cvcpkg builder run \
--name macos-builder-01 \
--max-jobs 2 \
--work-dir ~/builder-workBuilders connect via WebSocket (with HTTP long-poll fallback), register their platform capabilities, and wait for jobs.
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN=cvctok_<admin-token>
# Submit a single recipe:
cvcpkg builds submit --recipe zlib --platform linux --arch x86_64
# Submit a DAG of recipes (respects dependency order):
cvcpkg builds submit-dag \
--recipe zlib --recipe boost --recipe hdf5 \
--platform linux --arch x86_64
# Monitor progress (top-like dashboard):
cvcpkg builds monitor
# Follow a single job's build output in real time:
cvcpkg builds log <job-id> -f
# Follow all jobs in a DAG (great for CI):
cvcpkg builds follow-dag <dag-id>
# Pause/resume builds (e.g. to free builder capacity):
cvcpkg builds pause <job-id>
cvcpkg builds resume <job-id>
cvcpkg builds pause-dag <dag-id>
cvcpkg builds resume-dag <dag-id>
# Cancel builds:
cvcpkg builds cancel <job-id>
cvcpkg builds cancel-dag <dag-id># Check builders are connected:
cvcpkg builder list
# Check packages were published:
cvcpkg search --server https://cvcpkg.org
# Install a built package:
cvcpkg install zlib --prefix ./depsGitHub Actions has a 6-hour job time limit that is insufficient for large dependency builds (e.g. Qt6, VTK, LLVM). cvcpkg's remote builder system eliminates this constraint:
-
Set up a server (see Self-hosted server) or use
https://cvcpkg.org. -
Deploy builder agents on persistent build machines (bare metal, VMs, or containers without time limits).
-
Push your recipes to the server:
cvcpkg recipe push-all --recipes-dir ./recipes
-
Submit builds via the API or CLI:
cvcpkg builds submit-dag --recipe zlib --recipe boost \ --platform linux --arch x86_64 -
Simplify CI to just install prebuilt packages:
# .github/workflows/build.yml - name: Install dependencies run: | pip install cvcpkg cvcpkg install --from cvc-requirements.yaml --prefix ./deps - name: Build project run: cmake -S . -B build -DCMAKE_PREFIX_PATH=$PWD/deps && cmake --build build
Builders run on your own infrastructure with no time caps, and CI jobs become fast install-only workflows (typically under 2 minutes).
Place this file in your project root (e.g. alongside CMakeLists.txt):
# cvc-requirements.yaml — declare which libcvc-deps components you need.
#
# cvcpkg resolves these against the published catalog and installs
# exactly the matching per-component bundles for your platform.
platform: auto # auto-detect, or: linux | macos | windows
arch: auto # auto-detect, or: x86_64 | arm64
config: release # release | debug
link: shared # shared | static
# Pin the libcvc-deps release to consume bundles from:
libcvc-deps: ">=1.2.0"
# Components your project needs — only these are downloaded:
components:
- boost
- hdf5
- fftw3
- tiff
- vtk
- qt6# Resolve, download, verify, and install into ./deps:
cvcpkg install --from cvc-requirements.yaml --prefix ./deps
# Or specify overrides on the command line:
cvcpkg install --from cvc-requirements.yaml --prefix ./deps \
--config debug --link staticcmake -S . -B build -DCMAKE_PREFIX_PATH="$(pwd)/deps"All find_package() calls (Boost, HDF5, FFTW3, VTK, Qt6, etc.) will
resolve from the cvcpkg-managed prefix.
After a successful install, cvcpkg writes a lockfile:
# Commit this for reproducible CI builds:
git add cvcpkg.lock.yamlRe-running cvcpkg install with a lockfile present replays the exact
same downloads (same SHA-256 digests), regardless of catalog updates.
{
"configurePresets": [{
"name": "default",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "${sourceDir}/deps"
}
}]
}- name: Install libcvc-deps
run: |
pip install cvcpkg
cvcpkg install --from cvc-requirements.yaml --prefix ./deps
- name: Configure
run: cmake -S . -B build -DCMAKE_PREFIX_PATH=${{ github.workspace }}/depsIf you maintain a library that other CVC projects depend on, you can
publish it to the cvcpkg server so consumers can pull it with
cvcpkg install.
Create recipes/<your-package>/recipe.yaml:
# recipe.yaml
schema_version: 1
recipe:
name: my-library
upstream_version: "2.1.0"
cvc_revision: 1
description: "My library for CVC downstream consumers"
source:
type: tarball
url: "https://github.com/org/my-library/archive/refs/tags/v2.1.0.tar.gz"
sha256: "<sha256-of-tarball>"
depends:
build:
- name: boost
version: ">=1.83"
- name: hdf5
version: ">=1.10"
runtime:
- name: boost
version: ">=1.83"
- name: hdf5
version: ">=1.10"
build:
matrix:
- platform: linux
script: build.sh
- platform: macos
script: build.sh
- platform: windows
script: build.ps1
package:
files:
- "lib/**"
- "include/**"
- "share/**/cmake/**"
- "bin/**"
cmake_packages:
- name: MyLibrary
targets: ["MyLibrary::MyLibrary"]Each matrix entry's script names a build file next to recipe.yaml
(not inline shell). The script receives the staged source, the install
prefix, and the resolved dependency prefix via environment variables:
#!/bin/bash
# build.sh
set -e
cmake -S "$CVC_SOURCE_DIR" -B build \
-DCMAKE_BUILD_TYPE="$CVC_BUILD_TYPE" \
-DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBS" \
-DCMAKE_INSTALL_PREFIX="$CVC_INSTALL_DIR" \
-DCMAKE_PREFIX_PATH="$CVC_DEPS_PREFIX"
cmake --build build --parallel
cmake --install build# Build using the recipe (fetches source, runs cmake, stages output):
cvcpkg build my-library --prefix ./stage \
--config release --link shared
# Pack into a distributable archive:
cvcpkg pack my-library --prefix ./stage \
--config release --link shared# Publish to a cvcpkg-server (REST API):
export CVCPKG_TOKEN="cvctok_..."
export CVCPKG_SERVER_URL="https://cvcpkg.org"
cvcpkg publish my-library --output-dir ./dist
cvcpkg publish --all --output-dir ./dist
# Or publish to a storage backend (S3, SFTP, local dir):
cvcpkg publish --all --dest s3://my-bucket/cvcpkg/
cvcpkg publish --all --dest file:///shared/cvcpkg-repo/# Install with server extras:
pip install cvcpkg[server]
# Start the server:
cvcpkg-server run --state-dir /var/lib/cvcpkg --host 0.0.0.0 --port 8080
# Bootstrap the first admin token on a fresh server:
cvcpkg-server bootstrap --name admin --email admin@example.org
# After that, manage tokens via the client CLI (through the API):
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN="cvctok_<admin-token>"
cvcpkg token create --name ci-publisher --role publisher
cvcpkg token create --name dev-reader --role reader
# View audit log:
cvcpkg-server audit log --last 20
cvcpkg-server audit verifySome packages are not compiled — they contain platform-independent
content such as HTML/CSS assets, ISO images, media files, data bundles,
or configuration archives. cvcpkg supports a special any platform
for these recipes.
Set platform: any in every build.matrix entry. The builder
automatically assigns arch: noarch and skips the CMake configure
marker check:
# recipe.yaml
schema_version: 1
recipe:
name: my-data-bundle
upstream_version: "1.0.0"
cvc_revision: 1
description: "Platform-independent data files"
kind: data # optional — hints: data | media | config | iso
source:
type: tarball
url: "https://example.com/data-v1.0.0.tar.gz"
sha256: "<sha256>"
build:
matrix:
- platform: any
script: build.sh
package:
files:
- "share/**"script names a file next to recipe.yaml (not inline shell). The
build script receives the staged source and install prefix via
environment variables:
#!/bin/bash
# build.sh
set -e
mkdir -p "$CVC_INSTALL_DIR/share/my-data-bundle"
cp -r "$CVC_SOURCE_DIR"/* "$CVC_INSTALL_DIR/share/my-data-bundle/"| Aspect | Behaviour |
|---|---|
| Architecture | Automatically set to noarch — no user override needed |
| Build | Included in every platform's build-all run so it is always available |
| Cache key | Uses any/noarch — the same artifact is shared across all platforms |
| Dependencies | Other recipes can depend on any packages; they are included regardless of the consuming platform |
| Builder | Maps platform: any to ARCH=noarch and skips the cmake marker |
Recipe kind |
Optional recipe.kind field (e.g. data, media, config, iso) is emitted as meta.kind in the manifest for downstream tooling hints |
Consumers do not need to do anything special — any packages are
resolved automatically when listed as dependencies. If you want to
pull an any package directly:
platform: auto
components:
- my-data-bundle # resolved regardless of host platformany recipes support the same tags list as compiled recipes. Tags
are emitted as meta.tags in the manifest (comma-joined) and displayed
on the package server front page.
cvcpkg-server uses a token-based RBAC (role-based access control) system. Every mutating API call requires a bearer token; read-only endpoints are unauthenticated by default but can be locked down.
When setting up a new server for the first time, use the bootstrap
command to create the initial admin token:
cvcpkg-server bootstrap --name admin --email admin@example.orgThis only works when no admin tokens exist yet. The generated token is printed exactly once — store it in a password manager or secrets vault immediately. Then configure the client:
cvcpkg config set server https://cvcpkg.org
cvcpkg config set token cvctok_<your-admin-token>Users can register for an API token without contacting an admin. The server supports two registration modes, configured when starting the server:
# Default: anyone can register and immediately gets a token
cvcpkg-server run --registration-mode open ...
# Admin-gated: registration requests go to a queue for admin approval
cvcpkg-server run --registration-mode admin-gated ...The CVCPKG_REGISTRATION_MODE environment variable is also supported.
Open mode (default):
cvcpkg register --server https://cvcpkg.org \
--name alice --email alice@example.org --role reader
# Token is returned immediatelyAdmin-gated mode:
# User submits a request:
cvcpkg register --server https://cvcpkg.org \
--name bob --email bob@example.org --role publisher
# → "Registration request submitted. An admin will review it."
# Admin reviews pending requests:
cvcpkg token requests --status pending
# Approve a request (creates the token):
cvcpkg token approve 42
# → prints the token — send it to the requester
# Or deny it:
cvcpkg token deny 43Tokens are issued by an admin (or via self-service registration) and shown exactly once at creation time. Only an HMAC-SHA256 hash of the token is persisted on the server — the raw secret is never stored.
# Create a publisher token via the client CLI (talks to the server API):
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN="cvctok_<admin-token>"
cvcpkg token create --name ci-publisher --role publisher
# Create a reader token with 90-day expiry:
cvcpkg token create --name dev-reader --role reader \
--expires-in-days 90The raw token looks like cvctok_<base64url>. Store it securely
(e.g. in a CI secret) and pass it via the CVCPKG_TOKEN environment
variable or Authorization: Bearer <token> header.
| Role | Permissions |
|---|---|
reader |
Query catalog, list packages, download archives |
publisher |
All reader permissions plus publish packages, yank versions |
admin |
All permissions: publish, yank, unyank, delete, manage tokens, view audit log |
Use the cvcpkg token commands to manage tokens remotely via the
server's REST API. This is the recommended approach — it goes
through the same code path as normal requests, records audit entries,
and avoids race conditions with the running server.
# Set the server and admin token (or pass --server/--token each time):
export CVCPKG_SERVER_URL=https://cvcpkg.org
export CVCPKG_TOKEN="cvctok_<admin-token>"
# Create a token:
cvcpkg token create --name ci-publisher --role publisher
# List all tokens:
cvcpkg token list
# Revoke a token immediately:
cvcpkg token revoke --name ci-publisherRevoked tokens are rejected on the next API call — no restart needed.
Note:
cvcpkg-server token create/list/revokecommands exist for direct DB access when no server is running.cvcpkg-server bootstrapis the recommended way to create the first admin token. For all subsequent token management, use the client commands (cvcpkg token ...) which go through the HTTP API.
Organizations have their own membership model. An org owner can add or remove members to control who can publish to the org's namespace — without affecting the member's global token or access to anything else.
# List members of an org:
cvcpkg org members my-org
# Add a member (org owners or global admins):
cvcpkg org add-member my-org --name ci-publisher --role member
# Remove a member (revokes org access only, token stays valid):
cvcpkg org remove-member my-org --name ci-publisher| Org role | Permissions |
|---|---|
member |
Publish packages to the org's namespace |
owner |
All member permissions plus add/remove members, update org settings |
By default, GET /v1/catalog, GET /v1/packages, and
GET /v1/download/{filename} are public. To require authentication
for all endpoints, start the server with:
cvcpkg-server run --state-dir /var/lib/cvcpkg --require-auth-for-readscvcpkg-server maintains an index.yaml file in --state-dir that
lists every published bundle (name, version, platform, arch,
build_type, link, SHA-256 digest, archive URL, and optional signature
metadata). The index revision increments on each publish/yank/delete.
Clients call GET /v1/catalog to receive the full bundle list, then
GET /v1/download/{filename} to fetch individual archives.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /healthz |
none | Server health + uptime |
| GET | /v1/catalog |
reader/public | Full bundle catalog |
| GET | /v1/packages |
reader/public | Paginated package listing |
| GET | /v1/packages/{name} |
reader/public | Versions of a specific component |
| GET | /v1/download/{filename} |
reader/public | Download an archive |
| POST | /v1/publish |
publisher | Upload a new bundle |
| POST | /v1/packages/{name}/{version}/yank |
publisher | Yank a version (soft delete) |
| POST | /v1/packages/{name}/{version}/unyank |
admin | Restore a yanked version |
| DELETE | /v1/packages/{name}/{version} |
admin | Permanently delete a version |
| POST | /v1/tokens |
admin | Create a new API token |
| DELETE | /v1/tokens/{name} |
admin | Revoke a token |
| POST | /v1/tokens/{name}/rotate |
admin/self | Rotate a token's secret in place |
| GET | /v1/tokens |
admin | List all tokens |
| GET | /v1/audit |
admin | Paginated audit log |
| GET | /v1/audit/verify |
admin | Verify audit chain integrity |
| GET | /v1/orgs/{slug} |
public/member | Organization detail + members |
| POST | /v1/orgs/{slug}/members |
org owner | Add a member to an organization |
| DELETE | /v1/orgs/{slug}/members/{token_name} |
org owner | Remove a member from an organization |
| POST | /v1/builds |
publisher | Submit a single build job |
| POST | /v1/builds/dag |
publisher | Submit a DAG of build jobs |
| GET | /v1/builds |
publisher | List builds (filterable) |
| GET | /v1/builds/{job_id} |
publisher | Get build job details |
| POST | /v1/builds/{job_id}/cancel |
publisher | Cancel a pending/dispatched job |
| POST | /v1/builds/{job_id}/pause |
publisher | Pause a pending/dispatched job |
| POST | /v1/builds/{job_id}/resume |
publisher | Resume a paused job |
| POST | /v1/builds/dag/{dag_id}/cancel |
publisher | Cancel all pending/dispatched in DAG |
| POST | /v1/builds/dag/{dag_id}/pause |
publisher | Pause all pending/dispatched in DAG |
| POST | /v1/builds/dag/{dag_id}/resume |
publisher | Resume all paused jobs in DAG |
Every archive receives a SHA-256 digest at publish time, recorded in
the catalog. cvcpkg install verifies the digest after download
before extracting — a mismatch aborts the install.
# 1. Build a component from recipe:
cvcpkg build zlib --prefix ./stage \
--config release --link shared
# 2. Pack to an archive:
cvcpkg pack zlib --prefix ./stage \
--config release --link shared
# 3. Publish (to cvcpkg-server):
export CVCPKG_TOKEN="cvctok_..."
export CVCPKG_SERVER_URL="https://cvcpkg.org"
cvcpkg publish zlib --output-dir ./dist
# Or publish all archives in dist/:
cvcpkg publish --all --output-dir ./dist
# Publish to a storage backend instead:
cvcpkg publish --all --dest s3://my-bucket/cvcpkg/To attach a cryptographic signature at publish time, first generate
a signing key (see Package Signing below), then
pass --signing-key during pack:
cvcpkg pack zlib --prefix ./stage \
--config release --link shared \
--signing-key ~/.config/cvcpkg/keys/release.keyThe resulting archive will have a .sig sidecar file. When the
archive is published to cvcpkg-server, the signature and key
fingerprint are stored in the catalog so consumers can verify.
Yanking is a soft delete: the archive stays on disk but cvcpkg install will skip yanked versions (unless the lockfile pins one).
Only admins can unyank.
Deleting permanently removes the catalog entry. Use with care — consumers that pinned the deleted version will get download errors.
Every published package has a version string of the form:
<upstream_version>+cvc.<cvc_revision>
For example, 1.86.0+cvc.1 means upstream Boost 1.86.0, CVC recipe
revision 1. The +cvc.N suffix is SemVer build metadata — it is
ignored for range comparisons but used as a tiebreaker by the
resolver when multiple builds of the same upstream version exist.
The cvc_revision field in recipe.yaml controls the suffix:
recipe:
name: boost
upstream_version: "1.86.0"
cvc_revision: 1 # → published as 1.86.0+cvc.1The server rejects a publish with HTTP 409 Conflict if a package with the same 6-field key already exists:
(name, version, platform, arch, build_type, link)
The error message is:
"{name}=={version} (...) already published. Yank the existing version first, or use a new revision."
Because the version field includes the +cvc.N suffix, bumping
cvc_revision produces a different version string and is not
considered a duplicate. This is the intended mechanism for
re-publishing a corrected build of the same upstream version.
Note: yanking alone is not sufficient to re-publish — the duplicate check does not filter yanked entries. To re-publish the exact same version string, an admin must delete the old entry first.
When a recipe needs a rebuild (patch fix, build script change,
dependency update), bump its cvc_revision:
# Bump zlib and all downstream dependents:
cvcpkg rev-bump zlib
# Output:
# zlib: cvc_revision 1 → 2
# hdf5: cvc_revision 3 → 4
# vtk: cvc_revision 1 → 2The --cascade flag (default: on) automatically bumps every recipe
that transitively depends on the target. This ensures the entire
dependency chain is rebuilt and re-published against the patched
version, catching breakage early rather than shipping an inconsistent
set of binaries.
Why cascade? If a patch to openssl fixes a security issue,
every library linked against it (e.g. grpc, protobuf, qt6)
must be rebuilt to pick up the fix. Publishing only the patched
openssl without rebuilding downstream would leave consumers with
binaries linked against the old, vulnerable version. The cascade
ensures that either the full stack builds cleanly or the patch author
is forced to fix downstream breakage before publishing.
After bumping, the typical workflow is:
# 1. Bump revisions (edits recipe.yaml files in-place):
cvcpkg rev-bump openssl
# 2. Commit the bumped recipes:
git add recipes/ && git commit -m "rev-bump openssl + downstream"
# 3. Tag and push — CI rebuilds and publishes everything:
git tag v2.0.0 && git push origin v2.0.0| Term | Scope | Example | Purpose |
|---|---|---|---|
upstream_version |
Recipe | 1.86.0 |
The third-party project's own version |
cvc_revision |
Recipe | 3 |
Rebuild counter for CVC-specific patches or build fixes |
version (full) |
Published package | 1.86.0+cvc.3 |
Uniquely identifies this build in the catalog |
Catalog revision |
Server index | 42 |
Monotonic counter incremented on each publish/yank/delete; used by clients to detect catalog staleness |
cvcpkg supports Ed25519 package signing for publisher identity
verification. The cryptography package is a required dependency
and is installed automatically with cvcpkg.
Keys are stored in ~/.config/cvcpkg/keys/ (or
$XDG_CONFIG_HOME/cvcpkg/keys/) with three files per identity:
| File | Contents |
|---|---|
<label>.key |
PEM-encoded Ed25519 private key (mode 0600) |
<label>.pub |
PEM-encoded Ed25519 public key |
<label>.fp |
SHA-256 fingerprint of the raw 32-byte public key (hex) |
cvcpkg key generate --label release
# Output:
# Generated key 'release'
# Fingerprint: a1b2c3d4e5f6...
# Private key: /home/user/.config/cvcpkg/keys/release.key
# Public key: /home/user/.config/cvcpkg/keys/release.pubOptionally password-protect the private key:
cvcpkg key generate --label release --password "s3cret"cvcpkg key list
# Output:
# release a1b2c3d4e5f67890… (private+public)
# upstream-qt f0e1d2c3b4a59687… (public only)When a trusted publisher shares their public key, import it to enable signature verification:
cvcpkg key import publisher-release.pub --label upstream
# Imported 'upstream' (f0e1d2c3b4a5…)Share your public key with consumers:
cvcpkg key export --label release > release.pubThe easiest way: pass --signing-key to cvcpkg pack or
cvcpkg pack-all and the archive is signed automatically:
cvcpkg pack zlib --prefix ./stage \
--config release --link shared \
--signing-key ~/.config/cvcpkg/keys/release.keyThis creates the archive and a .sig sidecar file.
cvcpkg sign dist/zlib-1.3.1+cvc.1-linux-x86_64-release-shared.tar.gz \
--signing-key ~/.config/cvcpkg/keys/release.key
# Output:
# Signed: zlib-1.3.1+cvc.1-linux-x86_64-release-shared.tar.gz.sig
# (key: a1b2c3d4e5f6…)Signatures are stored in .sig YAML sidecar files:
signature: <base64url-encoded 64-byte Ed25519 signature>
key_fingerprint: <SHA-256 hex of the 32-byte Ed25519 public key>The signature covers the SHA-256 digest of the archive contents (not the raw file bytes directly), providing a standard digest-then-sign construction.
cvcpkg verify-sig dist/zlib-1.3.1+cvc.1-linux-x86_64-release-shared.tar.gz
# Output:
# Verified: signed by 'release' (a1b2c3d4e5f6…)The command looks for <archive>.sig by default, or use
--sig-file to specify a different path.
Pass --verify-signatures to cvcpkg install to verify every
downloaded archive before extraction:
cvcpkg install --prefix ./deps --verify-signatures boost hdf5 zlibIf a package in the catalog has a signature and the matching public key is in your keyring, verification happens automatically. If the signature is invalid or the signing key is not trusted, installation aborts with a clear error.
-
Key generation: Each publisher generates their own Ed25519 keypair with
cvcpkg key generate. -
Key distribution: The publisher shares their
.pubfile out-of-band (e.g. committed to the repo, posted on a website, or exchanged directly). -
Key import: Consumers import the publisher's public key with
cvcpkg key import. -
Verification: When
--verify-signaturesis enabled, cvcpkg checks the archive's signature against the local keyring. It first tries the key whose fingerprint matches the catalog entry, then falls back to trying all trusted keys (to support key rotation). -
Non-repudiation: The server records the signature and key fingerprint in the catalog at publish time, providing an audit trail of who signed each package.
- name: Sign and publish
env:
CVCPKG_TOKEN: ${{ secrets.CVCPKG_PUBLISHER_TOKEN }}
SIGNING_KEY: ${{ secrets.SIGNING_PRIVATE_KEY }}
run: |
# Write the signing key from CI secrets:
mkdir -p ~/.config/cvcpkg/keys
echo "$SIGNING_KEY" > ~/.config/cvcpkg/keys/ci.key
chmod 600 ~/.config/cvcpkg/keys/ci.key
# Build, pack (with signature), and publish:
cvcpkg build $COMPONENT --prefix ./stage
cvcpkg pack $COMPONENT --prefix ./stage \
--signing-key ~/.config/cvcpkg/keys/ci.key \
--config release --link sharedcvcpkg-server maintains a tamper-evident, append-only audit log. Every mutation (publish, yank, unyank, delete, token create, token revoke) is recorded with:
- Timestamp (UTC)
- Action (the operation performed)
- Actor (the token name that performed it)
- Target (the component or token affected)
- Detail (platform, SHA-256, etc.)
- Chain hash (SHA-256 of the previous entry for tamper detection)
# Last 20 entries:
cvcpkg-server audit log --last 20
# Filter by action:
cvcpkg-server audit log --action publish
# Filter by target:
cvcpkg-server audit log --target "boost==1.86.0+cvc.1"cvcpkg-server audit verify
# Output (if intact):
# chain intact (142 entries)The verify command walks the full chain and checks that each entry's
prev_sha256 matches the hash of the preceding entry. A broken
chain indicates tampering or data corruption.
By default, cvcpkg creates intermediate build trees in the system temp
directory ($TMPDIR, /tmp, etc.). For large builds this can exhaust
space on small temp partitions, or be slow on non-SSD storage.
Use --work-dir (or the CVCPKG_WORK_DIR environment variable)
to redirect build trees to a dedicated volume:
# Point builds at a fast NVMe scratch partition:
cvcpkg build-all --work-dir /mnt/scratch/cvcpkg-builds \
--config release --link shared
# Or set it globally via environment:
export CVCPKG_WORK_DIR=/mnt/scratch/cvcpkg-builds
cvcpkg pack-all --config release --link sharedThe directory is created automatically if it doesn't exist. Each recipe
gets its own sub-directory under --work-dir (e.g.
/mnt/scratch/cvcpkg-builds/cvcpkg-zlib-XXXXXXXX/).
When --work-dir is not set, the default prefix directory for
build-all (when --prefix is also omitted) is likewise placed in the
system temp directory.
cvcpkg-server supports mirror mode, where a read-only replica syncs its catalog from an upstream primary and proxies archive downloads on demand. Clients automatically discover healthy mirrors and use them as fallback download sources.
Start a mirror server pointing at an upstream primary:
cvcpkg-server run \
--mirror-mode \
--mirror-upstream https://cvcpkg.org \
--mirror-token cvctok_... \
--database-url postgresql+asyncpg://user:pass@localhost/mirror_db \
--state-dir ./mirror-data \
--port 8421| Flag | Env var | Description |
|---|---|---|
--mirror-mode |
CVCPKG_MIRROR_MODE |
Enable read-only mirror mode |
--mirror-upstream |
CVCPKG_MIRROR_UPSTREAM |
Upstream server URL (required) |
--mirror-token |
CVCPKG_MIRROR_TOKEN |
Token for upstream auth |
--mirror-sync-interval |
CVCPKG_MIRROR_SYNC_INTERVAL |
Catalog sync interval in seconds (default: 3600) |
Mirror-mode servers reject publish and upload requests (HTTP 403) and periodically sync the catalog from the upstream. Archive files are fetched on first request and cached locally.
Mirrors register themselves with the primary so clients can discover them:
curl -X POST https://cvcpkg.org/v1/mirrors/register \
-H 'Content-Type: application/json' \
-d '{"url": "https://eu.cvcpkg.org", "display_name": "EU Mirror", "contact": "ops@eu.cvcpkg.org"}'The primary health-checks registered mirrors every 5 minutes. After 3 consecutive failures a mirror is marked unhealthy and removed from the client mirror list. Re-registering clears rejection/unhealthy state.
# List all mirrors (admin-only, includes rejected/unhealthy)
curl -H "Authorization: Bearer $ADMIN_TOKEN" https://cvcpkg.org/v1/mirrors/all
# Reject a mirror
curl -X POST "https://cvcpkg.org/v1/mirrors/reject?url=https://bad.example.com" \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Permanently remove a mirror
curl -X DELETE "https://cvcpkg.org/v1/mirrors?url=https://old.example.com" \
-H "Authorization: Bearer $ADMIN_TOKEN"When CVCPKG_SERVER_URL is set, the install and sync commands
automatically fetch the mirror list from the server and inject mirror
URLs as fallback download sources. If the primary download fails,
mirrors are tried in order.
export CVCPKG_SERVER_URL=https://cvcpkg.org
cvcpkg install --from cvc-requirements.yaml --prefix ./depsThe download command fetches archives to a local directory without
extracting them:
# Download specific components
cvcpkg download zlib boost --output-dir ./archives
# With mirror failover
cvcpkg download zlib --server https://cvcpkg.org -o ./dist
# Pin a version
cvcpkg download zlib==1.3.1+cvc.1 -o ./dist --config debugWhen builds are interrupted or crash, they can leave behind orphaned
cvcpkg-* temporary directories in the system temp folder. The
clean command removes them:
# Remove work directories older than 2 hours (default):
cvcpkg clean
# Preview what would be removed:
cvcpkg clean --dry-run
# Remove directories older than 30 minutes:
cvcpkg clean --older-than 30
# Remove all cvcpkg work directories regardless of age:
cvcpkg clean --all
# Target a specific parent directory:
cvcpkg clean --work-dir /mnt/scratchThe CI workflows run cvcpkg clean automatically before and after
builds to prevent disk-full failures on shared runners.
When many CI runners publish archives concurrently (e.g. a tagged release building 4 macOS configs × 16 packages), the cvcpkg-server backend can run out of memory and restart, causing the reverse proxy to return 502 Bad Gateway.
Checklist:
-
Container memory limit — Ensure the backend container has enough memory for concurrent uploads. In
docker-compose.production.yml, setdeploy.resources.limits.memoryto at least 4–8 GB for production workloads with many concurrent publishers. -
Reverse proxy body limit — If using Apache, the
LimitRequestBodydirective must be large enough for the biggest archive (e.g. emsdk at ~840 MB). Set it to at least 1.1 GB:LimitRequestBody 1153433600For nginx, use
client_max_body_size 1100m;. -
Proxy timeout — Large chunked uploads can take several minutes. Ensure your proxy timeout is at least 900 s (
ProxyTimeout 900in Apache,proxy_read_timeout 900sin nginx).
Symptoms: Container restart count > 0 (docker inspect <container> --format '{{.RestartCount}}'), 502 responses in the proxy access log
concentrated in a short time window.
# from the repo root
pip install -e '.[progress,server]'
pytestpytest --cov=cvcpkg --cov-branch --cov-report=html:htmlcov tests/
open htmlcov/index.htmlCoverage reports are also generated as CI artifacts on every push/PR — download them from the workflow run's Artifacts section.
cvcpkg is a CyberPC Angel, LLC project — designed, funded, and maintained by the CyberPC Angel team, who own the project's intellectual property.
Community contributions are welcome via pull request; see the full list of everyone who has contributed on the contributors page.
MIT — Copyright (c) 2026 CyberPC Angel, LLC. See LICENSE for the full text.
