id-configurator
id-configurator is a CLI tool by InterDiode that configures a device (typically a
Docker image) in an air-gapped environment: no network access is required or ever used.
It reads a single configuration (JSON or YAML), and applies it to a filesystem tree, writing only the files that need to change: root certificates, system package manager sources (APT, YUM/DNF, APK) and language-specific package manager settings (pip/uv/poetry, Maven/Gradle/Ivy/SBT/Leiningen, Composer, npm, cargo, Go modules, RubyGems, NuGet). The result can then be used to build a Docker image or otherwise deployed to the target device.
How it works
--dstpoints to the destination directory that receives every modified or created file.id-configuratornever writes anywhere else.--src(optional) points to an existing filesystem, used to detect the current configuration (existing APT sources, existing Java keystores, etc.) so it can be extended or disabled instead of blindly overwritten.- The configuration is loaded from a file (
--config), from a base64-encoded string, typically an environment variable (--config-env/$ID_CONFIGURATOR_CONFIG), or downloaded over HTTP(S) from a URL (--config-url).--config-url-cacert-contentoptionally gives the PEM content of the CA certificate to trust for that one download, for an internal endpoint signed by a private CA the running system doesn't otherwise trust yet (e.g. before thecertificatesmodule has ever run) - it defaults to the system's own trust store otherwise.--config-url-insecuredisables certificate (and hostname) verification entirely instead, overriding--config-url-cacert-content- insecure, only meant for a first bootstrap against a server whose certificate isn't trustable, or trusted, yet by any other means. - On
apply, every module inspects the configuration and the source filesystem and decides for itself whether it has something to do; only relevant modules touch the destination directory. For example, the configured Alpine repositories are only written if--srccan be identified as an Alpine filesystem (i.e. it has an/etc/apkdirectory); and a source cloning Debian's official repository is only configured if that official repository is actually present in the filesystem given via--src(viauri-to-replace, see "Replacing an existing repository" below).
Installation
Requires Python 3.13+.
uv sync
Usage
# Apply a configuration file to a destination directory
uv run id-configurator apply --config config.yaml --dst ./output
# Take an existing filesystem into account (e.g. to extend its APT sources)
uv run id-configurator apply --config config.yaml --src /path/to/rootfs --dst ./output
# Use a base64-encoded configuration from the environment instead of a file
export ID_CONFIGURATOR_CONFIG=$(base64 -w0 config.yaml)
uv run id-configurator apply --dst ./output
# Download the configuration from an internal HTTPS server instead of a local file
uv run id-configurator apply \
--config-url https://config.internal.example/device.yaml \
--config-url-cacert-content "$(cat internal-ca.pem)" \
--dst ./output
# Validate a configuration without applying it
uv run id-configurator validate --config config.yaml
# List the available modules
uv run id-configurator list-modules
The tool can also be invoked as python -m id_configurator.
Configuration format
The configuration is a JSON or YAML object. Every configurations.* block is optional - a module that finds
nothing to do for it simply does not run. version is the configuration format's own schema version
(currently "1.0", and the default if omitted) - independent of the id-configurator package version - so a
future, incompatible format change can be told apart from this one:
version: "1.0"
uuid: <UUID>
organisation: <string>
description: <string>
configurations:
certificates:
- name: <string>
content: <PEM>
apt:
sources:
- name: <string>
uris: [<string>]
suites: [<string>]
components: [<string>]
signed-by: <ASCII-armored GPG public key> # or signed-by-file: <path already on the target>
uri-to-replace: <fnmatch pattern, optional> # see "Replacing an existing repository" below
apk:
repositories:
- name: <string>
url: <string>
key: <RSA public key, optional>
url_to_replace: <fnmatch pattern, optional>
yum:
repositories:
- name: <string>
index-url: <string>
gpg-url: <string, optional>
index-url-to-replace: <fnmatch pattern, optional>
keys:
- name: <string>
content: <GPG key>
python:
repositories:
- name: <string>
index-url: <string>
java:
repositories:
- name: <string>
url: <string>
php:
repositories:
- name: <string>
url: <string>
npm:
repositories:
- name: <string>
url: <string>
rust:
repositories:
- name: <string>
url: <string>
go:
repositories:
- name: <string>
url: <string>
ruby:
repositories:
- name: <string>
url: <string>
nuget:
repositories:
- name: <string>
url: <string>
Replacing an existing repository
apt.sources[].uri-to-replace, apk.repositories[].url_to_replace and yum.repositories[].index-url-to-replace
are optional fnmatch patterns. When set, the configured
repository is only written if the pattern matches at least one URI/URL already present in --src's existing
configuration - and only the matching existing entries are disabled, the rest are left untouched. This lets a
configuration safely target "whatever machine this is applied to": e.g. uri-to-replace: "http://deb.debian.org/*"
replaces Debian's default mirror if (and only if) it's actually there, without disturbing any other source a
given machine might already have. Leaving the field unset never disables anything - the configured repository
is simply added alongside whatever is already present.
Modules
| Module | Applies to |
|---|---|
certificates |
Installs root certificates into the system's CA store, rebuilding the trust bundle in pure Python (Debian/Ubuntu and RHEL/Fedora trust stores). |
apt |
Adds the configured APT sources (DEB822 format); disables only the existing entries matching uri-to-replace. |
apk |
Adds the configured Alpine /etc/apk/repositories entries and their keys; disables only the existing lines matching url_to_replace. |
yum |
Adds the configured YUM/DNF .repo files and imports their GPG keys; disables only the existing sections matching index-url-to-replace. Covers DNF too - same config files. |
python |
Points pip, uv, poetry and easy_install to the configured Python package indexes. |
java |
Adds root certificates to every detected JKS/PKCS12 keystore, and configures Maven, Gradle, Ivy, SBT and Leiningen repositories. |
php |
Registers the configured repositories with Composer; sets openssl.cafile whenever any certificate is configured, so PHP's own OpenSSL calls trust it too. |
npm |
Points npm at the configured registries and its own cafile; sets NODE_EXTRA_CA_CERTS system-wide whenever any certificate is configured, since Node ignores the system CA store. |
rust |
Points cargo at the configured registries, replacing crates.io. |
go |
Points the Go toolchain at the configured module proxies (GOPROXY), replacing proxy.golang.org, and disables GOSUMDB. |
ruby |
Points RubyGems at the configured sources, replacing rubygems.org. |
nuget |
Points NuGet at the configured package sources, replacing nuget.org. |
This tool's own modular structure is meant to be extended: see CLAUDE.md for the module interface and how to add one.
Development
uv sync # install dependencies
uv run pytest # run the test suite
uv run ruff check --fix . # lint
uv run ruff format . # format
uv run pre-commit run --all-files # run all pre-commit hooks
License
CeCILL v2.1 (GPL-compatible free software license). See LICENSE. Copyright InterDiode SA.
Metadata
Release files for id-configurator 2026.9.20
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| id_configurator-2026.9.20.tar.gz | 45.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| id_configurator-2026.9.20-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 99.3 kB
Release files / id_configurator-2026.9.20.tar.gz
| Download URL | id_configurator-2026.9.20.tar.gz |
|---|---|
| Size | 45.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7df647dfc8fdbb76d434ad726acfed760b0b9d828dc766718d10e91bd4238a44
|
|
BLAKE2b-256 checksum How to use checksums |
61c3fd33991f3cb7348f343b894e7eebfa9c63c431960167c833812fd98786ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / id_configurator-2026.9.20-py3-none-any.whl
| Download URL | id_configurator-2026.9.20-py3-none-any.whl |
|---|---|
| Size | 54.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3d44fc332a215bc426dc2050f7c051c8ce1c73768221d1685048b5e081bfa6a5
|
|
BLAKE2b-256 checksum How to use checksums |
2f02c0b689a8a1f2f79379a86f672d68963b1844b284aa4d5b9ec58c9311f902
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|