From 8ed0c6f8ec96d380e48636714f7584e9c755b451 Mon Sep 17 00:00:00 2001 From: Matthew Mellor Date: Wed, 4 Mar 2026 13:07:32 -0600 Subject: [PATCH 1/3] feat(container): add Rust language ecosystem support Add Rust as the 8th supported language in dev-toolchain: - Dockerfile: rust:1-slim-bookworm builder stage with cargo-binstall, cargo-audit, cargo-deny; COPY full toolchain to runtime - Makefile: HAS_RUST detection + clippy, rustfmt, cargo-audit, cargo-deny, cargo test across _lint, _format, _fix, _test, _security, _docs, _init targets - scripts/install-rust.sh: verify-only (tools COPY'd from builder) - tests/test-rust.sh: validate all Rust tools on PATH - STABILITY.md: update language count to 8 Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 18 +++++- Makefile | 135 ++++++++++++++++++++++++++++++++++++++++ STABILITY.md | 2 +- scripts/install-rust.sh | 84 +++++++++++++++++++++++++ tests/test-rust.sh | 62 ++++++++++++++++++ 5 files changed, 299 insertions(+), 2 deletions(-) create mode 100755 scripts/install-rust.sh create mode 100755 tests/test-rust.sh diff --git a/Dockerfile b/Dockerfile index 4c350f0..53223ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,15 @@ RUN go install mvdan.cc/gofumpt@latest # Install govulncheck RUN go install golang.org/x/vuln/cmd/govulncheck@latest +# === Rust builder stage === +# Provides Rust toolchain (rustup + cargo + rustc + clippy + rustfmt) and +# installs cargo-audit and cargo-deny via cargo-binstall. +FROM rust:1-slim-bookworm AS rust-builder +RUN curl -L --proto '=https' --tlsv1.2 -sSf \ + https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh \ + | bash +RUN cargo binstall --no-confirm cargo-audit cargo-deny + # === Node.js base: provides Node runtime for JS/TS tooling === FROM node:22-bookworm-slim AS node-base @@ -93,8 +102,14 @@ COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules RUN ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx +# Copy Rust toolchain from rust-builder (rustup + cargo + rustc + clippy + rustfmt + cargo-audit + cargo-deny) +COPY --from=rust-builder /usr/local/rustup /usr/local/rustup +COPY --from=rust-builder /usr/local/cargo /usr/local/cargo +ENV RUSTUP_HOME=/usr/local/rustup +ENV CARGO_HOME=/usr/local/cargo + # Set up environment -ENV PATH="/opt/devrail/bin:/usr/local/go/bin:${PATH}" +ENV PATH="/opt/devrail/bin:/usr/local/cargo/bin:/usr/local/go/bin:${PATH}" ENV DEVRAIL_LIB="/opt/devrail/lib" # Copy Go SDK from builder (required at runtime by golangci-lint, govulncheck) @@ -118,6 +133,7 @@ RUN bash /opt/devrail/scripts/install-ansible.sh RUN bash /opt/devrail/scripts/install-ruby.sh RUN bash /opt/devrail/scripts/install-go.sh RUN bash /opt/devrail/scripts/install-javascript.sh +RUN bash /opt/devrail/scripts/install-rust.sh RUN bash /opt/devrail/scripts/install-universal.sh # Allow git operations on mounted workspaces with different ownership diff --git a/Makefile b/Makefile index 2829baf..c0bcd18 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ HAS_ANSIBLE := $(filter ansible,$(LANGUAGES)) HAS_RUBY := $(filter ruby,$(LANGUAGES)) HAS_GO := $(filter go,$(LANGUAGES)) HAS_JAVASCRIPT := $(filter javascript,$(LANGUAGES)) +HAS_RUST := $(filter rust,$(LANGUAGES)) # --------------------------------------------------------------------------- # .PHONY declarations @@ -260,6 +261,21 @@ _lint: _check-config exit $$overall_exit; \ fi; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + ran_languages="$${ran_languages}\"rust\","; \ + rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \ + if [ -n "$$rs_files" ]; then \ + cargo clippy --all-targets --all-features -- -D warnings || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \ + else \ + echo '{"level":"info","msg":"skipping rust lint: no .rs files found","language":"rust"}' >&2; \ + fi; \ + if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \ + end_time=$$(date +%s%3N); \ + duration=$$((end_time - start_time)); \ + echo "{\"target\":\"lint\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \ + exit $$overall_exit; \ + fi; \ + fi; \ end_time=$$(date +%s%3N); \ duration=$$((end_time - start_time)); \ if [ $$overall_exit -eq 0 ]; then \ @@ -359,6 +375,21 @@ _format: _check-config exit $$overall_exit; \ fi; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + ran_languages="$${ran_languages}\"rust\","; \ + rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \ + if [ -n "$$rs_files" ]; then \ + cargo fmt --all -- --check || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \ + else \ + echo '{"level":"info","msg":"skipping rust format: no .rs files found","language":"rust"}' >&2; \ + fi; \ + if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \ + end_time=$$(date +%s%3N); \ + duration=$$((end_time - start_time)); \ + echo "{\"target\":\"format\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \ + exit $$overall_exit; \ + fi; \ + fi; \ end_time=$$(date +%s%3N); \ duration=$$((end_time - start_time)); \ if [ $$overall_exit -eq 0 ]; then \ @@ -458,6 +489,21 @@ _fix: _check-config exit $$overall_exit; \ fi; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + ran_languages="$${ran_languages}\"rust\","; \ + rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \ + if [ -n "$$rs_files" ]; then \ + cargo fmt --all || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \ + else \ + echo '{"level":"info","msg":"skipping rust fix: no .rs files found","language":"rust"}' >&2; \ + fi; \ + if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \ + end_time=$$(date +%s%3N); \ + duration=$$((end_time - start_time)); \ + echo "{\"target\":\"fix\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \ + exit $$overall_exit; \ + fi; \ + fi; \ end_time=$$(date +%s%3N); \ duration=$$((end_time - start_time)); \ if [ $$overall_exit -eq 0 ]; then \ @@ -579,6 +625,22 @@ _test: _check-config exit $$overall_exit; \ fi; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \ + if [ -n "$$rs_files" ] && [ -f "Cargo.toml" ]; then \ + ran_languages="$${ran_languages}\"rust\","; \ + cargo test --all-targets || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \ + else \ + skipped_languages="$${skipped_languages}\"rust\","; \ + echo '{"level":"info","msg":"skipping rust tests: no .rs files or Cargo.toml found","language":"rust"}' >&2; \ + fi; \ + if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \ + end_time=$$(date +%s%3N); \ + duration=$$((end_time - start_time)); \ + echo "{\"target\":\"test\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}],\"skipped\":[$${skipped_languages%,}]}"; \ + exit $$overall_exit; \ + fi; \ + fi; \ end_time=$$(date +%s%3N); \ duration=$$((end_time - start_time)); \ if [ -z "$${ran_languages}" ] && [ -n "$${skipped_languages}" ]; then \ @@ -694,6 +756,32 @@ _security: _check-config exit $$overall_exit; \ fi; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + if [ -f "Cargo.lock" ]; then \ + ran_languages="$${ran_languages}\"rust\","; \ + cargo audit || { overall_exit=1; failed_languages="$${failed_languages}\"rust:cargo-audit\","; }; \ + else \ + skipped_languages="$${skipped_languages}\"rust:cargo-audit\","; \ + echo '{"level":"info","msg":"skipping cargo audit: no Cargo.lock found","language":"rust"}' >&2; \ + fi; \ + if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \ + end_time=$$(date +%s%3N); \ + duration=$$((end_time - start_time)); \ + echo "{\"target\":\"security\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \ + exit $$overall_exit; \ + fi; \ + if [ -f "deny.toml" ]; then \ + cargo deny check || { overall_exit=1; failed_languages="$${failed_languages}\"rust:cargo-deny\","; }; \ + else \ + echo '{"level":"info","msg":"skipping cargo deny: no deny.toml found","language":"rust"}' >&2; \ + fi; \ + if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \ + end_time=$$(date +%s%3N); \ + duration=$$((end_time - start_time)); \ + echo "{\"target\":\"security\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \ + exit $$overall_exit; \ + fi; \ + fi; \ end_time=$$(date +%s%3N); \ duration=$$((end_time - start_time)); \ if [ -z "$${ran_languages}" ] && [ -n "$${skipped_languages}" ]; then \ @@ -813,6 +901,14 @@ _docs: _check-config _tv tsc "tsc --version"; \ _tv vitest "vitest --version"; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + _tv rustc "rustc --version"; \ + _tv cargo "cargo --version"; \ + _tv clippy "cargo clippy --version"; \ + _tv rustfmt "rustfmt --version"; \ + _tv cargo-audit "cargo audit --version"; \ + _tv cargo-deny "cargo deny --version"; \ + fi; \ _tv trivy "trivy --version"; \ _tv gitleaks "gitleaks version"; \ _tv git-cliff "git-cliff --version"; \ @@ -1019,6 +1115,45 @@ _init: _check-config 'build/' \ 'coverage/'; \ fi; \ + if [ -n "$(HAS_RUST)" ]; then \ + scaffold clippy.toml \ + '# clippy.toml -- DevRail Rust clippy configuration' \ + '# See: https://doc.rust-lang.org/clippy/lint_configuration.html' \ + 'too-many-arguments-threshold = 7'; \ + scaffold rustfmt.toml \ + '# rustfmt.toml -- DevRail Rust formatter configuration' \ + 'edition = "2021"' \ + 'max_width = 100' \ + 'use_field_init_shorthand = true' \ + 'use_try_shorthand = true'; \ + scaffold deny.toml \ + '# deny.toml -- DevRail cargo-deny configuration' \ + '# See: https://embarkstudios.github.io/cargo-deny/' \ + '' \ + '[advisories]' \ + 'vulnerability = "deny"' \ + 'unmaintained = "warn"' \ + 'yanked = "warn"' \ + '' \ + '[licenses]' \ + 'unlicensed = "deny"' \ + 'allow = [' \ + ' "MIT",' \ + ' "Apache-2.0",' \ + ' "BSD-2-Clause",' \ + ' "BSD-3-Clause",' \ + ' "ISC",' \ + ' "Unicode-3.0",' \ + ' "Unicode-DFS-2016",' \ + ']' \ + '' \ + '[bans]' \ + 'multiple-versions = "warn"' \ + '' \ + '[sources]' \ + 'unknown-registry = "deny"' \ + 'unknown-git = "warn"'; \ + fi; \ echo "{\"target\":\"init\",\"created\":[$${created%,}],\"skipped\":[$${skipped%,}]}" # --- _check: orchestrate all targets --- diff --git a/STABILITY.md b/STABILITY.md index 6dd8d92..bbc4379 100644 --- a/STABILITY.md +++ b/STABILITY.md @@ -17,7 +17,7 @@ DevRail has reached **v1.0** across all repositories. The core standards, toolch | **Makefile contract** | Stable | Two-layer delegation pattern, JSON summary output, `init` scaffolding. | | **Shell conventions** | Stable | `lib/log.sh`, `lib/platform.sh`, header format, and idempotency patterns are settled. | | **Conventional commits** | Stable | Types, scopes, and format are finalized. Pre-commit hook published. | -| **Language standards** | Stable | Python, Bash, Terraform, Ansible, Ruby, Go, JavaScript/TypeScript — all 7 ecosystems shipped. | +| **Language standards** | Stable | Python, Bash, Terraform, Ansible, Ruby, Go, JavaScript/TypeScript, Rust — all 8 ecosystems shipped. | | **Coding practices** | Stable | General principles (DRY, KISS, testing, error handling) are finalized. | | **Git workflow** | Stable | Branch strategy, PR process, and merge policy are finalized. | | **Release & versioning** | Stable | Semver policy is defined and in use across all repos. | diff --git a/scripts/install-rust.sh b/scripts/install-rust.sh new file mode 100755 index 0000000..00c3d7e --- /dev/null +++ b/scripts/install-rust.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# scripts/install-rust.sh — Verify Rust tooling for DevRail +# +# Purpose: Verifies that the Rust toolchain and Rust-based tools are available +# in the dev-toolchain container. The Rust SDK (rustup + cargo + rustc) +# and all tools are COPY'd from the rust-builder stage; this script +# only confirms they are on PATH. +# Usage: bash scripts/install-rust.sh [--help] +# Dependencies: lib/log.sh, lib/platform.sh +# +# Tools verified: +# - rustc (Rust compiler — COPY'd from builder) +# - cargo (Rust package manager — COPY'd from builder) +# - clippy (Linter — rustup component, COPY'd from builder) +# - rustfmt (Formatter — rustup component, COPY'd from builder) +# - cargo-audit (Dependency vulnerability scanner — built in builder) +# - cargo-deny (Dependency policy checker — built in builder) + +set -euo pipefail + +# --- Resolve library path --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEVRAIL_LIB="${DEVRAIL_LIB:-${SCRIPT_DIR}/../lib}" + +# shellcheck source=../lib/log.sh +source "${DEVRAIL_LIB}/log.sh" +# shellcheck source=../lib/platform.sh +source "${DEVRAIL_LIB}/platform.sh" + +# --- Help --- +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + log_info "install-rust.sh — Verify Rust tooling for DevRail" + log_info "Usage: bash scripts/install-rust.sh [--help]" + log_info "Tools: rustc, cargo, clippy, rustfmt, cargo-audit, cargo-deny" + exit 0 +fi + +# --- Main --- + +log_info "Verifying Rust tooling installation" + +# Verify rustc is available (COPY'd from builder) +if command -v rustc &>/dev/null; then + log_info "rustc is already installed" +else + log_warn "rustc not found — expected to be copied from Rust builder stage" +fi + +# Verify cargo is available (COPY'd from builder) +if command -v cargo &>/dev/null; then + log_info "cargo is already installed" +else + log_warn "cargo not found — expected to be copied from Rust builder stage" +fi + +# Verify clippy is available (rustup component, COPY'd from builder) +if command -v cargo-clippy &>/dev/null; then + log_info "clippy is already installed" +else + log_warn "clippy not found — expected to be copied from Rust builder stage" +fi + +# Verify rustfmt is available (rustup component, COPY'd from builder) +if command -v rustfmt &>/dev/null; then + log_info "rustfmt is already installed" +else + log_warn "rustfmt not found — expected to be copied from Rust builder stage" +fi + +# Verify cargo-audit is available (built in builder) +if command -v cargo-audit &>/dev/null; then + log_info "cargo-audit is already installed" +else + log_warn "cargo-audit not found — expected to be copied from Rust builder stage" +fi + +# Verify cargo-deny is available (built in builder) +if command -v cargo-deny &>/dev/null; then + log_info "cargo-deny is already installed" +else + log_warn "cargo-deny not found — expected to be copied from Rust builder stage" +fi + +log_info "Rust tooling verification complete" diff --git a/tests/test-rust.sh b/tests/test-rust.sh new file mode 100755 index 0000000..88e54d2 --- /dev/null +++ b/tests/test-rust.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# tests/test-rust.sh — Validate Rust tooling installation +# +# Purpose: Verifies that all Rust tools are installed and executable. +# Usage: bash tests/test-rust.sh [--help] +# Dependencies: lib/log.sh + +set -euo pipefail + +# --- Resolve library path --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEVRAIL_LIB="${DEVRAIL_LIB:-${SCRIPT_DIR}/../lib}" + +# shellcheck source=../lib/log.sh +source "${DEVRAIL_LIB}/log.sh" + +# --- Help --- +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + log_info "test-rust.sh — Validate Rust tooling installation" + log_info "Usage: bash tests/test-rust.sh [--help]" + log_info "Checks: rustc, cargo, cargo-clippy, rustfmt, cargo-audit, cargo-deny" + exit 0 +fi + +# --- Main --- + +log_info "Validating Rust tooling installation" + +FAILURES=0 + +# check_tool verifies a tool is on PATH and can report its version +check_tool() { + local tool="$1" + local version_flag="${2:---version}" + + if ! command -v "${tool}" &>/dev/null; then + log_error "${tool} is not on PATH" + FAILURES=$((FAILURES + 1)) + return + fi + + if "${tool}" "${version_flag}" &>/dev/null; then + log_info "${tool} — OK" + else + log_error "${tool} found but failed to execute ${version_flag}" + FAILURES=$((FAILURES + 1)) + fi +} + +check_tool "rustc" "--version" +check_tool "cargo" "--version" +check_tool "cargo-clippy" "--version" +check_tool "rustfmt" "--version" +check_tool "cargo-audit" "--version" +check_tool "cargo-deny" "--version" + +if [[ "${FAILURES}" -gt 0 ]]; then + log_error "Rust tooling validation failed: ${FAILURES} tool(s) missing or broken" + exit 1 +fi + +log_info "All Rust tools validated successfully" From 97d7a5d854fb21e2868c3b2c2686c40f95276c26 Mon Sep 17 00:00:00 2001 From: Matthew Mellor Date: Wed, 4 Mar 2026 14:18:33 -0600 Subject: [PATCH 2/3] =?UTF-8?q?docs(standards):=20add=20critical=20rule=20?= =?UTF-8?q?7=20=E2=80=94=20never=20suppress=20failing=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a seventh critical rule to all agent shim files: never suppress failing checks. Fix the root cause instead of adding blanket suppressions, commenting out code, or disabling rules. Co-Authored-By: Claude Opus 4.6 --- .cursorrules | 4 ++++ .opencode/agents.yaml | 4 ++++ AGENTS.md | 1 + CLAUDE.md | 1 + 4 files changed, 10 insertions(+) diff --git a/.cursorrules b/.cursorrules index 3956545..ad81bd5 100644 --- a/.cursorrules +++ b/.cursorrules @@ -19,6 +19,10 @@ Critical Rules: 6. Use the shared logging library. No raw `echo` for status messages. Use `log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from `lib/log.sh`. +7. Never suppress failing checks. When a lint, format, security, or test + check fails, fix the underlying issue. Never comment out code, add + suppression annotations, disable rules, or mark CI jobs as + allowed-to-fail to bypass a failing check. Quick Reference: diff --git a/.opencode/agents.yaml b/.opencode/agents.yaml index 67b0cdc..831e90b 100644 --- a/.opencode/agents.yaml +++ b/.opencode/agents.yaml @@ -24,6 +24,10 @@ agents: 6. Use the shared logging library. No raw `echo` for status messages. Use `log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from `lib/log.sh`. + 7. Never suppress failing checks. When a lint, format, security, or test + check fails, fix the underlying issue. Never comment out code, add + suppression annotations, disable rules, or mark CI jobs as + allowed-to-fail to bypass a failing check. Quick Reference: diff --git a/AGENTS.md b/AGENTS.md index 9d29d90..e2eb2d8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,7 @@ See DEVELOPMENT.md for the complete reference. 4. **Respect `.editorconfig`.** Never override formatting rules (indent style, line endings, trailing whitespace) without explicit instruction. 5. **Write idempotent scripts.** Every script must be safe to re-run. Check before acting: `command -v tool || install_tool`, `mkdir -p`, guard file writes with existence checks. 6. **Use the shared logging library.** No raw `echo` for status messages. Use `log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from `lib/log.sh`. +7. **Never suppress failing checks.** When a lint, format, security, or test check fails, fix the underlying issue. Never comment out code, add suppression annotations, disable rules, or mark CI jobs as allowed-to-fail to bypass a failing check. ## Quick Reference diff --git a/CLAUDE.md b/CLAUDE.md index cf1684f..fe5f560 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,7 @@ See DEVELOPMENT.md for the complete reference. 4. **Respect `.editorconfig`.** Never override formatting rules (indent style, line endings, trailing whitespace) without explicit instruction. 5. **Write idempotent scripts.** Every script must be safe to re-run. Check before acting: `command -v tool || install_tool`, `mkdir -p`, guard file writes with existence checks. 6. **Use the shared logging library.** No raw `echo` for status messages. Use `log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from `lib/log.sh`. +7. **Never suppress failing checks.** When a lint, format, security, or test check fails, fix the underlying issue. Never comment out code, add suppression annotations, disable rules, or mark CI jobs as allowed-to-fail to bypass a failing check. ## Quick Reference From c4c5471f0f7ed100fe46e4036175f5b0d9677b68 Mon Sep 17 00:00:00 2001 From: Matthew Mellor Date: Wed, 4 Mar 2026 14:46:49 -0600 Subject: [PATCH 3/3] fix(container): install curl in rust-builder stage The rust:1-slim-bookworm image does not ship curl. Add apt-get install for curl and ca-certificates so cargo-binstall can be downloaded. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 53223ed..42031f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,8 @@ RUN go install golang.org/x/vuln/cmd/govulncheck@latest # Provides Rust toolchain (rustup + cargo + rustc + clippy + rustfmt) and # installs cargo-audit and cargo-deny via cargo-binstall. FROM rust:1-slim-bookworm AS rust-builder +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \ + && rm -rf /var/lib/apt/lists/* RUN curl -L --proto '=https' --tlsv1.2 -sSf \ https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh \ | bash