Skip to content

White-label runnable packages

The white-label walkthrough brands the console by wrapping it in your own package. The runnable packs — runspec-linux, runspec-windows and friends — are different: you usually don't want to wrap them, you want the same package under your own name on your private index (Nexus, Artifactory, devpi), so hosts install mycorp-linux from infrastructure you control and every new upstream feature arrives without anyone hand-copying runnables.

runspec rebrand does exactly that: it takes a published wheel and writes a copy whose distribution name is yours. Nothing else changes.

Changed Unchanged
Distribution name (runspec-linux → mycorp-linux) Every .py file, byte for byte
*.dist-info / *.data directory names runspec.toml — runnables, prompts, autonomy
METADATA Name (optionally Version) Entry points / command names (system-info, cron, …)
Requires-Dist entries you ask it to re-point Import package (runspec_linux)
RECORD (regenerated, hashes valid) The runspec dependency and every version specifier

Because the copy still requires runspec and still ships runspec.toml, runspec local, runspec serve, the console's discovery and the MCP gateway see exactly the same runnables as with the upstream package.

Why not a wrapper package?

A wrapper (depend on runspec-linux-core, write your own runspec.toml and thin modules) is the right tool when you want to change behaviour — bake in defaults, trim runnables, add corporate ones. For an unchanged, renamed copy it's pure maintenance: every upstream runnable has to be re-copied by hand. A rebrand is mechanical, so it can run unattended.


1. Rebrand one release

rebrand is part of the core runspec library (0.59.0+), so it reaches a locked-down machine the same way runspec itself does — pip install runspec from your Nexus PyPI proxy. There is no separate script to download from GitHub, email or retype. It needs nothing beyond the standard library and pip.

# download runspec-linux 0.14.0 from the index pip is configured for, rename it
runspec rebrand runspec-linux==0.14.0 --name mycorp-linux --out dist/

# or rebrand a wheel you already have
runspec rebrand runspec_windows-0.9.0-py3-none-any.whl --name mycorp-windows --out dist/
✓ runspec-linux 0.14.0 → mycorp-linux 0.14.0
  wrote dist/mycorp_linux-0.14.0-py3-none-any.whl
  runnables: runspec_linux/runspec.toml

Then upload as usual: twine upload --repository-url https://nexus.mycorp.example/repository/pypi-internal/ dist/*.

Keep the upstream version (the default). mycorp-linux 0.14.0 is runspec-linux 0.14.0, so changelogs, bug reports and runspec-linux-core compatibility all line up. --version exists for the rare re-publish (0.14.0.post1), not for an independent version line.

2. The core packages — rename them or not

runspec-linux depends on runspec-linux-core, runspec-webops-core and runspec-fs-core; runspec-windows on the last two. The copy keeps those dependencies as-is, which is right when your Nexus group repository proxies PyPI: pip resolves mycorp-linux from your hosted repo and the cores (and runspec itself) through the proxy.

If your hosts must install only internally-named packages, rebrand the cores too and re-point the dependency with --rename-dep:

runspec rebrand runspec-linux-core==0.7.0   --name mycorp-linux-core   --out dist/
runspec rebrand runspec-webops-core==0.3.0  --name mycorp-webops-core  --out dist/
runspec rebrand runspec-fs-core==0.3.0      --name mycorp-fs-core      --out dist/

runspec rebrand runspec-linux==0.14.0 --name mycorp-linux --out dist/ \
    --rename-dep runspec-linux-core=mycorp-linux-core \
    --rename-dep runspec-webops-core=mycorp-webops-core \
    --rename-dep runspec-fs-core=mycorp-fs-core

The version specifier is kept (runspec-linux-core>=0.7.0 becomes mycorp-linux-core>=0.7.0), so the rebranded cores must keep upstream versions too. A --rename-dep the package doesn't declare is reported and ignored, so one shared set of flags works for both runspec-linux and runspec-windows. runspec itself is normally left alone — it's the library everything targets.

3. Keep it current automatically

Since a rebrand is deterministic, a scheduled CI job can mirror every new upstream release. This one is plain shell (shown as GitLab CI; it drops into Jenkins or GitHub Actions unchanged): for each pair it finds the latest upstream wheel, skips it if your hosted repo already has that version, otherwise rebrands and uploads. Re-running it is harmless, so a daily schedule is enough.

# .gitlab-ci.yml
mirror-runspec:
  image: python:3.12              # or your registry's mirror of it
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"   # the daily run
    - if: $CI_PIPELINE_SOURCE == "web"        # "Run pipeline" by hand
  variables:
    # Everything installs/downloads through Nexus: the group repo fronts both
    # the PyPI proxy (runspec, runspec-linux, …) and your hosted packages.
    PIP_INDEX_URL: https://nexus.mycorp.example/repository/pypi-group/simple
    # The hosted repo alone — to check what you've already published.
    HOSTED_INDEX: https://nexus.mycorp.example/repository/pypi-internal/simple
    TWINE_REPOSITORY_URL: https://nexus.mycorp.example/repository/pypi-internal/
    # TWINE_USERNAME / TWINE_PASSWORD: protected + masked CI/CD variables.
  script:
    - pip install twine "runspec>=0.59.0"
    - |
      set -euo pipefail
      for pair in runspec-linux:mycorp-linux runspec-windows:mycorp-windows; do
        up=${pair%%:*}; ours=${pair##*:}
        rm -rf up probe && mkdir up probe
        pip download --no-deps --only-binary=:all: -d up "$up"
        whl=$(ls up/*.whl)
        ver=$(basename "$whl" | cut -d- -f2)
        if pip download --no-deps --index-url "$HOSTED_INDEX" -d probe "$ours==$ver" >/dev/null 2>&1; then
          echo "$ours $ver already published"; continue
        fi
        runspec rebrand "$whl" --name "$ours" --out dist/
      done
      if ls dist/*.whl >/dev/null 2>&1; then
        twine upload dist/*
      else
        echo "nothing new"
      fi
  artifacts:
    paths: [dist/]
    expire_in: 1 week
    when: always

Set it up under Build → Pipeline schedules: target the default branch and pick a daily time. Protected variables are only exposed to pipelines on protected refs, so the schedule's target branch must be protected (the default branch usually is) or twine gets no credentials. If the hosted repo needs auth to read, put credentials in HOSTED_INDEX (https://$USER:$PASS@…) or a .netrc.

Add --rename-dep flags (and the core packages to the loop, before the packages that depend on them) if you rebrand the cores. Pin pair versions instead of taking the latest if you want to promote releases deliberately.


White-label consoles

A white-label console can carry your rebranded Windows pack instead of the upstream one, and keep it updated from its own Marketplace.

Depend on the copy, not the [windows] extra. runspec-console ships the Windows runnables as an optional extra, runspec-console[windows] (0.292.0+), so a wrapper that leaves it out gets no upstream copy. [all] doesn't include it either. Depend on your copy instead, with a lower bound only so the Marketplace can move it forward independently of the console:

[project]
name = "mycorp-console"
dependencies = [
    "runspec-console[all]>=0.292.0",
    "mycorp-windows>=0.10.0 ; sys_platform == 'win32'",
]

(Or leave it out and let operators install it from the Marketplace.)

Don't rebrand runspec-console itself: the console looks itself up by distribution name (discovery self-exclusion, config-seed detection, the update check), so brand it with a wrapper.

Point the Marketplace at your index only. Ship this in the console's config seed so every install offers your copies and never the public packages:

[marketplace]
auto_pip = false          # don't derive repos from pip config (pypi.org by default)

[[marketplace.repos]]
name     = "nexus"
url      = "https://nexus.mycorp.example/repository/pypi-group/simple"
scan     = false          # read exactly the packages below
packages = ["mycorp-windows", "mycorp-linux"]
pip_arg  = "index-url"    # installs resolve through this index

Use the group URL, not the hosted repo alone: installing mycorp-windows also pulls runspec, runspec-webops-core and runspec-fs-core (or your rebranded cores), which the group serves through its PyPI proxy. The installed and update chips match by distribution name, so mycorp-windows shows as installed on every venv that has it and offers each new version your mirror job publishes. If you keep auto_pip on, add hide = ["runspec-linux", "runspec-windows"] so operators aren't offered both names.


Things to know

Install one or the other, never both, in a venv. The copy and the upstream package own the same files (runspec_linux/…) and the same commands. pip will install both without complaint, but uninstalling either removes the shared files, and two versions overwrite each other. Choose one name per venv.

What stays upstream-branded. The long description (README), summary and project URLs are carried over verbatim — they still say runspec-linux and link to the runspec docs, which is accurate: it's the same code.