diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..6a7c387 --- /dev/null +++ b/.containerignore @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: MPL-2.0 +.git +.env +.env.* +!.env.example +*.pem +*.key +secrets +.cache +.local +target +build +dist +out +node_modules +*.log diff --git a/.machine_readable/descriptiles/STATE.a2ml b/.machine_readable/descriptiles/STATE.a2ml index 6afef11..9e5e996 100644 --- a/.machine_readable/descriptiles/STATE.a2ml +++ b/.machine_readable/descriptiles/STATE.a2ml @@ -7,7 +7,7 @@ project = "echidnabot" version = "0.1.0" last-updated = "2026-10-09" status = "active" -session = "Issue #173: re-point shared trust kernel to echidna-core 0.2.0 on upstream main; local slug and contract parser retained. Build and test blocked: Cargo is not installed in this sandbox; draft PR pending verification." +session = "Issue #173 follow-up: owner-requested fresh-checkout tooling via mise, Guix and rootless Podman/Wolfi. Offline shell checks pass; installation and real Rust/container/Guix validation blocked by sandbox tooling and network restrictions. PR #174 merged separately; development-environment follow-up pending verification." [project-context] name = "Echidnabot" @@ -454,3 +454,21 @@ changes = [ validation = "cargo build and cargo test both attempted, exit 127: cargo: command not found. Trust unit tests not run." coordination = "#166 remains open, but this checkout already pins tracing-opentelemetry 0.34 and rustls 0.23.45. No OTel or TLS dependency changes made; no security or CI pass claimed." follow-up = "Run cargo build and cargo test with repository-pinned Rust 1.99.0, confirm lockfile resolution and trust unit tests before marking draft PR ready." + +[session-2026-10-09-development-environment] +request = "Owner explicitly requested tool installation and repo-integrated mise, Guix environment settings and a Podman environment on Chainguard Wolfi; Guix provided as an explicit opt-in despite older deprecation guidance." +changes = [ + "Replace generic mise template with Just 1.58.0, rust-toolchain.toml discovery and Cargo tasks; bootstrap checksum-pins mise 2026.10.6 for Linux x86_64/aarch64.", + "Wire real Cargo build/test/format/lint/fetch and development-container Just recipes; add fresh-checkout documentation and README entry point.", + "Add authenticated pinned Guix channel plus native-prerequisite manifest, with documented FHS shell for downloaded Rust tools.", + "Use Chainguard Wolfi for development, builder and runtime Containerfile stages; add rootless Podman wrapper, named build caches and build-context exclusions.", + "Add offline script control-flow tests; no new .a2ml files.", +] +validation = "PASS: bash -n on all three shell scripts, tests/dev-tools.sh (mock install, failed download/checksum, Podman arguments and missing-image failure), git diff --check." +blockers = [ + "Mise and Just GitHub binary downloads fail at release-assets.githubusercontent.com; static.rust-lang.org and cgr.dev are unreachable under sandbox allowlist. No tools successfully installed.", + "Bootstrap preflight also finds CMake missing; pkg-config, Cargo, Just, Guix and Podman are absent. Rust build/tests and real container/Guix evaluation remain unverified.", + "Wolfi image digest cannot be verified here; rolling tag/APK repositories documented explicitly, not claimed fully reproducible.", + "PR #174 at 0896185 reports Cargo check failure (exit 101), Cargo test skipped; log download blocked at actions log host, so cause remains undiagnosed. Do not claim CI green or assume #166 caused it.", +] +follow-up = "Run bootstrap, cargo build/test and trust tests, Guix FHS environment and Podman build/run on an unrestricted host; inspect the failed Cargo check logs from merged PR #174 before readying the environment follow-up." diff --git a/Containerfile b/Containerfile index 12df388..ad2f876 100644 --- a/Containerfile +++ b/Containerfile @@ -1,40 +1,45 @@ # SPDX-License-Identifier: MPL-2.0 -# Build stage -FROM docker.io/library/rust:1.83-slim AS builder - -WORKDIR /build - -# Install build dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - pkg-config \ - libsqlite3-dev \ - && rm -rf /var/lib/apt/lists/* - -# Copy source -COPY . . - -# Build release binary -RUN cargo build --release - -# Runtime stage -FROM cgr.dev/chainguard/wolfi-base:latest - +# Override WOLFI_IMAGE with cgr.dev/chainguard/wolfi-base@sha256: +# for a reviewed, immutable release build. Never invent an unverified digest. +ARG WOLFI_IMAGE=cgr.dev/chainguard/wolfi-base:latest +FROM ${WOLFI_IMAGE} AS development + +RUN apk add --no-cache bash ca-certificates curl git build-base cmake \ + pkgconf sqlite-dev tar gzip xz coreutils libstdc++ + +ENV MISE_DATA_DIR=/opt/mise \ + MISE_CONFIG_DIR=/opt/mise-config \ + MISE_CACHE_DIR=/tmp/mise-cache \ + RUSTUP_HOME=/opt/rustup \ + CARGO_HOME=/opt/cargo \ + MISE_BIN_DIR=/usr/local/bin +ENV PATH="/opt/cargo/bin:/opt/mise/shims:${PATH}" +WORKDIR /workspace +COPY mise.toml rust-toolchain.toml ./ +COPY scripts/bootstrap.sh scripts/bootstrap.sh +RUN bash scripts/bootstrap.sh \ + && ln -s "$(mise which just)" /usr/local/bin/just \ + && chmod -R a+rX /opt/rustup /opt/cargo /opt/mise \ + && adduser -D -u 1000 dev \ + && mkdir -p /home/dev/.cargo /workspace/target \ + && chown -R dev:dev /home/dev /workspace + +ENV CARGO_HOME=/home/dev/.cargo HOME=/home/dev +USER dev +CMD ["bash"] + +FROM development AS builder +COPY --chown=dev:dev . . +RUN cargo build --locked --release + +FROM ${WOLFI_IMAGE} AS runtime LABEL org.opencontainers.image.source="https://github.com/hyperpolymath/echidnabot" LABEL org.opencontainers.image.description="Proof-aware CI bot that orchestrates ECHIDNA for theorem proof verification" LABEL org.opencontainers.image.licenses="MPL-2.0" - -# Install runtime dependencies -RUN apk add --no-cache sqlite-libs ca-certificates - -# Copy binary from builder -COPY --from=builder /build/target/release/echidnabot /usr/local/bin/echidnabot - -# Create non-root user -RUN adduser -D -u 1000 echidna +RUN apk add --no-cache sqlite-libs ca-certificates libstdc++ \ + && adduser -D -u 1000 echidna +COPY --from=builder /workspace/target/release/echidnabot /usr/local/bin/echidnabot USER echidna - WORKDIR /home/echidna - EXPOSE 8080 - ENTRYPOINT ["/usr/local/bin/echidnabot"] diff --git a/Justfile b/Justfile index a21a842..70a3fd1 100644 --- a/Justfile +++ b/Justfile @@ -20,9 +20,9 @@ set positional-arguments := true import? "build/contractile.just" # Project metadata — customize these -project := "rsr-template-repo" +project := "echidnabot" OWNER := "hyperpolymath" -REPO := "rsr-template-repo" +REPO := "echidnabot" version := "0.1.0" tier := "infrastructure" # 1 | 2 | infrastructure @@ -83,24 +83,11 @@ import? "build/just/assess.just" # Build the project (debug mode) build *args: - @echo "Building {{project}} (debug)..." - # TODO: Replace with your build command - # Examples: - # cargo build {{args}} # Rust - # mix compile {{args}} # Elixir - # zig build {{args}} # Zig - # deno task build {{args}} # Deno/AffineScript - @echo "Build complete" + cargo build --locked {{args}} # Build in release mode with optimizations build-release *args: - @echo "Building {{project}} (release)..." - # TODO: Replace with your release build command - # Examples: - # cargo build --release {{args}} - # MIX_ENV=prod mix compile {{args}} - # zig build -Doptimize=ReleaseFast {{args}} - @echo "Release build complete" + cargo build --locked --release {{args}} # Build and watch for changes (requires entr or similar) build-watch: @@ -127,24 +114,15 @@ clean-all: clean # Run all tests test *args: - @echo "Running tests..." - # TODO: Replace with your test command - # Examples: - # cargo test {{args}} - # mix test {{args}} - # zig build test {{args}} - # deno test {{args}} - @echo "Tests passed!" + cargo test --locked {{args}} # Run tests with verbose output test-verbose: - @echo "Running tests (verbose)..." - # TODO: Replace with verbose test command + cargo test --locked -- --nocapture # Smoke test test-smoke: - @echo "Smoke test..." - # TODO: Add basic sanity checks + cargo test --locked --test smoke # Run end-to-end tests (full pipeline: build → run → verify) e2e: @@ -227,31 +205,15 @@ fix: fmt # Format all source files [reversible: git checkout] fmt: - @echo "Formatting source files..." - # TODO: Replace with your formatter - # Examples: - # cargo fmt - # mix format - # gleam format - # deno fmt + cargo fmt --all # Check formatting without changes fmt-check: - @echo "Checking formatting..." - # TODO: Replace with your format check - # Examples: - # cargo fmt --check - # mix format --check-formatted - # gleam format --check + cargo fmt --all -- --check # Run linter lint: - @echo "Linting source files..." - # TODO: Replace with your linter - # Examples: - # cargo clippy -- -D warnings - # mix credo --strict - # gleam check + cargo clippy --locked --all-targets -- -D warnings # ═══════════════════════════════════════════════════════════════════════════════ # RUN & EXECUTE @@ -278,13 +240,7 @@ install: build-release # Install/check all dependencies deps: - @echo "Checking dependencies..." - # TODO: Replace with your dependency check - # Examples: - # cargo check - # mix deps.get - # gleam deps download - @echo "All dependencies satisfied" + cargo fetch --locked # Audit dependencies for vulnerabilities deps-audit: @@ -426,19 +382,7 @@ container-init: # Build container image via cerro-torre pipeline container-build *args: - #!/usr/bin/env bash - if [ -f "container/ct-build.sh" ]; then - cd container && ./ct-build.sh {{args}} - elif [ -f "container/Containerfile" ]; then - podman build -t {{project}}:latest -f container/Containerfile . - elif [ -f "build/Containerfile" ]; then - podman build -t {{project}}:latest -f build/Containerfile . - elif [ -f "Containerfile" ]; then - podman build -t {{project}}:latest -f Containerfile . - else - echo "No Containerfile found in container/, build/, or project root" - exit 1 - fi + podman build -t localhost/echidnabot:latest -f Containerfile {{args}} . # Verify compose configuration container-verify: @@ -779,3 +723,24 @@ handover-human path=".": secret-scan-trufflehog: @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true + +# Install the repository-pinned mise, Rust/Cargo and Just tools +bootstrap: + bash scripts/bootstrap.sh + +# Build the Chainguard Wolfi development image (requires only Podman) +dev-container-build: + podman build --target development -t localhost/echidnabot-dev -f Containerfile . + +# Open a rootless development shell, or execute a command in it +dev-container *args: + bash scripts/dev-container.sh {{args}} + +# Run both trust-module unit test paths +trust-test: + cargo test --locked --lib trust:: + +# Offline control-flow tests for development setup scripts (not real tool builds) +dev-tools-test: + bash -n scripts/bootstrap.sh scripts/dev-container.sh tests/dev-tools.sh + bash tests/dev-tools.sh diff --git a/README.adoc b/README.adoc index edd52bd..e9a913f 100644 --- a/README.adoc +++ b/README.adoc @@ -29,6 +29,13 @@ Part of the https://github.com/hyperpolymath/gitbot-fleet[gitbot-fleet] (Tier-1 Verifier role), coordinated by https://github.com/hyperpolymath/hypatia[Hypatia]. +=== Build and development setup + +See link:docs/DEVELOPMENT.adoc[Development environment] for a fresh-checkout +setup using pinned mise/Rust/Just tools, a Guix build environment, or a +rootless Podman development container based on Chainguard Wolfi. +The setup requires internet access; toolchain binaries are not vendored. + === Why does it exist? You’re writing formally verified software — proofs in Coq, Lean, Agda, diff --git a/docs/DEVELOPMENT.adoc b/docs/DEVELOPMENT.adoc new file mode 100644 index 0000000..1d1fa78 --- /dev/null +++ b/docs/DEVELOPMENT.adoc @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MPL-2.0 += Development environment +:toc: + +A standalone checkout can fetch both git dependencies; no sibling fleet +checkout is required. Choose the native, Guix, or Podman route below. +All routes need internet access for their first install/build. This repository +contains setup recipes, not vendored toolchain binaries or an offline SDK. + +== Tool ownership + +* `rust-toolchain.toml`: exact Rust version, Cargo, rustfmt and Clippy. + Mise reads this file; there is no second Rust version to drift. +* `mise.toml`: Just 1.58.0 and build/test/check tasks. Unrelated language + runtimes and fallback commands have been removed. +* `scripts/bootstrap.sh`: mise 2026.10.6, with SHA-256 checksums for Linux + x86_64 and aarch64 release archives. Checksums came from the upstream + release asset metadata. It installs into `$HOME/.local/bin` by default + (`MISE_BIN_DIR` overrides that location), without sudo or shell-profile edits. +* `Cargo.lock`: application dependencies. Normal commands use `--locked`; + dependency updates must deliberately regenerate and review this file. + +== Native Linux + +Install the native compiler/linker, Git, CA certificates, curl, archive tools, +CMake, pkg-config and SQLite headers through your OS package manager. +For Debian/Ubuntu: + +[source,sh] +---- +sudo apt-get update +sudo apt-get install -y build-essential cmake pkg-config libsqlite3-dev \ + git curl ca-certificates tar gzip xz-utils unzip +bash scripts/bootstrap.sh +export PATH="$HOME/.local/bin:$PATH" +mise run build +mise run test +mise run trust-test +mise run check +# Equivalent Just entry points, with the mise-managed tools on PATH: +mise exec -- just build +mise exec -- just test +---- + +The bootstrap fails on missing prerequisites or failed downloads; it never +prints a build/test success without executing the command. It explicitly +trusts this checkout's mise configuration, so inspect that configuration first. +It can be rerun after updating pins. For an interactive shell, use +`eval "$(mise activate bash)"` (or your shell's equivalent). + +Downloads require GitHub and its release-asset CDN, Rust's distribution +servers, crates.io's index/static download hosts and the git dependency +repositories. A GitHub-only network allowlist is insufficient. + +== Guix build environment (explicit opt-in) + +This environment is provided at the owner's request despite the older +estate-wide Guix deprecation guidance. It does not change the release build +system or make echidnabot a Guix package. `guix/manifest.scm` supplies native +build prerequisites; mise/rustup still supply the repository-pinned Rust. + +Install Guix on the host first. `guix/channels.scm` pins an authenticated Guix +GitHub-mirror revision (927070a6, dated 2025-11-16); it is not a claim to track +current Guix releases. Channel/package updates need separate review. Use this +file, not the legacy root `.guix-channel`, for the environment. + +On Linux with unprivileged user namespaces enabled: + +[source,sh] +---- +mkdir -p "$HOME/.cache/echidnabot-guix" +guix time-machine -C guix/channels.scm -- shell \ + --container --emulate-fhs --network \ + --share="$HOME/.cache/echidnabot-guix=/tools" \ + -m guix/manifest.scm -- env HOME=/tools bash +# Inside the Guix shell, in the shared checkout: +bash scripts/bootstrap.sh +export PATH="$HOME/.local/bin:$PATH" +mise run build +mise run test +---- + +The FHS container is intentional: mise and rustup distribute conventional +Linux binaries, which should not be assumed to run in a pure non-FHS Guix +shell. Tool downloads persist under the shared `/tools` home; source edits +and `target/` persist in the checkout. This requires access to Guix source +and substitute servers as well as the toolchain hosts above. Guix channel +and environment evaluation have not yet been exercised in the editing sandbox. + +== Rootless Podman on Chainguard Wolfi + +Install Podman using your host's supported packages (for example, +`sudo apt-get install podman uidmap slirp4netns fuse-overlayfs` on Debian). +Configure subordinate UID/GID ranges per your distribution's rootless Podman +guide. On macOS/Windows, initialise and start a Podman Linux VM first. +You do not need Rust, mise or Just on the host for this route. + +[source,sh] +---- +# Build the development target and enter a shell: +bash scripts/dev-container.sh --build +# Or run non-interactively after building: +bash scripts/dev-container.sh cargo build --locked +bash scripts/dev-container.sh cargo test --locked +bash scripts/dev-container.sh cargo test --locked --lib trust:: +bash scripts/dev-container.sh just fmt-check +# Application/runtime image (final Containerfile stage): +podman build -t localhost/echidnabot:latest -f Containerfile . +podman run --rm localhost/echidnabot:latest --help +---- + +All stages derive from `cgr.dev/chainguard/wolfi-base`. The development image +includes native build tools and the same mise-installed Rust/Just pins as the +host setup. The builder compiles with `--locked`; the final non-root runtime +image contains the application and runtime libraries, not the toolchains. + +The wrapper maps the caller to container UID/GID 1000 with `--userns=keep-id`, +mounts the checkout read/write at `/workspace`, and uses named volumes +`echidnabot-dev-target` and `echidnabot-dev-cargo` for build/dependency caches. +It does not mount a container-engine socket or require privileged mode. +`:Z` gives the checkout a private SELinux label: do not use the same checkout +concurrently from other labelled containers. `:U` changes ownership only on +the named cache volumes, not the source bind mount. +To reset caches after exiting all development containers: + +[source,sh] +---- +podman volume rm echidnabot-dev-target echidnabot-dev-cargo +---- + +The Wolfi default tag and APK repositories are rolling, so this is not yet a +bit-for-bit reproducible image. For release builds supply a reviewed image +digest with `--build-arg WOLFI_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:...`. +No digest is invented here: the registry was unreachable from the editing +sandbox. Full reproducibility would also require an APK repository snapshot. +Rebuild the development image when the Rust or Just pin changes. + +== Validation boundaries + +`cargo test --locked --lib trust::` covers the trust-level and source-axiom +paths changed for #173. Always run the full build and test commands too. +The current Rust CI wrapper documents a separate library-suite hang; do not +silently substitute a narrower test run and claim the full suite passed. +Live integration tests need an ECHIDNA server and relevant provers; these +are not bundled with this development environment. Build tools do not imply +that proof execution or the fleet's governance tooling is installed. + +In particular, `just validate-rsr` is not a defined recipe in this checkout; +installing Just does not create it. The inherited Justfile still contains +other estate-template recipes outside the build/test/format/lint paths wired +here. Do not treat their placeholder messages as verification evidence. + +The editing sandbox could not fetch mise/Just release assets, Rust distributions +or Wolfi images, and has no Podman or Guix. Shell syntax and mock-based wrapper +checks can be run there, but Rust build/tests, real tool installation, Guix +evaluation and container build/run remain pending in an unrestricted environment. diff --git a/guix/channels.scm b/guix/channels.scm new file mode 100644 index 0000000..a0096ee --- /dev/null +++ b/guix/channels.scm @@ -0,0 +1,14 @@ +;; SPDX-License-Identifier: MPL-2.0 +;; Explicit development-environment channel, not an echidnabot package channel. +;; GitHub mirror revision inspected on 2026-10-09. Bump deliberately. +(use-modules (guix channels)) + +(list (channel + (name 'guix) + (url "https://github.com/guix-mirror/guix") + (commit "927070a6f64da86a9c1694fe55c4316b2f9a4e11") + (introduction + (make-channel-introduction + "9edb3f66fd807b096b48283debdcddccfea34bad" + (openpgp-fingerprint + "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"))))) diff --git a/guix/manifest.scm b/guix/manifest.scm new file mode 100644 index 0000000..2cf6691 --- /dev/null +++ b/guix/manifest.scm @@ -0,0 +1,10 @@ +;; SPDX-License-Identifier: MPL-2.0 +;; Native build prerequisites. Rust/Just versions remain owned by mise and +;; rust-toolchain.toml, not by the Guix channel's Rust package version. +(use-modules (guix profiles)) + +(specifications->manifest + '("bash" "coreutils" "findutils" "diffutils" "grep" "sed" + "tar" "gzip" "xz" "unzip" "which" + "git" "curl" "nss-certs" + "gcc-toolchain" "make" "cmake" "pkg-config" "sqlite" "zlib")) diff --git a/mise.toml b/mise.toml index 6dd983f..26395a5 100644 --- a/mise.toml +++ b/mise.toml @@ -1,57 +1,31 @@ -[tools] -# Language runtimes -node = "latest" -python = "latest" -rust = "latest" -go = "latest" -zig = "latest" -java = "latest" -bun = "latest" -denojs = "latest" +# SPDX-License-Identifier: MPL-2.0 +# Rust (including Cargo, rustfmt and Clippy) comes from rust-toolchain.toml. +[settings] +idiomatic_version_file_enable_tools = ["rust"] -# Package managers -npm = "latest" -yarn = "latest" -pnpm = "latest" -pip = "latest" -cargo = "latest" -go-task = "latest" +[tools] +just = "1.58.0" -# Formatting & Linting -gofmt = "latest" -black = "latest" -isort = "latest" -ruff = "latest" -prettier = "latest" -shfmt = "latest" -stylua = "latest" +[tasks.build] +description = "Build with the committed dependency lock" +run = "cargo build --locked" -# Build tools -cmake = "latest" -make = "latest" -ninja = "latest" +[tasks.test] +description = "Run the full Rust test suite" +run = "cargo test --locked" -# Shell tools -git = "latest" -gnu-sed = "latest" -gnu-tar = "latest" -gnu-grep = "latest" +[tasks.trust-test] +description = "Run both shared trust-kernel integration paths" +run = "cargo test --locked --lib trust::" -# Testing -vitest = "latest" -pytest = "latest" -jest = "latest" +[tasks.check] +description = "Check formatting and lint all Rust targets" +run = ["cargo fmt --all -- --check", "cargo clippy --locked --all-targets -- -D warnings"] -[env] -# Common environment variables -NODE_ENV = "development" -PYTHONDONTWRITEBYTECODE = "1" -PYTHONUNBUFFERED = "1" +[tasks.dev-container] +description = "Build the Wolfi development image with Podman" +run = "podman build --target development -t localhost/echidnabot-dev -f Containerfile ." -# Task runner alias -[alias] -task = "go-task" -build = "cargo build --release || npm run build || go build" -test = "cargo test || npm test || go test ./..." -lint = "ruff check . || prettier --check . || black --check ." -fmt = "ruff format . || prettier --write . || black ." +[tasks.dev-tools-test] +description = "Offline bootstrap and Podman-wrapper control-flow tests" +run = ["bash -n scripts/bootstrap.sh scripts/dev-container.sh tests/dev-tools.sh", "bash tests/dev-tools.sh"] diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..dcd1dbc --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Install the pinned mise binary and this repository's Rust/Just toolchain. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +version=2026.10.6 +bin_dir=${MISE_BIN_DIR:-"$HOME/.local/bin"} +for cmd in curl tar gzip sha256sum cc cmake pkg-config git; do + command -v "$cmd" >/dev/null || { + printf 'Missing prerequisite: %s. See docs/DEVELOPMENT.adoc.\n' "$cmd" >&2 + exit 1 + } +done +case "$(uname -s)/$(uname -m)" in + Linux/x86_64) + arch=x64 + checksum=5135da6b71e6857efb22b2eb8d0fbc0061d061005a4994f1e7f8975d548a3e92 + ;; + Linux/aarch64|Linux/arm64) + arch=arm64 + checksum=60f0e34ea2088e822797393ed3d3b50d58dd9b45687006b31ac66ef68e99a2f4 + ;; + *) + echo 'Bootstrap supports Linux x86_64/aarch64; on other hosts use a Podman Linux VM.' >&2 + exit 1 + ;; +esac +mkdir -p "$bin_dir" +if [[ ! -x "$bin_dir/mise" ]] || [[ $("$bin_dir/mise" --version) != "$version "* ]]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + curl --fail --location --retry 3 --connect-timeout 20 --max-time 300 \ + "https://github.com/jdx/mise/releases/download/v$version/mise-v$version-linux-$arch.tar.gz" \ + --output "$tmp/mise.tar.gz" + printf '%s %s\n' "$checksum" "$tmp/mise.tar.gz" | sha256sum --check --status + tar -xzf "$tmp/mise.tar.gz" -C "$tmp" mise/bin/mise + install -m 0755 "$tmp/mise/bin/mise" "$bin_dir/mise" +fi +export PATH="$bin_dir:$PATH" +# Explicit invocation means the user has elected to trust this checkout. +mise trust "$PWD/mise.toml" +mise install +mise exec -- cargo --version +mise exec -- just --version +printf '\nReady. Add %s to PATH, then run: mise run build && mise run test\n' "$bin_dir" diff --git a/scripts/dev-container.sh b/scripts/dev-container.sh new file mode 100755 index 0000000..03b1b4e --- /dev/null +++ b/scripts/dev-container.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Rootless Podman development shell; no host Rust installation required. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." +command -v podman >/dev/null || { echo 'Install Podman first; see docs/DEVELOPMENT.adoc.' >&2; exit 1; } +image=${ECHIDNABOT_DEV_IMAGE:-localhost/echidnabot-dev} +if [[ ${1:-} == --build ]]; then + shift + podman build --target development --tag "$image" --file Containerfile . +fi +if ! podman image exists "$image"; then + echo 'Development image missing. Run scripts/dev-container.sh --build.' >&2 + exit 1 +fi +args=(--rm --userns=keep-id:uid=1000,gid=1000 --workdir /workspace + --volume "$PWD:/workspace:Z" + --volume echidnabot-dev-target:/workspace/target:U + --volume echidnabot-dev-cargo:/home/dev/.cargo:U) +if [[ -t 0 && -t 1 ]]; then args+=(-it); fi +if [[ $# == 0 ]]; then set -- bash; fi +exec podman run "${args[@]}" "$image" "$@" diff --git a/tests/dev-tools.sh b/tests/dev-tools.sh new file mode 100644 index 0000000..da31b4c --- /dev/null +++ b/tests/dev-tools.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Offline control-flow tests only: these do NOT validate downloaded tools/images. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." +repo=$PWD +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +mkdir -p "$tmp/bin" "$tmp/installed" +export TOOL_TEST_LOG="$tmp/calls" +export PATH="$tmp/bin:$PATH" +export MISE_BIN_DIR="$tmp/installed" + +for tool in cmake pkg-config cc; do + printf '#!/bin/sh\nexit 0\n' > "$tmp/bin/$tool" + chmod +x "$tmp/bin/$tool" +done +cat > "$tmp/installed/mise" <<'MOCK' +#!/usr/bin/env bash +set -eu +if [[ $1 == --version ]]; then echo '2026.10.6 linux-x64'; exit; fi +printf '%s\n' "$*" >> "$TOOL_TEST_LOG" +MOCK +chmod +x "$tmp/installed/mise" +bash scripts/bootstrap.sh > "$tmp/output" +grep -Fx "trust $repo/mise.toml" "$TOOL_TEST_LOG" +grep -Fx 'install' "$TOOL_TEST_LOG" +grep -Fx 'exec -- cargo --version' "$TOOL_TEST_LOG" +grep -Fx 'exec -- just --version' "$TOOL_TEST_LOG" + +# A failed download must fail closed, without claiming readiness. +rm "$tmp/installed/mise" +printf '#!/bin/sh\nexit 22\n' > "$tmp/bin/curl" +chmod +x "$tmp/bin/curl" +if bash scripts/bootstrap.sh > "$tmp/output" 2>&1; then + echo 'FAIL: failed download reported success' >&2; exit 1 +fi +if grep -q Ready "$tmp/output"; then exit 1; fi + +# An untrusted archive must not be extracted/installed. +cat > "$tmp/bin/curl" <<'MOCK' +#!/usr/bin/env bash +while [[ $# -gt 0 ]]; do + if [[ $1 == --output ]]; then printf 'bad archive' > "$2"; exit 0; fi + shift +done +exit 1 +MOCK +if bash scripts/bootstrap.sh > "$tmp/output" 2>&1; then + echo 'FAIL: invalid checksum accepted' >&2; exit 1 +fi +test ! -e "$tmp/installed/mise" + +cat > "$tmp/bin/podman" <<'MOCK' +#!/usr/bin/env bash +printf '%s\n' "$*" >> "$TOOL_TEST_LOG" +if [[ $1 == image && ${MOCK_IMAGE_MISSING:-0} == 1 ]]; then exit 1; fi +MOCK +chmod +x "$tmp/bin/podman" +: > "$TOOL_TEST_LOG" +bash scripts/dev-container.sh --build cargo test --locked --lib trust:: +grep -Fx 'build --target development --tag localhost/echidnabot-dev --file Containerfile .' "$TOOL_TEST_LOG" +grep -F -- '--userns=keep-id:uid=1000,gid=1000' "$TOOL_TEST_LOG" +grep -F -- "--volume $repo:/workspace:Z" "$TOOL_TEST_LOG" +grep -F -- 'localhost/echidnabot-dev cargo test --locked --lib trust::' "$TOOL_TEST_LOG" +: > "$TOOL_TEST_LOG" +if MOCK_IMAGE_MISSING=1 bash scripts/dev-container.sh > "$tmp/output" 2>&1; then + echo 'FAIL: missing image accepted' >&2; exit 1 +fi +if grep -q '^run ' "$TOOL_TEST_LOG"; then exit 1; fi +printf 'PASS: offline development-tool control-flow tests (no real builds performed)\n'