diff --git a/src/cli-claude-code/README.md b/src/cli-claude-code/README.md new file mode 100644 index 0000000..bfed87d --- /dev/null +++ b/src/cli-claude-code/README.md @@ -0,0 +1,298 @@ + +# Claude Code (cli-claude-code) + +Installs Anthropic's native Claude Code binary (claude) at an exact version, checked against a sha256 you supply, with its auto-updater and manual updates turned off. No Node.js needed. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-claude-code:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Claude Code version to install (e.g. 2.1.312). Set it with its sha256 here, or as CLI_CLAUDE_CODE_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| sha256_amd64 | sha256 of the linux-x64 claude binary (platforms.linux-x64.checksum in the release's manifest.json), needed on amd64. Set it here, or as CLI_CLAUDE_CODE_SHA256_AMD64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| sha256_arm64 | sha256 of the linux-arm64 claude binary (platforms.linux-arm64.checksum in the release's manifest.json), needed on arm64. Set it here, or as CLI_CLAUDE_CODE_SHA256_ARM64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set a pin. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## What it installs + +Anthropic's native `claude` binary, the same file the official installer +(`https://claude.ai/install.sh`) downloads, as `/usr/local/bin/claude`. It +needs no Node.js. The feature: + +- downloads + `https://downloads.claude.ai/claude-code-releases//linux-{x64,arm64}/claude` + (the glibc builds; Alpine and the bucket's `linux-*-musl` builds are out + of scope) and checks it against your sha256; +- creates `~/.claude` for the remote user, mode `0700` (see + [Keeping the login across rebuilds](#keeping-the-login-across-rebuilds)); +- sets `DISABLE_AUTOUPDATER=1` and `DISABLE_UPDATES=1` in the container + environment, so Claude Code doesn't update itself away from your pin + (see [Bumping the version](#bumping-the-version)). + +It declares no mounts and doesn't set `CLAUDE_CONFIG_DIR`: where Claude +Code's state and credentials live is your choice, not the feature's. + +## Pinning the version + +This feature has no default version. You choose the Claude Code release +and supply the sha256 of its binary for each arch you build on, and the +install fails unless it finds both. The download is always checked against +your sha256; the feature never fetches Anthropic's manifest itself, so the +sha you review is the one that counts. + +Each release publishes a `manifest.json` with the sha256 of every platform +binary, under `platforms..checksum`: + +```sh +VERSION=2.1.312 +curl -fsSL "https://downloads.claude.ai/claude-code-releases/${VERSION}/manifest.json" \ + | jq -r '.platforms["linux-x64"].checksum, .platforms["linux-arm64"].checksum' +``` + +| Arch | Manifest key | Binary | +| --- | --- | --- | +| amd64 | `platforms.linux-x64.checksum` | `/linux-x64/claude` | +| arm64 | `platforms.linux-arm64.checksum` | `/linux-arm64/claude` | + +The manifest is signed with Anthropic's release key from `2.1.89` on; to +check the signature before you trust the shas, follow +[Binary integrity and code signing](https://code.claude.com/docs/en/setup#binary-integrity-and-code-signing). +The version is the plain release number (`2.1.312`), the same as the npm +package `@anthropic-ai/claude-code`. You only need the sha for the arches +you actually build on. + +The pin can come from three places. The feature uses the first one that +sets either the version or a sha, and that place must set both: a version +in one place is never paired with a sha from another. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-claude-code:1": { + "version": "2.1.312", + "sha256_amd64": "4f4178ce41d83e23341ebfbaecdbcf8ea6bf7baea0690025abe49eeec3a5d01d", + "sha256_arm64": "dc4af28917455e009d26704b82a438353a06149e825b6cdf782908b2d28661f5" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the options from the +host's environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-claude-code:1": { + "version": "${localEnv:CLI_CLAUDE_CODE_VERSION}", + "sha256_amd64": "${localEnv:CLI_CLAUDE_CODE_SHA256_AMD64}", + "sha256_arm64": "${localEnv:CLI_CLAUDE_CODE_SHA256_ARM64}" +} +``` + +### 2. Environment variables in your image + +Set `CLI_CLAUDE_CODE_VERSION`, `CLI_CLAUDE_CODE_SHA256_AMD64` and +`CLI_CLAUDE_CODE_SHA256_ARM64` with `ENV` in the Dockerfile your +devcontainer builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_CLAUDE_CODE_VERSION=2.1.312 +ENV CLI_CLAUDE_CODE_SHA256_AMD64=4f4178ce41d83e23341ebfbaecdbcf8ea6bf7baea0690025abe49eeec3a5d01d +ENV CLI_CLAUDE_CODE_SHA256_ARM64=dc4af28917455e009d26704b82a438353a06149e825b6cdf782908b2d28661f5 +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-claude-code:1": {} +} +``` + +### 3. A pins file in your image + +Put the same `KEY=value` lines in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection; each feature reads only its own keys. + +```sh +# pins.env +CLI_CLAUDE_CODE_VERSION=2.1.312 +CLI_CLAUDE_CODE_SHA256_AMD64=4f4178ce41d83e23341ebfbaecdbcf8ea6bf7baea0690025abe49eeec3a5d01d +CLI_CLAUDE_CODE_SHA256_ARM64=dc4af28917455e009d26704b82a438353a06149e825b6cdf782908b2d28661f5 +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced: `KEY=value` lines (optionally after +`export`), `#` comments and quoted values are understood, and nothing is +expanded. To keep it somewhere else, set the `pins_file` option to its +path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-claude-code:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Bumping the version + +Claude Code normally updates itself in the background. This feature sets +two of Anthropic's documented switches in `containerEnv` +([environment variables](https://code.claude.com/docs/en/env-vars)), so +the version you pinned is the version that runs: + +- `DISABLE_AUTOUPDATER=1` stops the background updater; +- `DISABLE_UPDATES=1` also blocks the manual `claude update` and + `claude install`. Those manage the native installer's own per-user + layout (versions under `~/.local/share/claude/versions/`, a launcher at + `~/.local/bin/claude`), not this feature's `/usr/local/bin/claude`, so + they would leave a second copy that shadows the pinned one wherever + `~/.local/bin` comes first on `PATH`. + +To move to a newer release, change the pin: put the new version and its +two shas from that release's `manifest.json` wherever you set the pin, +and rebuild the container. + +## Keeping the login across rebuilds + +Claude Code keeps its credentials, settings and session history in +`~/.claude`, and its account details, MCP servers and per-project trust in +`~/.claude.json`, outside that directory unless `CLAUDE_CONFIG_DIR` says +otherwise. A rebuild discards both, so you sign in again each time unless +you choose one of the options below. Anthropic's own guidance is on +[Development containers](https://code.claude.com/docs/en/devcontainer). + +The feature declares no mount: a feature's mounts can't be switched off, +and whether credentials sit in a volume, and whether projects share one, +is a security choice that belongs to you. What it does is create +`~/.claude` in the image, owned by the remote user with mode `0700`. +Docker seeds an empty named volume with the ownership of the directory it +is mounted on, so a volume you mount there starts out writable by the +remote user instead of owned by root. + +In the snippets below, replace `/home/vscode` with your `remoteUser`'s +home directory (`/root` if the container runs as root). + +### (a) One volume per project + +Each devcontainer gets its own volume, named after its +`${devcontainerId}`, and `CLAUDE_CONFIG_DIR` puts `.claude.json` inside +it too. A login in one project isn't visible to another. This is the +pattern in Anthropic's +[reference configuration](https://github.com/anthropics/claude-code/blob/main/.devcontainer/devcontainer.json). + +```jsonc +"mounts": [ + "source=claude-code-config-${devcontainerId},target=/home/vscode/.claude,type=volume" +], +"containerEnv": { + "CLAUDE_CONFIG_DIR": "/home/vscode/.claude" +} +``` + +### (b) One volume shared by all projects + +Sign in once and every devcontainer that mounts the same volume is signed +in. The trade-off: every container that mounts it can read the same token, +including a container for a repository you trust less. + +```jsonc +"mounts": [ + "source=claude-code-config,target=/home/vscode/.claude,type=volume" +], +"containerEnv": { + "CLAUDE_CONFIG_DIR": "/home/vscode/.claude" +} +``` + +### (c) No volume, a token from the host + +Keep no credentials in the container at all and pass one in from the +host's environment on every start: either a long-lived +`CLAUDE_CODE_OAUTH_TOKEN`, which you generate once with +`claude setup-token`, or an `ANTHROPIC_API_KEY`. Settings and history +still don't survive a rebuild, but nothing secret is stored in a volume, +and revoking the token on the host side is enough. **This is the +recommended option for enterprise use.** + +```jsonc +"containerEnv": { + "CLAUDE_CODE_OAUTH_TOKEN": "${localEnv:CLAUDE_CODE_OAUTH_TOKEN}" +} +``` + +or + +```jsonc +"containerEnv": { + "ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}" +} +``` + +## Install order + +This feature deliberately declares no `installsAfter`: the Dev Container +CLI fetches every `installsAfter` reference from ghcr.io, so declaring one +would make every build reach ghcr.io for features you may not use. Set +the order yourself with `overrideFeatureInstallOrder` in these two cases. + +- **`ssl-ca`.** If you use this collection's `ssl-ca` feature for a + corporate root CA, the CA has to be trusted before Claude Code is + downloaded, so list `ssl-ca` first. +- **A feature that creates the remote user**, such as + `ghcr.io/devcontainers/features/common-utils` on an image without that + user. This feature creates `~/.claude` in the remote user's home, and + the install fails if that home doesn't exist yet, so list the + user-creating feature before this one. + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-claude-code" +] +``` + +## Corporate CAs at run time + +`ssl-ca` sets twelve CA variables in `containerEnv` (the full list is in +its NOTES.md): `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE` and seven other +per-tool variables point at the system bundle +`/etc/ssl/certs/ca-certificates.crt`, `SSL_CERT_DIR` at `/etc/ssl/certs`, +and `NODE_USE_SYSTEM_CA` and `UV_NATIVE_TLS` are `1`. The one that +matters here is `NODE_EXTRA_CA_CERTS`. + +The native binary has no Node.js installation behind it, so `ssl-ca`'s +`NODE_USE_SYSTEM_CA=1` doesn't apply to it. It doesn't need to: per +Anthropic's +[network configuration](https://code.claude.com/docs/en/network-config#ca-certificate-store) +docs, Claude Code trusts both its bundled Mozilla CA set and the operating +system's trust store by default (`CLAUDE_CODE_CERT_STORE=bundled,system`), +and the native binary can always read the system store. A root CA that +`ssl-ca` installs into the system trust store is therefore trusted with no +further setting. Claude Code also honours `NODE_EXTRA_CA_CERTS`, which +`ssl-ca` points at the system bundle, so nothing conflicts. Don't set +`CLAUDE_CODE_CERT_STORE=bundled`, which would drop the system store and +with it your CA. + +## OS support + +Debian and Ubuntu (glibc), on amd64 and arm64. The install adds +`curl` and `ca-certificates` if either is missing. + +## License + +Claude Code is proprietary and its binary ships no license file, so the feature installs none; your use is subject to Anthropic's [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms) or, on a Free, Pro or Max plan, its [Consumer Terms](https://www.anthropic.com/legal/consumer-terms) (see [Legal and compliance](https://code.claude.com/docs/en/legal-and-compliance)). + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-claude-code/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-keycloak/README.md b/src/cli-keycloak/README.md new file mode 100644 index 0000000..f285b23 --- /dev/null +++ b/src/cli-keycloak/README.md @@ -0,0 +1,279 @@ + +# Keycloak admin CLI (kcadm) (cli-keycloak) + +Installs a Keycloak admin CLI as kcadm: the official kcadm.sh (with a headless JRE) at an exact version, checked against a sha256 you supply, or a small curl wrapper around the Admin REST API. The two flavors share the name, not the syntax. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-keycloak:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| flavor | official: Keycloak's kcadm.sh, pinned, needs Java. rest: a curl wrapper, kcadm METHOD PATH [JSON], no pin. Set it here, or as CLI_KEYCLOAK_FLAVOR in the image environment, or in the pins file. Empty everywhere means official. | string | - | +| version | official only: Keycloak version to install (e.g. 26.7.5). Set it with its sha256 here, or as CLI_KEYCLOAK_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| sha256 | official only: sha256 of keycloak-.tar.gz (the same file on every arch). Set it here, or as CLI_KEYCLOAK_SHA256 in the image environment, or in the pins file, in the same place as the version. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set the flavor or a pin. Empty means /etc/devcontainer-features/pins.env. | string | - | +| install_jre | official only: install the distro's headless JRE (default-jre-headless, Java 21 on Debian 13 and Ubuntu 24.04). Turn it off if the image already has Java 21 or newer. Ignored for rest. | boolean | true | + +## Two flavors, one name + +The `flavor` option picks what `kcadm` is. **The two share the name +`kcadm` but not its syntax**, so a script written for one doesn't run +with the other. + +| Flavor | What `kcadm` is | Needs | Pin | +| --- | --- | --- | --- | +| `official` (default) | Keycloak's own [admin CLI](https://www.keycloak.org/docs/latest/server_admin/#the-admin-cli), `kcadm.sh`, also installed as `kcadm` | Java 21 or newer (installed by default) | version and sha256 | +| `rest` | a curl wrapper around the [Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/): `kcadm METHOD PATH [JSON]` | `curl` and `python3` (installed if missing) | none | + +```sh +# official +kcadm.sh config credentials --server "$KEYCLOAK_URL" --realm master --user "$KEYCLOAK_ADMIN" +kcadm.sh get realms/master/users + +# rest +kcadm GET /admin/realms/master/users +``` + +`official` puts `kcadm.sh` and its client jars in +`/usr/local/lib/keycloak-cli`, with `kcadm` and `kcadm.sh` on `PATH`. +`rest` installs only `/usr/local/bin/kcadm`; it downloads nothing, so it +needs no version or sha256. If one is set anyway (in any of the three +places below), the install warns that it is ignored and goes on. + +The flavor comes from the first of these that sets it: the `flavor` +option, `CLI_KEYCLOAK_FLAVOR` in the image environment, or +`CLI_KEYCLOAK_FLAVOR` in the pins file (see below). If none does, it's +`official`. Any value other than `official` or `rest` fails the install. + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-keycloak:1": { + "flavor": "rest" +} +``` + +## Pinning the version (`official`) + +`official` has no default version. You choose the Keycloak release and +supply the sha256 of its tarball, and the install fails unless it finds +both. The tarball is the same on every arch, so there is one `sha256`. + +Upstream publishes only `.sha1`, `.md5` and `.asc` files next to +`keycloak-.tar.gz` on +[github.com/keycloak/keycloak/releases](https://github.com/keycloak/keycloak/releases), +no sha256. So the sha256 is your own pin: download the tarball once, +check it against upstream's `.sha1` (or its `.asc` signature), and record +its sha256: + +```sh +v=26.7.5 +curl -fsSLO "https://github.com/keycloak/keycloak/releases/download/${v}/keycloak-${v}.tar.gz" +echo "$(curl -fsSL "https://github.com/keycloak/keycloak/releases/download/${v}/keycloak-${v}.tar.gz.sha1") keycloak-${v}.tar.gz" | sha1sum -c - +sha256sum "keycloak-${v}.tar.gz" +``` + +The version is the release tag as it is, with no `v` (`26.7.5`). Pick +the one that matches your Keycloak server. + +The pin can come from three places. The feature uses the first one that +sets either the version or the sha256, and that place must set both: a +version in one place is never paired with a sha256 from another. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-keycloak:1": { + "version": "26.7.5", + "sha256": "87d680d13cf4600d232ca0433a2553c57ebf03e56f7718fc0d03a1a8705497c8" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the options from the +host's environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-keycloak:1": { + "version": "${localEnv:CLI_KEYCLOAK_VERSION}", + "sha256": "${localEnv:CLI_KEYCLOAK_SHA256}" +} +``` + +### 2. Environment variables in your image + +Set `CLI_KEYCLOAK_VERSION` and `CLI_KEYCLOAK_SHA256` (and, if you like, +`CLI_KEYCLOAK_FLAVOR`) with `ENV` in the Dockerfile your devcontainer +builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_KEYCLOAK_VERSION=26.7.5 +ENV CLI_KEYCLOAK_SHA256=87d680d13cf4600d232ca0433a2553c57ebf03e56f7718fc0d03a1a8705497c8 +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-keycloak:1": {} +} +``` + +### 3. A pins file in your image + +Put the same `KEY=value` lines in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection; each feature reads only its own keys. + +```sh +# pins.env +CLI_KEYCLOAK_FLAVOR=official +CLI_KEYCLOAK_VERSION=26.7.5 +CLI_KEYCLOAK_SHA256=87d680d13cf4600d232ca0433a2553c57ebf03e56f7718fc0d03a1a8705497c8 +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced: `KEY=value` lines (optionally after +`export`), `#` comments and quoted values are understood, and nothing is +expanded. To keep it somewhere else, set the `pins_file` option to its +path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-keycloak:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Java (`official`) + +`kcadm.sh` runs on the JVM, and Keycloak 26 needs Java 21 or newer. With +`install_jre` on (the default), the feature installs the distro's +`default-jre-headless`, which is OpenJDK 21 on Debian 13 and Ubuntu +24.04. + +After installing, the feature checks the java that `kcadm.sh` will run, +picked the way `kcadm.sh` picks it: `$JAVA` if set, else +`$JAVA_HOME/bin/java` if `JAVA_HOME` is set, else `java` on `PATH`. So a +`JAVA_HOME` in your image pointing at an older JDK takes precedence over +the JRE just installed. With `install_jre` on, a missing java, or one +older than 21, fails the install, naming the java it found. + +Set `install_jre` to `false` if your image already has Java, for example +from the official `java` feature. The same check then only warns, and +the install succeeds either way. **Java 21 or newer must still be +reachable that way when you run kcadm.** `install_jre` is ignored for +`rest`. + +## The `rest` wrapper + +`kcadm METHOD PATH [JSON]` logs in as the admin (password grant, the +`admin-cli` client, the `master` realm), sends one request to +`$KEYCLOAK_URL$PATH` with the token, and prints the reply: pretty-printed +if it is JSON, as it is otherwise. It reads at run time: + +| Variable | Meaning | +| --- | --- | +| `KEYCLOAK_URL` | the server's base URL, e.g. `http://keycloak:8080` | +| `KEYCLOAK_ADMIN` | the admin user name | +| `KEYCLOAK_ADMIN_PASSWORD` | its password | + +All three are required. If one is unset or empty, kcadm names it and +exits 1. + +```sh +kcadm GET /admin/realms +kcadm POST /admin/realms/myrealm/users '{"username": "alice", "enabled": true}' +``` + +- The login form is URL-encoded, so any password works. +- The password, the token and the JSON body reach curl through stdin or a + header file in a private temporary directory, never as arguments, so + `ps` doesn't show them. +- The body is sent byte for byte; a leading `@` is not read as a file. +- A failed login prints `kcadm: login failed: HTTP ` and the + server's reply. Any other reply outside 2xx prints + `kcadm: METHOD PATH: HTTP ` and the reply body, all on stderr, + and exits 1. A connection failure shows curl's error and exits 1. + +It is ported from the craftainer app templates' `develop.d` wrapper +(`template-fastapi`'s `scripts/develop.d/40-kcadm.sh`), with the same +arguments and variables. The differences: `KEYCLOAK_URL` no longer +defaults to `http://keycloak:8080`, and errors are reported as above +instead of exiting silently. + +## Install order with `ssl-ca` + +If you also use this collection's `ssl-ca` feature for a corporate root +CA, the CA has to be trusted before the Keycloak tarball is downloaded. +Installed first, it is also in the system trust store when +`default-jre-headless` is installed, which builds the JRE's `cacerts` +from that store (Debian's `ca-certificates-java`). This feature deliberately declares no `installsAfter` on +`ssl-ca`: the Dev Container CLI fetches every `installsAfter` reference +from ghcr.io, so declaring one would make every build reach ghcr.io for +`ssl-ca` even when you don't use it. If you use `ssl-ca`, list it first in +`overrideFeatureInstallOrder`: + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-keycloak" +] +``` + +## Credentials + +The feature installs only the CLI: it declares no mounts and stores no +state. Keep the admin credentials in `KEYCLOAK_*` environment variables +and pass them in from the host instead of baking them into the image: + +```jsonc +"containerEnv": { + "KEYCLOAK_URL": "${localEnv:KEYCLOAK_URL}", + "KEYCLOAK_ADMIN": "${localEnv:KEYCLOAK_ADMIN}", + "KEYCLOAK_ADMIN_PASSWORD": "${localEnv:KEYCLOAK_ADMIN_PASSWORD}" +} +``` + +or, with Docker Compose, from an `env_file` that stays out of version +control: + +```yaml +services: + devcontainer: + env_file: .secrets/keycloak.env +``` + +`rest` reads them itself. `official`'s `kcadm.sh` doesn't: pass them to +`config credentials`, which keeps its session token in +`~/.keycloak/kcadm.config` (gone after a rebuild, so log in again). +`kcadm.sh` takes the password from `KC_CLI_PASSWORD` when `--password` is +not given, which keeps it off the command line: + +```sh +KC_CLI_PASSWORD="$KEYCLOAK_ADMIN_PASSWORD" \ + kcadm.sh config credentials --server "$KEYCLOAK_URL" --realm master --user "$KEYCLOAK_ADMIN" +``` + +## OS support + +Debian and Ubuntu, on amd64 and arm64. `official` needs `tar`, installs +`curl` and `ca-certificates` if `curl` is missing, and installs +`default-jre-headless` unless `install_jre` is `false`. `rest` installs +`curl`, `ca-certificates` and `python3` if they are missing. + +## License + +Keycloak is Apache-2.0 ([LICENSE.txt](https://github.com/keycloak/keycloak/blob/main/LICENSE.txt)). `official` installs the notice from the release tarball as `/usr/local/share/doc/keycloak-cli/LICENSE.txt`; the client libraries carry their own notices inside their jars. `rest` is this collection's own script, under its GPL-3.0 license. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-keycloak/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-mosquitto/README.md b/src/cli-mosquitto/README.md new file mode 100644 index 0000000..55e7161 --- /dev/null +++ b/src/cli-mosquitto/README.md @@ -0,0 +1,278 @@ + +# Mosquitto MQTT clients (cli-mosquitto) + +Installs the Eclipse Mosquitto MQTT clients (mosquitto_pub, mosquitto_sub, mosquitto_rr) at an exact upstream version you choose, e.g. 2.0.22: on Debian from repo.mosquitto.org, whose signing key the feature pins by fingerprint, on Ubuntu from the distro's own archive. The versions available differ by distro. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-mosquitto:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Mosquitto clients version to install, as the upstream version (e.g. 2.0.22), with no leading v and no Debian revision. It must exist for your distro (see the notes). Set it here, or as CLI_MOSQUITTO_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set the version. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## What it installs + +The Eclipse Mosquitto MQTT clients from the `mosquitto-clients` package, at +the exact upstream version you pin: `mosquitto_pub`, `mosquitto_sub` and +`mosquitto_rr`, in `/usr/bin`. Their library, `libmosquitto1`, is installed +at the same version. No broker is installed. + +Mosquitto publishes no prebuilt Linux binaries, so the package comes from +apt, and where it comes from depends on the distro: + +- **Debian:** from [repo.mosquitto.org](https://repo.mosquitto.org/debian/README.txt), + the Mosquitto project's own repository, which carries exact 2.0.x and + 2.1.x builds. The feature adds its ` main` suite. +- **Ubuntu:** from Ubuntu's own archive (`universe`). No repository is + added. + +Any other distribution fails the install, including Debian or Ubuntu +derivatives with another `ID` in `/etc/os-release`. + +After installing, the feature checks that all three clients are on `PATH` +and that each reports the pinned version, on a `libmosquitto` of the same +version. + +## Which versions exist where + +**A pin is distro-specific.** The two sources carry different versions, so +a pin that installs on Debian can fail on Ubuntu, and the other way round. +As of 2026-10-28: + +| Distro | Source | Versions (`mosquitto-clients`) | +| --- | --- | --- | +| Debian 13 (trixie) | repo.mosquitto.org, `trixie main` | 2.0.22, 2.1.0, 2.1.1, 2.1.2 (revisions `0mosquittoN~trixie1`) | +| Debian 13 (trixie) | Debian's own archive | 2.0.21 (`2.0.21-1`) | +| Ubuntu 24.04 (noble) | Ubuntu's archive, `universe` | 2.0.18 (`2.0.18-1build3`) | + +Sources: repo.mosquitto.org's +[`dists/trixie/main/binary-amd64/Packages`](https://repo.mosquitto.org/debian/dists/trixie/main/binary-amd64/Packages) +and `binary-arm64/Packages` (the same versions on both arches), Debian's +[madison for mosquitto-clients](https://qa.debian.org/madison.php?package=mosquitto-clients&text=on), +and Launchpad's +[publishing history for noble](https://launchpad.net/ubuntu/+source/mosquitto/+publishinghistory) +(no newer build in `noble-updates` or `noble-security`). +repo.mosquitto.org serves `bookworm`, `bullseye` and `trixie` (and the +frozen `buster` and `stretch`); see its +[list of suites](https://repo.mosquitto.org/debian/dists/). Check these +before you choose a pin: the lists change as new versions are released. + +So `2.0.22` (or `2.1.2`) works on Debian 13 and `2.0.18` on Ubuntu 24.04. +If your team builds on both, keep one pin per image, for example in each +image's pins file. + +### Several sources, one version: the highest Debian revision wins + +On Debian, both repo.mosquitto.org and Debian's archive are configured. +When both carry the same upstream version, the feature installs the +**highest Debian revision**, whichever source it comes from +([ADR 0003](../../docs/adrs/0003-pin-apt-installed-clis-by-upstream-version-with-a-verified-repo-key.md)). +It ignores apt pin priorities. For example, if a Debian point release +shipped `2.0.22-1`, the distro's `2.0.22-1` would win over +repo.mosquitto.org's `2.0.22-0mosquitto1~trixie1`. Today the two don't +overlap: repo.mosquitto.org has no 2.0.21 for trixie, so a `2.0.21` pin on +Debian 13 installs Debian's own `2.0.21-1`. When one source carries several +rebuilds of a version (repo.mosquitto.org has 2.1.0 up to +`0mosquitto4~trixie1`), the highest +is installed as well, and a later rebuild is picked up by the next build +without a pin change. + +### When a pin doesn't resolve + +A version your distro's sources don't have (e.g. `2.0.99`, or `2.0.22` on +Ubuntu 24.04) fails, and the error lists the versions available. The +feature never downgrades: if the image already has a newer +`mosquitto-clients` or `libmosquitto1`, apt refuses the install, and the +feature fails rather than forcing it. + +A pinned version keeps resolving as long as a source still carries it. +Ubuntu's release pocket (`noble`) is frozen at release, so `2.0.18` stays +available there. A security update to it would land in `noble-security` +and `noble-updates` as a newer revision of the same upstream version +(say `2.0.18-1ubuntu0.1`), which the feature would then install as the +highest revision; those two pockets keep only their newest revision. +Debian's stable archive replaces a package at each point release, so a +version only Debian's archive carries (today `2.0.21` on trixie) can stop +resolving when a point release moves to a new upstream version. The +feature uses no snapshot archive (snapshot.debian.org or +snapshot.ubuntu.com) to keep old revisions installable. + +## Pinning the version + +This feature has no default version. You choose the Mosquitto release, +and the install fails unless it finds one. + +The version is the **upstream** version: digits and dots, e.g. `2.0.22`. It +has no leading `v` and no Debian revision. `v2.0.22`, +`2.0.22-0mosquitto1~trixie1` and `2.0.` are rejected. + +The pin can come from three places. The feature uses the first one that +sets it. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-mosquitto:1": { + "version": "2.0.22" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the option from the host's +environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-mosquitto:1": { + "version": "${localEnv:CLI_MOSQUITTO_VERSION}" +} +``` + +### 2. An environment variable in your image + +Set `CLI_MOSQUITTO_VERSION` with `ENV` in the Dockerfile your devcontainer +builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_MOSQUITTO_VERSION=2.0.22 +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-mosquitto:1": {} +} +``` + +### 3. A pins file in your image + +Put a `KEY=value` line in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection, and each feature reads only its own keys. + +```sh +# pins.env +CLI_MOSQUITTO_VERSION=2.0.22 +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced. The feature understands `KEY=value` +lines (optionally after `export`), `#` comments and quoted values, and +expands nothing. To keep the file somewhere else, set the `pins_file` +option to its path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-mosquitto:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Trust model: no sha + +Unlike this collection's download features, you supply no sha256 here. +The `.deb` differs per distro, arch and source, so a sha pin would need a +set of shas nobody can keep current. Instead, apt checks every package +against its repository's signed index +([ADR 0003](../../docs/adrs/0003-pin-apt-installed-clis-by-upstream-version-with-a-verified-repo-key.md)): + +- **Debian:** the feature pins repo.mosquitto.org's signing key by a + fingerprint constant, `8277CCB49EC5B595F2D2C71361611AE430993623`, as the + official devcontainers features do. It fetches + `https://repo.mosquitto.org/debian/mosquitto-repo.gpg` over https and + checks that the exact key bytes it installs hold one primary key with + that fingerprint. Then it writes the key to + `/etc/apt/keyrings/mosquitto.gpg`, and + `/etc/apt/sources.list.d/mosquitto.list` trusts only that key file + (`signed-by`). A different key fails the install before anything is + written. If the Mosquitto project rotates its key, the feature needs a + new release. Debian's own archive stays trusted through the image's + own Debian keyring, as before. +- **Ubuntu:** nothing is added. The package comes from Ubuntu's archive, + signed with the Ubuntu archive key the image already trusts. + +On Debian, the install installs `curl`, `ca-certificates` and `gpg` if any +of them is missing, and leaves them in the image. + +## Install order with `ssl-ca` + +On Debian, repo.mosquitto.org is reached over https. Behind a +TLS-intercepting proxy, your corporate root CA must be trusted before this +feature runs. This feature deliberately declares no `installsAfter` on +`ssl-ca`: the Dev Container CLI fetches every `installsAfter` reference +from ghcr.io, so declaring one would make every build reach ghcr.io for +`ssl-ca`, even when you don't use it. If you use this collection's +`ssl-ca` feature, list it first in `overrideFeatureInstallOrder`: + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-mosquitto" +] +``` + +## Credentials + +The feature installs only the clients. It declares no mounts and stores no +state. The clients read **no environment variables** for the broker or +its credentials (only `XDG_CONFIG_HOME` and `HOME`, to find their config +file). Give them on the command line, with `-h`, `-p`, `-u` (`--username`) +and `-P` (`--pw`), or as one `-L mqtt://user:password@host:port/topic` URL. +Pass the values in from the host instead of baking them into the image: + +```jsonc +"containerEnv": { + "MQTT_HOST": "mqtt", + "MQTT_USERNAME": "${localEnv:MQTT_USERNAME}", + "MQTT_PASSWORD": "${localEnv:MQTT_PASSWORD}" +} +``` + +```sh +mosquitto_sub -h "$MQTT_HOST" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t 'sensors/#' +``` + +or, with Docker Compose, from an `env_file` that stays out of version +control (`env_file: .secrets/mqtt.env`). + +A password given with `-P` is visible to other processes in the container +(`ps`). To keep it off the command line, use the clients' config file. The +[mosquitto_pub man page](https://mosquitto.org/man/mosquitto_pub-1.html) +says: + +> The options below may be given on the command line, but may also be +> placed in a config file located at `$XDG_CONFIG_HOME/mosquitto_pub` or +> `$HOME/.config/mosquitto_pub` with one pair of `-option value` per line. +> The values in the config file will be used as defaults and can be +> overridden by using the command line. + +Each client reads its own file: `mosquitto_pub`, `mosquitto_sub` and +`mosquitto_rr` under `~/.config/` (or `$XDG_CONFIG_HOME`). Write them in a +`postCreateCommand`, from the variables above, with mode `0600`: + +```jsonc +"postCreateCommand": "mkdir -p ~/.config && for t in mosquitto_pub mosquitto_sub mosquitto_rr; do (umask 077 && printf -- '-h %s\\n-u %s\\n-P %s\\n' \"$MQTT_HOST\" \"$MQTT_USERNAME\" \"$MQTT_PASSWORD\" >~/.config/$t); done" +``` + +## OS support + +Debian 13 (trixie) and Ubuntu 24.04 (noble), on amd64 and arm64. On Debian +the suite is chosen from `VERSION_CODENAME` in `/etc/os-release`; a release +repo.mosquitto.org doesn't serve fails the install with a pointer to +[its list of suites](https://repo.mosquitto.org/debian/dists/). On Ubuntu +any release works whose archive carries the version you pin. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-mosquitto/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-opencode/README.md b/src/cli-opencode/README.md new file mode 100644 index 0000000..01e2c7c --- /dev/null +++ b/src/cli-opencode/README.md @@ -0,0 +1,312 @@ + +# opencode (cli-opencode) + +Installs the opencode AI coding agent (anomalyco/opencode) at an exact version, checked against a sha256 you supply, with its update checks turned off. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-opencode:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | opencode version to install, without the v of the release tag (e.g. 1.19.5). Set it with its sha256 here, or as CLI_OPENCODE_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| sha256_amd64 | sha256 of the release's opencode-linux-x64.tar.gz, needed on amd64. Set it here, or as CLI_OPENCODE_SHA256_AMD64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| sha256_arm64 | sha256 of the release's opencode-linux-arm64.tar.gz, needed on arm64. Set it here, or as CLI_OPENCODE_SHA256_ARM64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set a pin. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## What it installs + +The [opencode](https://opencode.ai) AI coding agent from the +[`anomalyco/opencode`](https://github.com/anomalyco/opencode) releases +(formerly `sst/opencode`), as `/usr/local/bin/opencode`. The feature: + +- downloads + `https://github.com/anomalyco/opencode/releases/download/v/opencode-linux-{x64,arm64}.tar.gz`, + checks it against your sha256 and installs the `opencode` binary from + it; +- creates `~/.config/opencode` and `~/.local/share/opencode` for the + remote user, mode `0700` (see + [Keeping the login across rebuilds](#keeping-the-login-across-rebuilds)); +- sets `OPENCODE_DISABLE_AUTOUPDATE=1` in the container environment, so + opencode doesn't check for or offer updates away from your pin (see + [Bumping the version](#bumping-the-version)). + +It declares no mounts and sets no state-path variable such as +`OPENCODE_CONFIG_DIR`: where opencode's state and credentials live is your +choice, not the feature's. + +The x64 build is the one opencode's own installer +(`https://opencode.ai/install`) picks for a CPU with AVX2. The releases +also carry `opencode-linux-x64-baseline.tar.gz` for x64 CPUs without +AVX2, and `*-musl` builds for Alpine; this feature installs neither. + +## Pinning the version + +This feature has no default version. You choose the opencode release and +supply the sha256 of its tarball for each arch you build on, and the +install fails unless it finds both. The download is always checked against +your sha256. + +opencode doesn't publish a checksums file, but GitHub shows each release +asset's sha256 (its `digest`), and you can compute it from the download +yourself: + +```sh +VERSION=1.19.5 +gh release view "v${VERSION}" --repo anomalyco/opencode --json assets \ + --jq '.assets[] | select(.name == "opencode-linux-x64.tar.gz" or .name == "opencode-linux-arm64.tar.gz") | "\(.name) \(.digest | ltrimstr("sha256:"))"' +# or +for a in x64 arm64; do + printf '%s ' "opencode-linux-${a}.tar.gz" + curl -fsSL "https://github.com/anomalyco/opencode/releases/download/v${VERSION}/opencode-linux-${a}.tar.gz" | sha256sum +done +``` + +| Arch | Asset | +| --- | --- | +| amd64 | `opencode-linux-x64.tar.gz` | +| arm64 | `opencode-linux-arm64.tar.gz` | + +The version is the release tag without its `v` (`1.19.5` for tag +`v1.19.5`); a leading `v` is rejected. You only need the sha for the +arches you actually build on. + +The pin can come from three places. The feature uses the first one that +sets either the version or a sha, and that place must set both: a version +in one place is never paired with a sha from another. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-opencode:1": { + "version": "1.19.5", + "sha256_amd64": "588310a336fa3b9df36b3f0225b26f679d453efaad0cf3d66c3651ef3ec1647a", + "sha256_arm64": "fae5be262d693b7c5e562856cc473e59e741772936db0c0600ac0d5ed5bd088b" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the options from the +host's environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-opencode:1": { + "version": "${localEnv:CLI_OPENCODE_VERSION}", + "sha256_amd64": "${localEnv:CLI_OPENCODE_SHA256_AMD64}", + "sha256_arm64": "${localEnv:CLI_OPENCODE_SHA256_ARM64}" +} +``` + +### 2. Environment variables in your image + +Set `CLI_OPENCODE_VERSION`, `CLI_OPENCODE_SHA256_AMD64` and +`CLI_OPENCODE_SHA256_ARM64` with `ENV` in the Dockerfile your devcontainer +builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_OPENCODE_VERSION=1.19.5 +ENV CLI_OPENCODE_SHA256_AMD64=588310a336fa3b9df36b3f0225b26f679d453efaad0cf3d66c3651ef3ec1647a +ENV CLI_OPENCODE_SHA256_ARM64=fae5be262d693b7c5e562856cc473e59e741772936db0c0600ac0d5ed5bd088b +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-opencode:1": {} +} +``` + +### 3. A pins file in your image + +Put the same `KEY=value` lines in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection; each feature reads only its own keys. + +```sh +# pins.env +CLI_OPENCODE_VERSION=1.19.5 +CLI_OPENCODE_SHA256_AMD64=588310a336fa3b9df36b3f0225b26f679d453efaad0cf3d66c3651ef3ec1647a +CLI_OPENCODE_SHA256_ARM64=fae5be262d693b7c5e562856cc473e59e741772936db0c0600ac0d5ed5bd088b +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced: `KEY=value` lines (optionally after +`export`), `#` comments and quoted values are understood, and nothing is +expanded. To keep it somewhere else, set the `pins_file` option to its +path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-opencode:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Bumping the version + +opencode checks for new versions in the background and shows a notice +when one is available; it doesn't install updates by itself, but `/update` +in the TUI and `opencode upgrade` do +([config: Autoupdate](https://opencode.ai/docs/config/#autoupdate)). This +feature sets `OPENCODE_DISABLE_AUTOUPDATE=1` in `containerEnv`, the +documented switch that "disable[s] update checks and notices" +([CLI: Environment variables](https://opencode.ai/docs/cli/#environment-variables)). +It has the same effect as `"autoupdate": false` in `opencode.json`, without +the feature writing your config. + +To move to a newer release, change the pin: put the new version and its +two shas wherever you set the pin, and rebuild the container. Don't use +`opencode upgrade` or `/update` in this container. They don't replace +`/usr/local/bin/opencode`; depending on what they detect they fail, or +(with `--method curl`, or an `opencode-ai` npm package installed globally) +install a second, unpinned copy elsewhere, such as `~/.opencode/bin`, that +can shadow the pinned one on `PATH`. + +## Keeping the login across rebuilds + +opencode keeps its global config (`opencode.json`, agents, plugins) in +`~/.config/opencode`, and its data in `~/.local/share/opencode`: the +provider credentials you add with `/connect` in `auth.json`, session and +message history, and logs +([Troubleshooting: Storage](https://opencode.ai/docs/troubleshooting/#storage), +[Providers: Credentials](https://opencode.ai/docs/providers/#credentials)). +It also uses `~/.cache/opencode` (downloaded plugins and tools, which +opencode rebuilds when the directory is deleted) and +`~/.local/state/opencode` (run-time state); the docs put no credentials +in either, and this feature doesn't create them. A +rebuild discards all of them, so you sign in again each time unless you +choose one of the options below. + +The feature declares no mount: a feature's mounts can't be switched off, +and whether credentials sit in a volume, and whether projects share one, +is a security choice that belongs to you. What it does is create +`~/.config/opencode` and `~/.local/share/opencode` in the image, owned by +the remote user with mode `0700`. Docker seeds an empty named volume with +the ownership of the directory it is mounted on, so volumes you mount +there start out writable by the remote user instead of owned by root. + +In the snippets below, replace `/home/vscode` with your `remoteUser`'s +home directory (`/root` if the container runs as root). The config volume +is optional: if you keep `opencode.json` in your repository or dotfiles +instead, mount only the data directory. + +### (a) Volumes per project + +Each devcontainer gets its own volumes, named after its +`${devcontainerId}`. A login in one project isn't visible to another. + +```jsonc +"mounts": [ + "source=opencode-data-${devcontainerId},target=/home/vscode/.local/share/opencode,type=volume", + "source=opencode-config-${devcontainerId},target=/home/vscode/.config/opencode,type=volume" +] +``` + +### (b) Volumes shared by all projects + +Sign in once and every devcontainer that mounts the same volume is signed +in. The trade-off: every container that mounts it can read the same +credentials, including a container for a repository you trust less. + +```jsonc +"mounts": [ + "source=opencode-data,target=/home/vscode/.local/share/opencode,type=volume", + "source=opencode-config,target=/home/vscode/.config/opencode,type=volume" +] +``` + +### (c) No volume, API keys from the host + +Keep no credentials in the container at all and pass the provider's API +key in from the host's environment on every start. opencode loads a +provider whose API-key variable is set, without `/connect`; the variable +names come from [models.dev](https://models.dev), which opencode uses for +its provider list. Common ones: + +| Provider | Variable | +| --- | --- | +| Anthropic | `ANTHROPIC_API_KEY` | +| OpenAI | `OPENAI_API_KEY` | +| Google (Gemini) | `GOOGLE_GENERATIVE_AI_API_KEY` or `GEMINI_API_KEY` | +| OpenRouter | `OPENROUTER_API_KEY` | +| Amazon Bedrock | `AWS_BEARER_TOKEN_BEDROCK`, or the usual `AWS_*` credentials | + +The [providers](https://opencode.ai/docs/providers/) page lists the rest. +History doesn't survive a rebuild, but nothing secret is stored in a +volume, and revoking the key on the host side is enough. **This is the +recommended option for enterprise use.** + +```jsonc +"containerEnv": { + "ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}", + "OPENAI_API_KEY": "${localEnv:OPENAI_API_KEY}" +} +``` + +## Install order + +This feature deliberately declares no `installsAfter`: the Dev Container +CLI fetches every `installsAfter` reference from ghcr.io, so declaring one +would make every build reach ghcr.io for features you may not use. Set +the order yourself with `overrideFeatureInstallOrder` in these two cases. + +- **`ssl-ca`.** If you use this collection's `ssl-ca` feature for a + corporate root CA, the CA has to be trusted before opencode is + downloaded, so list `ssl-ca` first. +- **A feature that creates the remote user**, such as + `ghcr.io/devcontainers/features/common-utils` on an image without that + user. This feature creates its state directories in the remote user's + home, and the install fails if that home doesn't exist yet, so list the + user-creating feature before this one. + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-opencode" +] +``` + +## Corporate CAs at run time + +opencode is a single [Bun](https://bun.com) executable, with its own +bundled root CAs. Its [network](https://opencode.ai/docs/network/#custom-certificates) +docs name `NODE_EXTRA_CA_CERTS` for trusting a custom CA ("This works for +both proxy connections and direct API access"). Bun also honours +`NODE_USE_SYSTEM_CA=1`, which adds the operating system's trust store, and +`SSL_CERT_FILE` / `SSL_CERT_DIR`, which add CA certificates on top of the +bundled ones +([Bun environment variables](https://bun.com/docs/runtime/env)). `ssl-ca` +sets all four in `containerEnv`: `NODE_EXTRA_CA_CERTS` and `SSL_CERT_FILE` +to the system bundle `/etc/ssl/certs/ca-certificates.crt` it updates, +`SSL_CERT_DIR` to `/etc/ssl/certs`, and `NODE_USE_SYSTEM_CA` to `1` (the +full list of twelve is in its NOTES.md). So a root CA it installs is +trusted with no further setting. Without `ssl-ca`, set +`NODE_EXTRA_CA_CERTS` to your CA's PEM file in `containerEnv`. + +opencode's TUI talks to a local HTTP server, so if you set `HTTPS_PROXY` +or `HTTP_PROXY`, also set `NO_PROXY=localhost,127.0.0.1` (same page). + +## OS support + +Debian and Ubuntu (glibc), on amd64 and arm64. The x64 build needs a CPU +with AVX2. The install adds `curl` and `ca-certificates` if either is +missing. + +## License + +opencode is MIT-licensed ([LICENSE](https://github.com/anomalyco/opencode/blob/dev/LICENSE)). Its release tarball holds only the binary, so the feature installs no license file. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-opencode/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-postgres-client/README.md b/src/cli-postgres-client/README.md new file mode 100644 index 0000000..9298141 --- /dev/null +++ b/src/cli-postgres-client/README.md @@ -0,0 +1,214 @@ + +# PostgreSQL client (psql) (cli-postgres-client) + +Installs the PostgreSQL client (psql, pg_dump, pg_restore) from the PostgreSQL apt repository (PGDG) at an exact upstream version you choose, e.g. 18.6. apt checks the packages against PGDG's signing key, which the feature pins by fingerprint. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-postgres-client:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | PostgreSQL client version to install, as the upstream version (e.g. 18.6), with no leading v and no Debian revision. Set it here, or as CLI_POSTGRES_CLIENT_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set the version. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## What it installs + +The PostgreSQL client package `postgresql-client-` (e.g. +`postgresql-client-18`) from the +[PostgreSQL apt repository (PGDG)](https://wiki.postgresql.org/wiki/Apt), +at the exact upstream version you pin. That gives you `psql`, `pg_dump`, +`pg_restore`, `pg_dumpall`, `pg_isready` and the other client tools. Debian's +`postgresql-client-common` puts them on `PATH` as `/usr/bin/psql` and so on. +These are `pg_wrapper` links, which run the newest client major installed. +`libpq5` comes from PGDG as well. No server is installed. + +The feature adds two PGDG suites, both signed with the same key: + +- `apt.postgresql.org`, `-pgdg`: the main suite. It keeps only the + latest few versions of each major. +- `apt-archive.postgresql.org`, `-pgdg-archive`: the archive. It + keeps every version PGDG has published, and every rebuild. Your pin keeps + working after the main suite has moved on. + +The archive's package index is large: each install downloads about 3.6 MB +more (about 21 MB unpacked). The apt lists are deleted afterwards, so none +of it stays in the image. + +After installing, the feature checks that `psql`, `pg_dump` and +`pg_restore` on `PATH` report the pinned version. If you already have a +newer client major in the image, `pg_wrapper` runs that one instead, and +the install fails. + +## Pinning the version + +This feature has no default version. You choose the PostgreSQL release, +and the install fails unless it finds one. Pick the one that matches your +server, e.g. `18.6` for a Postgres 18.6 server. + +The version is the **upstream** version: digits and dots, e.g. `18.6`. It +has no leading `v` and no Debian revision. `v18.6`, `18.6-1.pgdg13+2` and +`18.` are rejected. The major (`18`) picks the package +(`postgresql-client-18`). The feature then installs the `18.6-` +build PGDG has for your distro, so one pin works on every distro and arch +PGDG builds for. When PGDG has several revisions of that version, the +highest Debian revision is installed. For example, the archive carries both +`17.6-1` and `17.6-2`, and the feature installs `17.6-2`. A PGDG rebuild of +your version is therefore picked up silently at the next build (see +[ADR 0003](../../docs/adrs/0003-pin-apt-installed-clis-by-upstream-version-with-a-verified-repo-key.md)). + +A version PGDG doesn't have (e.g. `18.99`) fails, and the error lists the +versions available. The feature never downgrades: if the image already has +a newer build of the same package, apt refuses the install, and the +feature fails rather than forcing it. + +The pin can come from three places. The feature uses the first one that +sets it. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-postgres-client:1": { + "version": "18.6" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the option from the host's +environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-postgres-client:1": { + "version": "${localEnv:CLI_POSTGRES_CLIENT_VERSION}" +} +``` + +### 2. An environment variable in your image + +Set `CLI_POSTGRES_CLIENT_VERSION` with `ENV` in the Dockerfile your +devcontainer builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_POSTGRES_CLIENT_VERSION=18.6 +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-postgres-client:1": {} +} +``` + +### 3. A pins file in your image + +Put a `KEY=value` line in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection, and each feature reads only its own keys. + +```sh +# pins.env +CLI_POSTGRES_CLIENT_VERSION=18.6 +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced. The feature understands `KEY=value` +lines (optionally after `export`), `#` comments and quoted values, and +expands nothing. To keep the file somewhere else, set the `pins_file` +option to its path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-postgres-client:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Trust model: no sha + +Unlike this collection's download features, you supply no sha256 here. +PGDG builds a different `.deb` for each distro and arch, and so do its +dependencies such as `libpq5`. A sha pin would therefore need a whole set +of shas that nobody can keep current. Instead, apt checks every package +against the repository's signed index, so the thing to trust is PGDG's +signing key. + +The feature pins that key by a fingerprint constant, +`B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8`, as the official devcontainers +features do. It fetches +`https://www.postgresql.org/media/keys/ACCC4CF8.asc` over https and checks +that the exact key bytes it installs hold one primary key with that +fingerprint. Then it writes the key to `/etc/apt/keyrings/pgdg.gpg` and +`/etc/apt/keyrings/pgdg-archive.gpg`. Each `/etc/apt/sources.list.d/*.list` +entry trusts only its own key file (`signed-by`). A different key fails +the install before anything is written. If PGDG rotates its key, the +feature needs a new release. See +[ADR 0003](../../docs/adrs/0003-pin-apt-installed-clis-by-upstream-version-with-a-verified-repo-key.md). + +The install installs `curl`, `ca-certificates` and `gpg` if any of them is +missing, and leaves them in the image. + +## Install order with `ssl-ca` + +PGDG is reached over https. Behind a TLS-intercepting proxy, your corporate +root CA must be trusted before this feature runs. This feature deliberately +declares no `installsAfter` on `ssl-ca`: the Dev Container CLI fetches every +`installsAfter` reference from ghcr.io, so declaring one would make every +build reach ghcr.io for `ssl-ca`, even when you don't use it. If you use +this collection's `ssl-ca` feature, list it first in +`overrideFeatureInstallOrder`: + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-postgres-client" +] +``` + +## Credentials + +The feature installs only the client. It declares no mounts and stores no +state. Point the tools at your server with libpq's +[environment variables](https://www.postgresql.org/docs/current/libpq-envars.html): +`PGHOST`, `PGPORT`, `PGUSER`, `PGDATABASE`, and `PGPASSWORD` or +`PGPASSFILE`. A password file (`~/.pgpass` format, mode `0600`) keeps the +password out of the environment of every process. Pass them in from the +host instead of baking them into the image: + +```jsonc +"containerEnv": { + "PGHOST": "db", + "PGUSER": "${localEnv:PGUSER}", + "PGPASSWORD": "${localEnv:PGPASSWORD}" +} +``` + +or, with Docker Compose, from an `env_file` that stays out of version +control: + +```yaml +services: + devcontainer: + env_file: .secrets/postgres.env +``` + +## OS support + +Debian 13 (trixie) and Ubuntu 24.04 (noble) or later, on amd64 and arm64: +any release PGDG builds for. The suite is chosen from `VERSION_CODENAME` in +`/etc/os-release`. A release PGDG doesn't serve fails the install with a +pointer to +[PGDG's list of suites](https://apt.postgresql.org/pub/repos/apt/dists/). + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-postgres-client/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-rustfs/README.md b/src/cli-rustfs/README.md new file mode 100644 index 0000000..406d57c --- /dev/null +++ b/src/cli-rustfs/README.md @@ -0,0 +1,195 @@ + +# RustFS CLI (rc) (cli-rustfs) + +Installs rc, RustFS's S3 client, from the upstream .deb at an exact version, checked against a sha256 you supply. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-rustfs:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | rc (rustfs-cli) version to install, without the leading v (e.g. 0.1.36). Set it with its sha256 here, or as CLI_RUSTFS_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| sha256_amd64 | sha256 of rustfs-cli__amd64.deb, needed on amd64. Set it here, or as CLI_RUSTFS_SHA256_AMD64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| sha256_arm64 | sha256 of rustfs-cli__arm64.deb, needed on arm64. Set it here, or as CLI_RUSTFS_SHA256_ARM64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set a pin. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## Pinning the version + +This feature has no default version. You choose the rc release and supply +the sha256 of its `.deb` for each arch you build on, and the install fails +unless it finds both. The download is always checked against your sha256; +the feature never fetches upstream's checksum file, so the sha you review +is the one that counts. + +Get the shas from the release's `SHA256SUMS` on +[github.com/rustfs/cli/releases](https://github.com/rustfs/cli/releases): + +| Arch | Package | +| --- | --- | +| amd64 | `rustfs-cli__amd64.deb` | +| arm64 | `rustfs-cli__arm64.deb` | + +The version is the release number without the leading `v` (`0.1.36`, not +`v0.1.36`). You only need the sha for the arches you actually build on. +The package installs the statically linked `rc` binary as `/usr/bin/rc`, +with shell completions. + +The pin can come from three places. The feature uses the first one that +sets either the version or a sha, and that place must set both: a version +in one place is never paired with a sha from another. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-rustfs:1": { + "version": "0.1.36", + "sha256_amd64": "2eb2d646b04206112c44d65ad5107edf1d164fc93f705c978397bc11eb29a95f", + "sha256_arm64": "c5ad321059d6478994ee7f07abe131b81def0a09352f08df6eb982c612659ccb" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the options from the +host's environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-rustfs:1": { + "version": "${localEnv:CLI_RUSTFS_VERSION}", + "sha256_amd64": "${localEnv:CLI_RUSTFS_SHA256_AMD64}", + "sha256_arm64": "${localEnv:CLI_RUSTFS_SHA256_ARM64}" +} +``` + +### 2. Environment variables in your image + +Set `CLI_RUSTFS_VERSION`, `CLI_RUSTFS_SHA256_AMD64` and +`CLI_RUSTFS_SHA256_ARM64` with `ENV` in the Dockerfile your devcontainer +builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_RUSTFS_VERSION=0.1.36 +ENV CLI_RUSTFS_SHA256_AMD64=2eb2d646b04206112c44d65ad5107edf1d164fc93f705c978397bc11eb29a95f +ENV CLI_RUSTFS_SHA256_ARM64=c5ad321059d6478994ee7f07abe131b81def0a09352f08df6eb982c612659ccb +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-rustfs:1": {} +} +``` + +### 3. A pins file in your image + +Put the same `KEY=value` lines in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection; each feature reads only its own keys. + +```sh +# pins.env +CLI_RUSTFS_VERSION=0.1.36 +CLI_RUSTFS_SHA256_AMD64=2eb2d646b04206112c44d65ad5107edf1d164fc93f705c978397bc11eb29a95f +CLI_RUSTFS_SHA256_ARM64=c5ad321059d6478994ee7f07abe131b81def0a09352f08df6eb982c612659ccb +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced: `KEY=value` lines (optionally after +`export`), `#` comments and quoted values are understood, and nothing is +expanded. To keep it somewhere else, set the `pins_file` option to its +path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-rustfs:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Install order with `ssl-ca` + +If you also use this collection's `ssl-ca` feature for a corporate root +CA, the CA has to be trusted before the rc package is downloaded. This +feature deliberately declares no `installsAfter` on `ssl-ca`: the Dev +Container CLI fetches every `installsAfter` reference from ghcr.io, so +declaring one would make every build reach ghcr.io for `ssl-ca` even when +you don't use it. If you use `ssl-ca`, list it first in +`overrideFeatureInstallOrder`: + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-rustfs" +] +``` + +## Credentials + +The feature installs only the package: it declares no mounts and stores no +state. rc doesn't read the `AWS_*` environment variables +(`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and so on). Each command uses +the endpoint and keys of an alias, which `rc alias set` stores in rc's own +config file, `~/.config/rc/config.toml` (or `$RC_CONFIG_DIR/config.toml` +when `RC_CONFIG_DIR` is set). + +Rather than keeping that file in a volume, create the alias each time the +container is created, from variables you pass in from the host. The +variable names below are only examples; rc doesn't read them itself. + +```jsonc +"containerEnv": { + "RUSTFS_ENDPOINT": "${localEnv:RUSTFS_ENDPOINT}", + "RUSTFS_ACCESS_KEY": "${localEnv:RUSTFS_ACCESS_KEY}", + "RUSTFS_SECRET_KEY": "${localEnv:RUSTFS_SECRET_KEY}" +}, +"postCreateCommand": "rc alias set local \"$RUSTFS_ENDPOINT\" \"$RUSTFS_ACCESS_KEY\" \"$RUSTFS_SECRET_KEY\"" +``` + +With Docker Compose, the same variables can come from an `env_file` that +stays out of version control: + +```yaml +services: + devcontainer: + env_file: .secrets/s3.env +``` + +`rc alias set` checks the keys against the endpoint (a ListBuckets call) +before saving the alias, and saves nothing if the endpoint can't be +reached. So the S3 service has to be up when the command runs: with Docker +Compose, make the devcontainer service `depends_on` it with +`condition: service_healthy`. If the alias is missing, run the same +command again once the service is up. `rc alias set` replaces an alias of +the same name, so the command is safe to run on every create. See +`rc alias set --help` for `--region`, `--bucket-lookup`, `--insecure` and +`--ca-bundle`. + +## OS support + +Debian and Ubuntu, on amd64 and arm64. The package's binary is statically +linked, so it needs no particular glibc. The install runs `apt-get update` +and `apt-get install` for the downloaded `.deb`, and installs `curl` and +`ca-certificates` first if either is missing. + +**`/usr/bin/rc` clashes with Debian's `rc` package** (the Plan 9 `rc` +shell), which installs a file at the same path, so on an image that has +that package installed this feature's install fails. Remove the `rc` +shell from the image first if you need RustFS's `rc`. + +## License + +rc is dual-licensed MIT or Apache-2.0 ([LICENSE-MIT](https://github.com/rustfs/cli/blob/main/LICENSE-MIT), [LICENSE-APACHE](https://github.com/rustfs/cli/blob/main/LICENSE-APACHE)); the feature installs both notices from the `.deb` as `/usr/local/share/doc/rustfs-cli/LICENSE-MIT` and `LICENSE-APACHE`, alongside the package's own `/usr/share/doc/rustfs-cli/` copies (which images such as `ubuntu:24.04` drop). + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-rustfs/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-s5cmd/README.md b/src/cli-s5cmd/README.md new file mode 100644 index 0000000..8f75702 --- /dev/null +++ b/src/cli-s5cmd/README.md @@ -0,0 +1,171 @@ + +# s5cmd (cli-s5cmd) + +Installs s5cmd, a fast S3 and local filesystem client, at an exact version, checked against a sha256 you supply. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-s5cmd:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | s5cmd version to install, without the leading v (e.g. 2.3.0). Set it with its sha256 here, or as CLI_S5CMD_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| sha256_amd64 | sha256 of s5cmd__Linux-64bit.tar.gz, needed on amd64. Set it here, or as CLI_S5CMD_SHA256_AMD64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| sha256_arm64 | sha256 of s5cmd__Linux-arm64.tar.gz, needed on arm64. Set it here, or as CLI_S5CMD_SHA256_ARM64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set a pin. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## Pinning the version + +This feature has no default version. You choose the s5cmd release and +supply the sha256 of its tarball for each arch you build on, and the +install fails unless it finds both. The download is always checked against +your sha256; the feature never fetches upstream's checksum file, so the +sha you review is the one that counts. + +Get the shas from the release's `s5cmd_checksums.txt` on +[github.com/peak/s5cmd/releases](https://github.com/peak/s5cmd/releases): + +| Arch | Tarball | +| --- | --- | +| amd64 | `s5cmd__Linux-64bit.tar.gz` | +| arm64 | `s5cmd__Linux-arm64.tar.gz` | + +The version is the release number without the leading `v` (`2.3.0`, not +`v2.3.0`). You only need the sha for the arches you actually build on. + +The pin can come from three places. The feature uses the first one that +sets either the version or a sha, and that place must set both: a version +in one place is never paired with a sha from another. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-s5cmd:1": { + "version": "2.3.0", + "sha256_amd64": "de0fdbfa3aceae55e069ba81a0fc17b2026567637603734a387b2fca06c299b4", + "sha256_arm64": "1439f0d00ecedcd2a2f1f2c6749bbb0152b2257bf5086f29646ec8ae38798e24" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the options from the +host's environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-s5cmd:1": { + "version": "${localEnv:CLI_S5CMD_VERSION}", + "sha256_amd64": "${localEnv:CLI_S5CMD_SHA256_AMD64}", + "sha256_arm64": "${localEnv:CLI_S5CMD_SHA256_ARM64}" +} +``` + +### 2. Environment variables in your image + +Set `CLI_S5CMD_VERSION`, `CLI_S5CMD_SHA256_AMD64` and +`CLI_S5CMD_SHA256_ARM64` with `ENV` in the Dockerfile your devcontainer +builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_S5CMD_VERSION=2.3.0 +ENV CLI_S5CMD_SHA256_AMD64=de0fdbfa3aceae55e069ba81a0fc17b2026567637603734a387b2fca06c299b4 +ENV CLI_S5CMD_SHA256_ARM64=1439f0d00ecedcd2a2f1f2c6749bbb0152b2257bf5086f29646ec8ae38798e24 +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-s5cmd:1": {} +} +``` + +### 3. A pins file in your image + +Put the same `KEY=value` lines in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection; each feature reads only its own keys. + +```sh +# pins.env +CLI_S5CMD_VERSION=2.3.0 +CLI_S5CMD_SHA256_AMD64=de0fdbfa3aceae55e069ba81a0fc17b2026567637603734a387b2fca06c299b4 +CLI_S5CMD_SHA256_ARM64=1439f0d00ecedcd2a2f1f2c6749bbb0152b2257bf5086f29646ec8ae38798e24 +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced: `KEY=value` lines (optionally after +`export`), `#` comments and quoted values are understood, and nothing is +expanded. To keep it somewhere else, set the `pins_file` option to its +path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-s5cmd:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Install order with `ssl-ca` + +If you also use this collection's `ssl-ca` feature for a corporate root +CA, the CA has to be trusted before s5cmd is downloaded. This feature +deliberately declares no `installsAfter` on `ssl-ca`: the Dev Container +CLI fetches every `installsAfter` reference from ghcr.io, so declaring one +would make every build reach ghcr.io for `ssl-ca` even when you don't use +it. If you use `ssl-ca`, list it first in `overrideFeatureInstallOrder`: + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-s5cmd" +] +``` + +## Credentials + +The feature installs only the binary: it declares no mounts and stores no +state. s5cmd uses the AWS SDK, so it reads the usual `AWS_*` environment +variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, +`AWS_SESSION_TOKEN`, `AWS_REGION`, `AWS_PROFILE`), plus `S3_ENDPOINT_URL` +for an S3-compatible service such as RustFS or MinIO. Pass them in from +the host instead of baking them into the image: + +```jsonc +"containerEnv": { + "AWS_ACCESS_KEY_ID": "${localEnv:AWS_ACCESS_KEY_ID}", + "AWS_SECRET_ACCESS_KEY": "${localEnv:AWS_SECRET_ACCESS_KEY}", + "AWS_REGION": "${localEnv:AWS_REGION}", + "S3_ENDPOINT_URL": "${localEnv:S3_ENDPOINT_URL}" +} +``` + +or, with Docker Compose, from an `env_file` that stays out of version +control: + +```yaml +services: + devcontainer: + env_file: .secrets/s3.env +``` + +## OS support + +Debian and Ubuntu, on amd64 and arm64. The install needs `tar`, and +installs `curl` and `ca-certificates` if `curl` is missing. + +## License + +s5cmd is MIT-licensed ([LICENSE](https://github.com/peak/s5cmd/blob/master/LICENSE)); the feature installs the notice from the release tarball as `/usr/local/share/doc/s5cmd/LICENSE`. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-s5cmd/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/cli-valkey/README.md b/src/cli-valkey/README.md new file mode 100644 index 0000000..42fe253 --- /dev/null +++ b/src/cli-valkey/README.md @@ -0,0 +1,200 @@ + +# valkey-cli (cli-valkey) + +Installs valkey-cli, the Valkey command-line client, from upstream's binary release at an exact version, checked against a sha256 you supply. There are no default pins. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-valkey:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Valkey version to install, without a leading v (e.g. 9.1.3). Set it with its sha256 here, or as CLI_VALKEY_VERSION in the image environment, or in the pins file. There is no default. | string | - | +| sha256_amd64 | sha256 of valkey--noble-x86_64.tar.gz, needed on amd64. Set it here, or as CLI_VALKEY_SHA256_AMD64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| sha256_arm64 | sha256 of valkey--noble-arm64.tar.gz, needed on arm64. Set it here, or as CLI_VALKEY_SHA256_ARM64 in the image environment, or in the pins file, in the same place as the version. | string | - | +| pins_file | Path of the pins file in the image, read when neither the options nor the image environment set a pin. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## What it installs + +`valkey-cli`, as `/usr/local/bin/valkey-cli`, from upstream's prebuilt +binary release on [valkey.io/download](https://valkey.io/download/). The +release tarball (about 40 MB) also carries `valkey-server`, +`valkey-sentinel`, `valkey-benchmark` and the check tools; the feature +extracts only `bin/valkey-cli` and the license notice (see +[License](#license)) and installs no other binary. No `redis-cli` alias is +created. + +The feature always uses upstream's Ubuntu 24.04 (`noble`) build, on Debian +too. That build needs glibc 2.38 or later and OpenSSL 3 (`libssl.so.3`, +`libcrypto.so.3`). Debian 13 (glibc 2.41) and Ubuntu 24.04 (glibc 2.39) +have glibc new enough. OpenSSL 3 is in their base images today only as a +dependency of other packages, so when `ldconfig` finds no `libssl.so.3` +the feature installs `libssl3t64` (OpenSSL 3's package on both distros) +from the distro's archive. Otherwise nothing extra is installed. Older +distros such as Debian 12 or Ubuntu 22.04 are not supported; upstream's +`jammy` build exists for those, but this feature doesn't use it. + +## Pinning the version + +This feature has no default version. You choose the Valkey release and +supply the sha256 of its tarball for each arch you build on, and the +install fails unless it finds both. The download is always checked against +your sha256; the feature never fetches upstream's `.sha256` file, so the +sha you review is the one that counts. Pick the release that matches your +Valkey server, e.g. `9.1.3` for a 9.1 server. + +Get the shas from the `.sha256` file next to each tarball on +[valkey.io/download](https://valkey.io/download/) (each version page, such +as [9.1](https://valkey.io/download/9.1/), links them): + +| Arch | Tarball | +| --- | --- | +| amd64 | `https://download.valkey.io/releases/valkey--noble-x86_64.tar.gz` | +| arm64 | `https://download.valkey.io/releases/valkey--noble-arm64.tar.gz` | + +The version is the release number, which has no leading `v` (`9.1.3`). +You only need the sha for the arches you actually build on. + +This is a download feature with a consumer sha, like `cli-s5cmd`, not one +of the apt-installed CLIs (`cli-postgres-client`, `cli-mosquitto`) that pin +a version only (see +[ADR 0003](../../docs/adrs/0003-pin-apt-installed-clis-by-upstream-version-with-a-verified-repo-key.md), +which doesn't cover this feature). + +The pin can come from three places. The feature uses the first one that +sets either the version or a sha, and that place must set both: a version +in one place is never paired with a sha from another. + +### 1. Feature options + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-valkey:1": { + "version": "9.1.3", + "sha256_amd64": "26416df8a5b075ffb7d549e9a6445db9ec16e36d2259d8ea6e010dca35d53a54", + "sha256_arm64": "bf77cf1adf9dc2b1e1f42a01b611b5b8d00c5c158a200ab9ad1f8837d84d59da" + } +} +``` + +To keep the pin out of `devcontainer.json`, feed the options from the +host's environment: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-valkey:1": { + "version": "${localEnv:CLI_VALKEY_VERSION}", + "sha256_amd64": "${localEnv:CLI_VALKEY_SHA256_AMD64}", + "sha256_arm64": "${localEnv:CLI_VALKEY_SHA256_ARM64}" +} +``` + +### 2. Environment variables in your image + +Set `CLI_VALKEY_VERSION`, `CLI_VALKEY_SHA256_AMD64` and +`CLI_VALKEY_SHA256_ARM64` with `ENV` in the Dockerfile your devcontainer +builds from, and leave the feature's options empty: + +```dockerfile +ENV CLI_VALKEY_VERSION=9.1.3 +ENV CLI_VALKEY_SHA256_AMD64=26416df8a5b075ffb7d549e9a6445db9ec16e36d2259d8ea6e010dca35d53a54 +ENV CLI_VALKEY_SHA256_ARM64=bf77cf1adf9dc2b1e1f42a01b611b5b8d00c5c158a200ab9ad1f8837d84d59da +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/cli-valkey:1": {} +} +``` + +### 3. A pins file in your image + +Put the same `KEY=value` lines in a file and `COPY` it into the image at +`/etc/devcontainer-features/pins.env`. One file can hold the pins of every +feature in this collection; each feature reads only its own keys. + +```sh +# pins.env +CLI_VALKEY_VERSION=9.1.3 +CLI_VALKEY_SHA256_AMD64=26416df8a5b075ffb7d549e9a6445db9ec16e36d2259d8ea6e010dca35d53a54 +CLI_VALKEY_SHA256_ARM64=bf77cf1adf9dc2b1e1f42a01b611b5b8d00c5c158a200ab9ad1f8837d84d59da +``` + +```dockerfile +COPY pins.env /etc/devcontainer-features/pins.env +``` + +The file is parsed, never sourced: `KEY=value` lines (optionally after +`export`), `#` comments and quoted values are understood, and nothing is +expanded. To keep it somewhere else, set the `pins_file` option to its +path in the image: + +```jsonc +"ghcr.io/craftainer/devcontainer-features/cli-valkey:1": { + "pins_file": "/opt/pins/devcontainer.env" +} +``` + +## Install order with `ssl-ca` + +If you also use this collection's `ssl-ca` feature for a corporate root +CA, the CA has to be trusted before the tarball is downloaded. This feature +deliberately declares no `installsAfter` on `ssl-ca`: the Dev Container +CLI fetches every `installsAfter` reference from ghcr.io, so declaring one +would make every build reach ghcr.io for `ssl-ca` even when you don't use +it. If you use `ssl-ca`, list it first in `overrideFeatureInstallOrder`: + +```jsonc +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca", + "ghcr.io/craftainer/devcontainer-features/cli-valkey" +] +``` + +## Credentials + +The feature installs only the binary: it declares no mounts and stores no +state. For a password-protected server, pass the password in the +environment instead of with `-a`, which puts it on the command line. +`valkey-cli` reads `VALKEYCLI_AUTH`, and falls back to `REDISCLI_AUTH` +when that isn't set; `-a` or `--pass` win over both +([`parseEnv` in valkey-cli.c](https://github.com/valkey-io/valkey/blob/9.1.3/src/valkey-cli.c)). +`REDISCLI_AUTH` also works with `redis-cli`, so it suits a project that +uses both. Add `--user ` for an ACL user. Pass the variable in from +the host instead of baking it into the image: + +```jsonc +"containerEnv": { + "REDISCLI_AUTH": "${localEnv:VALKEY_PASSWORD}" +} +``` + +or, with Docker Compose, from an `env_file` that stays out of version +control: + +```yaml +services: + devcontainer: + env_file: .secrets/valkey.env +``` + +## OS support + +Debian 13 and Ubuntu 24.04 or later, on amd64 and arm64 (see "What it +installs" for the glibc and OpenSSL requirement). The install needs `tar`, +and installs `curl`, `ca-certificates` and `libssl3t64` if any is missing. + +## License + +Valkey is BSD-3-Clause ([COPYING](https://github.com/valkey-io/valkey/blob/unstable/COPYING)); the feature installs the notice from the release tarball (`share/LICENSE`) as `/usr/local/share/doc/valkey-cli/LICENSE`. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/cli-valkey/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/ssl-ca/README.md b/src/ssl-ca/README.md new file mode 100644 index 0000000..dc8a599 --- /dev/null +++ b/src/ssl-ca/README.md @@ -0,0 +1,285 @@ + +# Custom root CAs (ssl-ca) (ssl-ca) + +Installs custom root CA certificates (inline PEM, URLs checked against a sha256 you supply, or *.crt files already in the image) into the system trust store and every JDK cacerts it finds, and points the usual per-tool CA variables at the system bundle. List it first in overrideFeatureInstallOrder. + +## Example Usage + +```json +"features": { + "ghcr.io/craftainer/devcontainer-features/ssl-ca:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| certs | Inline PEM certificate(s), e.g. "${localEnv:CORP_CA_PEM}". Literal newlines or \n escapes both work. Set it here, or as SSL_CA_CERTS in the image environment, or in the pins file. | string | - | +| urls | URL(s) of PEM or DER certificates (https, http or file), separated by commas or blanks. Each needs its sha256 in sha256s, in the same order. Set both here, or as SSL_CA_URLS and SSL_CA_SHA256S in the image environment, or in the pins file. | string | - | +| sha256s | One sha256 per URL in urls, in the same order, separated by commas or blanks. Set it in the same place as urls. | string | - | +| pins_file | Path of the pins file in the image, read for SSL_CA_CERTS, SSL_CA_URLS and SSL_CA_SHA256S when neither the options nor the image environment set them. Empty means /etc/devcontainer-features/pins.env. | string | - | + +## What it does + +`ssl-ca` adds your organization's root CA certificates (for example the CA +of a TLS-inspecting proxy) to the container's trust store: + +1. It checks all its inputs first: a malformed URL list (a bad scheme, a + bad sha, a different number of URLs and shas) fails before anything is + downloaded or installed. +2. It checks the inline certificates and the pre-placed `*.crt` files + with `openssl x509`, writes the new ones to + `/usr/local/share/ca-certificates/ssl-ca-.crt`, named by + the first 16 hex digits of the certificate's sha256 fingerprint, so a + re-run doesn't duplicate them, and keeps the files already in that + directory. Then it runs `update-ca-certificates`, which rebuilds the + system bundle `/etc/ssl/certs/ca-certificates.crt` and the hashed + directory `/etc/ssl/certs`. +3. Only then does it fetch the URLs, so their https connections already + trust the CAs from step 2 (see "Behind a TLS-intercepting proxy"). It + checks and installs those certificates the same way and runs + `update-ca-certificates` again, then checks every certificate made it + into the bundle. +4. It imports every certificate in `/usr/local/share/ca-certificates` + into each JDK's `cacerts` it finds (see "Java"). +5. Through `containerEnv` it points the usual per-tool CA variables at the + system bundle (see "Environment variables"). + +With no certificate from any source the install fails, naming all of +them. There are no bundled certificates. + +## Supplying certificates + +The `certs` value, and the `urls` / `sha256s` pair, are each taken from +the first of these places that sets them: the feature option, then +`SSL_CA_CERTS` / `SSL_CA_URLS` / `SSL_CA_SHA256S` in your image's +environment, then the same keys in the pins file. `urls` and `sha256s` +always come from the same place: a URL list in one place is never paired +with shas from another. Inline certificates, URLs and pre-placed files can +be combined; all of them are installed. + +### Inline PEM (`certs`) + +One or more PEM certificates (`-----BEGIN CERTIFICATE-----` blocks). Text +between the blocks is ignored; any other PEM block, such as a private key, +fails the install without echoing it. Line breaks can be literal newlines +or `\n` escapes, so both of these work: + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/ssl-ca:1": { + // the host's CORP_CA_PEM, with real newlines or \n escapes + "certs": "${localEnv:CORP_CA_PEM}" + } +} +``` + +```sh +# on the host, e.g. in your shell profile +export CORP_CA_PEM="$(cat ~/corp-root-ca.pem)" +``` + +In a Dockerfile `ENV` or a pins file the value has to fit on one line, so +use `\n` escapes there (quoted in the pins file): + +```dockerfile +ENV SSL_CA_CERTS='-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----' +``` + +```sh +# pins.env: one line, generated with +# printf 'SSL_CA_CERTS="%s"\n' "$(awk '{ printf "%s\\n", $0 }' corp-root-ca.pem)" >>pins.env +SSL_CA_CERTS="-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----\n" +``` + +### A URL and its sha256 (`urls`, `sha256s`) + +Each URL is downloaded with the collection's checked fetch and must match +its sha256, given in the same order. Separate entries with commas or +blanks; a different number of URLs and shas fails. A URL may serve one or +more PEM certificates, or one DER certificate (`.cer`). `https://`, +`http://` (many internal PKIs publish their CA certificates over plain +http) and `file:///` URLs are accepted: the sha256 you pin is what makes +the download trustworthy, whatever the scheme. + +```jsonc +"ghcr.io/craftainer/devcontainer-features/ssl-ca:1": { + "urls": "https://pki.example.com/root-ca.pem, http://pki.example.com/issuing-ca.cer", + "sha256s": " " +} +``` + +Get each sha with `curl -fsSL | sha256sum` and check it against +what your PKI team publishes. Fetching a URL needs `curl`, which the +install adds if it is missing. + +**Behind a TLS-intercepting proxy.** The URLs are fetched after the +inline (`certs`) and pre-placed certificates are already in the system +trust store, and curl checks the download's TLS connection against that +store. So if the proxy re-signs https traffic, supply the proxy's CA +through `certs` or a pre-placed `.crt`, and the https URLs (for your other +internal CAs, say) download through it. A proxy CA that is itself only +reachable through one of the `urls` can't be trusted in time for its own +download: use `http://`, `file:///`, `certs` or a pre-placed file for that +one. + +That ordering doesn't cover the packages `ssl-ca` itself needs first. +When `openssl` or `ca-certificates` is missing, it installs them with apt +before any of your CAs is trusted, so if the image's apt sources are https +behind the intercepting proxy, that install fails. Start from an image +that already has `openssl` and `ca-certificates`, or whose apt sources the +proxy doesn't intercept. (`curl`, when `urls` need it, is installed after +the inline and pre-placed CAs are trusted, so a proxy CA given through +`certs` or a pre-placed `.crt` already covers that install.) + +### A certificate already in your image + +Copy the certificate into `/usr/local/share/ca-certificates/` (or a +subdirectory) in the Dockerfile your devcontainer builds from, and give +the feature no options. Each file must end in `.crt` and hold exactly one +PEM certificate, as `update-ca-certificates` requires; anything else fails +the install rather than being skipped. + +```dockerfile +COPY corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt +``` + +```jsonc +"build": { "dockerfile": "Dockerfile" }, +"features": { + "ghcr.io/craftainer/devcontainer-features/ssl-ca:1": {} +} +``` + +### The pins file + +The pins file is `/etc/devcontainer-features/pins.env` unless the +`pins_file` option names another path. It is parsed, not sourced, and can +hold the pins of every feature in this collection; `ssl-ca` reads only +`SSL_CA_CERTS`, `SSL_CA_URLS` and `SSL_CA_SHA256S`. If the `pins_file` +option names a file that doesn't exist, the install prints a warning (a +typo or a missing `COPY` is the usual cause), even when the options or the +image environment supply the certificates. + +```sh +# pins.env +SSL_CA_URLS="https://pki.example.com/root-ca.pem" +SSL_CA_SHA256S="" +``` + +## Install order + +**List `ssl-ca` first in `overrideFeatureInstallOrder`.** Features install +in an order the Dev Container CLI chooses, and a feature that downloads +something (every `cli-*` feature in this collection, and most others) +needs your CA trusted before it runs when it sits behind a TLS-inspecting +proxy: + +```jsonc +"features": { + "ghcr.io/craftainer/devcontainer-features/ssl-ca:1": { + "certs": "${localEnv:CORP_CA_PEM}" + }, + "ghcr.io/craftainer/devcontainer-features/cli-s5cmd:1": {} +}, +"overrideFeatureInstallOrder": [ + "ghcr.io/craftainer/devcontainer-features/ssl-ca" +] +``` + +Features not listed there keep their own order after the listed ones. + +Why the collection doesn't do this for you: a feature can't force itself +to install first +([vscode-remote-release#7150](https://github.com/microsoft/vscode-remote-release/issues/7150)), +and the only lever a feature has, `installsAfter`, makes the Dev Container +CLI fetch the named feature from ghcr.io on every build, even for +consumers who don't use `ssl-ca`. That would break the air-gapped and +proxied networks this feature exists for, so no feature here declares it. +`ssl-ca` itself declares none either: it is the one the others must +follow. + +The Dev Container CLI turns a feature's `containerEnv` into `ENV` lines +just before that feature's install step, so the variables below are also +set while every later feature installs (and while `ssl-ca` itself runs), +but not for features that ran earlier. The same `ENV` lines replace any +value your image already set: a `CURL_CA_BUNDLE` or `SSL_CERT_FILE` from +your Dockerfile's `ENV` is overridden with the system bundle, both for +`ssl-ca`'s own URL downloads and in the built image. To point a tool at a +different bundle, set the variable at run time in your `devcontainer.json` +(`containerEnv` or `remoteEnv`), which takes precedence over the image's +`ENV`; that changes what the running container uses, not what `ssl-ca`'s +downloads used at build time. + +## Environment variables + +`ssl-ca` sets these in `containerEnv`, each pointing at the system bundle +`/etc/ssl/certs/ca-certificates.crt` unless noted: + +| Variable | Read by | Notes | +| --- | --- | --- | +| `SSL_CERT_FILE` | OpenSSL and everything built on it (Python `ssl`, Ruby, Go, Bun, uv, ...) | A file. | +| `SSL_CERT_DIR` | the same | `/etc/ssl/certs`, the hashed directory `update-ca-certificates` maintains. | +| `REQUESTS_CA_BUNDLE` | Python `requests` | Replaces certifi's bundle. | +| `CURL_CA_BUNDLE` | the `curl` command (and `requests`, as a fallback) | Not read by libcurl in other programs. | +| `PIP_CERT` | pip (`--cert`) | | +| `NODE_EXTRA_CA_CERTS` | Node.js, Bun, Claude Code | Added to the bundled roots; read once at process start. | +| `NODE_USE_SYSTEM_CA` | Node.js (on Linux since 23.9), Bun | `1`: also trust the system store, which on Linux means `SSL_CERT_FILE` / `SSL_CERT_DIR` (else `/etc/ssl/cert.pem` and `/etc/ssl/certs`). Older Node ignores it, so `NODE_EXTRA_CA_CERTS` covers those. | +| `AWS_CA_BUNDLE` | AWS CLI and SDKs | | +| `GIT_SSL_CAINFO` | git over https | Overrides `http.sslCAInfo`. | +| `CARGO_HTTP_CAINFO` | cargo | Overrides `http.cainfo`. | +| `DENO_CERT` | Deno | Added to Deno's default (Mozilla) store. | +| `UV_SYSTEM_CERTS` | uv 0.11.0 and later | `1`: use the platform's store instead of uv's bundled Mozilla roots. uv also honours `SSL_CERT_FILE`, which overrides both uv switches. | +| `UV_NATIVE_TLS` | uv | The same switch under its older name, which uv documents as deprecated in favour of `UV_SYSTEM_CERTS`; still set because uv before 0.11.0 reads only this one. | + +The variables are static, so they are set even when the bundle doesn't +contain your CA yet; that only happens if the install failed, which stops +the build. + +## Java + +Every certificate in `/usr/local/share/ca-certificates` is imported into +each `cacerts` keystore found at `/usr/lib/jvm/*/lib/security/cacerts` +(and `.../jre/lib/security/cacerts`) and under `$JAVA_HOME`, with that +JDK's own `keytool` and the JDK's default store password `changeit`. JDKs +that share one store (Debian and Ubuntu link every packaged JDK's +`cacerts` to `/etc/ssl/certs/java/cacerts`) are handled once, and a +certificate already in a store (same sha256 fingerprint, under any alias) +is skipped, so a re-run changes nothing. New entries get the alias +`ssl-ca-`. With no JDK present this step is skipped silently; +a store `keytool` can't open with `changeit` fails the install. + +A JDK installed **after** `ssl-ca` isn't covered by this step. A Debian or +Ubuntu packaged JDK (`default-jre-headless`, `openjdk-*`) still gets the +certificates: its `ca-certificates-java` dependency builds +`/etc/ssl/certs/java/cacerts` from `/etc/ssl/certs`, where +`update-ca-certificates` has already linked them (checked on `debian:13`: +`default-jre-headless` installed after `ssl-ca` lists the CA in +`keytool -cacerts`). A JDK from elsewhere, +such as the `ghcr.io/devcontainers/features/java` feature (SDKMAN) or a +tarball, brings its own `cacerts`: install it before `ssl-ca`, or import +the CA yourself. + +## What isn't covered + +- **Alpine** and other non-Debian images: Debian and Ubuntu, amd64 and + arm64, only. +- **Tools with their own bundled store that ignore the variables above**, + for example some Electron apps, Firefox/NSS databases, and statically + linked binaries with an embedded root list. Point them at + `/etc/ssl/certs/ca-certificates.crt` with their own setting. +- **A JDK installed after `ssl-ca`** that doesn't use Debian's + `ca-certificates-java` (see "Java"). +- **Certificates added at run time.** The trust store is built into the + image; to change it, change the input and rebuild. + +## OS support + +Debian and Ubuntu, on amd64 and arm64. The install adds `openssl` and +`ca-certificates` if either is missing, and `curl` when `urls` are given. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/craftainer/devcontainer-features/blob/main/src/ssl-ca/devcontainer-feature.json). Add additional notes to a `NOTES.md`._