From 1fae0d5b75f14fad7b509d7941fc8d6c381fa5c5 Mon Sep 17 00:00:00 2001 From: cyc Date: Fri, 24 Apr 2026 10:31:24 +0800 Subject: [PATCH 1/3] Squashed 'template/' changes from e5c123cf..087f26e3 087f26e3 chore: release v0.9.11 (#109) d672949e feat: Jetson auto-detect for [build] network + dedupe _detect_lang (#102 + #104) (#107) 364ae6f9 test(coverage) + fix(i18n): fill audit gaps, localise _sanitize_lang, close #103 (#105) 13e28b9a chore: release v0.9.10 (#100) 3a2b3578 feat(ci): multi-arch docker build via native matrix runners (#99) git-subtree-dir: template git-subtree-split: 087f26e38f4f4bac7eef034d2922f040226a3c39 --- template/.codecov.yaml => .codecov.yaml | 0 .github/workflows/build-worker.yaml | 225 +++++ .github/workflows/main.yaml | 23 - .../workflows/release-worker.yaml | 0 .../workflows/self-test.yaml | 0 .gitignore | 10 +- .hadolint.yaml | 13 +- .version | 1 + Dockerfile | 62 -- template/LICENSE => LICENSE | 0 Makefile | 1 - template/Makefile.ci => Makefile.ci | 0 README.md | 455 +++++++-- bridge.yaml | 5 - build.sh | 1 - template/compose.yaml => compose.yaml | 0 .../config => config}/pip/requirements.txt | 0 {template/config => config}/pip/setup.sh | 0 config/release_bridge.yaml | 20 - config/scan_bridge.yaml | 5 - {template/config => config}/shell/bashrc | 0 .../config => config}/shell/terminator/config | 0 .../shell/terminator/setup.sh | 0 .../config => config}/shell/tmux/README.adoc | 0 .../config => config}/shell/tmux/setup.sh | 0 .../config => config}/shell/tmux/tmux.conf | 0 doc/README.ja.md | 147 --- doc/README.zh-CN.md | 147 --- doc/README.zh-TW.md | 147 --- doc/changelog/CHANGELOG.md | 955 +++++++++++++++++- {template/doc => doc}/readme/README.ja.md | 0 {template/doc => doc}/readme/README.zh-CN.md | 0 {template/doc => doc}/readme/README.zh-TW.md | 0 doc/test/TEST.md | 548 +++++++++- .../Dockerfile.example | 10 + .../Dockerfile.test-tools | 0 exec.sh | 1 - template/init.sh => init.sh | 0 run.sh | 1 - {template/script => script}/ci/ci.sh | 0 {template/script => script}/docker/Makefile | 0 {template/script => script}/docker/_lib.sh | 24 +- .../script => script}/docker/_tui_backend.sh | 0 .../script => script}/docker/_tui_conf.sh | 2 +- {template/script => script}/docker/build.sh | 24 +- {template/script => script}/docker/exec.sh | 19 +- {template/script => script}/docker/i18n.sh | 30 +- {template/script => script}/docker/run.sh | 19 +- {template/script => script}/docker/setup.sh | 25 +- .../script => script}/docker/setup_tui.sh | 24 +- {template/script => script}/docker/stop.sh | 19 +- script/entrypoint.sh | 14 - setup.conf | 34 +- setup.sh | 1 - setup_tui.sh | 1 - stop.sh | 1 - template/.github/workflows/build-worker.yaml | 112 -- template/.gitignore | 8 - template/.hadolint.yaml | 12 - template/.version | 1 - template/README.md | 422 -------- template/doc/changelog/CHANGELOG.md | 870 ---------------- template/doc/test/TEST.md | 550 ---------- template/setup.conf | 334 ------ .../fresh_clone_portability_spec.bats | 0 .../integration/init_new_repo_spec.bats | 0 .../integration/upgrade_spec.bats | 0 .../test => test}/smoke/display_env.bats | 0 test/smoke/ros_env.bats | 46 - .../test => test}/smoke/script_help.bats | 0 .../test => test}/smoke/test_helper.bash | 0 {template/test => test}/unit/bashrc_spec.bats | 0 .../test => test}/unit/build_sh_spec.bats | 19 +- {template/test => test}/unit/ci_spec.bats | 0 .../test => test}/unit/compose_gen_spec.bats | 0 .../test => test}/unit/exec_sh_spec.bats | 14 +- {template/test => test}/unit/init_spec.bats | 0 {template/test => test}/unit/lib_spec.bats | 54 +- .../test => test}/unit/pip_setup_spec.bats | 0 {template/test => test}/unit/run_sh_spec.bats | 14 +- {template/test => test}/unit/setup_spec.bats | 46 + .../test => test}/unit/smoke_helper_spec.bats | 0 .../test => test}/unit/stop_sh_spec.bats | 14 +- .../test => test}/unit/template_spec.bats | 77 +- .../unit/terminator_config_spec.bats | 0 .../unit/terminator_setup_spec.bats | 0 {template/test => test}/unit/test_helper.bash | 0 .../test => test}/unit/tmux_conf_spec.bats | 0 .../test => test}/unit/tmux_setup_spec.bats | 0 .../test => test}/unit/tui_backend_spec.bats | 0 {template/test => test}/unit/tui_spec.bats | 111 ++ .../test => test}/unit/upgrade_spec.bats | 0 template/upgrade.sh => upgrade.sh | 0 93 files changed, 2517 insertions(+), 3201 deletions(-) rename template/.codecov.yaml => .codecov.yaml (100%) create mode 100644 .github/workflows/build-worker.yaml delete mode 100644 .github/workflows/main.yaml rename {template/.github => .github}/workflows/release-worker.yaml (100%) rename {template/.github => .github}/workflows/self-test.yaml (100%) mode change 120000 => 100644 .hadolint.yaml create mode 100644 .version delete mode 100644 Dockerfile rename template/LICENSE => LICENSE (100%) delete mode 120000 Makefile rename template/Makefile.ci => Makefile.ci (100%) delete mode 100644 bridge.yaml delete mode 120000 build.sh rename template/compose.yaml => compose.yaml (100%) rename {template/config => config}/pip/requirements.txt (100%) rename {template/config => config}/pip/setup.sh (100%) delete mode 100644 config/release_bridge.yaml delete mode 100644 config/scan_bridge.yaml rename {template/config => config}/shell/bashrc (100%) rename {template/config => config}/shell/terminator/config (100%) rename {template/config => config}/shell/terminator/setup.sh (100%) rename {template/config => config}/shell/tmux/README.adoc (100%) rename {template/config => config}/shell/tmux/setup.sh (100%) rename {template/config => config}/shell/tmux/tmux.conf (100%) delete mode 100644 doc/README.ja.md delete mode 100644 doc/README.zh-CN.md delete mode 100644 doc/README.zh-TW.md rename {template/doc => doc}/readme/README.ja.md (100%) rename {template/doc => doc}/readme/README.zh-CN.md (100%) rename {template/doc => doc}/readme/README.zh-TW.md (100%) rename {template/dockerfile => dockerfile}/Dockerfile.example (91%) rename {template/dockerfile => dockerfile}/Dockerfile.test-tools (100%) delete mode 120000 exec.sh rename template/init.sh => init.sh (100%) delete mode 120000 run.sh rename {template/script => script}/ci/ci.sh (100%) rename {template/script => script}/docker/Makefile (100%) rename {template/script => script}/docker/_lib.sh (96%) rename {template/script => script}/docker/_tui_backend.sh (100%) rename {template/script => script}/docker/_tui_conf.sh (99%) rename {template/script => script}/docker/build.sh (94%) rename {template/script => script}/docker/exec.sh (93%) rename {template/script => script}/docker/i18n.sh (51%) rename {template/script => script}/docker/run.sh (96%) rename {template/script => script}/docker/setup.sh (98%) rename {template/script => script}/docker/setup_tui.sh (96%) rename {template/script => script}/docker/stop.sh (91%) delete mode 100755 script/entrypoint.sh delete mode 120000 setup.sh delete mode 120000 setup_tui.sh delete mode 120000 stop.sh delete mode 100644 template/.github/workflows/build-worker.yaml delete mode 100644 template/.gitignore delete mode 100644 template/.hadolint.yaml delete mode 100644 template/.version delete mode 100644 template/README.md delete mode 100644 template/doc/changelog/CHANGELOG.md delete mode 100644 template/doc/test/TEST.md delete mode 100644 template/setup.conf rename {template/test => test}/integration/fresh_clone_portability_spec.bats (100%) rename {template/test => test}/integration/init_new_repo_spec.bats (100%) rename {template/test => test}/integration/upgrade_spec.bats (100%) rename {template/test => test}/smoke/display_env.bats (100%) delete mode 100644 test/smoke/ros_env.bats rename {template/test => test}/smoke/script_help.bats (100%) rename {template/test => test}/smoke/test_helper.bash (100%) rename {template/test => test}/unit/bashrc_spec.bats (100%) rename {template/test => test}/unit/build_sh_spec.bats (95%) rename {template/test => test}/unit/ci_spec.bats (100%) rename {template/test => test}/unit/compose_gen_spec.bats (100%) rename {template/test => test}/unit/exec_sh_spec.bats (88%) rename {template/test => test}/unit/init_spec.bats (100%) rename {template/test => test}/unit/lib_spec.bats (87%) rename {template/test => test}/unit/pip_setup_spec.bats (100%) rename {template/test => test}/unit/run_sh_spec.bats (94%) rename {template/test => test}/unit/setup_spec.bats (96%) rename {template/test => test}/unit/smoke_helper_spec.bats (100%) rename {template/test => test}/unit/stop_sh_spec.bats (88%) rename {template/test => test}/unit/template_spec.bats (90%) rename {template/test => test}/unit/terminator_config_spec.bats (100%) rename {template/test => test}/unit/terminator_setup_spec.bats (100%) rename {template/test => test}/unit/test_helper.bash (100%) rename {template/test => test}/unit/tmux_conf_spec.bats (100%) rename {template/test => test}/unit/tmux_setup_spec.bats (100%) rename {template/test => test}/unit/tui_backend_spec.bats (100%) rename {template/test => test}/unit/tui_spec.bats (90%) rename {template/test => test}/unit/upgrade_spec.bats (100%) rename template/upgrade.sh => upgrade.sh (100%) diff --git a/template/.codecov.yaml b/.codecov.yaml similarity index 100% rename from template/.codecov.yaml rename to .codecov.yaml diff --git a/.github/workflows/build-worker.yaml b/.github/workflows/build-worker.yaml new file mode 100644 index 0000000..aa3a30c --- /dev/null +++ b/.github/workflows/build-worker.yaml @@ -0,0 +1,225 @@ +name: Docker Build & Smoke Test + +on: + workflow_call: + inputs: + image_name: + required: true + type: string + description: "Container image name (e.g. ros_noetic)" + build_args: + required: false + type: string + default: "" + description: "Multi-line KEY=VALUE build args" + build_runtime: + required: false + type: boolean + default: true + description: "Whether to build runtime stage" + platforms: + required: false + type: string + default: "linux/amd64" + description: | + Comma-separated target platforms. Each runs as a parallel job on + its own native runner (linux/amd64 → ubuntu-latest, linux/arm64 + → ubuntu-24.04-arm), so arm64 builds avoid QEMU emulation + (which would take 30-60 min per run on an amd64 host) and stay + in the 5-15 min range. + + Each matrix shard runs the full pipeline (test-tools build, + test stage smoke tests, devel stage, runtime stage) natively + for its platform. + + Default "linux/amd64" preserves single-platform behavior — + existing downstream repos see no CI change until they opt in + by adding `platforms: linux/amd64,linux/arm64` in their + main.yaml call. + + Supported values: linux/amd64, linux/arm64. Other values are + rejected by the compute-matrix step. + +jobs: + # ──────────────────────────────────────────────────────────────── + # Parse the comma-separated `platforms` input into a matrix + # definition. Each supported platform gets the right runner label + # (native arm64 runners are available for public repos under the + # ycpss91255-docker org) and a canonical HARDWARE build-arg value. + # ──────────────────────────────────────────────────────────────── + compute-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} + steps: + - id: set + env: + PLATFORMS: ${{ inputs.platforms }} + run: | + items="" + IFS=',' read -ra plats <<< "${PLATFORMS}" + for p in "${plats[@]}"; do + p="$(echo "${p}" | tr -d '[:space:]')" + case "${p}" in + linux/amd64) + items+='{"platform":"linux/amd64","runner":"ubuntu-latest","hardware":"x86_64"},' + ;; + linux/arm64) + items+='{"platform":"linux/arm64","runner":"ubuntu-24.04-arm","hardware":"aarch64"},' + ;; + "") + continue + ;; + *) + echo "::error::Unsupported platform '${p}'. Supported: linux/amd64, linux/arm64" + exit 1 + ;; + esac + done + if [ -z "${items}" ]; then + echo "::error::No valid platforms found in '${PLATFORMS}'" + exit 1 + fi + json="{\"include\":[${items%,}]}" + echo "matrix=${json}" >> "${GITHUB_OUTPUT}" + + # ──────────────────────────────────────────────────────────────── + # Per-platform build. Each shard runs on its native runner so + # arm64 doesn't pay QEMU emulation cost. Full test + devel + + # runtime pipeline runs per platform. + # ──────────────────────────────────────────────────────────────── + build: + needs: compute-matrix + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.compute-matrix.outputs.matrix) }} + runs-on: ${{ matrix.runner }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check template version + run: | + LOCAL_VER="" + if [ -f template/.version ]; then + LOCAL_VER=$(cat template/.version | tr -d '[:space:]') + fi + if [ -n "${LOCAL_VER}" ]; then + LATEST_VER=$(git ls-remote --tags --sort=-v:refname \ + https://github.com/ycpss91255-docker/template.git \ + | grep -oP 'refs/tags/v\d+\.\d+\.\d+$' | head -1 | sed 's|refs/tags/||') + if [ -n "${LATEST_VER}" ] && [ "${LOCAL_VER}" != "${LATEST_VER}" ]; then + echo "::warning::template ${LOCAL_VER} → ${LATEST_VER} available" + else + echo "template is up to date (${LOCAL_VER})" + fi + fi + + - name: Set up Docker Buildx + # docker-container driver is required even on single-platform + # builds now: the downstream Dockerfile's `test` stage + # references `test-tools:local` via `COPY --from=`, and with + # docker-container the tag built in the previous buildx step + # stays in the same builder's internal store, which subsequent + # build-push-action steps can resolve. The legacy `docker` + # driver stores images in the host daemon, which isn't + # accessible from a matrix job's buildx container. + uses: docker/setup-buildx-action@v3 + with: + driver: docker-container + + - name: Generate .env + run: | + cat > .env << EOF + USER_NAME=ci + USER_GROUP=ci + USER_UID=1000 + USER_GID=1000 + HARDWARE=${{ matrix.hardware }} + DOCKER_HUB_USER=ci + GPU_ENABLED=false + IMAGE_NAME=${{ inputs.image_name }} + WS_PATH=/tmp/workspace + EOF + mkdir -p /tmp/workspace + + - name: Build test-tools image + # Must use buildx (not plain `docker build`) so `test-tools:local` + # lands in buildx's internal image store — subsequent build + # steps on the same builder resolve `COPY --from=test-tools:local` + # from there. docker-container's builder container doesn't see + # host daemon images. + uses: docker/build-push-action@v6 + with: + context: . + file: template/dockerfile/Dockerfile.test-tools + tags: test-tools:local + platforms: ${{ matrix.platform }} + push: false + + - name: Build test stage (includes smoke tests) + uses: docker/build-push-action@v6 + with: + context: . + target: test + platforms: ${{ matrix.platform }} + push: false + build-args: | + USER=ci + GROUP=ci + UID=1000 + GID=1000 + HARDWARE=${{ matrix.hardware }} + ${{ inputs.build_args }} + + - name: Build devel stage + uses: docker/build-push-action@v6 + with: + context: . + target: devel + platforms: ${{ matrix.platform }} + push: false + build-args: | + USER=ci + GROUP=ci + UID=1000 + GID=1000 + HARDWARE=${{ matrix.hardware }} + ${{ inputs.build_args }} + + - name: Build runtime stage + if: ${{ inputs.build_runtime }} + uses: docker/build-push-action@v6 + with: + context: . + target: runtime + platforms: ${{ matrix.platform }} + push: false + build-args: | + USER=ci + GROUP=ci + UID=1000 + GID=1000 + ${{ inputs.build_args }} + + # ──────────────────────────────────────────────────────────────── + # Stable-name aggregator so downstream branch-protection rules + # keep working. Keeping this job's name as `docker-build` + # preserves the status-check context that public-ycpss91255-docker + # container repos currently require on their `main` branch + # (`call-docker-build / docker-build`). Matrix shards publish as + # `call-docker-build / build (linux/amd64)` etc. — those are the + # actual build runs; this one just gates on them. + # ──────────────────────────────────────────────────────────────── + docker-build: + needs: build + if: always() + runs-on: ubuntu-latest + steps: + - name: Aggregate matrix result + run: | + result="${{ needs.build.result }}" + echo "Matrix build result: ${result}" + if [ "${result}" != "success" ]; then + exit 1 + fi diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml deleted file mode 100644 index f495a8a..0000000 --- a/.github/workflows/main.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: Main CI/CD - -on: - push: - branches: [main, master] - tags: - - 'v*' - pull_request: - workflow_dispatch: - -jobs: - call-docker-build: - uses: ycpss91255-docker/template/.github/workflows/build-worker.yaml@v0.9.9 - with: - image_name: ros1_bridge - build_runtime: false - - call-release: - needs: call-docker-build - if: startsWith(github.ref, 'refs/tags/') - uses: ycpss91255-docker/template/.github/workflows/release-worker.yaml@v0.9.9 - with: - archive_name_prefix: ros1_bridge diff --git a/template/.github/workflows/release-worker.yaml b/.github/workflows/release-worker.yaml similarity index 100% rename from template/.github/workflows/release-worker.yaml rename to .github/workflows/release-worker.yaml diff --git a/template/.github/workflows/self-test.yaml b/.github/workflows/self-test.yaml similarity index 100% rename from template/.github/workflows/self-test.yaml rename to .github/workflows/self-test.yaml diff --git a/.gitignore b/.gitignore index 5da6bac..51e3633 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -.env -compose.yaml coverage/ -.Dockerfile.generated +.env +multi_run/ +*.swp +*.swo +.DS_Store +.vscode/ +.idea/ diff --git a/.hadolint.yaml b/.hadolint.yaml deleted file mode 120000 index 6299ce9..0000000 --- a/.hadolint.yaml +++ /dev/null @@ -1 +0,0 @@ -template/.hadolint.yaml \ No newline at end of file diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..658c0ef --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,12 @@ +--- +ignored: + - DL3003 # Use WORKDIR to switch to a directory + - DL3004 # Do not use sudo (we need it for user setup) + - DL3006 # Always tag the version of an image explicitly + - DL3007 # Using latest is prone to errors + - DL3008 # Pin versions in apt get install + - DL3013 # Pin versions in pip install + - DL3018 # Pin versions in apk add + - DL3046 # useradd without flag -l + - DL4006 # Set SHELL option -o pipefail (alpine stages use default shell) + diff --git a/.version b/.version new file mode 100644 index 0000000..9781ff2 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +v0.9.11 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b26cd70..0000000 --- a/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -ARG IMAGE="osrf/ros:foxy-ros1-bridge" - -############################## test tool sources ############################## -FROM bats/bats:latest AS bats-src - -FROM alpine:latest AS bats-extensions -RUN apk add --no-cache git && \ - git clone --depth 1 -b v0.3.0 \ - https://github.com/bats-core/bats-support /bats/bats-support && \ - git clone --depth 1 -b v2.1.0 \ - https://github.com/bats-core/bats-assert /bats/bats-assert - -FROM alpine:latest AS lint-tools -RUN apk add --no-cache curl xz && \ - curl -fsSL \ - https://github.com/koalaman/shellcheck/releases/download/v0.10.0/shellcheck-v0.10.0.linux.x86_64.tar.xz \ - | tar -xJ -C /tmp && \ - mv /tmp/shellcheck-v0.10.0/shellcheck /usr/local/bin/shellcheck && \ - curl -fsSL -o /usr/local/bin/hadolint \ - https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 && \ - chmod +x /usr/local/bin/hadolint - -############################## devel ############################## -FROM ${IMAGE} AS devel - -ARG BRIDGE_FILE="bridge.yaml" - -COPY --chmod=0755 script/entrypoint.sh /entrypoint.sh -COPY --chmod=0644 "${BRIDGE_FILE}" /bridge.yaml -COPY --chmod=0644 config/ /config/ - -ENTRYPOINT ["/entrypoint.sh"] -CMD ["ros2", "run", "ros1_bridge", "parameter_bridge"] - -############################## test (ephemeral) ############################## -FROM devel AS test - -# Install lint tools -COPY --from=lint-tools /usr/local/bin/shellcheck /usr/local/bin/shellcheck -COPY --from=lint-tools /usr/local/bin/hadolint /usr/local/bin/hadolint - -# Lint: ShellCheck (.sh) + Hadolint (Dockerfile) -COPY .hadolint.yaml /lint/.hadolint.yaml -COPY Dockerfile /lint/Dockerfile -COPY template/script/docker/build.sh template/script/docker/run.sh template/script/docker/exec.sh template/script/docker/stop.sh /lint/ -COPY script/entrypoint.sh /lint/ -RUN shellcheck -S warning /lint/*.sh -RUN cd /lint && hadolint Dockerfile - -# Install bats -COPY --from=bats-src /opt/bats /opt/bats -COPY --from=bats-src /usr/lib/bats /usr/lib/bats -COPY --from=bats-extensions /bats /usr/lib/bats -RUN ln -sf /opt/bats/bin/bats /usr/local/bin/bats - -ENV BATS_LIB_PATH="/usr/lib/bats" - -# Smoke test -COPY template/test/smoke/test_helper.bash template/test/smoke/script_help.bats /smoke_test/ -COPY test/smoke/ /smoke_test/ - -RUN bats /smoke_test/ diff --git a/template/LICENSE b/LICENSE similarity index 100% rename from template/LICENSE rename to LICENSE diff --git a/Makefile b/Makefile deleted file mode 120000 index e91e0c2..0000000 --- a/Makefile +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/Makefile \ No newline at end of file diff --git a/template/Makefile.ci b/Makefile.ci similarity index 100% rename from template/Makefile.ci rename to Makefile.ci diff --git a/README.md b/README.md index ae7ce91..4fd2b77 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,422 @@ -# ROS 1 Bridge Docker Environment +# template -**[English](README.md)** | **[繁體中文](doc/README.zh-TW.md)** | **[简体中文](doc/README.zh-CN.md)** | **[日本語](doc/README.ja.md)** +[![Self Test](https://github.com/ycpss91255-docker/template/actions/workflows/self-test.yaml/badge.svg)](https://github.com/ycpss91255-docker/template/actions/workflows/self-test.yaml) +[![codecov](https://codecov.io/gh/ycpss91255-docker/template/branch/main/graph/badge.svg)](https://codecov.io/gh/ycpss91255-docker/template) -> **TL;DR** — ROS 1/2 bridge container based on `osrf/ros:foxy-ros1-bridge`. Bridges ROS 1 (Noetic) and ROS 2 (Foxy) topics via `parameter_bridge`. -> -> ```bash -> ./build.sh && ./run.sh -> ``` +![Language](https://img.shields.io/badge/Language-Bash-blue?style=flat-square) +![Testing](https://img.shields.io/badge/Testing-Bats-orange?style=flat-square) +![ShellCheck](https://img.shields.io/badge/ShellCheck-Compliant-brightgreen?style=flat-square) +![Coverage](https://img.shields.io/badge/Coverage-Kcov-blueviolet?style=flat-square) +[![License](https://img.shields.io/badge/License-GPL--3.0-yellow?style=flat-square)](./LICENSE) + +Shared template for Docker container repos in the [ycpss91255-docker](https://github.com/ycpss91255-docker) organization. + +**[English](README.md)** | **[繁體中文](doc/readme/README.zh-TW.md)** | **[简体中文](doc/readme/README.zh-CN.md)** | **[日本語](doc/readme/README.ja.md)** --- ## Table of Contents -- [Features](#features) +- [TL;DR](#tldr) +- [Overview](#overview) - [Quick Start](#quick-start) -- [Usage](#usage) -- [Bridge Configuration](#bridge-configuration) -- [Architecture](#architecture) +- [CI Reusable Workflows](#ci-reusable-workflows) +- [Running Template Tests](#running-template-tests) +- [Tests](#tests) - [Directory Structure](#directory-structure) --- -## Features +## TL;DR -- **Pre-built bridge image**: based on `osrf/ros:foxy-ros1-bridge` with both ROS 1 and ROS 2 -- **Parameter bridge**: configurable topic bridging via YAML -- **Smoke Test**: Bats tests verify both ROS environments and bridge availability -- **Docker Compose**: single `compose.yaml` for build and run -- **Example configs**: includes scan and camera bridge configurations +```bash +# New repo from scratch: init + first commit + subtree + init.sh +mkdir && cd +git init +git commit --allow-empty -m "chore: initial commit" +git subtree add --prefix=template \ + https://github.com/ycpss91255-docker/template.git main --squash +./template/init.sh + +# Upgrade to latest +make upgrade-check # check +make upgrade # pull + update version + workflow tag + +# Run CI +make test # ShellCheck + Bats + Kcov +make help # show all commands +``` -## Quick Start +## Overview -```bash -# 1. Build -./build.sh +This repo consolidates shared scripts, tests, and CI workflows used across all Docker container repos. Instead of maintaining identical files in 15+ repos, each repo pulls this template as a **git subtree** and uses symlinks. -# 2. Run (requires ROS master running) -./run.sh +### Architecture -# 3. Enter running container -./exec.sh +```mermaid +graph TB + subgraph template["template (shared repo)"] + scripts[".hadolint.yaml / Makefile.ci / compose.yaml"] + smoke["test/smoke/
script_help.bats
display_env.bats"] + config["config/
bashrc / tmux / terminator / pip"] + mgmt["script/docker/
build.sh / run.sh / exec.sh / stop.sh / setup.sh"] + workflows["Reusable Workflows
build-worker.yaml
release-worker.yaml"] + end + + subgraph consumer["Docker Repo (e.g. ros_noetic)"] + symlinks["build.sh → template/script/docker/build.sh
run.sh → template/script/docker/run.sh
exec.sh / stop.sh / .hadolint.yaml"] + dockerfile["Dockerfile
compose.yaml
.env.example
script/entrypoint.sh"] + repo_test["test/smoke/
ros_env.bats (repo-specific)"] + main_yaml["main.yaml
→ calls reusable workflows"] + end + + template -- "git subtree" --> consumer + scripts -. symlink .-> symlinks + smoke -. "Dockerfile COPY" .-> repo_test + workflows -. "@tag reference" .-> main_yaml ``` -## Usage +### CI/CD Flow -### Build +```mermaid +flowchart LR + subgraph local["Local"] + build_test["./build.sh test"] + make_test["make test"] + end + + subgraph ci_container["CI Container (kcov/kcov)"] + shellcheck["ShellCheck"] + hadolint["Hadolint"] + bats["Bats smoke tests"] + end + + subgraph github["GitHub Actions"] + build_worker["build-worker.yaml
(from template)"] + release_worker["release-worker.yaml
(from template)"] + end + + build_test --> ci_container + make_test -->|"script/ci/ci.sh"| ci_container + shellcheck --> hadolint --> bats + + push["git push / PR"] --> build_worker + build_worker -->|"docker build test"| ci_container + tag["git tag v*"] --> release_worker + release_worker -->|"tar.gz + zip"| release["GitHub Release"] +``` -```bash -./build.sh # Build devel (default) -./build.sh test # Build with smoke tests +### What's included -docker compose build devel # Equivalent +| File | Description | +|------|-------------| +| `build.sh` | Build containers (TTY-aware `--setup` launches `setup_tui.sh`, else runs `setup.sh`) | +| `run.sh` | Run containers (X11/Wayland support; same `--setup` semantics as `build.sh`) | +| `exec.sh` | Exec into running containers | +| `stop.sh` | Stop and remove containers | +| `setup_tui.sh` | Interactive setup.conf editor (dialog / whiptail front-end) | +| `script/docker/setup.sh` | Auto-detect system parameters and generate `.env` + `compose.yaml` | +| `script/docker/_tui_backend.sh` | dialog/whiptail wrapper functions used by `setup_tui.sh` | +| `script/docker/_tui_conf.sh` | INI validators + read/write for `setup_tui.sh` and `setup.sh` writeback | +| `script/docker/_lib.sh` | Shared helpers (`_load_env`, `_compose`, `_compose_project`, ...) | +| `script/docker/i18n.sh` | Shared language detection (`_detect_lang`, `_LANG`) | +| `config/` | Container-internal shell configs (bashrc, tmux, terminator, pip) | +| `setup.conf` | Single per-repo runtime configuration (image / build / deploy / gui / network / volumes) | +| `test/smoke/` | Shared smoke tests + runtime assertion helpers (see below) | +| `test/unit/` | Template self-tests (bats + kcov) | +| `test/integration/` | Level-1 `init.sh` end-to-end tests | +| `.hadolint.yaml` | Shared Hadolint rules | +| `Makefile` | Repo entry (`make build`, `make run`, `make stop`, etc.) | +| `Makefile.ci` | Template CI entry (`make test`, `make -f Makefile.ci lint`, etc.) | +| `init.sh` | First-time symlink setup + new-repo scaffolding | +| `upgrade.sh` | Subtree version upgrade | +| `script/ci/ci.sh` | CI pipeline (local + remote) | +| `dockerfile/Dockerfile.example` | Multi-stage Dockerfile template for new repos | +| `dockerfile/Dockerfile.test-tools` | Pre-built lint/test tools image (shellcheck, hadolint, bats, bats-mock) | +| `.github/workflows/` | Reusable CI workflows (build + release) | + +### Dockerfile stages (convention) + +Downstream repos follow a standard multi-stage layout, defined in +`dockerfile/Dockerfile.example`. All stages share a common base image +parameterized by `ARG BASE_IMAGE`. + +| Stage | Parent | Purpose | Shipped? | +|-------|--------|---------|----------| +| `sys` | `${BASE_IMAGE}` | User/group, sudo, timezone, locale, APT mirror | intermediate | +| `base` | `sys` | Development tools and language packages | intermediate | +| `devel` | `base` | App-specific tools + `entrypoint.sh` + PlotJuggler (env repos) | **yes** (primary artifact) | +| `test` | `devel` | Ephemeral: ShellCheck + Hadolint + Bats smoke (all from `test-tools:local`) | no (discarded) | +| `runtime-base` (optional) | `sys` | Minimal runtime deps (sudo, tini) | intermediate | +| `runtime` (optional) | `runtime-base` | Slim runtime image (application repos only) | yes, when enabled | + +Notes: +- Repos that only ship a developer image (`env/*`) skip `runtime-base` / + `runtime` — the section stays commented in `Dockerfile.example`. +- `test` is always built from `devel`, so runtime assertions inside + `test/smoke/_env.bats` see the same binaries / files a user would + find after `docker run ... :devel`. +- `Dockerfile.test-tools` builds a separate `test-tools:local` image (not + part of the stage chain above) that the `test` stage copies bats / + shellcheck / hadolint binaries from via `COPY --from=test-tools:local`. + +### Smoke test helpers (for downstream repos) + +`test/smoke/test_helper.bash` (loaded by every smoke spec via +`load "${BATS_TEST_DIRNAME}/test_helper"`) ships a small set of runtime +assertion helpers. Downstream repos should prefer these over ad-hoc +`[ -f ... ]` / `command -v` checks so failures produce decorated +diagnostics pointing at the missing artifact. + +| Helper | Usage | +|--------|-------| +| `assert_cmd_installed ` | Fails unless `` is on `PATH` | +| `assert_cmd_runs [flag]` | Fails unless ` ` exits 0 (default flag: `--version`) | +| `assert_file_exists ` | Fails unless `` is a regular file | +| `assert_dir_exists ` | Fails unless `` is a directory | +| `assert_file_owned_by ` | Fails unless ``'s owner is `` | +| `assert_pip_pkg ` | Fails unless `pip show ` returns 0 | + +### What stays in each repo (not shared) + +- `Dockerfile` +- `compose.yaml` +- `.env.example` +- `script/entrypoint.sh` +- `doc/` and `README.md` +- Repo-specific smoke tests + +## Per-repo runtime configuration + +Each downstream repo drives its runtime config — GPU reservation, GUI +env/volumes, network mode, extra volume mounts — through a single +`setup.conf` INI file. `setup.sh` reads it (plus system detection) and +regenerates both `.env` and `compose.yaml`; users never hand-edit those +two derived artifacts. + +### One conf, six sections + +``` +[image] rules = prefix:docker_, suffix:_ws, @default:unknown +[build] apt_mirror_ubuntu, apt_mirror_debian # Dockerfile build args +[deploy] gpu_mode (auto|force|off), gpu_count, gpu_capabilities +[gui] mode (auto|force|off) +[network] mode (host|bridge|none), ipc, privileged +[volumes] mount_1 (workspace, auto-populated on first run) + mount_2..mount_N (extra host mounts; devices via /dev path) ``` -### Run +Template default lives at `template/setup.conf`; per-repo overrides go +at `/setup.conf`. Section-level **replace** strategy: a section +present in the per-repo file fully replaces the template's section; +omitted sections fall back to template. -```bash -./run.sh # Run with default bridge config +On first `setup.sh` run (no per-repo setup.conf yet), the template file +is copied to the repo and the detected workspace is written to +`[volumes] mount_1`. Subsequent runs read `mount_1` as source of truth +— clear it to opt out of mounting a workspace. Edit via: -# Or with custom bridge mode -docker compose run --rm devel ros2 run ros1_bridge dynamic_bridge +```bash +./setup_tui.sh # interactive dialog/whiptail editor +./setup_tui.sh volumes # jump directly to one section +./build.sh --setup # launches setup_tui.sh under TTY; setup.sh otherwise +./template/init.sh --gen-conf # plain copy of template/setup.conf to repo root ``` -### Enter running container +### When setup.sh runs + +`setup.sh` runs only when explicitly triggered — it is not re-run on +every build or launch: + +- **`./template/init.sh`** runs it once after the skeleton lands +- **`./build.sh --setup` / `./run.sh --setup`** (or `-s`) re-runs it on demand +- **First-time bootstrap**: `./build.sh` / `./run.sh` auto-run setup.sh + the very first time (when `.env` is missing, e.g. after a fresh CI + clone) — no manual `--setup` needed + +### Drift detection + +`setup.sh` stores `SETUP_CONF_HASH`, `SETUP_GUI_DETECTED`, and +`SETUP_TIMESTAMP` in `.env`. On every `./build.sh` / `./run.sh`, +stored values are compared against the current setup.conf hash + system +detection; a `[WARNING]` is printed (non-blocking) when any of the +following changed since last setup: + +- `setup.conf` contents (conf hash) +- GPU / GUI detection +- `USER_UID` (user identity change) + +Re-run with `--setup` to regenerate `.env` + `compose.yaml`. + +### Derived artifacts (gitignored) + +- `.env` — runtime variable values + `SETUP_*` drift metadata +- `compose.yaml` — full compose with baseline + conditional blocks + +Open `compose.yaml` anytime to inspect the repo's current effective +configuration. + +## Quick Start + +### Adding to a new repo ```bash -./exec.sh -./exec.sh bash -``` +# 1. Initialize empty repo (skip if you already have one with at least one commit) +mkdir && cd +git init +git commit --allow-empty -m "chore: initial commit" -## Bridge Configuration +# 2. Add subtree +git subtree add --prefix=template \ + https://github.com/ycpss91255-docker/template.git main --squash -The default bridge config is `bridge.yaml`. Additional configs are in `config/`: +# 3. Initialize symlinks (one command; runs setup.sh under the hood) +./template/init.sh +``` -| File | Description | -|------|-------------| -| `bridge.yaml` | Default config (LaserScan `/scan`) | -| `config/scan_bridge.yaml` | LaserScan bridge | -| `config/release_bridge.yaml` | Camera + depth topics bridge | +> `git subtree add` requires `HEAD` to exist. On a freshly `git init`-ed repo with no commits, it fails with `ambiguous argument 'HEAD'` and `working tree has modifications`. The empty commit creates `HEAD` so subtree can merge into it. -To use a different config, rebuild with: +### Updating ```bash -docker compose build --build-arg BRIDGE_FILE=config/release_bridge.yaml devel +# Check if update available +make upgrade-check + +# Upgrade to latest (subtree pull + version file + workflow tag) +make upgrade + +# Or specify a version +./template/upgrade.sh v0.3.0 ``` -### YAML Format +## CI Reusable Workflows + +Repos replace local `build-worker.yaml` / `release-worker.yaml` with calls to this repo's reusable workflows: ```yaml -topics: - - topic: /scan - type: sensor_msgs/msg/LaserScan - queue_size: 10 +# .github/workflows/main.yaml +jobs: + call-docker-build: + uses: ycpss91255-docker/template/.github/workflows/build-worker.yaml@v1 + with: + image_name: ros_noetic + build_args: | + ROS_DISTRO=noetic + ROS_TAG=ros-base + UBUNTU_CODENAME=focal + + call-release: + needs: call-docker-build + if: startsWith(github.ref, 'refs/tags/') + uses: ycpss91255-docker/template/.github/workflows/release-worker.yaml@v1 + with: + archive_name_prefix: ros_noetic ``` -## Architecture +### build-worker.yaml inputs -```mermaid -graph TD - EXT1["bats/bats:latest"] - EXT2["alpine:latest"] - EXT3["osrf/ros:foxy-ros1-bridge"] +| Input | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `image_name` | string | yes | - | Container image name | +| `build_args` | string | no | `""` | Multi-line KEY=VALUE build args | +| `build_runtime` | boolean | no | `true` | Whether to build runtime stage | - EXT1 --> bats-src["bats-src"] - EXT2 --> bats-ext["bats-extensions"] +### release-worker.yaml inputs - EXT3 --> devel["devel\nentrypoint + bridge config"] +| Input | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `archive_name_prefix` | string | yes | - | Archive name prefix | +| `extra_files` | string | no | `""` | Space-separated extra files | - bats-src --> test["test (ephemeral)\nsmoke tests, discarded after build"] - bats-ext --> test - devel --> test +## Running Template Tests +Using `Makefile.ci` (from template root): +```bash +make -f Makefile.ci test # Full CI (ShellCheck + Bats + Kcov) via docker compose +make -f Makefile.ci lint # ShellCheck only +make -f Makefile.ci clean # Remove coverage reports +make help # Show repo targets +make -f Makefile.ci help # Show CI targets ``` -## Smoke Tests +Or directly: +```bash +./script/ci/ci.sh # Full CI via docker compose +./script/ci/ci.sh --ci # Run inside container (used by compose) +``` + +## Tests See [TEST.md](doc/test/TEST.md) for details. ## Directory Structure -```text -ros1_bridge/ -├── compose.yaml # Docker Compose definition -├── Dockerfile # Multi-stage build (devel + test) -├── build.sh -> template/build.sh # Symlink -├── run.sh -> template/run.sh # Symlink -├── exec.sh -> template/exec.sh # Symlink -├── stop.sh -> template/stop.sh # Symlink -├── Makefile -> template/Makefile # Symlink -├── .template_version # Template subtree version (v0.4.1) -├── template/ # Shared scripts, tests, CI (git subtree) +``` +template/ +├── init.sh # Initialize repo (new or existing) +├── upgrade.sh # Upgrade template subtree version ├── script/ -│ └── entrypoint.sh # Sources ROS 1 + ROS 2, loads bridge config -├── bridge.yaml # Default bridge configuration -├── config/ # Additional bridge configs -│ ├── scan_bridge.yaml # LaserScan bridge -│ └── release_bridge.yaml # Camera + depth bridge -├── doc/ # Translated READMEs -│ ├── README.zh-TW.md # Traditional Chinese -│ ├── README.zh-CN.md # Simplified Chinese -│ └── README.ja.md # Japanese +│ ├── docker/ # Docker operation scripts (symlinked by repos) +│ │ ├── build.sh +│ │ ├── run.sh +│ │ ├── exec.sh +│ │ ├── stop.sh +│ │ ├── setup.sh # .env generator +│ │ ├── _lib.sh # Shared helpers (_load_env, _compose, _compose_project) +│ │ ├── i18n.sh # Shared language detection (_detect_lang, _LANG) +│ │ └── Makefile +│ └── ci/ +│ └── ci.sh # CI pipeline (local + remote) +├── dockerfile/ +│ ├── Dockerfile.test-tools # Pre-built lint/test tools image +│ └── Dockerfile.example # Dockerfile template for new repos (sys → base → devel → test → [runtime]) +├── setup.conf # Single runtime config (per-repo override mirror: /setup.conf) +├── config/ # Container-internal shell/tool configs +│ ├── image_name.conf # Default IMAGE_NAME detection rules +│ ├── pip/ +│ │ ├── setup.sh +│ │ └── requirements.txt +│ └── shell/ +│ ├── bashrc +│ ├── terminator/ +│ │ ├── setup.sh +│ │ └── config +│ └── tmux/ +│ ├── setup.sh +│ └── tmux.conf +├── test/ +│ ├── smoke/ # Shared smoke tests + runtime assertion helpers +│ │ ├── test_helper.bash # → assert_cmd_installed / _runs / file / dir / owned_by / pip_pkg +│ │ ├── script_help.bats +│ │ └── display_env.bats +│ ├── unit/ # Template self-tests (bats + kcov) +│ │ ├── test_helper.bash +│ │ ├── bashrc_spec.bats +│ │ ├── ci_spec.bats # ci.sh _install_deps +│ │ ├── lib_spec.bats # _lib.sh +│ │ ├── pip_setup_spec.bats +│ │ ├── setup_spec.bats +│ │ ├── smoke_helper_spec.bats # Runtime assertion helpers +│ │ ├── template_spec.bats +│ │ ├── terminator_config_spec.bats +│ │ ├── terminator_setup_spec.bats +│ │ ├── tmux_conf_spec.bats +│ │ └── tmux_setup_spec.bats +│ └── integration/ +│ └── init_new_repo_spec.bats # Level-1 init.sh end-to-end +├── Makefile.ci # Template CI entry (make test/lint/...) +├── compose.yaml # Docker CI runner +├── .hadolint.yaml # Shared Hadolint rules +├── codecov.yml ├── .github/workflows/ -│ └── main.yaml # CI/CD (calls template reusable workflows) -└── test/smoke/ # Bats environment tests (repo-specific) - └── ros_env.bats +│ ├── self-test.yaml # Template CI +│ ├── build-worker.yaml # Reusable build workflow +│ └── release-worker.yaml # Reusable release workflow +├── doc/ +│ ├── readme/ # README translations (zh-TW / zh-CN / ja) +│ ├── test/TEST.md # Test catalog (spec tables) +│ └── changelog/CHANGELOG.md # Release notes +├── .gitignore +├── LICENSE +└── README.md ``` diff --git a/bridge.yaml b/bridge.yaml deleted file mode 100644 index 78da0d5..0000000 --- a/bridge.yaml +++ /dev/null @@ -1,5 +0,0 @@ -topics: - - topic: /scan - type: sensor_msgs/msg/LaserScan - queue_size: 10 - diff --git a/build.sh b/build.sh deleted file mode 120000 index 34b91c4..0000000 --- a/build.sh +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/build.sh \ No newline at end of file diff --git a/template/compose.yaml b/compose.yaml similarity index 100% rename from template/compose.yaml rename to compose.yaml diff --git a/template/config/pip/requirements.txt b/config/pip/requirements.txt similarity index 100% rename from template/config/pip/requirements.txt rename to config/pip/requirements.txt diff --git a/template/config/pip/setup.sh b/config/pip/setup.sh similarity index 100% rename from template/config/pip/setup.sh rename to config/pip/setup.sh diff --git a/config/release_bridge.yaml b/config/release_bridge.yaml deleted file mode 100644 index 78c7d8d..0000000 --- a/config/release_bridge.yaml +++ /dev/null @@ -1,20 +0,0 @@ -topics: - - topic: /camera/camera/aligned_depth_to_color/camera_info - type: sensor_msgs/msg/CameraInfo - queue_size: 10 - - topic: /camera/camera/aligned_depth_to_color/image_raw - type: sensor_msgs/msg/Image - queue_size: 10 - - topic: /camera/camera/color/camera_info - type: sensor_msgs/msg/CameraInfo - queue_size: 10 - - topic: /camera/camera/color/image_raw - type: sensor_msgs/msg/Image - queue_size: 10 - - topic: /camera/camera/depth/camera_info - type: sensor_msgs/msg/CameraInfo - queue_size: 10 - - topic: /camera/camera/depth/image_rect_raw - type: sensor_msgs/msg/Image - queue_size: 10 - diff --git a/config/scan_bridge.yaml b/config/scan_bridge.yaml deleted file mode 100644 index 78da0d5..0000000 --- a/config/scan_bridge.yaml +++ /dev/null @@ -1,5 +0,0 @@ -topics: - - topic: /scan - type: sensor_msgs/msg/LaserScan - queue_size: 10 - diff --git a/template/config/shell/bashrc b/config/shell/bashrc similarity index 100% rename from template/config/shell/bashrc rename to config/shell/bashrc diff --git a/template/config/shell/terminator/config b/config/shell/terminator/config similarity index 100% rename from template/config/shell/terminator/config rename to config/shell/terminator/config diff --git a/template/config/shell/terminator/setup.sh b/config/shell/terminator/setup.sh similarity index 100% rename from template/config/shell/terminator/setup.sh rename to config/shell/terminator/setup.sh diff --git a/template/config/shell/tmux/README.adoc b/config/shell/tmux/README.adoc similarity index 100% rename from template/config/shell/tmux/README.adoc rename to config/shell/tmux/README.adoc diff --git a/template/config/shell/tmux/setup.sh b/config/shell/tmux/setup.sh similarity index 100% rename from template/config/shell/tmux/setup.sh rename to config/shell/tmux/setup.sh diff --git a/template/config/shell/tmux/tmux.conf b/config/shell/tmux/tmux.conf similarity index 100% rename from template/config/shell/tmux/tmux.conf rename to config/shell/tmux/tmux.conf diff --git a/doc/README.ja.md b/doc/README.ja.md deleted file mode 100644 index 92ecda4..0000000 --- a/doc/README.ja.md +++ /dev/null @@ -1,147 +0,0 @@ -# ROS 1 Bridge Docker Environment - -**[English](../README.md)** | **[繁體中文](README.zh-TW.md)** | **[简体中文](README.zh-CN.md)** | **[日本語](README.ja.md)** - -> **TL;DR** — `osrf/ros:foxy-ros1-bridge` ベースの ROS 1/2 ブリッジコンテナ。`parameter_bridge` で ROS 1 (Noetic) と ROS 2 (Foxy) の topic をブリッジ。 -> -> ```bash -> ./build.sh && ./run.sh -> ``` - ---- - -## 目次 - -- [特徴](#特徴) -- [クイックスタート](#クイックスタート) -- [使い方](#使い方) -- [ブリッジ設定](#ブリッジ設定) -- [アーキテクチャ](#アーキテクチャ) -- [ディレクトリ構成](#ディレクトリ構成) - ---- - -## 特徴 - -- **ビルド済み bridge イメージ**:`osrf/ros:foxy-ros1-bridge` ベース、ROS 1 と ROS 2 を同梱 -- **Parameter bridge**:YAML 設定で topic ブリッジを構成可能 -- **Smoke Test**:Bats テストで両 ROS 環境と bridge の可用性を検証 -- **Docker Compose**:`compose.yaml` 一つでビルドと実行を管理 -- **サンプル設定**:scan と camera のブリッジ設定ファイルを同梱 - -## クイックスタート - -```bash -# 1. ビルド -./build.sh - -# 2. 実行(ROS master が起動済みであること) -./run.sh - -# 3. 起動中のコンテナに接続 -./exec.sh -``` - -## 使い方 - -### ビルド - -```bash -./build.sh # devel をビルド(デフォルト) -./build.sh test # smoke test 付きビルド - -docker compose build devel # 同等のコマンド -``` - -### 実行 - -```bash -./run.sh # デフォルトの bridge 設定で実行 - -# カスタム bridge モードを使用 -docker compose run --rm devel ros2 run ros1_bridge dynamic_bridge -``` - -### 起動中のコンテナに接続 - -```bash -./exec.sh -./exec.sh bash -``` - -## ブリッジ設定 - -デフォルトの bridge 設定は `bridge.yaml`。追加設定ファイルは `config/` ディレクトリ内: - -| ファイル | 説明 | -|----------|------| -| `bridge.yaml` | デフォルト設定(LaserScan `/scan`) | -| `config/scan_bridge.yaml` | LaserScan bridge | -| `config/release_bridge.yaml` | Camera + depth topics bridge | - -異なる設定で再ビルド: - -```bash -docker compose build --build-arg BRIDGE_FILE=config/release_bridge.yaml devel -``` - -### YAML フォーマット - -```yaml -topics: - - topic: /scan - type: sensor_msgs/msg/LaserScan - queue_size: 10 -``` - -## アーキテクチャ - -```mermaid -graph TD - EXT1["bats/bats:latest"] - EXT2["alpine:latest"] - EXT3["osrf/ros:foxy-ros1-bridge"] - - EXT1 --> bats-src["bats-src"] - EXT2 --> bats-ext["bats-extensions"] - - EXT3 --> devel["devel\nentrypoint + bridge config"] - - bats-src --> test["test一時的\nsmoke/ ビルド後に破棄"] - bats-ext --> test - devel --> test - -``` - -## Smoke Tests - -詳細は [TEST.md](test/TEST.md) を参照。 - -## ディレクトリ構成 - -```text -ros1_bridge/ -├── compose.yaml # Docker Compose 定義 -├── Dockerfile # マルチステージビルド(devel + test) -├── build.sh -> template/build.sh # Symlink -├── run.sh -> template/run.sh # Symlink -├── exec.sh -> template/exec.sh # Symlink -├── stop.sh -> template/stop.sh # Symlink -├── Makefile -> template/Makefile # Symlink -├── .template_version # Template subtree バージョン(v0.4.1) -├── template/ # 共有スクリプト、テスト、CI(git subtree) -├── script/ -│ └── entrypoint.sh # ROS 1 + ROS 2 を source、bridge 設定を読み込み -├── bridge.yaml # デフォルト bridge 設定 -├── config/ # 追加 bridge 設定 -│ ├── scan_bridge.yaml # LaserScan bridge -│ └── release_bridge.yaml # Camera + depth bridge -├── doc/ # 翻訳版 README -│ ├── README.zh-TW.md # 繁体字中国語 -│ ├── README.zh-CN.md # 簡体字中国語 -│ └── README.ja.md # 日本語 -├── .github/workflows/ -│ └── main.yaml # CI/CD(template reusable workflows を呼び出し) -└── test/smoke/ # Bats 環境テスト(repo 固有) - └── ros_env.bats -``` diff --git a/doc/README.zh-CN.md b/doc/README.zh-CN.md deleted file mode 100644 index 3598e34..0000000 --- a/doc/README.zh-CN.md +++ /dev/null @@ -1,147 +0,0 @@ -# ROS 1 Bridge Docker Environment - -**[English](../README.md)** | **[繁體中文](README.zh-TW.md)** | **[简体中文](README.zh-CN.md)** | **[日本語](README.ja.md)** - -> **TL;DR** — 基于 `osrf/ros:foxy-ros1-bridge` 的 ROS 1/2 bridge 容器。通过 `parameter_bridge` 桥接 ROS 1 (Noetic) 与 ROS 2 (Foxy) topics。 -> -> ```bash -> ./build.sh && ./run.sh -> ``` - ---- - -## 目录 - -- [特性](#特性) -- [快速开始](#快速开始) -- [使用方式](#使用方式) -- [Bridge 设置](#bridge-设置) -- [架构](#架构) -- [目录结构](#目录结构) - ---- - -## 特性 - -- **预建 bridge 镜像**:基于 `osrf/ros:foxy-ros1-bridge`,同时包含 ROS 1 和 ROS 2 -- **Parameter bridge**:通过 YAML 设置可配置的 topic 桥接 -- **Smoke Test**:Bats 测试验证两个 ROS 环境及 bridge 可用性 -- **Docker Compose**:一个 `compose.yaml` 管理构建与执行 -- **示例设置**:内含 scan 和 camera bridge 设置文件 - -## 快速开始 - -```bash -# 1. 构建 -./build.sh - -# 2. 执行(需要 ROS master 已启动) -./run.sh - -# 3. 进入已启动的容器 -./exec.sh -``` - -## 使用方式 - -### 构建 - -```bash -./build.sh # 构建 devel(默认) -./build.sh test # 构建含 smoke test - -docker compose build devel # 等效命令 -``` - -### 执行 - -```bash -./run.sh # 以默认 bridge 设置执行 - -# 或使用自定义 bridge 模式 -docker compose run --rm devel ros2 run ros1_bridge dynamic_bridge -``` - -### 进入已启动的容器 - -```bash -./exec.sh -./exec.sh bash -``` - -## Bridge 设置 - -默认 bridge 设置为 `bridge.yaml`。额外设置文件在 `config/` 目录: - -| 文件 | 说明 | -|------|------| -| `bridge.yaml` | 默认设置(LaserScan `/scan`) | -| `config/scan_bridge.yaml` | LaserScan bridge | -| `config/release_bridge.yaml` | Camera + depth topics bridge | - -使用不同设置重新构建: - -```bash -docker compose build --build-arg BRIDGE_FILE=config/release_bridge.yaml devel -``` - -### YAML 格式 - -```yaml -topics: - - topic: /scan - type: sensor_msgs/msg/LaserScan - queue_size: 10 -``` - -## 架构 - -```mermaid -graph TD - EXT1["bats/bats:latest"] - EXT2["alpine:latest"] - EXT3["osrf/ros:foxy-ros1-bridge"] - - EXT1 --> bats-src["bats-src"] - EXT2 --> bats-ext["bats-extensions"] - - EXT3 --> devel["devel\nentrypoint + bridge config"] - - bats-src --> test["test临时性\nsmoke/ 执行后即丢"] - bats-ext --> test - devel --> test - -``` - -## Smoke Tests - -详见 [TEST.md](test/TEST.md)。 - -## 目录结构 - -```text -ros1_bridge/ -├── compose.yaml # Docker Compose 定义 -├── Dockerfile # 多阶段构建(devel + test) -├── build.sh -> template/build.sh # Symlink -├── run.sh -> template/run.sh # Symlink -├── exec.sh -> template/exec.sh # Symlink -├── stop.sh -> template/stop.sh # Symlink -├── Makefile -> template/Makefile # Symlink -├── .template_version # Template subtree 版本(v0.4.1) -├── template/ # 共用脚本、测试、CI(git subtree) -├── script/ -│ └── entrypoint.sh # Source ROS 1 + ROS 2,载入 bridge 设置 -├── bridge.yaml # 默认 bridge 设置 -├── config/ # 额外 bridge 设置 -│ ├── scan_bridge.yaml # LaserScan bridge -│ └── release_bridge.yaml # Camera + depth bridge -├── doc/ # 翻译版 README -│ ├── README.zh-TW.md # 繁体中文 -│ ├── README.zh-CN.md # 简体中文 -│ └── README.ja.md # 日文 -├── .github/workflows/ -│ └── main.yaml # CI/CD(调用 template reusable workflows) -└── test/smoke/ # Bats 环境测试(repo 专属) - └── ros_env.bats -``` diff --git a/doc/README.zh-TW.md b/doc/README.zh-TW.md deleted file mode 100644 index 8450b4d..0000000 --- a/doc/README.zh-TW.md +++ /dev/null @@ -1,147 +0,0 @@ -# ROS 1 Bridge Docker Environment - -**[English](../README.md)** | **[繁體中文](README.zh-TW.md)** | **[简体中文](README.zh-CN.md)** | **[日本語](README.ja.md)** - -> **TL;DR** — 基於 `osrf/ros:foxy-ros1-bridge` 的 ROS 1/2 bridge 容器。透過 `parameter_bridge` 橋接 ROS 1 (Noetic) 與 ROS 2 (Foxy) topics。 -> -> ```bash -> ./build.sh && ./run.sh -> ``` - ---- - -## 目錄 - -- [特色](#特色) -- [快速開始](#快速開始) -- [使用方式](#使用方式) -- [Bridge 設定](#bridge-設定) -- [架構](#架構) -- [目錄結構](#目錄結構) - ---- - -## 特色 - -- **預建 bridge 映像**:基於 `osrf/ros:foxy-ros1-bridge`,同時包含 ROS 1 和 ROS 2 -- **Parameter bridge**:透過 YAML 設定可配置的 topic 橋接 -- **Smoke Test**:Bats 測試驗證兩個 ROS 環境及 bridge 可用性 -- **Docker Compose**:一個 `compose.yaml` 管理建置與執行 -- **範例設定**:內含 scan 和 camera bridge 設定檔 - -## 快速開始 - -```bash -# 1. 建置 -./build.sh - -# 2. 執行(需要 ROS master 已啟動) -./run.sh - -# 3. 進入已啟動的容器 -./exec.sh -``` - -## 使用方式 - -### 建置 - -```bash -./build.sh # 建置 devel(預設) -./build.sh test # 建置含 smoke test - -docker compose build devel # 等效指令 -``` - -### 執行 - -```bash -./run.sh # 以預設 bridge 設定執行 - -# 或使用自定義 bridge 模式 -docker compose run --rm devel ros2 run ros1_bridge dynamic_bridge -``` - -### 進入已啟動的容器 - -```bash -./exec.sh -./exec.sh bash -``` - -## Bridge 設定 - -預設 bridge 設定為 `bridge.yaml`。額外設定檔在 `config/` 目錄: - -| 檔案 | 說明 | -|------|------| -| `bridge.yaml` | 預設設定(LaserScan `/scan`) | -| `config/scan_bridge.yaml` | LaserScan bridge | -| `config/release_bridge.yaml` | Camera + depth topics bridge | - -使用不同設定重新建置: - -```bash -docker compose build --build-arg BRIDGE_FILE=config/release_bridge.yaml devel -``` - -### YAML 格式 - -```yaml -topics: - - topic: /scan - type: sensor_msgs/msg/LaserScan - queue_size: 10 -``` - -## 架構 - -```mermaid -graph TD - EXT1["bats/bats:latest"] - EXT2["alpine:latest"] - EXT3["osrf/ros:foxy-ros1-bridge"] - - EXT1 --> bats-src["bats-src"] - EXT2 --> bats-ext["bats-extensions"] - - EXT3 --> devel["devel\nentrypoint + bridge config"] - - bats-src --> test["test暫時性\nsmoke/ 執行後丟棄"] - bats-ext --> test - devel --> test - -``` - -## Smoke Tests - -詳見 [TEST.md](test/TEST.md)。 - -## 目錄結構 - -```text -ros1_bridge/ -├── compose.yaml # Docker Compose 定義 -├── Dockerfile # 多階段建置(devel + test) -├── build.sh -> template/build.sh # Symlink -├── run.sh -> template/run.sh # Symlink -├── exec.sh -> template/exec.sh # Symlink -├── stop.sh -> template/stop.sh # Symlink -├── Makefile -> template/Makefile # Symlink -├── .template_version # Template subtree 版本(v0.4.1) -├── template/ # 共用腳本、測試、CI(git subtree) -├── script/ -│ └── entrypoint.sh # Source ROS 1 + ROS 2,載入 bridge 設定 -├── bridge.yaml # 預設 bridge 設定 -├── config/ # 額外 bridge 設定 -│ ├── scan_bridge.yaml # LaserScan bridge -│ └── release_bridge.yaml # Camera + depth bridge -├── doc/ # 翻譯版 README -│ ├── README.zh-TW.md # 繁體中文 -│ ├── README.zh-CN.md # 簡體中文 -│ └── README.ja.md # 日文 -├── .github/workflows/ -│ └── main.yaml # CI/CD(呼叫 template reusable workflows) -└── test/smoke/ # Bats 環境測試(repo 專屬) - └── ros_env.bats -``` diff --git a/doc/changelog/CHANGELOG.md b/doc/changelog/CHANGELOG.md index efae9f1..96587b0 100644 --- a/doc/changelog/CHANGELOG.md +++ b/doc/changelog/CHANGELOG.md @@ -1,76 +1,961 @@ -**[English](CHANGELOG.md)** | **[繁體中文](CHANGELOG.zh-TW.md)** | **[简体中文](CHANGELOG.zh-CN.md)** | **[日本語](CHANGELOG.ja.md)** - # Changelog -Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [v0.9.11] - 2026-04-24 + +### Fixed +- **`_lib.sh` fallback `_detect_lang` returned `"zh"` for `zh_TW` (issue + #103)** — a copy-paste typo in the fallback used when `i18n.sh` was + absent (the Dockerfile `/lint` stage). Fixed to `"zh-TW"`. The + follow-up `#104` dedupe below then REMOVED the fallback entirely; the + only remaining `_detect_lang` is in `i18n.sh`. + +### Changed +- **`[build] network` now defaults to `auto` (issue #102)**. On Jetson + (detected via `/etc/nv_tegra_release`) setup.sh resolves `auto` to + `host`, so first-time `./build.sh` succeeds without the DNS failures + that Jetson's broken bridge NAT used to cause. Desktop hosts stay on + Docker's default bridge. Explicit `host` / `bridge` / `none` / + `default` still pass through unchanged; new `off` value for explicit + opt-out. New `_resolve_build_network` helper mirrors + `_resolve_runtime`'s Jetson-aware pattern. +- **`_detect_lang` deduplicated: single canonical definition in + `i18n.sh` (issue #104)**. Previously `build.sh` / `run.sh` / + `exec.sh` / `stop.sh` / `_lib.sh` each shipped an inline fallback + `_detect_lang` for when `i18n.sh` wasn't reachable (Dockerfile + `/lint` stage). That invited drift — see #103 where `_lib.sh`'s + copy had silently returned `zh` instead of `zh-TW` for months. + `Dockerfile.example`'s test stage now COPYs `_lib.sh` + `i18n.sh` + + `_tui_conf.sh` alongside `*.sh`; scripts look up `_lib.sh` in the + template layout OR as a sibling, with a clear error when neither + exists. Downstream repos using a custom Dockerfile (not based on + `Dockerfile.example`) need to mirror this COPY in their test stage. +- **`_sanitize_lang` warning now localises to the system `$LANG`**. v0.9.7 + Agent A scoped this helper out of i18n; a user with `LANG=zh_TW.UTF-8` + who typed `--lang xxx` still saw an English WARNING. Now we re-detect + from the system env (can't trust `_LANG` — it holds the invalid input + the user just passed) and print the warning in zh-TW / zh-CN / ja + where applicable, falling back to English for other locales. + +### Added +- **Coverage audit follow-up (+9 unit tests)**. Kcov run flagged four + small untested branches in `_lib.sh` and `_tui_conf.sh`; filling them + raised non-TUI coverage from 94.4% → 95.7%. New tests: + - `_lib_msg count` / `caps` translation keys exercised in all four + languages (previously only Files / Identity / etc. were asserted). + - `_mount_container_path` helper — four cases (plain / + with-mode / env-var-interpolated / no-colon fallback). The symmetric + `_mount_host_path` was already covered; the container-side parser + had zero unit tests. + - `_upsert_conf_value` "section not found" branch — appends a fresh + `[section]` header + key when called against a conf that doesn't + yet have that section. + - `_upsert_conf_value` "section present, key absent at EOF" branch — + appends the key to the last section when target key isn't there. + - `_write_setup_conf` final-section override flush — an override key + whose target is the LAST section in the template gets emitted + via the EOF-flush path (previously only the mid-file append branch + was asserted). + - `_write_setup_conf` removed_keys + flush interplay — ensures a key + listed in `removed_keys` does NOT reappear via the EOF flush. + + TUI interactive flows (`_edit_section_*`) in `setup_tui.sh` remain + at ~17% — they require a dialog/whiptail stub framework to drive, + cost doesn't justify coverage-for-its-own-sake. `setup_tui.sh` + validators / I/O helpers are covered at unit level via `tui_spec`. + +## [v0.9.10] - 2026-04-24 + +### Added +- **Multi-arch support in `build-worker.yaml`** — new `platforms` input + (default `"linux/amd64"`, accepts `"linux/amd64,linux/arm64"`). Each + requested platform runs as a parallel matrix shard on its own native + runner (amd64 → `ubuntu-latest`, arm64 → `ubuntu-24.04-arm`), so arm64 + builds avoid QEMU emulation and stay in the 5-15 min range instead of + 30-60 min. Full pipeline (test-tools → test stage smoke → devel → + runtime) runs natively per platform. Covers Jetson (Nano / Xavier / + Orin, all aarch64) and modern Raspberry Pi (4 / 5 on 64-bit OS) and + standard x86 hosts. 32-bit ARM (armv7/v6) intentionally unsupported — + no native runner exists and QEMU emulation would balloon CI time; + modern Pi defaults to 64-bit OS. + +### Changed +- **`build-worker.yaml` now uses the `docker-container` buildx driver** + (was `docker`). Required for multi-arch builds. Side effect: + `test-tools:local` is built via `docker/build-push-action@v6` (not + plain `docker build`) so the tag lands in buildx's internal image + store, visible to the subsequent test-stage build's + `COPY --from=test-tools:local` on the same builder. +- **Matrix job names**: per-platform shards are called + `call-docker-build / build (linux/amd64)` etc. A stable-name + aggregator job `call-docker-build / docker-build` gates on all + shards — downstream `main` branch protection rules that require + `call-docker-build / docker-build` keep working without changes. + +## [v0.9.9] - 2026-04-24 + +### Added +- **`[deploy] runtime` setup.conf key** — Docker runtime override at + service level in compose.yaml. Required on Jetson (JetPack) because + its nvidia-container-toolkit runs in csv mode and refuses the modern + `--gpus` flow that `deploy.resources.reservations.devices` uses. + Values: + - `auto` — emit `runtime: nvidia` on Jetson (detected via + `/etc/nv_tegra_release`), omit on desktop (default). + - `nvidia` — force emit on all hosts (e.g. csv-mode toolkit on x86). + - `off` — never emit (Docker default runc). + + `setup.sh` resolves via new `_detect_jetson` + `_resolve_runtime` + helpers; `SETUP_DETECT_JETSON=true|false` env var overrides the + filesystem probe (used by tests). `setup_tui.sh` gains a matching + picker in `[deploy]` section with 4-language i18n; + `_validate_runtime` accepts `auto|nvidia|off` or empty. + +### Changed +- **`_lib.sh` `_print_config_summary` now honours `${_LANG}`**. Previously + the build/run config summary (Files / Identity / Resolved / Customize + sections, plus user / hardware / workspace / GPU enabled / GUI enabled + / network / privileged labels) was hardcoded English regardless of + `--lang` / `SETUP_LANG`. Agent A's v0.9.7 i18n PR explicitly scoped + this out as "too much to bite off"; user feedback after the Jetson + upgrade: `./run.sh --lang zh-TW` still looked English because the + summary is 90% of the output. A new `_lib_msg` translation table + covers `en` / `zh-TW` / `zh-CN` / `ja`. Technical identifiers kept + untranslated: file names (`setup.conf` / `.env` / `compose.yaml`), + INI section names (`[image]` / ...), `.env` variable names (`TZ`, + `APT_MIRROR_*`, `IPC`, `CAPS`), and command strings in the Customize + hint. + +## [v0.9.8] - 2026-04-23 + +### Fixed +- **`upgrade.sh` no longer leaves the repo destroyed if `git subtree + pull` misbehaves**. On Jetson L4T (ships an older `git-subtree.sh`), + running `upgrade.sh v0.9.5 → v0.9.7` fast-forwarded the synthetic + squash commit onto HEAD, moving `template/*` to repo root and + deleting repo-specific files (Dockerfile, compose.yaml, bridge.yaml, + etc.). `upgrade.sh` now: + - **Pre-flight** — fails fast with actionable messages when `git + config user.name / user.email` is unset (a Jetson-specific trigger + for the partial-state bug), or when a merge / rebase / cherry-pick + is in progress (`.git/MERGE_HEAD`, `.git/rebase-merge`, etc.). + - **Post-flight integrity check** — after the subtree pull, verifies + `template/.version`, `template/init.sh`, and + `template/script/docker/setup.sh` still exist. If any is missing, + hard-resets to the pre-pull HEAD and exits with a diagnostic. The + working tree is restored; no manual cleanup required. + - **Step numbering** — corrected from mixed "1/4 / 2/3 / 3/3" to + "1/4 / 2/4 / 3/4 / 4/4". +- **`test/unit/upgrade_spec.bats`** gains 12 regression tests covering + the three new guards + structural invariants (ordering: identity + check before pull, integrity check after pull, HEAD snapshotted for + rollback). +- **`test/integration/upgrade_spec.bats`** (new, 6 tests) drives the + real `upgrade.sh` end-to-end against a fake template remote (bare + repo with `v0.9.5` / `v0.9.7` tags) attached to a sandbox downstream + repo. Covers happy path (version bump, new content, `main.yaml` + `@tag` rewrite), idempotent re-run, `--check`, the two pre-flight + guards, and the destructive-FF rollback (stubs `git-subtree pull` + via `GIT_EXEC_PATH` to simulate the Jetson bug, asserts repo is + restored to pre-pull HEAD). Total: 592 → 610 (+12 unit + 6 + integration). + +## [v0.9.7] - 2026-04-23 + +### Changed +- **Full i18n coverage for `build.sh` / `run.sh` / `exec.sh` / `stop.sh`**. + Previously only `usage()` (help text) honoured `--lang` / `SETUP_LANG`; + runtime log lines (`First run — bootstrapping`, `regenerating .env / + compose.yaml`, `ERROR: setup did not produce .env`, `Container is + already running`, `is not running`, `No instances found`, ...) were + hardcoded English regardless of language. Each script now ships a + local `_msg()` translation table covering `en` / `zh-TW` / `zh-CN` / + `ja`, matching the existing `setup.sh` pattern. English remains the + default when no flag / env var is set, so existing tooling and CI + output are unchanged. + +### Added +- **Root-level `setup.sh` symlink**. `init.sh` now links + `/setup.sh` to `template/script/docker/setup.sh` alongside the + existing `build.sh` / `run.sh` / `exec.sh` / `stop.sh` / `setup_tui.sh` + / `Makefile` symlinks. Consumer repos can now invoke `./setup.sh` + directly for scripted / CI regeneration of `.env` + `compose.yaml`, + instead of relying on the indirect `./build.sh --setup` or + `./setup_tui.sh` Save paths. +- **`setup.sh -h` / `--help`**. `script/docker/setup.sh` gains a + `usage()` block documenting `--base-path` and `--lang`, following the + existing `build.sh` case-per-`_LANG` scaffolding (English-only for + now; future translations plug in via the existing `_msg` framework). +- **`test/unit/exec_sh_spec.bats`** (18 tests) and + **`test/unit/stop_sh_spec.bats`** (17 tests): new unit specs + covering argument parsing, the container-running precheck hints in + `exec.sh`, the `--all` / `--instance` branches in `stop.sh`, all + four languages of usage text, runtime log-line i18n, and the + fallback `_detect_lang` branches (`LANG=zh_TW.UTF-8` etc. when + `template/` is absent). +- Log-line i18n regression tests in `test/unit/build_sh_spec.bats` + (+7) and `test/unit/run_sh_spec.bats` (+6) assert that `--lang + ` actually translates the runtime logs (bootstrap, drift-regen, + err_no_env, already-running), not just `--help`. + +### Fixed +- **`setup.sh` symlink-invocation robustness**. `setup.sh` previously + located its `i18n.sh` / `_tui_conf.sh` siblings and the template + `setup.conf` via `dirname "${BASH_SOURCE[0]}"`, which resolved to the + repo root when the script was invoked through `/setup.sh` + (symlink). `setup.sh` now runs `readlink -f` once at load and stores + the real script directory in `_SETUP_SCRIPT_DIR`; every sibling + source and template-relative path reads from that variable. + +## [v0.9.6] - 2026-04-23 + +### Added +- **`[build] network` setup.conf key**: overrides Docker's build-time + network mode. Empty (default) = Docker decides (bridge + NAT). Set + to `host` when the host's bridge NAT is unusable: stripped embedded + kernels (e.g. Jetson L4T missing `iptable_raw`), hosts with + `"iptables": false` in daemon.json, or firewall-locked CI runners. + `setup.sh` writes `BUILD_NETWORK=` to `.env` and emits + `build.network: ` under each service in `compose.yaml`; + `build.sh` forwards `--network ` to the auxiliary + `docker build` invocation for `test-tools`. `setup_tui.sh` gains a + matching `[build] Build network` menu item and + `_validate_build_network` validator (accepts empty / `host` / + `bridge` / `none` / `default`). +- **Integration test** `fresh_clone_portability_spec.bats` covers the + fresh-clone-on-a-different-machine path end-to-end (real `build.sh` + + `setup.sh`, no mocks): both the stale-absolute-path auto-migrate + and the portable `${WS_PATH}` round-trip. + +### Changed +- **`_dump_conf_section` hides empty-valued keys** in the + `_print_config_summary` output. Lines like `shm_size =` (using the + template default) are noise in the config dump; they're now + filtered. Sections whose every key is empty collapse to nothing and + the section header is skipped too (via the existing + `[[ -z ${_content} ]]` check in the caller). + +## [v0.9.5] - 2026-04-23 + +### Changed +- **`build.sh` / `run.sh` auto-regenerate on drift**. `_check_setup_drift` + now returns non-zero when `setup.conf` / GPU / GUI / USER_UID drifted + from `.env`; the drift branch in `build.sh` / `run.sh` re-runs + `setup.sh` automatically instead of printing a WARNING and continuing + with stale `.env`. `.env` + `compose.yaml` are derived artifacts with + no user-owned data to preserve, so re-running is always safe. Fixes + the footgun where `git pull` + `./build.sh` silently used the + previous machine's `WS_PATH`. Users who preferred the warn-only + behaviour can still edit `.env` freely — drift is only re-triggered + by changes to `setup.conf` or detected hardware, not by editing + `.env` directly. + +## [v0.9.4] - 2026-04-23 + +### Fixed +- **`[volumes] mount_1` portability**: `setup.sh` used to bake the + absolute host workspace path into `setup.conf` on first-time + bootstrap. Committing that file broke fresh clones on any other + machine whose filesystem layout differed — `_load_env` resolved + `WS_PATH` to a directory that doesn't exist and docker tried to + mount it. `setup.sh` now writes `mount_1` in the portable + `${WS_PATH}:/home/${USER_NAME}/work` form so docker-compose resolves + `${WS_PATH}` per-machine from `.env`. When a stale absolute path + (baked from another machine, absent locally) is encountered, + `setup.sh` warns and auto-migrates `mount_1` back to the portable + form. Users who intentionally pin an existing absolute path still + get that value honored. + +## [v0.9.3] - 2026-04-23 + +### BREAKING +- **`template/VERSION` renamed to `template/.version`**. Dotfile keeps + version metadata out of casual `ls`. Clean break — `upgrade.sh` / + `init.sh` / `build-worker.yaml` no longer read `template/VERSION` or + the even older `.template_version`. Downstream repos pick up the + rename automatically via `./template/upgrade.sh `: the + subtree pull drops `template/VERSION` and lands `template/.version`, + and the new `upgrade.sh`/`init.sh` code reads the new location. + Anyone running the old `upgrade.sh` binary against the new tag sees + "unknown" as the local version — cosmetic only, the upgrade still + succeeds. + +### Changed +- **Codecov config consolidated** into `.codecov.yaml`. Historical + duplicate `codecov.yml` removed — Codecov precedence had it silently + overriding `.codecov.yaml` since PR #62, so the strict `ignore:` + + `patch: 100%` rules in `.codecov.yaml` were dead config. `.codecov.yaml` + now carries the relaxed policy from `codecov.yml` (threshold 1%, + patch informational) plus the previously-ignored `test/**` and + `.github/**` ignores. No behavior change for contributors. + +## [v0.9.2] - 2026-04-23 + ### Fixed -- Restore `.env.example` (removed during APT-mirror refactor) so `setup.sh`'s IMAGE_NAME detection has its documented fallback. +- **`build.sh` / `run.sh` bootstrap**: fresh clones (where `compose.yaml` + is gitignored since v0.9.0) now bootstrap correctly. Two regressions + fixed: + 1. Bootstrap condition now also checks `compose.yaml`; previously a + clone with `.env` + `setup.conf` present but `compose.yaml` absent + skipped to the drift-check path and died in `_load_env` with a + cryptic "No such file" error. + 2. Bootstrap path no longer dispatches through `_run_interactive`, + which on a TTY launches `setup_tui.sh`. A user who pressed + Esc / Ctrl+C in the TUI previously ended up with no `.env`. + Bootstrap now calls `setup.sh` directly; TUI stays reserved for + the explicit `--setup` flag. +- **`build.sh` / `run.sh` defensive guard**: if `setup.sh` returns + without producing `.env` (cancelled TUI, setup crash, …), surface a + clear error pointing at `--setup` instead of failing deep in + `_load_env`. -## [v1.4.1] - 2026-03-25 +## [v0.9.1] - 2026-04-23 + +### Changed +- **`upgrade.sh` / `init.sh` default to HTTPS** for the template remote + (`https://github.com/ycpss91255-docker/template.git`). Fresh clones / + CI runners / first-time contributors no longer need an SSH key to + `./template/upgrade.sh`. Override with `TEMPLATE_REMOTE=git@...` env + var for private forks or SSH-agent setups. 4-language READMEs and + `init.sh` docstring updated accordingly. + +## [v0.9.0] - 2026-04-23 + +### Added (Wave 1 + Wave 2 — 2026-04-22) +- **GPU MIG detection** (`_detect_mig` / `_list_gpu_instances` in + `_tui_conf.sh`): when host has NVIDIA MIG mode enabled, the deploy + editor opens with a msgbox listing GPU / MIG instance UUIDs and + advising `NVIDIA_VISIBLE_DEVICES=` via `[environment]` + since `count=N` targets whole GPUs only +- **`[build] tz` key**: container timezone exposed as a setup.conf + value; pipes through to compose.yaml `build.args` as + `TZ: ${TZ:-Asia/Taipei}`. Empty keeps Dockerfile default +- **`[devices] cgroup_rule_*`**: `device_cgroup_rules:` block for USB + hotplug / dynamic device nodes; TUI devices editor now has a + sub-menu to pick between device bindings and cgroup rules. New + `_validate_cgroup_rule` validator + +### Changed +- `[image] rule_*` dedup on write: re-adding a rule that already + exists at another slot moves it to the new position instead of + leaving two identical entries +- `_edit_list_section` add now reuses empty slots (e.g. cleared + `mount_1` after user opted out of workspace), preventing the next + mount from leapfrogging to `mount_2` +- TUI image-rule type picker simplified to function names only + (`prefix` / `suffix` / `@basename` / `@default`); format + example + shown in the value inputbox +- TUI footer buttons (`Save` / `Enter` / `Cancel`) no longer i18n'd; + consistent English across all locales +- `_TUI_LANG_UPPER` initialised at source time so sourcing `setup_tui.sh` + and calling a section editor directly (tests, REPL) no longer + crashes on unbound variable under `set -u` +- **CLI consistency**: `exec.sh` / `stop.sh` now accept `--lang LANG` + (matches `build.sh` / `run.sh`); `stop.sh` gains `-a` short flag + for `--all` (matches common CLI patterns). Unknown lang values + warn and fall back to `en` via `_sanitize_lang` +- **`--gen-image-conf` alias removed** from `init.sh` / `upgrade.sh`; + the `--gen-conf` name is the only spelling. The alias was a + rename-artifact and not documented outside in-tree help +- **`tui.sh` → `setup_tui.sh`**: pairs with `setup.sh` and makes the + "interactive editor for setup.conf" relationship explicit. + `init.sh` now creates `setup_tui.sh` and removes any stale `tui.sh` + symlink left behind by pre-rename installs +- **`_print_config_summary` full dump**: `build.sh` / `run.sh` now + print every populated `setup.conf` section (image / build / deploy / + gui / network / security / resources / environment / tmpfs / + devices / volumes) alongside identity, file paths, and the resolved + GPU/GUI/TZ flags — so users see every value this run consumes + without having to diff `.env` or run `docker compose config` + +### Added +- **`[build] target_arch` TARGETARCH override**: new scalar key + alongside the `arg_N` list. Non-empty value pins Docker's + `TARGETARCH` build arg for both the main image and the test-tools + image (main via compose `build.args`, test-tools via + `build.sh --build-arg`). Empty (default) leaves BuildKit's + auto-detection intact. Valid values: `amd64` / `arm64` / `arm` / + `386` / `ppc64le` / `s390x` / `riscv64`. `setup_tui.sh` → Build + adds a dedicated menu entry; `_validate_target_arch` catches typos + like `aarch64` / `x86_64` (BuildKit uses `arm64` / `amd64`). +- **`Dockerfile.test-tools` multi-arch**: `ARG TARGETARCH=amd64` + branches the ShellCheck + Hadolint download URLs via a `case` + statement. BuildKit auto-fills on amd64 / arm64 hosts; falls back + to amd64 binaries on legacy builders. Rejects unsupported arches + loudly instead of silently grabbing a wrong-arch binary +- **`setup_tui.sh --lang ` surfaces a TUI msgbox** before + the main menu opens. Previously the `_sanitize_lang` stderr warning + scrolled away as soon as dialog/whiptail cleared the screen; the + user saw a silently-English TUI with no hint why. New + `_warn_if_lang_rejected` helper captures the raw input and opens a + "Language fallback" msgbox listing the valid codes + +### Performance +- **`make test` no longer runs kcov** — the dev loop pays for bats + + shellcheck only. `make coverage` keeps the full kcov path for CI + and release checks. `ci.sh --ci` honors `$COVERAGE=1` to include + kcov when the outer `--coverage` flag is set +- **`bats --jobs $(nproc)` parallelism** — GNU parallel runs the + 524-test suite concurrently across files and within files. All + specs already use per-test `mktemp -d` dirs so there's no shared + filesystem state. Combined effect (cached apt): + before ~1m27s (serial + kcov) → now ~42s (parallel, no kcov) ≈ 2x + faster on the dev loop + +### BREAKING +- **Language code `zh` renamed to `zh-TW`** (BCP-47). `--lang zh` + no longer accepted; use `--lang zh-TW` (Taiwan Traditional). + `zh-CN` / `ja` / `en` unchanged +- **`@env_example` image-name rule removed**: legacy rule that read + `IMAGE_NAME` from `.env.example` deleted along with its TUI option + + i18n keys. `.env` is a setup.sh-derived artifact so the rule + created a cycle. Replace with explicit `rule_N = @default:` + or set `IMAGE_NAME` directly + +### Removed (tried, reverted) +- **B7 vim keybindings** (attempted `DIALOGRC bindkey j/k/h/l`): + reverted in `ccc0dbc`. `dialog` 1.3 rejects letter curses_keys — + only symbolic names (`TAB` / `DOWN` / `UP` / `ENTER`) are valid. + See repo-root `TODO.md` for alternative-backend options (gum / + fzf / textual) queued for a future PR + +### Changed (TUI UX 重構 — 2026-04-21 本地) +- **主選單重組**:11 項平鋪 → 5 常用(network / deploy / gui / volumes / + environment)+ `advanced` 子選單(image / build / devices / tmpfs / + security) +- **Save UX**:去掉 `__save` menu item,改用 dialog/whiptail 的 + `--extra-button --extra-label "Save & Exit"`(exit code 3 = save + 訊號,0 = 進選中項,1 = Cancel) +- **List sections 統一 single-layer**:volumes / environment / devices / + tmpfs / ports 點 item 直接 inputbox;**空值 + OK = mark_removed** + (該 key 從 setup.conf 消失);list menu 只保留 Add / Back +- **Conditional triggers**:`shm_size` 不再是主選單項,改為 + `[network] ipc != host` 時從 network 結尾彈出;`ports` 改為 + `mode == bridge` 時從 network 結尾彈出 +- **privileged 遷移**:從 `[network] privileged` 搬到新 `[security] + privileged`。TUI 的 privileged yesno 由 Advanced → Security 編輯 +- **`[security]` 新 section**:privileged / cap_add_* / cap_drop_* / + security_opt_*。先前 compose.yaml 硬編的 SYS_ADMIN / NET_ADMIN / + MKNOD / seccomp:unconfined 改為 setup.conf template 預設值,可由 + TUI 或手編調整 + +### Removed +- **cgroup (`device_cgroup_rules`)**:setup.conf 註解、parser、TUI、 + compose.yaml `device_cgroup_rules:` 產生邏輯全拿掉。使用者手寫 + `cgroup_N = ...` 會被忽略 + +### Added +- **Interactive TUI** (`setup_tui.sh`) for editing `/setup.conf` via + dialog (with whiptail fallback). Main menu + direct-jump subcommands + (`./setup_tui.sh image|build|network|deploy|gui|volumes`). Validates + mount format, GPU count, and enum fields before save. On save, + invokes `setup.sh` automatically to regenerate `.env` + + `compose.yaml`. Symlinked from each repo root via `init.sh`. + 4-language i18n (en / zh / zh-CN / ja). +- **`_tui_backend.sh`** — dialog/whiptail abstraction + (`_tui_menu`, `_tui_radiolist`, `_tui_checklist`, `_tui_inputbox`, + `_tui_yesno`, `_tui_msgbox`). Preferred backend auto-detected; + exits with install hint when neither is installed. +- **`_tui_conf.sh`** — pure-logic INI read/write helpers: + `_load_setup_conf_full` (full file with section order preserved), + `_write_setup_conf` (comment-preserving overwrite), + `_upsert_conf_value` (single-key in-place edit), plus validators + (`_validate_mount`, `_validate_gpu_count`, `_validate_enum`) and + mount-string parsers. +- **`[build]` section** in `setup.conf` for Dockerfile build args + (`apt_mirror_ubuntu`, `apt_mirror_debian`). Empty value keeps the + hard-coded Taiwan mirror defaults. +- **Workspace writeback**: on first run (when `/setup.conf` does + not exist), `setup.sh` detects the workspace host path, copies + `template/setup.conf` to `/setup.conf`, and writes the + detected workspace into `[volumes] mount_1`. Subsequent runs read + `mount_1` as the source of truth. Clearing `mount_1` is treated as + opt-out; the workspace is omitted from `compose.yaml` and `setup.sh` + does not re-populate it. +- `build.sh` / `run.sh` `--setup` / `-s` is now **TTY-aware**: under + an interactive terminal with `setup_tui.sh` available, it launches the + TUI; otherwise it runs `setup.sh` non-interactively (unchanged + behaviour for CI / non-TTY). +- `init.sh _create_symlinks` adds `setup_tui.sh` alongside the existing + five symlinks. +- **Single `setup.conf`** at repo root consolidates all runtime + configuration consumed by `setup.sh`: `[image]`, `[build]`, + `[deploy]`, `[gui]`, `[network]`, `[volumes]`. Template default + lives at `template/setup.conf`; per-repo override at + `/setup.conf` uses section-level replace strategy (a section + present in the per-repo file fully replaces the template's section; + omitted sections fall back to template). +- `setup.sh` new helpers: `_parse_ini_section`, `_load_setup_conf`, + `_get_conf_value`, `_get_conf_list_sorted`, `_resolve_gpu`, + `_resolve_gui`, `detect_gui`, `_compute_conf_hash`, + `_check_setup_drift`, and `generate_compose_yaml`. `setup.sh` now + emits a full `compose.yaml` alongside `.env` with conditional GPU + `deploy` block, conditional GUI env/volumes, and extra volumes from + `[volumes]` section. +- **Drift detection** via `.env` metadata: setup.sh writes + `SETUP_CONF_HASH`, `SETUP_GUI_DETECTED`, `SETUP_TIMESTAMP` into + `.env`; `build.sh` / `run.sh` compare stored values against current + state and warn when `setup.conf` was modified, GPU/GUI detection + changed, or UID changed. Warnings are non-blocking; user re-runs with + `--setup` to regenerate. +- `build.sh` / `run.sh` **`--setup`** (`-s`) flag: forces setup.sh to + regenerate `.env` + `compose.yaml`. Default behaviour: auto-bootstrap + on missing `.env` (first run / CI fresh clone); warn on drift if + `.env` exists. +- `init.sh` new option: `--gen-conf` copies `template/setup.conf` to + `/setup.conf` for per-repo override. `--gen-image-conf` is kept + as a back-compat alias. +- New unit spec `test/unit/compose_gen_spec.bats` (14 tests) covering + `generate_compose_yaml` conditional output. ### Changed -- move smoke/ to test/smoke/ -- move READMEs to doc/, entrypoint.sh to script/ +- **PR #74's `template/config/setup/` directory removed**: the + separate `image_name.conf` / `gpu.conf` / `gui.conf` / `network.conf` + / `volumes.conf` files introduced in #74 are consolidated into a + single `setup.conf` INI. `config/` now strictly contains container + internal configs (bashrc, tmux, pip, terminator); runtime wiring + lives at repo root alongside `Dockerfile`. +- `compose.yaml` is now a **derived artifact** (gitignored) generated + by `setup.sh` on every invocation. Users inspect it for the current + effective runtime config; source of truth is `setup.conf`. +- **BREAKING — setup.conf section rename**: + `[image_name]` → `[image]`; `[gpu]` → `[deploy]` with keys prefixed + (`mode` → `gpu_mode`, `count` → `gpu_count`, + `capabilities` → `gpu_capabilities`). Also introduces `[build]` + (apt mirrors). Template `setup.conf` updated; per-repo overrides + must use the new names. +- `detect_image_name` now reads `[image] rules` (comma-separated + ordered list) from `setup.conf` instead of a dedicated + `image_name.conf` rule file. Rule semantics unchanged + (`prefix:`, `suffix:`, `@env_example`, `@basename`, `@default:`). +- `build.sh` / `run.sh`: removed `--no-env` flag (semantic reversed — + setup.sh no longer runs by default, so the opposite `--setup` flag + was introduced). `exec.sh` / `stop.sh` unchanged (container state is + already frozen when they run). +- `write_env` signature expanded with new columns written to `.env`: + `NETWORK_MODE`, `IPC_MODE`, `PRIVILEGED`, `GPU_COUNT`, + `GPU_CAPABILITIES`, `SETUP_CONF_HASH`, `SETUP_GUI_DETECTED`, + `SETUP_TIMESTAMP`. +- `generate_compose_yaml` baseline: only `${WS_PATH}:/home/${USER_NAME}/work` + is always emitted; `/dev:/dev` now lives in `setup.conf`'s + `[volumes]` template default (user-replaceable). GUI-related + volumes/env are emitted iff `[gui] mode` resolves enabled. +- Version tracking moved from `.template_version` (repo root, manually + maintained) to `template/VERSION` (inside subtree, auto-synced by + `git subtree pull`). `init.sh` and `upgrade.sh` automatically clean up + the legacy `.template_version` file. `build-worker.yaml` reads + `template/VERSION` with `.template_version` fallback for transition. + +### Documentation +- README (4 languages) and `init.sh` header now document the full + bootstrap sequence for a brand-new repo: `git init` + an empty initial + commit must run before `git subtree add`, otherwise subtree fails with + `ambiguous argument 'HEAD'` and `working tree has modifications`. + +### Removed +- `template/config/image_name.conf` (content absorbed into + `template/setup.conf` under `[image_name] rules =`). +- `--no-env` flag on `build.sh` / `run.sh` (replaced by default + no-run-setup + opt-in `--setup`). + +### Fixed +- `test/smoke/test_helper.bash`: `assert_cmd_installed` now returns `1` + after calling `fail`, so callers can short-circuit via `|| return 1` + instead of silently falling through. `assert_cmd_runs` and + `assert_pip_pkg` now short-circuit when the target command is missing, + so they no longer execute `run ` and emit a spurious + Bats BW01 warning. +- `test/unit/lib_spec.bats`, `test/unit/pip_setup_spec.bats`, + `test/unit/setup_spec.bats`: replace `run ` / `assert_failure` + pairs with `run -127 ` on the five tests whose command is + expected to exit 127 (`_load_env` missing arg, `_compose` on empty + PATH, `pip setup.sh` without pip, `setup.sh main --base-path` / + `--lang` missing value). Silences Bats BW01. Files that use `run -N` + flags now declare `bats_require_minimum_version 1.5.0` to silence + BW02. + +## [v0.8.1] - 2026-04-15 + +### Fixed +- `upgrade.sh`: drop the auto-appended `Co-Authored-By: Claude ...` + trailer from the `chore: update template references` commit message. + AI-attribution lines are visual noise for reviewers and the project + convention is to omit them everywhere (PR body, commit message, code). + +## [v0.8.0] - 2026-04-15 + +### Added +- `test/smoke/test_helper.bash`: shared runtime assertion helpers for + downstream-repo smoke specs — `assert_cmd_installed`, `assert_cmd_runs`, + `assert_file_exists`, `assert_dir_exists`, `assert_file_owned_by`, + `assert_pip_pkg`. Each prints a decorated diagnostic on failure so the + bats log points at the exact missing artifact. Keeps downstream smoke + specs terse and self-documenting. +- `init.sh` new-repo skeleton now emits two sample smoke assertions + (`entrypoint.sh is installed and executable`, `bash is available on + PATH`) demonstrating the shared helpers, instead of one bare + `[ -x /entrypoint.sh ]` assertion. +- `test/unit/ci_spec.bats` (5 tests): covers `script/ci/ci.sh` + `_install_deps` — happy path plus the three explicit error branches + for `apt-get update` / `apt-get install` / `git clone bats-mock`. +- `test/unit/smoke_helper_spec.bats` (19 tests): unit coverage for every + runtime assertion helper above, including failure paths. +- `test/unit/setup_spec.bats`: 3 new `detect_ws_path` cases — explicit + ERROR on missing `base_path`, and path-normalization coverage for + strategies 1 and 3 when the input contains `..` segments. +- `test/unit/init_spec.bats` (15 tests): unit coverage for `init.sh` + helpers previously reachable only through the Level-1 integration + test — `_detect_template_version` (git-remote parsing, failure + paths, rc-tag filtering), `_create_version_file` (parameterized + version, `unknown` fallback, overwrite), `_create_new_repo` + (workflow `@ref` threading including empty-ref → `@main` fallback), + and `_create_symlinks` (full symlink set, stale-file replacement, + custom `.hadolint.yaml` preservation). +- `test/unit/ci_spec.bats`: 3 new `_run_shellcheck` tests — wired-file + regression guard, `script/docker/*.sh` discovery via `find`, and + strict-mode propagation on lint failure. ### Fixed -- update README directory structure and test counts (#1) +- `init.sh`: stop hard-coding `v0.5.0` as the fallback version in the + generated `main.yaml`. Workflow refs now fall back to the `main` branch + (a valid git ref) when no tag is detected, instead of an arbitrary old + tag. Version detection is done once up-front and shared between + `.template_version` and the reusable-workflow `@ref`. +- `script/docker/setup.sh` `detect_ws_path`: normalize `base_path` with + `cd ... && pwd -P` before composing sibling/parent paths, so relative + or `..`-laden inputs do not produce surprising matches. Emits a clear + error when the base path does not exist. +- `script/docker/setup.sh`: use `${0:-}` consistently in the + `BASH_SOURCE == $0` guard (line 400) for parity with line 51. +- `script/ci/ci.sh` `_install_deps`: emit explicit error messages when + `apt-get update`, `apt-get install`, or `git clone bats-mock` fails, + instead of relying on `set -e` to exit silently. + +### Changed +- `script/ci/ci.sh`: guard `main "$@"` and `set -euo pipefail` behind a + `BASH_SOURCE == $0` check so the helpers (`_install_deps`, `_die`) can + be sourced by unit tests without executing the CI pipeline. Matches + the pattern already used in `script/docker/setup.sh`. +- `init.sh`: wrap top-level flow in `main()` + `BASH_SOURCE == $0` guard + so helpers (`_detect_template_version`, `_create_version_file`, + `_create_new_repo`, `_create_symlinks`) are sourceable from unit + tests without triggering a full `init.sh` run. Strict mode is also + gated so sourcing respects the caller's settings. Behaviour when + invoked directly is unchanged. -## [v1.4.0] - 2026-03-20 +## [v0.7.2] - 2026-04-14 ### Changed -- test: add script_help.bats for shell script -h/--help tests +- Align `build.sh` / `run.sh` / `exec.sh` / `stop.sh` with Google Shell Style + Guide: wrap top-level logic in a `main()` function with `local` variables, + fix `case` indentation. Behavior unchanged. +- `config/pip/setup.sh`, `config/shell/tmux/setup.sh`, + `config/shell/terminator/setup.sh`: drop `-x` from strict mode + (`set -eux` → `set -euo pipefail`) so docker build logs stay quieter. + Tracing can still be enabled on demand via `bash -x`. +- `script/ci/ci.sh`: refactor kcov `--exclude-path` into a readable array + instead of one long comma-joined string. Behavior unchanged. +- Re-indent all `.bats` files under `test/smoke/`, `test/unit/`, and + `test/integration/` from 4-space to 2-space per Google Shell Style Guide. + Heredoc bodies untouched. Behavior unchanged; all 247 tests still pass. -## [v1.3.1] - 2026-03-19 +## [v0.7.1] - 2026-04-10 + +### Fixed +- `run.sh` foreground devel: `./run.sh` appeared to hang for ~10s after the + user typed `exit` because the cleanup trap ran `compose down` with the + default 10s SIGTERM grace period. Pass `-t 0` so the already-exited + interactive container is killed immediately. + +## [v0.7.0] - 2026-04-09 ### Added -- add stop.sh for stopping background containers +- `build.sh` / `run.sh` / `exec.sh` / `stop.sh`: `--dry-run` flag prints the + `docker` / `docker compose` commands that would run instead of executing them. + Useful for debugging compose / env / instance resolution without side effects. +- `exec.sh`: precheck refuses with a friendly error pointing at `./run.sh` + (and `--instance NAME` if applicable) when the target container is not running, + instead of letting `compose exec` print the cryptic `service "devel" is not running`. -## [v1.3.0] - 2026-03-19 +### Changed +- Refactor: extracted shared helpers (`_LANG` setup, `_load_env`, `_compute_project_name`, + `_compose`, `_compose_project`) into `template/script/docker/_lib.sh`. `build.sh`, + `run.sh`, `exec.sh`, and `stop.sh` now source `_lib.sh` and call the helpers instead + of duplicating the same i18n / env-loading / compose-flag boilerplate. +- `exec.sh`: passes the user command as a positional array (`"$@"`) to `compose exec`, + so arguments containing whitespace are preserved instead of being word-split. +- `run.sh`: trap is now `trap _devel_cleanup EXIT` (calls a named function) instead of + an inline string-expanded command, matching `build.sh`'s style. + +## [v0.6.8] - 2026-04-09 ### Added -- auto down before up -d, remove stop.sh -- add stop.sh to clean up background containers +- `run.sh` / `exec.sh` / `stop.sh`: `--instance NAME` flag for parallel container instances + - `./run.sh --instance dev2` starts a parallel container alongside the default + - `./exec.sh --instance dev2 [cmd]` enters that named instance + - `./stop.sh --instance dev2` stops only that one + - `./stop.sh --all` stops the default + every named instance for this image +- Project name and container name now include `${INSTANCE_SUFFIX}` so each + instance has isolated docker compose project (own network/volumes) +- `init.sh`-generated `compose.yaml` uses + `container_name: ${IMAGE_NAME}${INSTANCE_SUFFIX:-}` + - Default invocation (no `--instance`) keeps the clean name `${IMAGE_NAME}` — + backward-compatible with external tools that grep `docker exec ${IMAGE_NAME}` ### Changed -- exec.sh use -t flag for target, args as command +- `run.sh`: foreground devel now refuses to start if a container with the + default name is already running. Use `./stop.sh` first or pass + `--instance NAME` to start a parallel one. + +### Note +- Existing 17 consumer repos must update their `compose.yaml` to use + `container_name: ${IMAGE_NAME}${INSTANCE_SUFFIX:-}` (one-line edit) before + `--instance` works there. Default behavior unchanged until they upgrade. + +## [v0.6.7] - 2026-04-09 + +### Added +- `test/integration/init_new_repo_spec.bats`: 21 Level-1 integration tests + - Verifies `init.sh` produces a complete repo skeleton in an empty dir + (Dockerfile, compose.yaml, .env.example, symlinks, doc tree, .github/workflows, etc.) + - Runs inside the existing `make -f Makefile.ci test` container — no Docker needed + - Total tests: 180 → 201 (180 unit + 21 integration) +- `.github/workflows/self-test.yaml`: new `integration-e2e` job (Level 2) + - Runs `init.sh` → `build.sh test` → `build.sh` → `run.sh -d` → `exec.sh` → `stop.sh` + on a synthetic temp repo, on a real GitHub runner with Docker daemon + - `release` job now depends on both `test` and `integration-e2e` +- `script/ci/ci.sh`: now also runs `bats test/integration/` alongside `test/unit/` + +## [v0.6.6] - 2026-04-09 + +### Fixed +- `run.sh`: foreground `devel` mode could not be entered via `./exec.sh` from another terminal + - Symptom: `service "devel" is not running` even though `docker ps` showed it + - Root cause: foreground used `compose run --name`, which creates a one-off container + invisible to `compose exec` (the underlying mechanism behind `./exec.sh`) + - Fix: foreground `devel` now uses `compose up -d` + `compose exec devel bash` + + a `trap … down EXIT` to preserve the original "exit shell = container gone" semantic + - Other targets (`test`, `runtime`, ...) still use `compose run --rm` (one-shot stages + that don't need exec) + - `compose.yaml` `container_name: ${IMAGE_NAME}` is unchanged, so external scripts + that do `docker exec ${IMAGE_NAME}` (e.g. local CI helpers) continue to work + +### Removed +- `stop.sh`: orphan-container cleanup `docker rm -f "${IMAGE_NAME}"` no longer needed + (no more orphan from `compose run --name`) + +## [v0.6.5] - 2026-04-09 + +### Fixed +- `build.sh`/`run.sh`/`exec.sh`/`stop.sh`: graceful fallback when `i18n.sh` is missing + - v0.6.1 added `source template/script/docker/i18n.sh` but consumer Dockerfile + `test` stages do `COPY *.sh /lint/` without the template tree, so the source + failed and broke smoke tests in all consumer repos + - Fix: each script checks for i18n.sh and falls back to inline `_detect_lang` + if missing — no Dockerfile changes required in consumer repos + +## [v0.6.4] - 2026-04-09 + +### Fixed +- `upgrade.sh`: greedy sed pattern clobbered `release-worker.yaml@` reference, + replacing it with `build-worker.yaml@` and breaking release CI in consumer repos + - Root cause: `s|template/\.github/workflows/.*@v[0-9.]*|...build-worker.yaml@...|` + matched both worker references; the dedicated `release-worker` line that follows + only worked when the first sed didn't already overwrite it + - Fix: drop the greedy first sed, keep only the per-worker-name targeted seds -## [v1.2.1] - 2026-03-19 +## [v0.6.3] - 2026-04-09 + +### Added +- `upgrade.sh`: `--gen-image-conf` flag (delegates to `init.sh --gen-image-conf`) + - Lets users copy `image_name.conf` to repo root for per-repo customization + without needing to remember the init.sh path + +## [v0.6.2] - 2026-04-09 ### Changed -- remove lint-worker.yaml, lint runs in Dockerfile test stage +- Remove all `# LCOV_EXCL_*` markers from shell scripts to expose real coverage + - Coverage now reflects actual instrumented lines (95.76% vs prior masked 100%) + - 2 new direct-run tests for `tmux/setup.sh` and `terminator/setup.sh` (171 total) + - Remaining 10 uncovered lines in `setup.sh` are kcov bash backend limitations + (case `;;` arms, `done` redirect close, child-bash guards) -## [v1.2.0] - 2026-03-19 +## [v0.6.1] - 2026-04-08 ### Added -- add ShellCheck + Hadolint to Dockerfile test stage +- `build.sh`: `--clean-tools` flag to remove `test-tools:local` image after build +- `script/docker/i18n.sh`: shared `_detect_lang()` and `_LANG` initialization + - Sourced by build.sh, run.sh, exec.sh, stop.sh, setup.sh + - Eliminates ~28 lines of duplication across 5 scripts + - Adding a new language now requires editing only one file +- `dockerfile/Dockerfile.test-tools`: include `bats-mock` (jasonkarns v1.2.5) + - Other repos' smoke tests can now use `stub`/`unstub` for command mocking -## [v1.1.1] - 2026-03-18 +### Changed +- `build.sh`: keep `test-tools:local` image by default (was removed on EXIT) + - Avoids race conditions in parallel builds + - Subsequent builds skip the test-tools build (Docker layer cache) + - Use `--clean-tools` to restore old behavior -- Initial release +## [v0.6.0] - 2026-04-01 -## [v1.1.0] - 2026-03-18 +### Added +- `build.sh`: `--no-cache` flag for force rebuild (passes to both + test-tools image build and docker compose build) +- `config/image_name.conf`: rule-driven IMAGE_NAME detection + - Rule types: `prefix:`, `suffix:`, `@env_example`, `@basename`, `@default:` + - Per-repo override: place `image_name.conf` in repo root + - Default rules: `@env_example` → `prefix:docker_` → `suffix:_ws` → `@default:unknown` +- `init.sh --gen-image-conf`: copy template's image_name.conf to repo root + for per-repo customization ### Changed -- add .hadolint.yaml to ignore inapplicable rules -- add ShellCheck and Hadolint static analysis +- `detect_image_name`: refactored to read rules from `image_name.conf` instead + of hardcoded logic +- **BREAKING**: `image_name.conf` keywords now require `@` prefix + (`env_example` → `@env_example`, `basename` → `@basename`) to distinguish + from user-defined values +- Default conf order: `@env_example` → `prefix:docker_` → `suffix:_ws` → `@default:unknown` + (`.env.example` highest priority; `@default:unknown` as final fallback + prints INFO log so users know to set IMAGE_NAME explicitly) +- New `@default:` keyword: explicit fallback value with INFO log +- WARNING only when no rule matches AND no `@default:` set (custom conf scenario) + +### Fixed +- `stop.sh`: remove orphan container left by `docker compose run --name` + (`docker compose down` only cleans up `up`-mode containers, not `run`-mode) +- `upgrade.sh`: re-run `init.sh` after subtree pull to sync symlinks + (avoids stale symlinks when template directory structure changes) + +### Removed +- Stale comments referencing `get_param.sh` (historical, no longer relevant) -## [v1.0.0] - 2026-03-18 +## [v0.5.0] - 2026-03-31 ### Added -- add -h/--help support to all interactive scripts -- initial ROS 1/2 bridge container +- `setup.sh`: add `APT_MIRROR_UBUNTU` and `APT_MIRROR_DEBIAN` to `.env` + - Default: `tw.archive.ubuntu.com` (Ubuntu), `mirror.twds.com.tw` (Debian) + - Preserves existing values from `.env` on re-run +- `setup.sh`: warn when `IMAGE_NAME` cannot be detected and `.env.example` not found +- `display_env.bats`: auto-skip GUI tests for headless repos +- `dockerfile/Dockerfile.test-tools`: pre-built test tools image (ShellCheck + Hadolint + Bats) +- `dockerfile/Dockerfile.example`: Dockerfile template for new repos +- `init.sh`: support creating new repo with full project structure +- `build.sh`: auto-build `test-tools:local` before compose build +- 5 new tests (137 total) ### Changed -- unify help text to usage() function, add smoke test tables -- Add .env.example -- Add detach mode to run.sh and rewrite exec.sh -- initial commit +- **BREAKING**: Directory restructure + - `build.sh`, `run.sh`, `exec.sh`, `stop.sh`, `Makefile`, `setup.sh` → `script/docker/` + - `ci.sh` → `script/ci/` + - `init.sh`, `upgrade.sh` → template root (user-facing) +- Other repos symlink path: `template/build.sh` → `template/script/docker/build.sh` + +## [v0.4.2] - 2026-03-30 ### Fixed -- revert assert_output back to assert_line in smoke test -- release-worker.yaml archive list and exec.sh bugs +- `run.sh`: set `--name "${IMAGE_NAME}"` in foreground mode (`docker compose run`) so container name matches `container_name` in compose.yaml + +### Removed +- `script/migrate.sh`: all repos migrated, no longer needed +- i18n translations for TEST.md and CHANGELOG.md (keep English only) + +## [v0.4.1] - 2026-03-29 + +### Changed +- Rename `test/smoke_test/` → `test/smoke/` +- Fix README.md TOC anchor and add missing Tests section + +## [v0.4.0] - 2026-03-29 + +### Changed +- Move `config/` back to root level (was `script/config/` in v0.3.0) — configs are not scripts +- Fix `self-test.yaml` release archive: remove stale root `setup.sh` reference +- Fix mermaid architecture diagrams: `setup.sh` shown in correct `script/` box +- Add Table of Contents to zh-TW and zh-CN READMEs +- Add `Makefile.ci` entry to "What's included" table (all translations) +- Fix "Running Tests" section to use `make -f Makefile.ci` (all translations) +- Rename `test/smoke_test/` → `test/smoke/` + +## [v0.3.0] - 2026-03-29 + +### Changed +- **BREAKING**: Rename repo `docker_template` → `template` +- **BREAKING**: Move `setup.sh` → `script/setup.sh` +- **BREAKING**: Move `config/` → `script/config/` (reverted in v0.4.0) +- Apply Google Shell Style Guide to all shell scripts +- Split `Makefile` into `Makefile` (repo entry) + `Makefile.ci` (CI entry) +- Fix directory structure, test counts, bashrc style in documentation +- 132 tests (was 124) + +### Migration notes +- Other repos: subtree prefix changes from `docker_template/` to `template/` +- `CONFIG_SRC` path in Dockerfile: `docker_template/config` → `template/config` +- Symlinks: `docker_template/*.sh` → `template/*.sh` + +## [v0.2.0] - 2026-03-28 + +### Added +- `script/ci.sh`: CI pipeline script (local + remote) +- `Makefile`: unified command entry +- Restructured `test/unit/` and `test/smoke_test/` +- Restructured `doc/` with i18n (readme/, test/, changelog/) +- Coverage permissions fix (chown with HOST_UID/HOST_GID) + +### Changed +- `smoke_test/` moved to `test/smoke_test/` (**BREAKING**: Dockerfile COPY path change) +- `compose.yaml` calls `script/ci.sh --ci` instead of inline bash +- `self-test.yaml` calls `script/ci.sh` instead of docker compose directly + +## [v0.1.0] - 2026-03-28 + +### Added +- **Shared shell scripts**: `build.sh`, `run.sh` (with X11/Wayland support), `exec.sh`, `stop.sh` +- **setup.sh**: `.env` generator merged from `docker_setup_helper` (auto-detect UID/GID, GPU, workspace path, image name) +- **Config files**: bashrc, tmux, terminator, pip configs from `docker_setup_helper` +- **Shared smoke tests** (`smoke_test/`): + - `script_help.bats` — 16 tests for script help/usage + - `display_env.bats` — 10 tests for X11/Wayland environment (GUI repos) + - `test_helper.bash` — unified bats loader +- **Template self-tests** (`test/`): 114 tests with ShellCheck + Bats + Kcov coverage +- **CI reusable workflows**: + - `build-worker.yaml` — parameterized Docker build + smoke test + - `release-worker.yaml` — parameterized GitHub Release + - `self-test.yaml` — template's own CI +- **`migrate.sh`**: batch migration script for converting repos from `docker_setup_helper` to `template` +- `.hadolint.yaml`: shared Hadolint rules +- `.codecov.yaml`: coverage configuration +- Documentation: README (English), README.zh-TW.md, README.zh-CN.md, README.ja.md, TEST.md + +### Changed +- `setup.sh` default `_base_path` traverses 1 level up (`/..`) instead of 2 (`/../..`) to match new `template/setup.sh` location + +### Migration notes +- Replace `docker_setup_helper/` subtree with `template/` subtree +- Shell scripts at root become symlinks to `template/` +- Local `build-worker.yaml` / `release-worker.yaml` replaced by reusable workflow calls in `main.yaml` +- Dockerfile `CONFIG_SRC` path: `docker_setup_helper/src/config` → `template/config` +- Shared smoke tests loaded via `COPY template/smoke_test/` in Dockerfile (not symlinks) +[v0.6.8]: https://github.com/ycpss91255-docker/template/compare/v0.6.7...v0.6.8 +[v0.6.7]: https://github.com/ycpss91255-docker/template/compare/v0.6.6...v0.6.7 +[v0.6.6]: https://github.com/ycpss91255-docker/template/compare/v0.6.5...v0.6.6 +[v0.6.5]: https://github.com/ycpss91255-docker/template/compare/v0.6.4...v0.6.5 +[v0.6.4]: https://github.com/ycpss91255-docker/template/compare/v0.6.3...v0.6.4 +[v0.6.3]: https://github.com/ycpss91255-docker/template/compare/v0.6.2...v0.6.3 +[v0.6.2]: https://github.com/ycpss91255-docker/template/compare/v0.6.1...v0.6.2 +[v0.6.1]: https://github.com/ycpss91255-docker/template/compare/v0.6.0...v0.6.1 +[v0.6.0]: https://github.com/ycpss91255-docker/template/compare/v0.5.0...v0.6.0 +[v0.5.0]: https://github.com/ycpss91255-docker/template/compare/v0.4.2...v0.5.0 +[v0.4.2]: https://github.com/ycpss91255-docker/template/compare/v0.4.1...v0.4.2 +[v0.4.1]: https://github.com/ycpss91255-docker/template/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/ycpss91255-docker/template/compare/v0.3.0...v0.4.0 +[v0.3.0]: https://github.com/ycpss91255-docker/template/compare/v0.2.0...v0.3.0 +[v0.2.0]: https://github.com/ycpss91255-docker/template/compare/v0.1.0...v0.2.0 +[v0.1.0]: https://github.com/ycpss91255-docker/template/releases/tag/v0.1.0 diff --git a/template/doc/readme/README.ja.md b/doc/readme/README.ja.md similarity index 100% rename from template/doc/readme/README.ja.md rename to doc/readme/README.ja.md diff --git a/template/doc/readme/README.zh-CN.md b/doc/readme/README.zh-CN.md similarity index 100% rename from template/doc/readme/README.zh-CN.md rename to doc/readme/README.zh-CN.md diff --git a/template/doc/readme/README.zh-TW.md b/doc/readme/README.zh-TW.md similarity index 100% rename from template/doc/readme/README.zh-TW.md rename to doc/readme/README.zh-TW.md diff --git a/doc/test/TEST.md b/doc/test/TEST.md index 6395637..d3efe4a 100644 --- a/doc/test/TEST.md +++ b/doc/test/TEST.md @@ -1,62 +1,550 @@ # TEST.md -**24 tests** total. +Template self-tests: **654 tests** total (611 unit + 43 integration). -## test/smoke/ros_env.bats +## Test Files -### ROS environment (4) +### test/unit/lib_spec.bats (38) | Test | Description | |------|-------------| -| `ROS 1 (noetic) setup.bash exists` | `/opt/ros/noetic/setup.bash` exists | -| `ROS 2 (foxy) setup.bash exists` | `/opt/ros/foxy/setup.bash` exists | -| `ROS 1 environment can be sourced` | ROS 1 (noetic) setup script sources without error | -| `ROS 2 environment can be sourced after ROS 1` | ROS 2 (foxy) sources after ROS 1 without error | +| `_lib.sh sets _LANG to 'en' when LANG is unset` | Default language | +| `_lib.sh sets _LANG to 'zh-TW' for zh_TW.UTF-8` | Traditional Chinese | +| `_lib.sh sets _LANG to 'zh-CN' for zh_CN.UTF-8` | Simplified Chinese | +| `_lib.sh sets _LANG to 'zh-CN' for zh_SG (Singapore)` | Singapore variant | +| `_lib.sh sets _LANG to 'ja' for ja_JP.UTF-8` | Japanese | +| `_lib.sh honors SETUP_LANG override` | Env override | +| `_lib.sh is idempotent when sourced twice` | Double-source guard | +| `_load_env exports variables from a .env file` | Env loader works | +| `_load_env errors when no path is given` | Required arg check | +| `_compute_project_name with empty instance produces clean PROJECT_NAME` | Default instance | +| `_compute_project_name with named instance suffixes both` | Named instance | +| `_compute_project_name exports INSTANCE_SUFFIX so child processes see it` | Export propagation | +| `_compose with DRY_RUN=true prints command instead of running` | DRY_RUN path | +| `_compose without DRY_RUN tries to invoke docker compose (sanity)` | Real-call branch | +| `_compose_project pre-fills -p / -f / --env-file from PROJECT_NAME and FILE_PATH` | Project wrapper | +| `_sanitize_lang accepts en / zh-TW / zh-CN / ja unchanged` | Lang validator pass-through | +| `_sanitize_lang warns and falls back to 'en' for unsupported values` | Unknown lang fallback | +| `_sanitize_lang warns for the old bare 'zh' code (post zh→zh-TW rename)` | Legacy lang rejection | +| `_dump_conf_section extracts keys from the named section` | INI section dump | +| `_dump_conf_section stops at the next section header` | Section boundary | +| `_dump_conf_section returns silent empty for missing file` | Missing file | +| `_dump_conf_section returns silent empty for unknown section` | Missing section | +| `_print_config_summary prints files, identity, all populated sections, resolved` | Full config dump | +| `_print_config_summary hides sections that are empty in setup.conf` | Empty-section skip | +| `_print_config_summary warns when setup.conf is missing` | Missing-conf hint | -### Bridge (1) +### test/unit/setup_spec.bats (111) + +Covers core detection (user/hardware/docker/GPU/GUI), the INI parser +(`_parse_ini_section`), setup.conf section merging (`_load_setup_conf` +with replace strategy), image_name rule engine via `[image] rules`, +resolvers (`_resolve_gpu`, `_resolve_gui`), workspace path detection, +conf hash computation, drift detection, `write_env` (now including +runtime values + SETUP_* metadata), the `main()` CLI, and workspace +writeback (first-time bootstrap / user-edit respect / opt-out). + +| Category | Tests | +|----------|-------| +| `detect_user_info` / `detect_hardware` / `detect_docker_hub_user` / `detect_gpu` / `detect_gui` | 11 | +| `_parse_ini_section` (section isolation, comments, trim, missing) | 6 | +| `_load_setup_conf` (SETUP_CONF env, per-repo, template, replace) | 4 | +| `_get_conf_value` / `_get_conf_list_sorted` (incl. empty-value skip) | 5 | +| `_resolve_gpu` / `_resolve_gui` | 7 | +| `detect_image_name` (template default, per-repo rules, @default, order) | 7 | +| `detect_ws_path` (strategies 1/2/3 + missing base_path) | 5 | +| `_compute_conf_hash` | 2 | +| `write_env` (all fields + SETUP_* metadata) | 1 | +| `_check_setup_drift` (no-op, silent, conf drift, GPU drift) | 4 | +| `main` (unknown arg, --base-path / --lang missing value) | 3 | +| `_msg` / `_detect_lang` i18n | 6 | +| `[build]` apt_mirror (empty fallback, override) | 2 | +| Workspace writeback (first-time, respect user edit, opt-out) | 3 | + +### test/unit/tui_spec.bats (82) + +Pure-logic unit tests for the TUI support libraries (`_tui_conf.sh`). +No dialog/whiptail invocations here — strictly validators, mount-string +parsers, and setup.conf round-trip. + +| Category | Tests | +|----------|-------| +| `_validate_mount` (valid forms, env-var expansion, reject missing/extra colons, invalid mode) | 8 | +| `_validate_gpu_count` ('all', positive int, reject 0/negative/non-numeric/empty) | 6 | +| `_validate_enum` (match, non-match, empty) | 3 | +| `_mount_host_path` (plain, with mode, with env-var host) | 3 | +| `_load_setup_conf_full` + `_write_setup_conf` (section order, kv, comment preservation, untouched keys, round-trip) | 5 | +| `_upsert_conf_value` (updates existing, leaves other sections untouched) | 2 | + +### test/unit/tui_backend_spec.bats (23) + +Backend detection and wrapper-level arg forwarding. Uses a stub +`dialog` / `whiptail` binary installed on PATH that logs argv and echoes +a canned response; exercised with `TUI_STUB_RESPONSE` / `TUI_STUB_EXIT`. + +| Category | Tests | +|----------|-------| +| `_backend_detect` (prefers dialog, falls back to whiptail, prints install hint when neither) | 3 | +| `_tui_guard` (rejects empty backend) | 1 | +| `_tui_inputbox` (forwards title/prompt/initial, returns canned response, propagates non-zero on cancel) | 2 | +| `_tui_menu` (computes item count, forwards tag/label pairs) | 1 | +| `_tui_radiolist` (forwards tag/label/state triples) | 1 | +| `_tui_checklist` (passes `--separate-output`) | 1 | +| `_tui_msgbox` / `_tui_yesno` (correct flags, propagates exit code) | 2 | + +### test/unit/build_sh_spec.bats (32) + +Unit tests for `build.sh` argument handling and control flow. Uses a +sandbox tree mirroring the expected layout (build.sh + `template/` subtree +with real `_lib.sh` / `i18n.sh`, mock `setup.sh`). `docker` is PATH-shimmed +so the stub captures argv; `build.sh` is symlinked (not copied) so kcov +attributes coverage to the real source file. + +Covers: `--help` (en/zh/zh-CN/ja), `--setup`/`-s`, auto-bootstrap on +missing `.env` / `setup.conf` / `compose.yaml`, drift-check path when +all three are present, bootstrap staying non-interactive (setup.sh +direct, not `setup_tui.sh`), defensive guard when setup produces no +`.env`, TARGETARCH build-arg forwarding, `--no-cache`, `--clean-tools`, +positional `TARGET`, `--lang` argument validation, fallback +`_detect_lang` branches (zh_TW/zh_CN/ja), real (non-dry-run) docker +build invocation, and **runtime log-line i18n** (bootstrap / +drift-regen / err_no_env messages translate in all four languages via +the local `_msg()` table; English remains the default). + +### test/unit/run_sh_spec.bats (30) + +Unit tests for `run.sh`. Mirrors the build_sh_spec.bats harness; +`docker ps` reads from a controllable stub file so tests can simulate +"container already running" scenarios. + +Covers: `--help` (en/zh/zh-CN/ja), `--setup`/`-s`, bootstrap on +missing `.env` / `setup.conf` / `compose.yaml`, drift-check path, +bootstrap staying non-interactive (setup.sh, not TUI), defensive guard +when setup produces no `.env`, `--detach`, devel vs non-devel TARGET +routing, `--instance`, already-running guard, Wayland xhost path, +`--lang` / `--instance` argument validation, fallback `_detect_lang` +branches, and **runtime log-line i18n** (bootstrap + already-running +error translate in all four languages via the local `_msg()` table). + +### test/unit/exec_sh_spec.bats (18) + +Unit tests for `exec.sh` argument parsing, the container-running +precheck, and i18n. Sandbox tree mirrors build_sh_spec.bats; +`docker ps` reads from a controllable stub file so tests can toggle +"container running" state without a real docker daemon. `.env` is +pre-seeded so `_load_env` / `_compute_project_name` succeed without a +bootstrap step. + +Covers: `--help` (en/zh/zh-CN/ja), `--lang` / `--target` / `--instance` +value validation, English-default not-running error, Chinese / +Simplified Chinese / Japanese not-running error text, instance-specific +vs default start hints, `--dry-run` bypassing the guard, compose exec +routing when container is running, and fallback `_detect_lang` +branches when `template/` is absent. + +### test/unit/stop_sh_spec.bats (16) + +Unit tests for `stop.sh` argument parsing, the `--all` multi-instance +teardown, and i18n. `docker ps -a` output is PATH-shimmed via +`${DOCKER_PS_A_FILE}` so tests can seed the project list for the `--all` +branch. + +Covers: `--help` (en/zh/zh-CN/ja), `--lang` / `--instance` value +validation, default teardown via `docker compose down`, named-instance +suffix in project name, `--all` no-instances English message, +Chinese / Simplified Chinese / Japanese translations of the +no-instances message, `--all` multi-project teardown loop, and +fallback `_detect_lang` branches. + +### test/unit/compose_gen_spec.bats (38) + +Covers `generate_compose_yaml` conditional output: AUTO-GENERATED +header, baseline workspace volume, network/ipc/privileged env-var +references, `test` service presence, image name threading, and +conditional GPU deploy block + GUI env/volumes + extra volumes from +`[volumes]` section. + +| Test | Description | +|------|-------------| +| `outputs AUTO-GENERATED header` | Header check | +| `always emits workspace volume` | Baseline | +| `emits network_mode/ipc/privileged via env var` | env-var baked | +| `emits test service with profiles: [test]` | test service | +| `image field contains repo name` | Image name | +| `does NOT emit /dev:/dev by default (not in baseline)` | Baseline scope | +| `GPU enabled => deploy block present` | GPU on | +| `GPU disabled => no deploy block` | GPU off | +| `GPU with specific count and capabilities` | GPU args | +| `GUI enabled => DISPLAY env + X11 volumes present` | GUI on | +| `GUI disabled => no DISPLAY env + no X11 volumes` | GUI off | +| `extra volumes appended after baseline` | volumes list | +| `empty extras => no extra mount lines` | empty list | +| `with GUI+GPU+extras => all sections present` | fully loaded | + +### test/unit/template_spec.bats (105) + +| Test | Description | +|------|-------------| +| `build.sh exists and is executable` | File check | +| `run.sh exists and is executable` | File check | +| `exec.sh exists and is executable` | File check | +| `stop.sh exists and is executable` | File check | +| `setup.sh exists and is executable` | File check | +| `ci.sh exists and is executable` | File check | +| `ci.sh uses set -euo pipefail` | Shell convention | +| `Makefile exists (repo entry)` | File check | +| `Makefile has build target` | Makefile target | +| `Makefile.ci exists (template CI)` | File check | +| `Makefile.ci has test target` | Makefile target | +| `Makefile.ci has lint target` | Makefile target | +| `test/smoke/test_helper.bash exists` | Directory structure | +| `test/smoke/script_help.bats exists` | Directory structure | +| `test/smoke/display_env.bats exists` | Directory structure | +| `test/unit/ directory exists` | Directory structure | +| `doc/readme/ directory exists` | Directory structure | +| `doc/test/ directory exists` | Directory structure | +| `doc/changelog/ directory exists` | Directory structure | +| `build.sh references template/script/docker/setup.sh` | Path reference | +| `run.sh references template/script/docker/setup.sh` | Path reference | +| `build.sh uses set -euo pipefail` | Shell convention | +| `build.sh supports --no-cache flag` | Force rebuild flag | +| `build.sh passes --no-cache to docker compose build when set` | NO_CACHE forwarded | +| `build.sh keeps test-tools image by default (cleanup gated by CLEAN_TOOLS)` | Default keep tools | +| `build.sh supports --clean-tools flag` | Clean tools flag | +| `build.sh removes test-tools image when --clean-tools is set` | CLEAN_TOOLS forwarded | +| `run.sh uses set -euo pipefail` | Shell convention | +| `exec.sh uses set -euo pipefail` | Shell convention | +| `stop.sh uses set -euo pipefail` | Shell convention | +| `_lib.sh derives PROJECT_NAME from DOCKER_HUB_USER and IMAGE_NAME` | Shared derivation | +| `_lib.sh _compose_project wraps -p with PROJECT_NAME` | Shared compose wrapper | +| `_lib.sh defines _load_env helper` | Shared env loader | +| `_lib.sh defines _compute_project_name helper` | Shared helper | +| `_lib.sh defines _compose wrapper` | Shared compose wrapper | +| `build.sh routes compose call through _compose_project` | Uses shared lib | +| `run.sh routes compose calls through _compose_project` | Uses shared lib | +| `exec.sh routes compose call through _compose_project` | Uses shared lib | +| `stop.sh routes compose call through _compose_project` | Uses shared lib | +| `exec.sh loads .env via _load_env helper` | Uses shared lib | +| `stop.sh loads .env via _load_env helper` | Uses shared lib | +| `stop.sh no longer needs orphan cleanup (run.sh devel uses up not run)` | No more orphan | +| `run.sh devel target uses compose up -d (not compose run --name)` | up + exec model | +| `run.sh devel branch uses compose exec to enter shell` | up + exec model | +| `run.sh devel branch installs trap to auto-down on exit` | Auto cleanup | +| `run.sh _devel_cleanup uses short timeout to avoid 10s grace period` | Fast exit | +| `run.sh non-devel TARGET still uses compose run --rm` | One-shot stages | +| `run.sh devel branch does not use 'compose run --name'` | Old pattern gone | +| `run.sh supports --instance flag` | --instance | +| `exec.sh supports --instance flag` | --instance | +| `stop.sh supports --instance flag` | --instance | +| `stop.sh supports --all flag` | --all | +| `run.sh exports INSTANCE_SUFFIX env var to compose` | env passing | +| `exec.sh exports INSTANCE_SUFFIX env var to compose` | env passing | +| `stop.sh exports INSTANCE_SUFFIX env var to compose` | env passing | +| `run.sh refuses when default container already running and no --instance` | collision | +| `init.sh-generated compose.yaml uses parameterized container_name` | template gen | +| `run.sh -h shows --instance in help` | help text | +| `exec.sh -h shows --instance in help` | help text | +| `stop.sh -h shows --instance in help` | help text | +| `build.sh supports --dry-run flag` | --dry-run | +| `run.sh supports --dry-run flag` | --dry-run | +| `exec.sh supports --dry-run flag` | --dry-run | +| `stop.sh supports --dry-run flag` | --dry-run | +| `build.sh -h shows --dry-run in help` | --dry-run help | +| `run.sh -h shows --dry-run in help` | --dry-run help | +| `exec.sh -h shows --dry-run in help` | --dry-run help | +| `stop.sh -h shows --dry-run in help` | --dry-run help | +| `exec.sh checks container is running before exec` | precheck | +| `exec.sh precheck error mentions run.sh hint` | friendly hint | +| `exec.sh exits non-zero with friendly hint when container not running` | precheck e2e | +| `exec.sh --dry-run skips precheck and prints compose command` | dry-run e2e | +| `script/docker/i18n.sh exists` | i18n module exists | +| `Dockerfile.test-tools includes bats-mock` | bats-mock available in test image | +| `i18n.sh defines _detect_lang function` | _detect_lang in i18n.sh | +| `build.sh sources _lib.sh` | build.sh uses shared lib | +| `run.sh sources _lib.sh` | run.sh uses shared lib | +| `exec.sh sources _lib.sh` | exec.sh uses shared lib | +| `stop.sh sources _lib.sh` | stop.sh uses shared lib | +| `_lib.sh sources i18n.sh (delegates language detection)` | _lib delegates i18n | +| `setup.sh sources i18n.sh` | setup.sh uses shared i18n | +| `build.sh -h works when i18n.sh is missing (consumer Dockerfile /lint scenario)` | i18n fallback | +| `run.sh -h works when i18n.sh is missing` | i18n fallback | +| `exec.sh -h works when i18n.sh is missing` | i18n fallback | +| `stop.sh -h works when i18n.sh is missing` | i18n fallback | +| `setup.sh does not redefine _detect_lang` | No duplication | +| `VERSION file exists in template root` | Version file check | +| `upgrade.sh reads version from template/VERSION` | VERSION path | +| `upgrade.sh does not write .template_version` | No legacy write | +| `upgrade.sh runs init.sh after subtree pull` | Sync symlinks | +| `upgrade.sh cleans up legacy .template_version` | Legacy cleanup | +| `upgrade.sh supports --gen-conf flag` | Flag exists | +| `upgrade.sh --gen-conf delegates to init.sh --gen-conf` | Delegation | +| `upgrade.sh --help mentions --gen-conf` | Help text | +| `upgrade.sh updates main.yaml @tag without clobbering release-worker.yaml` | sed regression | +| `run.sh contains XDG_SESSION_TYPE check` | X11/Wayland branch | +| `run.sh contains xhost +SI:localuser for wayland` | Wayland xhost | +| `run.sh contains xhost +local: for X11` | X11 xhost | +| `setup.sh default _base_path uses /..` | Path resolution | +| `setup.sh default _base_path uses double parent traversal` | Repo root traversal | + +### test/unit/bashrc_spec.bats (14) + +| Test | Description | +|------|-------------| +| `defines alias_func` | Function definition | +| `defines swc` | Function definition | +| `defines color_git_branch` | Function definition | +| `defines ros_complete` | Function definition | +| `defines ros_source` | Function definition | +| `defines ebc alias` | Alias definition | +| `defines sbc alias` | Alias definition | +| `alias_func is called` | Function call | +| `color_git_branch is called` | Function call | +| `ros_complete is called` | Function call | +| `ros_source is called` | Function call | +| `swc searches for catkin devel/setup.bash` | Catkin search | +| `ros_source references ROS_DISTRO` | ROS env var | +| `color_git_branch sets PS1` | PS1 setting | + +### test/unit/pip_setup_spec.bats (3) + +| Test | Description | +|------|-------------| +| `pip setup.sh runs pip install with requirements.txt` | pip install | +| `pip setup.sh sets PIP_BREAK_SYSTEM_PACKAGES=1` | Break system packages | +| `pip setup.sh fails when pip is not available` | Missing pip error | + +### test/unit/ci_spec.bats (8) + +| Test | Description | +|------|-------------| +| `_install_deps: skips apt-get and git when bats is already installed` | No-op fast path | +| `_install_deps: dies with clear error when apt-get update fails` | Explicit `apt-get update` error | +| `_install_deps: dies with clear error when apt-get install fails` | Explicit `apt-get install` error | +| `_install_deps: dies with clear error when git clone bats-mock fails` | Explicit `git clone` error | +| `_install_deps: happy path succeeds when bats absent and all deps install cleanly` | Full install path | +| `_run_shellcheck: invokes shellcheck against every expected script` | Wired-file regression guard | +| `_run_shellcheck: picks up every .sh file in script/docker/` | `find` covers new scripts | +| `_run_shellcheck: exits non-zero when shellcheck fails on any script` | Strict-mode propagation | + +### test/unit/init_spec.bats (13) + +Unit coverage for `init.sh` helpers that previous rounds exercised only +through the Level-1 integration test. Complements +`test/integration/init_new_repo_spec.bats` by locking edge cases that +are hard to trigger from a real `bash template/init.sh` invocation +(network-down version detection, main.yaml `@ref` fallback, +`_create_version_file` with no argument). + +| Test | Description | +|------|-------------| +| `_detect_template_version: parses newest vX.Y.Z tag from git ls-remote` | Happy path + head -1 | +| `_detect_template_version: returns empty when git ls-remote fails` | Network-down fallback | +| `_detect_template_version: returns empty when no v*.*.* tags exist` | Nothing to match | +| `_detect_template_version: ignores non-semver tags (e.g. rc suffixes)` | Regex filters rc / pre-release | +| `_detect_template_version: reads VERSION file when present (no network)` | VERSION file priority | +| `_detect_template_version: VERSION file takes priority over git ls-remote` | Local-first resolution | +| `init.sh removes legacy .template_version when present` | Legacy cleanup | +| `init.sh succeeds when no legacy .template_version exists` | Clean state | +| `_create_new_repo: main.yaml uses given ref in workflow @ref` | Ref threading | +| `_create_new_repo: main.yaml falls back to @main when ref arg omitted` | Default ref | +| `_create_new_repo: main.yaml falls back to @main when ref arg is empty` | Empty-string → `@main` | +| `_create_new_repo: generates .env.example with IMAGE_NAME=` | Fallback image name | +| `_create_symlinks: produces all five docker-script symlinks` | Symlink set | +| `_create_symlinks: replaces a stale file at the symlink path` | Re-init over existing files | +| `_create_symlinks: keeps custom .hadolint.yaml when it differs` | Custom-hadolint preservation | + +### test/unit/smoke_helper_spec.bats (19) + +Exercises the runtime assertion helpers shipped in +`test/smoke/test_helper.bash` (used by downstream-repo smoke specs via +`load "${BATS_TEST_DIRNAME}/test_helper"`). | Test | Description | |------|-------------| -| `ros1_bridge package is available` | `ros2 pkg list` includes ros1_bridge | +| `assert_cmd_installed passes when cmd is on PATH` | Happy path | +| `assert_cmd_installed fails with descriptive message when cmd missing` | Missing cmd | +| `assert_cmd_installed errors when cmd arg missing` | Required arg check | +| `assert_cmd_runs passes when cmd exits 0` | Happy path | +| `assert_cmd_runs uses custom version flag when given` | Custom flag | +| `assert_cmd_runs fails when cmd exits non-zero` | Broken binary | +| `assert_cmd_runs fails when cmd is not installed` | Missing cmd | +| `assert_file_exists passes when file is a regular file` | Happy path | +| `assert_file_exists fails when path is missing` | Missing path | +| `assert_file_exists fails when path is a directory` | Type check | +| `assert_dir_exists passes when path is a directory` | Happy path | +| `assert_dir_exists fails when path is missing` | Missing path | +| `assert_dir_exists fails when path is a file` | Type check | +| `assert_file_owned_by passes when owner matches` | Happy path | +| `assert_file_owned_by fails with owner diff when user mismatches` | Owner mismatch | +| `assert_file_owned_by fails when path missing` | Missing path | +| `assert_pip_pkg passes when pip show returns 0` | Package installed | +| `assert_pip_pkg fails when pip show returns non-zero` | Package missing | +| `assert_pip_pkg fails when pip is not installed` | pip itself missing | -### Bridge config (3) +### test/unit/terminator_config_spec.bats (10) | Test | Description | |------|-------------| -| `bridge.yaml exists` | `/bridge.yaml` exists | -| `entrypoint.sh exists and is executable` | `/entrypoint.sh` is executable | -| `config directory exists` | `/config` directory exists | +| `has [global_config] section` | Config section | +| `has [keybindings] section` | Config section | +| `has [profiles] section` | Config section | +| `has [layouts] section` | Config section | +| `has [plugins] section` | Config section | +| `profiles has [[default]]` | Default profile | +| `default profile disables system font` | Font setting | +| `default profile has infinite scrollback` | Scrollback setting | +| `layouts has Window type` | Window layout | +| `layouts has Terminal type` | Terminal layout | -## test/smoke/script_help.bats +### test/unit/terminator_setup_spec.bats (8) -### build.sh (3) +| Test | Description | +|------|-------------| +| `check_deps returns 0 when terminator is installed` | Dependency check | +| `check_deps fails when terminator is not installed` | Missing dep | +| `_entry_point calls main when deps pass` | Entry point | +| `_entry_point fails when deps missing` | Entry point fail | +| `main creates terminator config directory` | Config dir | +| `main copies terminator config file` | Config copy | +| `main calls chown with correct user and group` | Permissions | +| `script runs entry_point when executed directly` | Direct-run guard | + +### test/unit/tmux_conf_spec.bats (12) + +| Test | Description | +|------|-------------| +| `defines prefix key` | tmux prefix | +| `sets default shell to bash` | Shell setting | +| `sets default terminal` | Terminal setting | +| `enables mouse support` | Mouse | +| `enables vi status-keys` | vi mode | +| `enables vi mode-keys` | vi mode | +| `defines split-window bindings` | Split bindings | +| `defines reload config binding` | Reload binding | +| `enables status bar` | Status bar | +| `sets status bar position` | Status bar position | +| `declares tpm plugin` | tpm plugin | +| `initializes tpm at end of file` | tpm init | + +### test/unit/tmux_setup_spec.bats (9) + +| Test | Description | +|------|-------------| +| `check_deps returns 0 when tmux and git are installed` | Dependency check | +| `check_deps fails when tmux is not installed` | Missing tmux | +| `check_deps fails when git is not installed` | Missing git | +| `_entry_point calls main when deps pass` | Entry point | +| `_entry_point fails when deps missing` | Entry point fail | +| `main clones tpm repository` | tpm clone | +| `main creates tmux config directory` | Config dir | +| `main copies tmux.conf to config directory` | Config copy | +| `script runs entry_point when executed directly` | Direct-run guard | + +### test/unit/upgrade_spec.bats (20) + +Unit tests for `upgrade.sh` helpers. Uses the sed-range pattern to extract +one function at a time into a minimal harness (with `_log` / `_error` +stubs), so each helper runs in a sandboxed git repo without needing to +source the full `upgrade.sh` (which would trigger its top-level +`cd REPO_ROOT`). + +Covers: `_warn_config_drift` (silent / fires on drift / diff hint), +the three safety guards added after the v0.9.7 Jetson incident +(`_require_git_identity`, `_require_clean_merge_state`, +`_verify_subtree_intact` with rollback), and structural invariants that +pin call-ordering in `_upgrade` (identity check runs before subtree +pull, integrity verification runs after, pre-pull HEAD is snapshotted +for rollback). | Test | Description | |------|-------------| -| `build.sh -h exits 0` | Help exits successfully | -| `build.sh --help exits 0` | Help exits successfully | -| `build.sh -h prints usage` | Help output contains "Usage:" | +| `_warn_config_drift silent when no template/config in HEAD` | Initial setup | +| `_warn_config_drift silent when pre and post hashes match` | No drift | +| `_warn_config_drift prints WARNING + diff hint when hashes differ` | Drift reported | +| `upgrade.sh defines _warn_config_drift` | Helper present | +| `upgrade.sh invokes _warn_config_drift after subtree pull` | Call site present | +| `upgrade.sh captures pre-pull template/config tree hash` | Snapshot taken | +| `_require_git_identity succeeds when name + email are set` | Happy path | +| `_require_git_identity fails when user.email is unset` | Email guard | +| `_require_git_identity fails when user.name is unset` | Name guard | +| `_require_clean_merge_state succeeds in clean repo` | Happy path | +| `_require_clean_merge_state fails when MERGE_HEAD exists` | Mid-merge guard | +| `_require_clean_merge_state fails when rebase-merge dir exists` | Mid-rebase guard | +| `_verify_subtree_intact succeeds when all markers present` | Happy path | +| `_verify_subtree_intact rolls back when template/.version is missing` | Destructive-FF rollback | +| `_verify_subtree_intact rolls back when template/script/docker/setup.sh is missing` | Marker rollback | +| `upgrade.sh calls _require_git_identity before subtree pull` | Pre-flight ordering | +| `upgrade.sh calls _verify_subtree_intact after subtree pull` | Post-flight ordering | +| `upgrade.sh snapshots pre-pull HEAD for rollback` | Rollback anchor | + +### test/integration/init_new_repo_spec.bats (35) -### run.sh (3) +End-to-end verification that `init.sh` produces a complete repo skeleton in +an empty directory. **Level 1** (file generation only, no Docker). The +**Level 2** equivalent (real `build.sh` / `run.sh` / `exec.sh` / `stop.sh`) +runs as the `integration-e2e` job in `.github/workflows/self-test.yaml`, +which has access to a Docker daemon on the host runner. | Test | Description | |------|-------------| -| `run.sh -h exits 0` | Help exits successfully | -| `run.sh --help exits 0` | Help exits successfully | -| `run.sh -h prints usage` | Help output contains "Usage:" | +| `init.sh detects empty dir and creates new repo skeleton` | Smoke | +| `new repo: Dockerfile is copied from template` | Dockerfile gen | +| `new repo: compose.yaml exists and references the repo name` | compose gen | +| `new repo: .env.example contains IMAGE_NAME=` | env fallback | +| `new repo: script/entrypoint.sh exists and is executable` | entrypoint gen | +| `new repo: smoke test skeleton exists for the repo` | smoke skeleton | +| `new repo: .github/workflows/main.yaml exists with reusable workflow ref` | CI gen | +| `new repo: .gitignore exists` | gitignore | +| `new repo: doc/ tree exists with README translations` | i18n docs | +| `new repo: doc/test/TEST.md exists` | TEST.md gen | +| `new repo: doc/changelog/CHANGELOG.md exists` | CHANGELOG gen | +| `new repo: build.sh symlink → template/script/docker/build.sh` | symlink target | +| `new repo: run.sh / exec.sh / stop.sh / Makefile symlinks correct` | symlink set | +| `new repo: template/VERSION exists (no legacy .template_version)` | version file | +| `new repo: re-running init.sh on the result is idempotent` | idempotent | +| `new repo: init.sh creates setup_tui.sh symlink (not legacy tui.sh)` | post-rename symlink | +| `new repo: init.sh removes stale tui.sh symlink from earlier versions` | upgrade cleanup | +| `new repo: build.sh -h works against the generated symlink` | smoke build.sh | +| `new repo: run.sh -h works against the generated symlink` | smoke run.sh | +| `new repo: exec.sh -h works against the generated symlink` | smoke exec.sh | +| `new repo: stop.sh -h works against the generated symlink` | smoke stop.sh | +| `init.sh --gen-conf copies setup.conf to repo root` | setup.conf gen | +| `init.sh --gen-conf refuses to overwrite existing setup.conf` | overwrite safety | +| `new repo: .gitignore contains compose.yaml (derived artifact)` | gitignore compose.yaml | +| `new repo: .gitignore contains .env (derived artifact)` | gitignore .env | +| `new repo: compose.yaml has AUTO-GENERATED header (produced by setup.sh)` | setup.sh generated compose.yaml | +| `new repo: per-repo setup.conf not created by default` | template default usage | -### exec.sh (3) +### test/integration/fresh_clone_portability_spec.bats (2) + +End-to-end verification for the fresh-clone-on-a-different-machine scenario: +the consumer repo's `setup.conf` has already been committed by another +contributor and carries either a stale absolute `mount_1` path (the Jetson +bug) or the portable `${WS_PATH}` form. Runs the real `build.sh` + +`setup.sh` (no mocks) and asserts the auto-migration / per-machine detection +pipeline lands a valid `.env` + `compose.yaml`. **Level 1** (no Docker +invocation — `build.sh --dry-run`). | Test | Description | |------|-------------| -| `exec.sh -h exits 0` | Help exits successfully | -| `exec.sh --help exits 0` | Help exits successfully | -| `exec.sh -h prints usage` | Help output contains "Usage:" | +| `fresh clone with stale absolute mount_1: build.sh auto-migrates + generates local .env` | Stale-path auto-migrate | +| `fresh clone with portable ${WS_PATH} mount_1: no warning, .env gets local path` | Happy path round-trip | + +### test/integration/upgrade_spec.bats (6) -### stop.sh (3) +End-to-end verification for `upgrade.sh` driving a real subtree update +against a fake template remote (bare repo with `v0.9.5` / `v0.9.7` tags +on a minimal subtree layout) attached to a sandbox downstream repo. +**Level 1** (no Docker). Exercises the happy path, the pre-flight +guards, and — most importantly — the destructive-FF rollback path added +after the Jetson v0.9.7 incident (stubs `git-subtree pull` via +`GIT_EXEC_PATH` to simulate the bug and asserts the repo is restored). | Test | Description | |------|-------------| -| `stop.sh -h exits 0` | Help exits successfully | -| `stop.sh --help exits 0` | Help exits successfully | -| `stop.sh -h prints usage` | Help output contains "Usage:" | +| `upgrade.sh v0.9.7: bumps template/.version, pulls new content, updates main.yaml` | Happy path | +| `upgrade.sh v0.9.7 is idempotent on a second run` | Re-run is no-op | +| `upgrade.sh --check reports update available from v0.9.5 → v0.9.7` | --check flag | +| `upgrade.sh fails fast when git identity is missing` | Pre-flight identity guard | +| `upgrade.sh fails fast when MERGE_HEAD is present` | Pre-flight merge-state guard | +| `upgrade.sh rolls back when git-subtree does a destructive fast-forward` | Destructive-FF rollback | diff --git a/template/dockerfile/Dockerfile.example b/dockerfile/Dockerfile.example similarity index 91% rename from template/dockerfile/Dockerfile.example rename to dockerfile/Dockerfile.example index 6b3f0dc..9de4ca5 100644 --- a/template/dockerfile/Dockerfile.example +++ b/dockerfile/Dockerfile.example @@ -133,6 +133,16 @@ COPY .hadolint.yaml /lint/.hadolint.yaml COPY Dockerfile /lint/Dockerfile COPY compose.yaml /lint/compose.yaml COPY *.sh /lint/ +# Helpers sourced by the root-level scripts. Must sit next to them so +# build.sh / run.sh / exec.sh / stop.sh / setup.sh can source _lib.sh +# (which in turn sources i18n.sh); setup.sh also sources _tui_conf.sh. +# Issue #104: removing these used to be compensated by inline +# `_detect_lang` fallbacks in every script — now the canonical +# definition lives once in i18n.sh. +COPY template/script/docker/_lib.sh \ + template/script/docker/i18n.sh \ + template/script/docker/_tui_conf.sh \ + /lint/ RUN shellcheck -S warning /lint/*.sh RUN cd /lint && hadolint Dockerfile diff --git a/template/dockerfile/Dockerfile.test-tools b/dockerfile/Dockerfile.test-tools similarity index 100% rename from template/dockerfile/Dockerfile.test-tools rename to dockerfile/Dockerfile.test-tools diff --git a/exec.sh b/exec.sh deleted file mode 120000 index 6baff8b..0000000 --- a/exec.sh +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/exec.sh \ No newline at end of file diff --git a/template/init.sh b/init.sh similarity index 100% rename from template/init.sh rename to init.sh diff --git a/run.sh b/run.sh deleted file mode 120000 index a4c4fbe..0000000 --- a/run.sh +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/run.sh \ No newline at end of file diff --git a/template/script/ci/ci.sh b/script/ci/ci.sh similarity index 100% rename from template/script/ci/ci.sh rename to script/ci/ci.sh diff --git a/template/script/docker/Makefile b/script/docker/Makefile similarity index 100% rename from template/script/docker/Makefile rename to script/docker/Makefile diff --git a/template/script/docker/_lib.sh b/script/docker/_lib.sh similarity index 96% rename from template/script/docker/_lib.sh rename to script/docker/_lib.sh index 1746c3d..fe7cc64 100644 --- a/template/script/docker/_lib.sh +++ b/script/docker/_lib.sh @@ -16,24 +16,14 @@ if [[ -n "${_DOCKER_LIB_SOURCED:-}" ]]; then fi _DOCKER_LIB_SOURCED=1 -# _detect_lang prints the language code derived from $LANG. -_detect_lang() { - case "${LANG:-}" in - zh_TW*) echo "zh" ;; - zh_CN*|zh_SG*) echo "zh-CN" ;; - ja*) echo "ja" ;; - *) echo "en" ;; - esac -} - -# Load i18n.sh if present, otherwise fall back to a minimal _LANG. +# i18n.sh lives next to _lib.sh in every deployment surface (consumer +# repo's template/script/docker/, and the /lint/ stage where the +# Dockerfile COPYs both). Issue #104 removed the duplicate fallback +# `_detect_lang` definitions that had already drifted (#103) — the +# one canonical _detect_lang + _LANG assignment lives in i18n.sh. _lib_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" -if [[ -f "${_lib_dir}/i18n.sh" ]]; then - # shellcheck disable=SC1091 - source "${_lib_dir}/i18n.sh" -else - _LANG="${SETUP_LANG:-$(_detect_lang)}" -fi +# shellcheck disable=SC1091 +source "${_lib_dir}/i18n.sh" unset _lib_dir # _load_env sources the given .env file with allexport so every assignment diff --git a/template/script/docker/_tui_backend.sh b/script/docker/_tui_backend.sh similarity index 100% rename from template/script/docker/_tui_backend.sh rename to script/docker/_tui_backend.sh diff --git a/template/script/docker/_tui_conf.sh b/script/docker/_tui_conf.sh similarity index 99% rename from template/script/docker/_tui_conf.sh rename to script/docker/_tui_conf.sh index a3c5749..3ec552d 100644 --- a/template/script/docker/_tui_conf.sh +++ b/script/docker/_tui_conf.sh @@ -165,7 +165,7 @@ _validate_build_network() { local _v="${1-}" [[ -z "${_v}" ]] && return 0 case "${_v}" in - host|bridge|none|default) return 0 ;; + auto|host|bridge|none|default|off) return 0 ;; *) return 1 ;; esac } diff --git a/template/script/docker/build.sh b/script/docker/build.sh similarity index 94% rename from template/script/docker/build.sh rename to script/docker/build.sh index 2892883..347484a 100755 --- a/template/script/docker/build.sh +++ b/script/docker/build.sh @@ -5,22 +5,22 @@ set -euo pipefail FILE_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" readonly FILE_PATH +# _lib.sh lives at template/script/docker/_lib.sh in normal consumer +# repos, OR alongside build.sh when the Dockerfile `test` stage COPYs +# scripts + helpers into /lint/. Issue #104 deduplicated the previously +# inlined fallback `_detect_lang`; we now always have i18n.sh via +# _lib.sh's sibling load. if [[ -f "${FILE_PATH}/template/script/docker/_lib.sh" ]]; then # shellcheck disable=SC1091 source "${FILE_PATH}/template/script/docker/_lib.sh" +elif [[ -f "${FILE_PATH}/_lib.sh" ]]; then + # shellcheck disable=SC1091 + source "${FILE_PATH}/_lib.sh" else - # Fallback for /lint stage which COPYs only *.sh from repo root and has - # no template/ tree. Only _LANG is needed for `usage()`; other helpers - # are unused in this stage. - _detect_lang() { - case "${LANG:-}" in - zh_TW*) echo "zh-TW" ;; - zh_CN*|zh_SG*) echo "zh-CN" ;; - ja*) echo "ja" ;; - *) echo "en" ;; - esac - } - _LANG="${SETUP_LANG:-$(_detect_lang)}" + printf "[build] ERROR: cannot find _lib.sh — expected one of:\n" >&2 + printf " %s\n" "${FILE_PATH}/template/script/docker/_lib.sh" >&2 + printf " %s\n" "${FILE_PATH}/_lib.sh" >&2 + exit 1 fi _msg() { diff --git a/template/script/docker/exec.sh b/script/docker/exec.sh similarity index 93% rename from template/script/docker/exec.sh rename to script/docker/exec.sh index e298526..3005fe7 100755 --- a/template/script/docker/exec.sh +++ b/script/docker/exec.sh @@ -5,20 +5,19 @@ set -euo pipefail FILE_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" readonly FILE_PATH +# _lib.sh lookup: template/script/docker/_lib.sh in consumer repos, or +# sibling _lib.sh in /lint/ (Dockerfile test stage). See build.sh. if [[ -f "${FILE_PATH}/template/script/docker/_lib.sh" ]]; then # shellcheck disable=SC1091 source "${FILE_PATH}/template/script/docker/_lib.sh" +elif [[ -f "${FILE_PATH}/_lib.sh" ]]; then + # shellcheck disable=SC1091 + source "${FILE_PATH}/_lib.sh" else - # Fallback for /lint stage. See build.sh for rationale. - _detect_lang() { - case "${LANG:-}" in - zh_TW*) echo "zh-TW" ;; - zh_CN*|zh_SG*) echo "zh-CN" ;; - ja*) echo "ja" ;; - *) echo "en" ;; - esac - } - _LANG="${SETUP_LANG:-$(_detect_lang)}" + printf "[exec] ERROR: cannot find _lib.sh — expected one of:\n" >&2 + printf " %s\n" "${FILE_PATH}/template/script/docker/_lib.sh" >&2 + printf " %s\n" "${FILE_PATH}/_lib.sh" >&2 + exit 1 fi _msg() { diff --git a/template/script/docker/i18n.sh b/script/docker/i18n.sh similarity index 51% rename from template/script/docker/i18n.sh rename to script/docker/i18n.sh index 0bb76c8..c085226 100644 --- a/template/script/docker/i18n.sh +++ b/script/docker/i18n.sh @@ -25,19 +25,41 @@ _detect_lang() { # _sanitize_lang [] # # Reads the current value of the nameref, and if it's not in -# {en, zh-TW, zh-CN, ja} prints a WARNING to stderr and rewrites +# {en, zh-TW, zh-CN, ja} prints a warning to stderr and rewrites # the nameref to "en". Callers invoke this right after parsing # --lang so typos don't silently fall through to English at message # lookup time (visible warning, safe default, non-fatal). +# +# Warning language: the user just demonstrated they don't know the +# right --lang value, so we can't trust _LANG (it holds the invalid +# input). Instead we re-detect from the system's $LANG env var so +# the warning appears in the user's actual locale. _sanitize_lang() { local -n _sl_ref="${1:?"${FUNCNAME[0]}: missing outvar name"}" local _who="${2:-tui}" case "${_sl_ref}" in en|zh-TW|zh-CN|ja) return 0 ;; esac - printf "[%s] WARNING: unsupported --lang value %q, falling back to 'en'\n" \ - "${_who}" "${_sl_ref}" >&2 - printf "[%s] allowed: en | zh-TW | zh-CN | ja\n" "${_who}" >&2 + local _sys_lang + _sys_lang="$(_detect_lang)" + case "${_sys_lang}" in + zh-TW) + printf "[%s] 警告:不支援的 --lang 值 %q,改用 'en'\n" "${_who}" "${_sl_ref}" >&2 + printf "[%s] 可用值:en | zh-TW | zh-CN | ja\n" "${_who}" >&2 + ;; + zh-CN) + printf "[%s] 警告:不支持的 --lang 值 %q,改用 'en'\n" "${_who}" "${_sl_ref}" >&2 + printf "[%s] 可用值:en | zh-TW | zh-CN | ja\n" "${_who}" >&2 + ;; + ja) + printf "[%s] 警告: サポート外の --lang 値 %q, 'en' にフォールバックします\n" "${_who}" "${_sl_ref}" >&2 + printf "[%s] 利用可能: en | zh-TW | zh-CN | ja\n" "${_who}" >&2 + ;; + *) + printf "[%s] WARNING: unsupported --lang value %q, falling back to 'en'\n" "${_who}" "${_sl_ref}" >&2 + printf "[%s] allowed: en | zh-TW | zh-CN | ja\n" "${_who}" >&2 + ;; + esac _sl_ref="en" } diff --git a/template/script/docker/run.sh b/script/docker/run.sh similarity index 96% rename from template/script/docker/run.sh rename to script/docker/run.sh index 1d5c634..007fe14 100755 --- a/template/script/docker/run.sh +++ b/script/docker/run.sh @@ -5,20 +5,19 @@ set -euo pipefail FILE_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" readonly FILE_PATH +# _lib.sh lookup: template/script/docker/_lib.sh in consumer repos, or +# sibling _lib.sh in /lint/ (Dockerfile test stage). See build.sh. if [[ -f "${FILE_PATH}/template/script/docker/_lib.sh" ]]; then # shellcheck disable=SC1091 source "${FILE_PATH}/template/script/docker/_lib.sh" +elif [[ -f "${FILE_PATH}/_lib.sh" ]]; then + # shellcheck disable=SC1091 + source "${FILE_PATH}/_lib.sh" else - # Fallback for /lint stage. See build.sh for rationale. - _detect_lang() { - case "${LANG:-}" in - zh_TW*) echo "zh-TW" ;; - zh_CN*|zh_SG*) echo "zh-CN" ;; - ja*) echo "ja" ;; - *) echo "en" ;; - esac - } - _LANG="${SETUP_LANG:-$(_detect_lang)}" + printf "[run] ERROR: cannot find _lib.sh — expected one of:\n" >&2 + printf " %s\n" "${FILE_PATH}/template/script/docker/_lib.sh" >&2 + printf " %s\n" "${FILE_PATH}/_lib.sh" >&2 + exit 1 fi _msg() { diff --git a/template/script/docker/setup.sh b/script/docker/setup.sh similarity index 98% rename from template/script/docker/setup.sh rename to script/docker/setup.sh index eb3613c..a67478c 100755 --- a/template/script/docker/setup.sh +++ b/script/docker/setup.sh @@ -576,6 +576,27 @@ _resolve_runtime() { esac } +# _resolve_build_network +# mode=host / bridge / none / default → pass through +# mode=auto → "host" iff _detect_jetson, else "" (issue #102) +# mode=off | "" → "" (no network key emitted; Docker defaults to bridge) +# +# Jetson L4T kernels commonly lack the iptables modules docker's bridge +# NAT needs, so first-time `docker build` on Jetson dies with DNS +# resolution failures before the apt step. Auto-promoting to host-net +# on Jetson removes the trap door; desktop hosts keep default bridge. +_resolve_build_network() { + local _mode="${1:-}" + local -n _rbn_out="${2:?}" + case "${_mode}" in + host|bridge|none|default) _rbn_out="${_mode}" ;; + auto) + if _detect_jetson; then _rbn_out="host"; else _rbn_out=""; fi + ;; + off|""|*) _rbn_out="" ;; + esac +} + # ════════════════════════════════════════════════════════════════════ # _compute_conf_hash # @@ -1199,8 +1220,10 @@ main() { # compose.yaml and `--network ` to the auxiliary test-tools # docker build. Typical value: `host`, for hosts whose docker bridge # NAT is unusable (stripped embedded kernels, iptables:false). + local build_network_mode="" + _get_conf_value _build_k _build_v "network" "auto" build_network_mode local build_network="" - _get_conf_value _build_k _build_v "network" "" build_network + _resolve_build_network "${build_network_mode}" build_network local gpu_mode="" gpu_count="" gpu_caps="" runtime_mode="" local gui_mode="" diff --git a/template/script/docker/setup_tui.sh b/script/docker/setup_tui.sh similarity index 96% rename from template/script/docker/setup_tui.sh rename to script/docker/setup_tui.sh index 802191c..cfa8043 100755 --- a/template/script/docker/setup_tui.sh +++ b/script/docker/setup_tui.sh @@ -95,11 +95,11 @@ declare -gA _TUI_MSG_EN=( [build.target_arch.prompt]=$'Docker TARGETARCH override\n - Empty = let BuildKit auto-fill from host / --platform (default)\n - amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64\n - Applies to the main image + the test-tools image\n - Pin when you need cross-builds or explicit control' [build.target_arch.auto]="(auto)" [build.network.label]="Build network" - [build.network.prompt]=$'Docker build-time network (only the build stage; runtime is separate)\n - Empty = Docker default (bridge + NAT)\n - host = use the host network stack. Required when the host\'s bridge\n NAT is unusable: stripped embedded kernels (e.g. Jetson L4T\n missing iptable_raw), hosts with iptables: false, or firewall-\n locked CI runners.\n - bridge / none / default = explicit variants (rarely needed)' - [build.network.default]="(default: bridge)" + [build.network.prompt]=$'Docker build-time network (only the build stage; runtime is separate)\n - auto = detect Jetson (/etc/nv_tegra_release) → host; desktop → Docker default (default)\n - host = force host network stack. Required when bridge NAT is\n unusable (stripped kernels, iptables: false, firewall-locked CI)\n - bridge / none / default = explicit Docker modes\n - off (or empty) = explicit opt-out; stay on Docker default bridge' + [build.network.default]="(default: auto)" [build.args.label]="Extra build args" [err.invalid_target_arch]="Invalid TARGETARCH. Use empty or amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64." - [err.invalid_build_network]="Invalid build network. Use empty or host / bridge / none / default." + [err.invalid_build_network]="Invalid build network. Use auto / host / bridge / none / default / off (or empty)." [network.title]="Network" [network.mode.prompt]="Network mode" [network.mode.host]="host (share host network stack)" @@ -248,11 +248,11 @@ declare -gA _TUI_MSG_ZH_TW=( [build.target_arch.prompt]=$'Docker TARGETARCH 覆寫\n - 留空 = 交給 BuildKit 依 host / --platform 自動填(預設)\n - amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64\n - 同時套用主 image 與 test-tools image\n - 需要跨架構編譯或明確指定時才填' [build.target_arch.auto]="(自動)" [build.network.label]="Build 網路" - [build.network.prompt]=$'Docker build 階段使用的網路(不影響 runtime 容器的網路)\n - 留空 = Docker 預設(bridge + NAT)\n - host = 改用 host 網路 stack。當主機的 bridge NAT 無法用時需要:\n 例如 kernel 缺 iptable_raw(Jetson L4T)、\n daemon.json 有 iptables: false、或 CI runner 防火牆限制。\n - bridge / none / default = 其他明確選項(很少用到)' - [build.network.default]="(預設:bridge)" + [build.network.prompt]=$'Docker build 階段使用的網路(不影響 runtime 容器的網路)\n - auto = 自動偵測 Jetson(/etc/nv_tegra_release)→ host;桌機 → Docker 預設(預設)\n - host = 強制 host 網路 stack。當主機的 bridge NAT 無法用時需要:\n 例如 kernel 缺 iptable_raw(Jetson L4T)、\n daemon.json 有 iptables: false、或 CI runner 防火牆限制\n - bridge / none / default = 其他明確 Docker 選項\n - off(或留空)= 明確關閉;用 Docker 預設的 bridge' + [build.network.default]="(預設:auto)" [build.args.label]="額外 build args" [err.invalid_target_arch]="TARGETARCH 無效,請填空值或 amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64。" - [err.invalid_build_network]="Build 網路無效,請填空值或 host / bridge / none / default。" + [err.invalid_build_network]="Build 網路無效,請填 auto / host / bridge / none / default / off(或留空)。" [network.title]="Network" [network.mode.prompt]="網路模式" [network.mode.host]="host(共用主機網路堆疊)" @@ -399,11 +399,11 @@ declare -gA _TUI_MSG_ZH_CN=( [build.target_arch.prompt]=$'Docker TARGETARCH 覆盖\n - 留空 = 交给 BuildKit 依 host / --platform 自动填(默认)\n - amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64\n - 同时应用于主 image 与 test-tools image\n - 需要跨架构构建或明确指定时才填' [build.target_arch.auto]="(自动)" [build.network.label]="Build 网络" - [build.network.prompt]=$'Docker build 阶段使用的网络(不影响 runtime 容器的网络)\n - 留空 = Docker 默认(bridge + NAT)\n - host = 改用 host 网络 stack。当主机的 bridge NAT 无法用时需要:\n 例如 kernel 缺 iptable_raw(Jetson L4T)、\n daemon.json 有 iptables: false、或 CI runner 防火墙限制。\n - bridge / none / default = 其他明确选项(很少用到)' - [build.network.default]="(默认:bridge)" + [build.network.prompt]=$'Docker build 阶段使用的网络(不影响 runtime 容器的网络)\n - auto = 自动检测 Jetson(/etc/nv_tegra_release)→ host;桌机 → Docker 默认(默认)\n - host = 强制 host 网络 stack。当主机的 bridge NAT 无法用时需要:\n 例如 kernel 缺 iptable_raw(Jetson L4T)、\n daemon.json 有 iptables: false、或 CI runner 防火墙限制\n - bridge / none / default = 其他明确 Docker 选项\n - off(或留空)= 明确关闭;用 Docker 默认的 bridge' + [build.network.default]="(默认:auto)" [build.args.label]="额外 build args" [err.invalid_target_arch]="TARGETARCH 无效,请填空值或 amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64。" - [err.invalid_build_network]="Build 网络无效,请填空值或 host / bridge / none / default。" + [err.invalid_build_network]="Build 网络无效,请填 auto / host / bridge / none / default / off(或留空)。" [network.title]="Network" [network.mode.prompt]="网络模式" [network.mode.host]="host(共用主机网络栈)" @@ -545,11 +545,11 @@ declare -gA _TUI_MSG_JA=( [build.target_arch.prompt]=$'Docker TARGETARCH 上書き\n - 空 = BuildKit が host / --platform から自動補完(デフォルト)\n - amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64\n - メイン image と test-tools image の両方に適用\n - クロスビルドや明示指定が必要なときのみ設定' [build.target_arch.auto]="(自動)" [build.network.label]="Build ネットワーク" - [build.network.prompt]=$'Docker build 時のネットワーク(runtime コンテナは別管理)\n - 空 = Docker デフォルト(bridge + NAT)\n - host = ホストのネットワークスタックを利用。ホストの bridge NAT が\n 使えない場合に必要:kernel が iptable_raw 欠落(Jetson L4T)、\n daemon.json に iptables: false、CI runner のファイアウォール制限など。\n - bridge / none / default = 明示指定(めったに使用しない)' - [build.network.default]="(デフォルト:bridge)" + [build.network.prompt]=$'Docker build 時のネットワーク(runtime コンテナは別管理)\n - auto = Jetson(/etc/nv_tegra_release)検出時は host、デスクトップは Docker 既定(既定)\n - host = 強制的にホストネットワーク stack を使用。ホストの bridge NAT が\n 使えない場合に必要:kernel が iptable_raw 欠落(Jetson L4T)、\n daemon.json に iptables: false、CI runner のファイアウォール制限など\n - bridge / none / default = 明示指定(Docker の既知モード)\n - off(または空)= 明示的にオプトアウト。Docker 既定の bridge を使用' + [build.network.default]="(デフォルト:auto)" [build.args.label]="追加 build args" [err.invalid_target_arch]="TARGETARCH が不正です。空、または amd64 / arm64 / arm / 386 / ppc64le / s390x / riscv64 を指定してください。" - [err.invalid_build_network]="Build ネットワークが不正です。空、または host / bridge / none / default を指定してください。" + [err.invalid_build_network]="Build ネットワークが不正です。auto / host / bridge / none / default / off(または空)を指定してください。" [network.title]="Network" [network.mode.prompt]="ネットワークモード" [network.mode.host]="host(ホストネットワークスタックを共有)" diff --git a/template/script/docker/stop.sh b/script/docker/stop.sh similarity index 91% rename from template/script/docker/stop.sh rename to script/docker/stop.sh index 5a9db15..de74a4d 100755 --- a/template/script/docker/stop.sh +++ b/script/docker/stop.sh @@ -5,20 +5,19 @@ set -euo pipefail FILE_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" readonly FILE_PATH +# _lib.sh lookup: template/script/docker/_lib.sh in consumer repos, or +# sibling _lib.sh in /lint/ (Dockerfile test stage). See build.sh. if [[ -f "${FILE_PATH}/template/script/docker/_lib.sh" ]]; then # shellcheck disable=SC1091 source "${FILE_PATH}/template/script/docker/_lib.sh" +elif [[ -f "${FILE_PATH}/_lib.sh" ]]; then + # shellcheck disable=SC1091 + source "${FILE_PATH}/_lib.sh" else - # Fallback for /lint stage. See build.sh for rationale. - _detect_lang() { - case "${LANG:-}" in - zh_TW*) echo "zh-TW" ;; - zh_CN*|zh_SG*) echo "zh-CN" ;; - ja*) echo "ja" ;; - *) echo "en" ;; - esac - } - _LANG="${SETUP_LANG:-$(_detect_lang)}" + printf "[stop] ERROR: cannot find _lib.sh — expected one of:\n" >&2 + printf " %s\n" "${FILE_PATH}/template/script/docker/_lib.sh" >&2 + printf " %s\n" "${FILE_PATH}/_lib.sh" >&2 + exit 1 fi _msg() { diff --git a/script/entrypoint.sh b/script/entrypoint.sh deleted file mode 100755 index e0f6a02..0000000 --- a/script/entrypoint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -# source ROS1 + ROS2 -source /opt/ros/noetic/setup.bash -source /opt/ros/foxy/setup.bash - -_bridge_file="/bridge.yaml" -if [ -s "${_bridge_file}" ]; then - printf "Loading ROS2 bridge parameters from %s\n" "${_bridge_file}" - rosparam load "${_bridge_file}" -fi - -exec "${@}" diff --git a/setup.conf b/setup.conf index 7c67fd9..b4b0be2 100644 --- a/setup.conf +++ b/setup.conf @@ -71,8 +71,27 @@ rule_3 = @basename # When empty, no --build-arg is passed and no TARGETARCH line appears # in compose.yaml's build.args, so BuildKit's auto-detection stays # intact. Validated via the TUI as `amd64|arm64|`. +# +# network: Docker build-time network mode. Only the user's own +# Dockerfile RUN stages (apk/apt/curl/git clone) care — runtime is +# controlled separately via [network] mode. +# auto → auto-detect Jetson (/etc/nv_tegra_release) and +# emit host-net only there; desktop hosts stay on +# Docker's default bridge. Default. +# host → force host-net for all hosts. Necessary when +# Docker's default bridge NAT is unusable: +# stripped embedded kernels (Jetson L4T missing +# iptable_raw), firewall-locked CI runners, hosts +# with `iptables: false` in daemon.json. +# bridge|none|default → explicit Docker-known network modes. +# off → never emit (explicitly opt out of auto). +# When non-empty (after resolve), setup.sh writes BUILD_NETWORK to +# .env and emits `build.network: ` under each service in +# compose.yaml; build.sh forwards `--network ` to the +# auxiliary `docker build` call for test-tools. [build] target_arch = +network = auto arg_1 = APT_MIRROR_UBUNTU=tw.archive.ubuntu.com arg_2 = APT_MIRROR_DEBIAN=mirror.twds.com.tw arg_3 = TZ=Asia/Taipei @@ -100,10 +119,23 @@ arg_3 = TZ=Asia/Taipei # utility nvidia-smi etc. # graphics OpenGL/Vulkan rendering. # Combine by space: "compute utility graphics" +# +# runtime Docker runtime override (emitted as `runtime:` at +# service level in compose.yaml). Needed on Jetson +# (JetPack) because its nvidia-container-toolkit runs +# in csv mode and refuses the modern `--gpus` flow +# that deploy.resources.reservations.devices uses. +# auto Emit runtime: nvidia on Jetson +# (detected via /etc/nv_tegra_release), +# omit on desktop (default). +# nvidia Force emit runtime: nvidia on all hosts +# (e.g. csv-mode toolkit on x86). +# off Never emit (Docker default runc). [deploy] gpu_mode = auto gpu_count = all gpu_capabilities = gpu compute utility graphics +runtime = auto # ═════════════════════════════════════════════════════════════════════ # [gui] — GUI display support @@ -302,4 +334,4 @@ device_1 = /dev:/dev # mount_5 = /etc/machine-id:/etc/machine-id:ro # dbus integration # mount_6 = ${HOME}/.ssh:/root/.ssh:ro # host ssh keys (use cautiously) [volumes] -mount_1 = ${WS_PATH}:/home/${USER_NAME}/work +mount_1 = diff --git a/setup.sh b/setup.sh deleted file mode 120000 index abee69e..0000000 --- a/setup.sh +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/setup.sh \ No newline at end of file diff --git a/setup_tui.sh b/setup_tui.sh deleted file mode 120000 index 7532403..0000000 --- a/setup_tui.sh +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/setup_tui.sh \ No newline at end of file diff --git a/stop.sh b/stop.sh deleted file mode 120000 index 8dd94d4..0000000 --- a/stop.sh +++ /dev/null @@ -1 +0,0 @@ -template/script/docker/stop.sh \ No newline at end of file diff --git a/template/.github/workflows/build-worker.yaml b/template/.github/workflows/build-worker.yaml deleted file mode 100644 index 0ab42a9..0000000 --- a/template/.github/workflows/build-worker.yaml +++ /dev/null @@ -1,112 +0,0 @@ -name: Docker Build & Smoke Test - -on: - workflow_call: - inputs: - image_name: - required: true - type: string - description: "Container image name (e.g. ros_noetic)" - build_args: - required: false - type: string - default: "" - description: "Multi-line KEY=VALUE build args" - build_runtime: - required: false - type: boolean - default: true - description: "Whether to build runtime stage" - -jobs: - docker-build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Check template version - run: | - LOCAL_VER="" - if [ -f template/.version ]; then - LOCAL_VER=$(cat template/.version | tr -d '[:space:]') - fi - if [ -n "${LOCAL_VER}" ]; then - LATEST_VER=$(git ls-remote --tags --sort=-v:refname \ - https://github.com/ycpss91255-docker/template.git \ - | grep -oP 'refs/tags/v\d+\.\d+\.\d+$' | head -1 | sed 's|refs/tags/||') - if [ -n "${LATEST_VER}" ] && [ "${LOCAL_VER}" != "${LATEST_VER}" ]; then - echo "::warning::template ${LOCAL_VER} → ${LATEST_VER} available" - else - echo "template is up to date (${LOCAL_VER})" - fi - fi - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver: docker - - - name: Generate .env - run: | - cat > .env << EOF - USER_NAME=ci - USER_GROUP=ci - USER_UID=1000 - USER_GID=1000 - HARDWARE=x86_64 - DOCKER_HUB_USER=ci - GPU_ENABLED=false - IMAGE_NAME=${{ inputs.image_name }} - WS_PATH=/tmp/workspace - EOF - mkdir -p /tmp/workspace - - - name: Build test-tools image - run: | - if [ -f template/dockerfile/Dockerfile.test-tools ]; then - docker build -t test-tools:local \ - -f template/dockerfile/Dockerfile.test-tools . - fi - - - name: Build test stage (includes smoke tests) - uses: docker/build-push-action@v6 - with: - context: . - target: test - push: false - build-args: | - USER=ci - GROUP=ci - UID=1000 - GID=1000 - HARDWARE=x86_64 - ${{ inputs.build_args }} - - - name: Build devel stage - uses: docker/build-push-action@v6 - with: - context: . - target: devel - push: false - build-args: | - USER=ci - GROUP=ci - UID=1000 - GID=1000 - HARDWARE=x86_64 - ${{ inputs.build_args }} - - - name: Build runtime stage - if: ${{ inputs.build_runtime }} - uses: docker/build-push-action@v6 - with: - context: . - target: runtime - push: false - build-args: | - USER=ci - GROUP=ci - UID=1000 - GID=1000 - ${{ inputs.build_args }} diff --git a/template/.gitignore b/template/.gitignore deleted file mode 100644 index 51e3633..0000000 --- a/template/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -coverage/ -.env -multi_run/ -*.swp -*.swo -.DS_Store -.vscode/ -.idea/ diff --git a/template/.hadolint.yaml b/template/.hadolint.yaml deleted file mode 100644 index 658c0ef..0000000 --- a/template/.hadolint.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -ignored: - - DL3003 # Use WORKDIR to switch to a directory - - DL3004 # Do not use sudo (we need it for user setup) - - DL3006 # Always tag the version of an image explicitly - - DL3007 # Using latest is prone to errors - - DL3008 # Pin versions in apt get install - - DL3013 # Pin versions in pip install - - DL3018 # Pin versions in apk add - - DL3046 # useradd without flag -l - - DL4006 # Set SHELL option -o pipefail (alpine stages use default shell) - diff --git a/template/.version b/template/.version deleted file mode 100644 index 8031930..0000000 --- a/template/.version +++ /dev/null @@ -1 +0,0 @@ -v0.9.9 diff --git a/template/README.md b/template/README.md deleted file mode 100644 index 4fd2b77..0000000 --- a/template/README.md +++ /dev/null @@ -1,422 +0,0 @@ -# template - -[![Self Test](https://github.com/ycpss91255-docker/template/actions/workflows/self-test.yaml/badge.svg)](https://github.com/ycpss91255-docker/template/actions/workflows/self-test.yaml) -[![codecov](https://codecov.io/gh/ycpss91255-docker/template/branch/main/graph/badge.svg)](https://codecov.io/gh/ycpss91255-docker/template) - -![Language](https://img.shields.io/badge/Language-Bash-blue?style=flat-square) -![Testing](https://img.shields.io/badge/Testing-Bats-orange?style=flat-square) -![ShellCheck](https://img.shields.io/badge/ShellCheck-Compliant-brightgreen?style=flat-square) -![Coverage](https://img.shields.io/badge/Coverage-Kcov-blueviolet?style=flat-square) -[![License](https://img.shields.io/badge/License-GPL--3.0-yellow?style=flat-square)](./LICENSE) - -Shared template for Docker container repos in the [ycpss91255-docker](https://github.com/ycpss91255-docker) organization. - -**[English](README.md)** | **[繁體中文](doc/readme/README.zh-TW.md)** | **[简体中文](doc/readme/README.zh-CN.md)** | **[日本語](doc/readme/README.ja.md)** - ---- - -## Table of Contents - -- [TL;DR](#tldr) -- [Overview](#overview) -- [Quick Start](#quick-start) -- [CI Reusable Workflows](#ci-reusable-workflows) -- [Running Template Tests](#running-template-tests) -- [Tests](#tests) -- [Directory Structure](#directory-structure) - ---- - -## TL;DR - -```bash -# New repo from scratch: init + first commit + subtree + init.sh -mkdir && cd -git init -git commit --allow-empty -m "chore: initial commit" -git subtree add --prefix=template \ - https://github.com/ycpss91255-docker/template.git main --squash -./template/init.sh - -# Upgrade to latest -make upgrade-check # check -make upgrade # pull + update version + workflow tag - -# Run CI -make test # ShellCheck + Bats + Kcov -make help # show all commands -``` - -## Overview - -This repo consolidates shared scripts, tests, and CI workflows used across all Docker container repos. Instead of maintaining identical files in 15+ repos, each repo pulls this template as a **git subtree** and uses symlinks. - -### Architecture - -```mermaid -graph TB - subgraph template["template (shared repo)"] - scripts[".hadolint.yaml / Makefile.ci / compose.yaml"] - smoke["test/smoke/
script_help.bats
display_env.bats"] - config["config/
bashrc / tmux / terminator / pip"] - mgmt["script/docker/
build.sh / run.sh / exec.sh / stop.sh / setup.sh"] - workflows["Reusable Workflows
build-worker.yaml
release-worker.yaml"] - end - - subgraph consumer["Docker Repo (e.g. ros_noetic)"] - symlinks["build.sh → template/script/docker/build.sh
run.sh → template/script/docker/run.sh
exec.sh / stop.sh / .hadolint.yaml"] - dockerfile["Dockerfile
compose.yaml
.env.example
script/entrypoint.sh"] - repo_test["test/smoke/
ros_env.bats (repo-specific)"] - main_yaml["main.yaml
→ calls reusable workflows"] - end - - template -- "git subtree" --> consumer - scripts -. symlink .-> symlinks - smoke -. "Dockerfile COPY" .-> repo_test - workflows -. "@tag reference" .-> main_yaml -``` - -### CI/CD Flow - -```mermaid -flowchart LR - subgraph local["Local"] - build_test["./build.sh test"] - make_test["make test"] - end - - subgraph ci_container["CI Container (kcov/kcov)"] - shellcheck["ShellCheck"] - hadolint["Hadolint"] - bats["Bats smoke tests"] - end - - subgraph github["GitHub Actions"] - build_worker["build-worker.yaml
(from template)"] - release_worker["release-worker.yaml
(from template)"] - end - - build_test --> ci_container - make_test -->|"script/ci/ci.sh"| ci_container - shellcheck --> hadolint --> bats - - push["git push / PR"] --> build_worker - build_worker -->|"docker build test"| ci_container - tag["git tag v*"] --> release_worker - release_worker -->|"tar.gz + zip"| release["GitHub Release"] -``` - -### What's included - -| File | Description | -|------|-------------| -| `build.sh` | Build containers (TTY-aware `--setup` launches `setup_tui.sh`, else runs `setup.sh`) | -| `run.sh` | Run containers (X11/Wayland support; same `--setup` semantics as `build.sh`) | -| `exec.sh` | Exec into running containers | -| `stop.sh` | Stop and remove containers | -| `setup_tui.sh` | Interactive setup.conf editor (dialog / whiptail front-end) | -| `script/docker/setup.sh` | Auto-detect system parameters and generate `.env` + `compose.yaml` | -| `script/docker/_tui_backend.sh` | dialog/whiptail wrapper functions used by `setup_tui.sh` | -| `script/docker/_tui_conf.sh` | INI validators + read/write for `setup_tui.sh` and `setup.sh` writeback | -| `script/docker/_lib.sh` | Shared helpers (`_load_env`, `_compose`, `_compose_project`, ...) | -| `script/docker/i18n.sh` | Shared language detection (`_detect_lang`, `_LANG`) | -| `config/` | Container-internal shell configs (bashrc, tmux, terminator, pip) | -| `setup.conf` | Single per-repo runtime configuration (image / build / deploy / gui / network / volumes) | -| `test/smoke/` | Shared smoke tests + runtime assertion helpers (see below) | -| `test/unit/` | Template self-tests (bats + kcov) | -| `test/integration/` | Level-1 `init.sh` end-to-end tests | -| `.hadolint.yaml` | Shared Hadolint rules | -| `Makefile` | Repo entry (`make build`, `make run`, `make stop`, etc.) | -| `Makefile.ci` | Template CI entry (`make test`, `make -f Makefile.ci lint`, etc.) | -| `init.sh` | First-time symlink setup + new-repo scaffolding | -| `upgrade.sh` | Subtree version upgrade | -| `script/ci/ci.sh` | CI pipeline (local + remote) | -| `dockerfile/Dockerfile.example` | Multi-stage Dockerfile template for new repos | -| `dockerfile/Dockerfile.test-tools` | Pre-built lint/test tools image (shellcheck, hadolint, bats, bats-mock) | -| `.github/workflows/` | Reusable CI workflows (build + release) | - -### Dockerfile stages (convention) - -Downstream repos follow a standard multi-stage layout, defined in -`dockerfile/Dockerfile.example`. All stages share a common base image -parameterized by `ARG BASE_IMAGE`. - -| Stage | Parent | Purpose | Shipped? | -|-------|--------|---------|----------| -| `sys` | `${BASE_IMAGE}` | User/group, sudo, timezone, locale, APT mirror | intermediate | -| `base` | `sys` | Development tools and language packages | intermediate | -| `devel` | `base` | App-specific tools + `entrypoint.sh` + PlotJuggler (env repos) | **yes** (primary artifact) | -| `test` | `devel` | Ephemeral: ShellCheck + Hadolint + Bats smoke (all from `test-tools:local`) | no (discarded) | -| `runtime-base` (optional) | `sys` | Minimal runtime deps (sudo, tini) | intermediate | -| `runtime` (optional) | `runtime-base` | Slim runtime image (application repos only) | yes, when enabled | - -Notes: -- Repos that only ship a developer image (`env/*`) skip `runtime-base` / - `runtime` — the section stays commented in `Dockerfile.example`. -- `test` is always built from `devel`, so runtime assertions inside - `test/smoke/_env.bats` see the same binaries / files a user would - find after `docker run ... :devel`. -- `Dockerfile.test-tools` builds a separate `test-tools:local` image (not - part of the stage chain above) that the `test` stage copies bats / - shellcheck / hadolint binaries from via `COPY --from=test-tools:local`. - -### Smoke test helpers (for downstream repos) - -`test/smoke/test_helper.bash` (loaded by every smoke spec via -`load "${BATS_TEST_DIRNAME}/test_helper"`) ships a small set of runtime -assertion helpers. Downstream repos should prefer these over ad-hoc -`[ -f ... ]` / `command -v` checks so failures produce decorated -diagnostics pointing at the missing artifact. - -| Helper | Usage | -|--------|-------| -| `assert_cmd_installed ` | Fails unless `` is on `PATH` | -| `assert_cmd_runs [flag]` | Fails unless ` ` exits 0 (default flag: `--version`) | -| `assert_file_exists ` | Fails unless `` is a regular file | -| `assert_dir_exists ` | Fails unless `` is a directory | -| `assert_file_owned_by ` | Fails unless ``'s owner is `` | -| `assert_pip_pkg ` | Fails unless `pip show ` returns 0 | - -### What stays in each repo (not shared) - -- `Dockerfile` -- `compose.yaml` -- `.env.example` -- `script/entrypoint.sh` -- `doc/` and `README.md` -- Repo-specific smoke tests - -## Per-repo runtime configuration - -Each downstream repo drives its runtime config — GPU reservation, GUI -env/volumes, network mode, extra volume mounts — through a single -`setup.conf` INI file. `setup.sh` reads it (plus system detection) and -regenerates both `.env` and `compose.yaml`; users never hand-edit those -two derived artifacts. - -### One conf, six sections - -``` -[image] rules = prefix:docker_, suffix:_ws, @default:unknown -[build] apt_mirror_ubuntu, apt_mirror_debian # Dockerfile build args -[deploy] gpu_mode (auto|force|off), gpu_count, gpu_capabilities -[gui] mode (auto|force|off) -[network] mode (host|bridge|none), ipc, privileged -[volumes] mount_1 (workspace, auto-populated on first run) - mount_2..mount_N (extra host mounts; devices via /dev path) -``` - -Template default lives at `template/setup.conf`; per-repo overrides go -at `/setup.conf`. Section-level **replace** strategy: a section -present in the per-repo file fully replaces the template's section; -omitted sections fall back to template. - -On first `setup.sh` run (no per-repo setup.conf yet), the template file -is copied to the repo and the detected workspace is written to -`[volumes] mount_1`. Subsequent runs read `mount_1` as source of truth -— clear it to opt out of mounting a workspace. Edit via: - -```bash -./setup_tui.sh # interactive dialog/whiptail editor -./setup_tui.sh volumes # jump directly to one section -./build.sh --setup # launches setup_tui.sh under TTY; setup.sh otherwise -./template/init.sh --gen-conf # plain copy of template/setup.conf to repo root -``` - -### When setup.sh runs - -`setup.sh` runs only when explicitly triggered — it is not re-run on -every build or launch: - -- **`./template/init.sh`** runs it once after the skeleton lands -- **`./build.sh --setup` / `./run.sh --setup`** (or `-s`) re-runs it on demand -- **First-time bootstrap**: `./build.sh` / `./run.sh` auto-run setup.sh - the very first time (when `.env` is missing, e.g. after a fresh CI - clone) — no manual `--setup` needed - -### Drift detection - -`setup.sh` stores `SETUP_CONF_HASH`, `SETUP_GUI_DETECTED`, and -`SETUP_TIMESTAMP` in `.env`. On every `./build.sh` / `./run.sh`, -stored values are compared against the current setup.conf hash + system -detection; a `[WARNING]` is printed (non-blocking) when any of the -following changed since last setup: - -- `setup.conf` contents (conf hash) -- GPU / GUI detection -- `USER_UID` (user identity change) - -Re-run with `--setup` to regenerate `.env` + `compose.yaml`. - -### Derived artifacts (gitignored) - -- `.env` — runtime variable values + `SETUP_*` drift metadata -- `compose.yaml` — full compose with baseline + conditional blocks - -Open `compose.yaml` anytime to inspect the repo's current effective -configuration. - -## Quick Start - -### Adding to a new repo - -```bash -# 1. Initialize empty repo (skip if you already have one with at least one commit) -mkdir && cd -git init -git commit --allow-empty -m "chore: initial commit" - -# 2. Add subtree -git subtree add --prefix=template \ - https://github.com/ycpss91255-docker/template.git main --squash - -# 3. Initialize symlinks (one command; runs setup.sh under the hood) -./template/init.sh -``` - -> `git subtree add` requires `HEAD` to exist. On a freshly `git init`-ed repo with no commits, it fails with `ambiguous argument 'HEAD'` and `working tree has modifications`. The empty commit creates `HEAD` so subtree can merge into it. - -### Updating - -```bash -# Check if update available -make upgrade-check - -# Upgrade to latest (subtree pull + version file + workflow tag) -make upgrade - -# Or specify a version -./template/upgrade.sh v0.3.0 -``` - -## CI Reusable Workflows - -Repos replace local `build-worker.yaml` / `release-worker.yaml` with calls to this repo's reusable workflows: - -```yaml -# .github/workflows/main.yaml -jobs: - call-docker-build: - uses: ycpss91255-docker/template/.github/workflows/build-worker.yaml@v1 - with: - image_name: ros_noetic - build_args: | - ROS_DISTRO=noetic - ROS_TAG=ros-base - UBUNTU_CODENAME=focal - - call-release: - needs: call-docker-build - if: startsWith(github.ref, 'refs/tags/') - uses: ycpss91255-docker/template/.github/workflows/release-worker.yaml@v1 - with: - archive_name_prefix: ros_noetic -``` - -### build-worker.yaml inputs - -| Input | Type | Required | Default | Description | -|-------|------|----------|---------|-------------| -| `image_name` | string | yes | - | Container image name | -| `build_args` | string | no | `""` | Multi-line KEY=VALUE build args | -| `build_runtime` | boolean | no | `true` | Whether to build runtime stage | - -### release-worker.yaml inputs - -| Input | Type | Required | Default | Description | -|-------|------|----------|---------|-------------| -| `archive_name_prefix` | string | yes | - | Archive name prefix | -| `extra_files` | string | no | `""` | Space-separated extra files | - -## Running Template Tests - -Using `Makefile.ci` (from template root): -```bash -make -f Makefile.ci test # Full CI (ShellCheck + Bats + Kcov) via docker compose -make -f Makefile.ci lint # ShellCheck only -make -f Makefile.ci clean # Remove coverage reports -make help # Show repo targets -make -f Makefile.ci help # Show CI targets -``` - -Or directly: -```bash -./script/ci/ci.sh # Full CI via docker compose -./script/ci/ci.sh --ci # Run inside container (used by compose) -``` - -## Tests - -See [TEST.md](doc/test/TEST.md) for details. - -## Directory Structure - -``` -template/ -├── init.sh # Initialize repo (new or existing) -├── upgrade.sh # Upgrade template subtree version -├── script/ -│ ├── docker/ # Docker operation scripts (symlinked by repos) -│ │ ├── build.sh -│ │ ├── run.sh -│ │ ├── exec.sh -│ │ ├── stop.sh -│ │ ├── setup.sh # .env generator -│ │ ├── _lib.sh # Shared helpers (_load_env, _compose, _compose_project) -│ │ ├── i18n.sh # Shared language detection (_detect_lang, _LANG) -│ │ └── Makefile -│ └── ci/ -│ └── ci.sh # CI pipeline (local + remote) -├── dockerfile/ -│ ├── Dockerfile.test-tools # Pre-built lint/test tools image -│ └── Dockerfile.example # Dockerfile template for new repos (sys → base → devel → test → [runtime]) -├── setup.conf # Single runtime config (per-repo override mirror: /setup.conf) -├── config/ # Container-internal shell/tool configs -│ ├── image_name.conf # Default IMAGE_NAME detection rules -│ ├── pip/ -│ │ ├── setup.sh -│ │ └── requirements.txt -│ └── shell/ -│ ├── bashrc -│ ├── terminator/ -│ │ ├── setup.sh -│ │ └── config -│ └── tmux/ -│ ├── setup.sh -│ └── tmux.conf -├── test/ -│ ├── smoke/ # Shared smoke tests + runtime assertion helpers -│ │ ├── test_helper.bash # → assert_cmd_installed / _runs / file / dir / owned_by / pip_pkg -│ │ ├── script_help.bats -│ │ └── display_env.bats -│ ├── unit/ # Template self-tests (bats + kcov) -│ │ ├── test_helper.bash -│ │ ├── bashrc_spec.bats -│ │ ├── ci_spec.bats # ci.sh _install_deps -│ │ ├── lib_spec.bats # _lib.sh -│ │ ├── pip_setup_spec.bats -│ │ ├── setup_spec.bats -│ │ ├── smoke_helper_spec.bats # Runtime assertion helpers -│ │ ├── template_spec.bats -│ │ ├── terminator_config_spec.bats -│ │ ├── terminator_setup_spec.bats -│ │ ├── tmux_conf_spec.bats -│ │ └── tmux_setup_spec.bats -│ └── integration/ -│ └── init_new_repo_spec.bats # Level-1 init.sh end-to-end -├── Makefile.ci # Template CI entry (make test/lint/...) -├── compose.yaml # Docker CI runner -├── .hadolint.yaml # Shared Hadolint rules -├── codecov.yml -├── .github/workflows/ -│ ├── self-test.yaml # Template CI -│ ├── build-worker.yaml # Reusable build workflow -│ └── release-worker.yaml # Reusable release workflow -├── doc/ -│ ├── readme/ # README translations (zh-TW / zh-CN / ja) -│ ├── test/TEST.md # Test catalog (spec tables) -│ └── changelog/CHANGELOG.md # Release notes -├── .gitignore -├── LICENSE -└── README.md -``` diff --git a/template/doc/changelog/CHANGELOG.md b/template/doc/changelog/CHANGELOG.md deleted file mode 100644 index ebc3e72..0000000 --- a/template/doc/changelog/CHANGELOG.md +++ /dev/null @@ -1,870 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [v0.9.9] - 2026-04-24 - -### Added -- **`[deploy] runtime` setup.conf key** — Docker runtime override at - service level in compose.yaml. Required on Jetson (JetPack) because - its nvidia-container-toolkit runs in csv mode and refuses the modern - `--gpus` flow that `deploy.resources.reservations.devices` uses. - Values: - - `auto` — emit `runtime: nvidia` on Jetson (detected via - `/etc/nv_tegra_release`), omit on desktop (default). - - `nvidia` — force emit on all hosts (e.g. csv-mode toolkit on x86). - - `off` — never emit (Docker default runc). - - `setup.sh` resolves via new `_detect_jetson` + `_resolve_runtime` - helpers; `SETUP_DETECT_JETSON=true|false` env var overrides the - filesystem probe (used by tests). `setup_tui.sh` gains a matching - picker in `[deploy]` section with 4-language i18n; - `_validate_runtime` accepts `auto|nvidia|off` or empty. - -### Changed -- **`_lib.sh` `_print_config_summary` now honours `${_LANG}`**. Previously - the build/run config summary (Files / Identity / Resolved / Customize - sections, plus user / hardware / workspace / GPU enabled / GUI enabled - / network / privileged labels) was hardcoded English regardless of - `--lang` / `SETUP_LANG`. Agent A's v0.9.7 i18n PR explicitly scoped - this out as "too much to bite off"; user feedback after the Jetson - upgrade: `./run.sh --lang zh-TW` still looked English because the - summary is 90% of the output. A new `_lib_msg` translation table - covers `en` / `zh-TW` / `zh-CN` / `ja`. Technical identifiers kept - untranslated: file names (`setup.conf` / `.env` / `compose.yaml`), - INI section names (`[image]` / ...), `.env` variable names (`TZ`, - `APT_MIRROR_*`, `IPC`, `CAPS`), and command strings in the Customize - hint. - -## [v0.9.8] - 2026-04-23 - -### Fixed -- **`upgrade.sh` no longer leaves the repo destroyed if `git subtree - pull` misbehaves**. On Jetson L4T (ships an older `git-subtree.sh`), - running `upgrade.sh v0.9.5 → v0.9.7` fast-forwarded the synthetic - squash commit onto HEAD, moving `template/*` to repo root and - deleting repo-specific files (Dockerfile, compose.yaml, bridge.yaml, - etc.). `upgrade.sh` now: - - **Pre-flight** — fails fast with actionable messages when `git - config user.name / user.email` is unset (a Jetson-specific trigger - for the partial-state bug), or when a merge / rebase / cherry-pick - is in progress (`.git/MERGE_HEAD`, `.git/rebase-merge`, etc.). - - **Post-flight integrity check** — after the subtree pull, verifies - `template/.version`, `template/init.sh`, and - `template/script/docker/setup.sh` still exist. If any is missing, - hard-resets to the pre-pull HEAD and exits with a diagnostic. The - working tree is restored; no manual cleanup required. - - **Step numbering** — corrected from mixed "1/4 / 2/3 / 3/3" to - "1/4 / 2/4 / 3/4 / 4/4". -- **`test/unit/upgrade_spec.bats`** gains 12 regression tests covering - the three new guards + structural invariants (ordering: identity - check before pull, integrity check after pull, HEAD snapshotted for - rollback). -- **`test/integration/upgrade_spec.bats`** (new, 6 tests) drives the - real `upgrade.sh` end-to-end against a fake template remote (bare - repo with `v0.9.5` / `v0.9.7` tags) attached to a sandbox downstream - repo. Covers happy path (version bump, new content, `main.yaml` - `@tag` rewrite), idempotent re-run, `--check`, the two pre-flight - guards, and the destructive-FF rollback (stubs `git-subtree pull` - via `GIT_EXEC_PATH` to simulate the Jetson bug, asserts repo is - restored to pre-pull HEAD). Total: 592 → 610 (+12 unit + 6 - integration). - -## [v0.9.7] - 2026-04-23 - -### Changed -- **Full i18n coverage for `build.sh` / `run.sh` / `exec.sh` / `stop.sh`**. - Previously only `usage()` (help text) honoured `--lang` / `SETUP_LANG`; - runtime log lines (`First run — bootstrapping`, `regenerating .env / - compose.yaml`, `ERROR: setup did not produce .env`, `Container is - already running`, `is not running`, `No instances found`, ...) were - hardcoded English regardless of language. Each script now ships a - local `_msg()` translation table covering `en` / `zh-TW` / `zh-CN` / - `ja`, matching the existing `setup.sh` pattern. English remains the - default when no flag / env var is set, so existing tooling and CI - output are unchanged. - -### Added -- **Root-level `setup.sh` symlink**. `init.sh` now links - `/setup.sh` to `template/script/docker/setup.sh` alongside the - existing `build.sh` / `run.sh` / `exec.sh` / `stop.sh` / `setup_tui.sh` - / `Makefile` symlinks. Consumer repos can now invoke `./setup.sh` - directly for scripted / CI regeneration of `.env` + `compose.yaml`, - instead of relying on the indirect `./build.sh --setup` or - `./setup_tui.sh` Save paths. -- **`setup.sh -h` / `--help`**. `script/docker/setup.sh` gains a - `usage()` block documenting `--base-path` and `--lang`, following the - existing `build.sh` case-per-`_LANG` scaffolding (English-only for - now; future translations plug in via the existing `_msg` framework). -- **`test/unit/exec_sh_spec.bats`** (18 tests) and - **`test/unit/stop_sh_spec.bats`** (17 tests): new unit specs - covering argument parsing, the container-running precheck hints in - `exec.sh`, the `--all` / `--instance` branches in `stop.sh`, all - four languages of usage text, runtime log-line i18n, and the - fallback `_detect_lang` branches (`LANG=zh_TW.UTF-8` etc. when - `template/` is absent). -- Log-line i18n regression tests in `test/unit/build_sh_spec.bats` - (+7) and `test/unit/run_sh_spec.bats` (+6) assert that `--lang - ` actually translates the runtime logs (bootstrap, drift-regen, - err_no_env, already-running), not just `--help`. - -### Fixed -- **`setup.sh` symlink-invocation robustness**. `setup.sh` previously - located its `i18n.sh` / `_tui_conf.sh` siblings and the template - `setup.conf` via `dirname "${BASH_SOURCE[0]}"`, which resolved to the - repo root when the script was invoked through `/setup.sh` - (symlink). `setup.sh` now runs `readlink -f` once at load and stores - the real script directory in `_SETUP_SCRIPT_DIR`; every sibling - source and template-relative path reads from that variable. - -## [v0.9.6] - 2026-04-23 - -### Added -- **`[build] network` setup.conf key**: overrides Docker's build-time - network mode. Empty (default) = Docker decides (bridge + NAT). Set - to `host` when the host's bridge NAT is unusable: stripped embedded - kernels (e.g. Jetson L4T missing `iptable_raw`), hosts with - `"iptables": false` in daemon.json, or firewall-locked CI runners. - `setup.sh` writes `BUILD_NETWORK=` to `.env` and emits - `build.network: ` under each service in `compose.yaml`; - `build.sh` forwards `--network ` to the auxiliary - `docker build` invocation for `test-tools`. `setup_tui.sh` gains a - matching `[build] Build network` menu item and - `_validate_build_network` validator (accepts empty / `host` / - `bridge` / `none` / `default`). -- **Integration test** `fresh_clone_portability_spec.bats` covers the - fresh-clone-on-a-different-machine path end-to-end (real `build.sh` - + `setup.sh`, no mocks): both the stale-absolute-path auto-migrate - and the portable `${WS_PATH}` round-trip. - -### Changed -- **`_dump_conf_section` hides empty-valued keys** in the - `_print_config_summary` output. Lines like `shm_size =` (using the - template default) are noise in the config dump; they're now - filtered. Sections whose every key is empty collapse to nothing and - the section header is skipped too (via the existing - `[[ -z ${_content} ]]` check in the caller). - -## [v0.9.5] - 2026-04-23 - -### Changed -- **`build.sh` / `run.sh` auto-regenerate on drift**. `_check_setup_drift` - now returns non-zero when `setup.conf` / GPU / GUI / USER_UID drifted - from `.env`; the drift branch in `build.sh` / `run.sh` re-runs - `setup.sh` automatically instead of printing a WARNING and continuing - with stale `.env`. `.env` + `compose.yaml` are derived artifacts with - no user-owned data to preserve, so re-running is always safe. Fixes - the footgun where `git pull` + `./build.sh` silently used the - previous machine's `WS_PATH`. Users who preferred the warn-only - behaviour can still edit `.env` freely — drift is only re-triggered - by changes to `setup.conf` or detected hardware, not by editing - `.env` directly. - -## [v0.9.4] - 2026-04-23 - -### Fixed -- **`[volumes] mount_1` portability**: `setup.sh` used to bake the - absolute host workspace path into `setup.conf` on first-time - bootstrap. Committing that file broke fresh clones on any other - machine whose filesystem layout differed — `_load_env` resolved - `WS_PATH` to a directory that doesn't exist and docker tried to - mount it. `setup.sh` now writes `mount_1` in the portable - `${WS_PATH}:/home/${USER_NAME}/work` form so docker-compose resolves - `${WS_PATH}` per-machine from `.env`. When a stale absolute path - (baked from another machine, absent locally) is encountered, - `setup.sh` warns and auto-migrates `mount_1` back to the portable - form. Users who intentionally pin an existing absolute path still - get that value honored. - -## [v0.9.3] - 2026-04-23 - -### BREAKING -- **`template/VERSION` renamed to `template/.version`**. Dotfile keeps - version metadata out of casual `ls`. Clean break — `upgrade.sh` / - `init.sh` / `build-worker.yaml` no longer read `template/VERSION` or - the even older `.template_version`. Downstream repos pick up the - rename automatically via `./template/upgrade.sh `: the - subtree pull drops `template/VERSION` and lands `template/.version`, - and the new `upgrade.sh`/`init.sh` code reads the new location. - Anyone running the old `upgrade.sh` binary against the new tag sees - "unknown" as the local version — cosmetic only, the upgrade still - succeeds. - -### Changed -- **Codecov config consolidated** into `.codecov.yaml`. Historical - duplicate `codecov.yml` removed — Codecov precedence had it silently - overriding `.codecov.yaml` since PR #62, so the strict `ignore:` + - `patch: 100%` rules in `.codecov.yaml` were dead config. `.codecov.yaml` - now carries the relaxed policy from `codecov.yml` (threshold 1%, - patch informational) plus the previously-ignored `test/**` and - `.github/**` ignores. No behavior change for contributors. - -## [v0.9.2] - 2026-04-23 - -### Fixed -- **`build.sh` / `run.sh` bootstrap**: fresh clones (where `compose.yaml` - is gitignored since v0.9.0) now bootstrap correctly. Two regressions - fixed: - 1. Bootstrap condition now also checks `compose.yaml`; previously a - clone with `.env` + `setup.conf` present but `compose.yaml` absent - skipped to the drift-check path and died in `_load_env` with a - cryptic "No such file" error. - 2. Bootstrap path no longer dispatches through `_run_interactive`, - which on a TTY launches `setup_tui.sh`. A user who pressed - Esc / Ctrl+C in the TUI previously ended up with no `.env`. - Bootstrap now calls `setup.sh` directly; TUI stays reserved for - the explicit `--setup` flag. -- **`build.sh` / `run.sh` defensive guard**: if `setup.sh` returns - without producing `.env` (cancelled TUI, setup crash, …), surface a - clear error pointing at `--setup` instead of failing deep in - `_load_env`. - -## [v0.9.1] - 2026-04-23 - -### Changed -- **`upgrade.sh` / `init.sh` default to HTTPS** for the template remote - (`https://github.com/ycpss91255-docker/template.git`). Fresh clones / - CI runners / first-time contributors no longer need an SSH key to - `./template/upgrade.sh`. Override with `TEMPLATE_REMOTE=git@...` env - var for private forks or SSH-agent setups. 4-language READMEs and - `init.sh` docstring updated accordingly. - -## [v0.9.0] - 2026-04-23 - -### Added (Wave 1 + Wave 2 — 2026-04-22) -- **GPU MIG detection** (`_detect_mig` / `_list_gpu_instances` in - `_tui_conf.sh`): when host has NVIDIA MIG mode enabled, the deploy - editor opens with a msgbox listing GPU / MIG instance UUIDs and - advising `NVIDIA_VISIBLE_DEVICES=` via `[environment]` - since `count=N` targets whole GPUs only -- **`[build] tz` key**: container timezone exposed as a setup.conf - value; pipes through to compose.yaml `build.args` as - `TZ: ${TZ:-Asia/Taipei}`. Empty keeps Dockerfile default -- **`[devices] cgroup_rule_*`**: `device_cgroup_rules:` block for USB - hotplug / dynamic device nodes; TUI devices editor now has a - sub-menu to pick between device bindings and cgroup rules. New - `_validate_cgroup_rule` validator - -### Changed -- `[image] rule_*` dedup on write: re-adding a rule that already - exists at another slot moves it to the new position instead of - leaving two identical entries -- `_edit_list_section` add now reuses empty slots (e.g. cleared - `mount_1` after user opted out of workspace), preventing the next - mount from leapfrogging to `mount_2` -- TUI image-rule type picker simplified to function names only - (`prefix` / `suffix` / `@basename` / `@default`); format + example - shown in the value inputbox -- TUI footer buttons (`Save` / `Enter` / `Cancel`) no longer i18n'd; - consistent English across all locales -- `_TUI_LANG_UPPER` initialised at source time so sourcing `setup_tui.sh` - and calling a section editor directly (tests, REPL) no longer - crashes on unbound variable under `set -u` -- **CLI consistency**: `exec.sh` / `stop.sh` now accept `--lang LANG` - (matches `build.sh` / `run.sh`); `stop.sh` gains `-a` short flag - for `--all` (matches common CLI patterns). Unknown lang values - warn and fall back to `en` via `_sanitize_lang` -- **`--gen-image-conf` alias removed** from `init.sh` / `upgrade.sh`; - the `--gen-conf` name is the only spelling. The alias was a - rename-artifact and not documented outside in-tree help -- **`tui.sh` → `setup_tui.sh`**: pairs with `setup.sh` and makes the - "interactive editor for setup.conf" relationship explicit. - `init.sh` now creates `setup_tui.sh` and removes any stale `tui.sh` - symlink left behind by pre-rename installs -- **`_print_config_summary` full dump**: `build.sh` / `run.sh` now - print every populated `setup.conf` section (image / build / deploy / - gui / network / security / resources / environment / tmpfs / - devices / volumes) alongside identity, file paths, and the resolved - GPU/GUI/TZ flags — so users see every value this run consumes - without having to diff `.env` or run `docker compose config` - -### Added -- **`[build] target_arch` TARGETARCH override**: new scalar key - alongside the `arg_N` list. Non-empty value pins Docker's - `TARGETARCH` build arg for both the main image and the test-tools - image (main via compose `build.args`, test-tools via - `build.sh --build-arg`). Empty (default) leaves BuildKit's - auto-detection intact. Valid values: `amd64` / `arm64` / `arm` / - `386` / `ppc64le` / `s390x` / `riscv64`. `setup_tui.sh` → Build - adds a dedicated menu entry; `_validate_target_arch` catches typos - like `aarch64` / `x86_64` (BuildKit uses `arm64` / `amd64`). -- **`Dockerfile.test-tools` multi-arch**: `ARG TARGETARCH=amd64` - branches the ShellCheck + Hadolint download URLs via a `case` - statement. BuildKit auto-fills on amd64 / arm64 hosts; falls back - to amd64 binaries on legacy builders. Rejects unsupported arches - loudly instead of silently grabbing a wrong-arch binary -- **`setup_tui.sh --lang ` surfaces a TUI msgbox** before - the main menu opens. Previously the `_sanitize_lang` stderr warning - scrolled away as soon as dialog/whiptail cleared the screen; the - user saw a silently-English TUI with no hint why. New - `_warn_if_lang_rejected` helper captures the raw input and opens a - "Language fallback" msgbox listing the valid codes - -### Performance -- **`make test` no longer runs kcov** — the dev loop pays for bats + - shellcheck only. `make coverage` keeps the full kcov path for CI - and release checks. `ci.sh --ci` honors `$COVERAGE=1` to include - kcov when the outer `--coverage` flag is set -- **`bats --jobs $(nproc)` parallelism** — GNU parallel runs the - 524-test suite concurrently across files and within files. All - specs already use per-test `mktemp -d` dirs so there's no shared - filesystem state. Combined effect (cached apt): - before ~1m27s (serial + kcov) → now ~42s (parallel, no kcov) ≈ 2x - faster on the dev loop - -### BREAKING -- **Language code `zh` renamed to `zh-TW`** (BCP-47). `--lang zh` - no longer accepted; use `--lang zh-TW` (Taiwan Traditional). - `zh-CN` / `ja` / `en` unchanged -- **`@env_example` image-name rule removed**: legacy rule that read - `IMAGE_NAME` from `.env.example` deleted along with its TUI option - + i18n keys. `.env` is a setup.sh-derived artifact so the rule - created a cycle. Replace with explicit `rule_N = @default:` - or set `IMAGE_NAME` directly - -### Removed (tried, reverted) -- **B7 vim keybindings** (attempted `DIALOGRC bindkey j/k/h/l`): - reverted in `ccc0dbc`. `dialog` 1.3 rejects letter curses_keys — - only symbolic names (`TAB` / `DOWN` / `UP` / `ENTER`) are valid. - See repo-root `TODO.md` for alternative-backend options (gum / - fzf / textual) queued for a future PR - -### Changed (TUI UX 重構 — 2026-04-21 本地) -- **主選單重組**:11 項平鋪 → 5 常用(network / deploy / gui / volumes / - environment)+ `advanced` 子選單(image / build / devices / tmpfs / - security) -- **Save UX**:去掉 `__save` menu item,改用 dialog/whiptail 的 - `--extra-button --extra-label "Save & Exit"`(exit code 3 = save - 訊號,0 = 進選中項,1 = Cancel) -- **List sections 統一 single-layer**:volumes / environment / devices / - tmpfs / ports 點 item 直接 inputbox;**空值 + OK = mark_removed** - (該 key 從 setup.conf 消失);list menu 只保留 Add / Back -- **Conditional triggers**:`shm_size` 不再是主選單項,改為 - `[network] ipc != host` 時從 network 結尾彈出;`ports` 改為 - `mode == bridge` 時從 network 結尾彈出 -- **privileged 遷移**:從 `[network] privileged` 搬到新 `[security] - privileged`。TUI 的 privileged yesno 由 Advanced → Security 編輯 -- **`[security]` 新 section**:privileged / cap_add_* / cap_drop_* / - security_opt_*。先前 compose.yaml 硬編的 SYS_ADMIN / NET_ADMIN / - MKNOD / seccomp:unconfined 改為 setup.conf template 預設值,可由 - TUI 或手編調整 - -### Removed -- **cgroup (`device_cgroup_rules`)**:setup.conf 註解、parser、TUI、 - compose.yaml `device_cgroup_rules:` 產生邏輯全拿掉。使用者手寫 - `cgroup_N = ...` 會被忽略 - -### Added -- **Interactive TUI** (`setup_tui.sh`) for editing `/setup.conf` via - dialog (with whiptail fallback). Main menu + direct-jump subcommands - (`./setup_tui.sh image|build|network|deploy|gui|volumes`). Validates - mount format, GPU count, and enum fields before save. On save, - invokes `setup.sh` automatically to regenerate `.env` + - `compose.yaml`. Symlinked from each repo root via `init.sh`. - 4-language i18n (en / zh / zh-CN / ja). -- **`_tui_backend.sh`** — dialog/whiptail abstraction - (`_tui_menu`, `_tui_radiolist`, `_tui_checklist`, `_tui_inputbox`, - `_tui_yesno`, `_tui_msgbox`). Preferred backend auto-detected; - exits with install hint when neither is installed. -- **`_tui_conf.sh`** — pure-logic INI read/write helpers: - `_load_setup_conf_full` (full file with section order preserved), - `_write_setup_conf` (comment-preserving overwrite), - `_upsert_conf_value` (single-key in-place edit), plus validators - (`_validate_mount`, `_validate_gpu_count`, `_validate_enum`) and - mount-string parsers. -- **`[build]` section** in `setup.conf` for Dockerfile build args - (`apt_mirror_ubuntu`, `apt_mirror_debian`). Empty value keeps the - hard-coded Taiwan mirror defaults. -- **Workspace writeback**: on first run (when `/setup.conf` does - not exist), `setup.sh` detects the workspace host path, copies - `template/setup.conf` to `/setup.conf`, and writes the - detected workspace into `[volumes] mount_1`. Subsequent runs read - `mount_1` as the source of truth. Clearing `mount_1` is treated as - opt-out; the workspace is omitted from `compose.yaml` and `setup.sh` - does not re-populate it. -- `build.sh` / `run.sh` `--setup` / `-s` is now **TTY-aware**: under - an interactive terminal with `setup_tui.sh` available, it launches the - TUI; otherwise it runs `setup.sh` non-interactively (unchanged - behaviour for CI / non-TTY). -- `init.sh _create_symlinks` adds `setup_tui.sh` alongside the existing - five symlinks. -- **Single `setup.conf`** at repo root consolidates all runtime - configuration consumed by `setup.sh`: `[image]`, `[build]`, - `[deploy]`, `[gui]`, `[network]`, `[volumes]`. Template default - lives at `template/setup.conf`; per-repo override at - `/setup.conf` uses section-level replace strategy (a section - present in the per-repo file fully replaces the template's section; - omitted sections fall back to template). -- `setup.sh` new helpers: `_parse_ini_section`, `_load_setup_conf`, - `_get_conf_value`, `_get_conf_list_sorted`, `_resolve_gpu`, - `_resolve_gui`, `detect_gui`, `_compute_conf_hash`, - `_check_setup_drift`, and `generate_compose_yaml`. `setup.sh` now - emits a full `compose.yaml` alongside `.env` with conditional GPU - `deploy` block, conditional GUI env/volumes, and extra volumes from - `[volumes]` section. -- **Drift detection** via `.env` metadata: setup.sh writes - `SETUP_CONF_HASH`, `SETUP_GUI_DETECTED`, `SETUP_TIMESTAMP` into - `.env`; `build.sh` / `run.sh` compare stored values against current - state and warn when `setup.conf` was modified, GPU/GUI detection - changed, or UID changed. Warnings are non-blocking; user re-runs with - `--setup` to regenerate. -- `build.sh` / `run.sh` **`--setup`** (`-s`) flag: forces setup.sh to - regenerate `.env` + `compose.yaml`. Default behaviour: auto-bootstrap - on missing `.env` (first run / CI fresh clone); warn on drift if - `.env` exists. -- `init.sh` new option: `--gen-conf` copies `template/setup.conf` to - `/setup.conf` for per-repo override. `--gen-image-conf` is kept - as a back-compat alias. -- New unit spec `test/unit/compose_gen_spec.bats` (14 tests) covering - `generate_compose_yaml` conditional output. - -### Changed -- **PR #74's `template/config/setup/` directory removed**: the - separate `image_name.conf` / `gpu.conf` / `gui.conf` / `network.conf` - / `volumes.conf` files introduced in #74 are consolidated into a - single `setup.conf` INI. `config/` now strictly contains container - internal configs (bashrc, tmux, pip, terminator); runtime wiring - lives at repo root alongside `Dockerfile`. -- `compose.yaml` is now a **derived artifact** (gitignored) generated - by `setup.sh` on every invocation. Users inspect it for the current - effective runtime config; source of truth is `setup.conf`. -- **BREAKING — setup.conf section rename**: - `[image_name]` → `[image]`; `[gpu]` → `[deploy]` with keys prefixed - (`mode` → `gpu_mode`, `count` → `gpu_count`, - `capabilities` → `gpu_capabilities`). Also introduces `[build]` - (apt mirrors). Template `setup.conf` updated; per-repo overrides - must use the new names. -- `detect_image_name` now reads `[image] rules` (comma-separated - ordered list) from `setup.conf` instead of a dedicated - `image_name.conf` rule file. Rule semantics unchanged - (`prefix:`, `suffix:`, `@env_example`, `@basename`, `@default:`). -- `build.sh` / `run.sh`: removed `--no-env` flag (semantic reversed — - setup.sh no longer runs by default, so the opposite `--setup` flag - was introduced). `exec.sh` / `stop.sh` unchanged (container state is - already frozen when they run). -- `write_env` signature expanded with new columns written to `.env`: - `NETWORK_MODE`, `IPC_MODE`, `PRIVILEGED`, `GPU_COUNT`, - `GPU_CAPABILITIES`, `SETUP_CONF_HASH`, `SETUP_GUI_DETECTED`, - `SETUP_TIMESTAMP`. -- `generate_compose_yaml` baseline: only `${WS_PATH}:/home/${USER_NAME}/work` - is always emitted; `/dev:/dev` now lives in `setup.conf`'s - `[volumes]` template default (user-replaceable). GUI-related - volumes/env are emitted iff `[gui] mode` resolves enabled. -- Version tracking moved from `.template_version` (repo root, manually - maintained) to `template/VERSION` (inside subtree, auto-synced by - `git subtree pull`). `init.sh` and `upgrade.sh` automatically clean up - the legacy `.template_version` file. `build-worker.yaml` reads - `template/VERSION` with `.template_version` fallback for transition. - -### Documentation -- README (4 languages) and `init.sh` header now document the full - bootstrap sequence for a brand-new repo: `git init` + an empty initial - commit must run before `git subtree add`, otherwise subtree fails with - `ambiguous argument 'HEAD'` and `working tree has modifications`. - -### Removed -- `template/config/image_name.conf` (content absorbed into - `template/setup.conf` under `[image_name] rules =`). -- `--no-env` flag on `build.sh` / `run.sh` (replaced by default - no-run-setup + opt-in `--setup`). - -### Fixed -- `test/smoke/test_helper.bash`: `assert_cmd_installed` now returns `1` - after calling `fail`, so callers can short-circuit via `|| return 1` - instead of silently falling through. `assert_cmd_runs` and - `assert_pip_pkg` now short-circuit when the target command is missing, - so they no longer execute `run ` and emit a spurious - Bats BW01 warning. -- `test/unit/lib_spec.bats`, `test/unit/pip_setup_spec.bats`, - `test/unit/setup_spec.bats`: replace `run ` / `assert_failure` - pairs with `run -127 ` on the five tests whose command is - expected to exit 127 (`_load_env` missing arg, `_compose` on empty - PATH, `pip setup.sh` without pip, `setup.sh main --base-path` / - `--lang` missing value). Silences Bats BW01. Files that use `run -N` - flags now declare `bats_require_minimum_version 1.5.0` to silence - BW02. - -## [v0.8.1] - 2026-04-15 - -### Fixed -- `upgrade.sh`: drop the auto-appended `Co-Authored-By: Claude ...` - trailer from the `chore: update template references` commit message. - AI-attribution lines are visual noise for reviewers and the project - convention is to omit them everywhere (PR body, commit message, code). - -## [v0.8.0] - 2026-04-15 - -### Added -- `test/smoke/test_helper.bash`: shared runtime assertion helpers for - downstream-repo smoke specs — `assert_cmd_installed`, `assert_cmd_runs`, - `assert_file_exists`, `assert_dir_exists`, `assert_file_owned_by`, - `assert_pip_pkg`. Each prints a decorated diagnostic on failure so the - bats log points at the exact missing artifact. Keeps downstream smoke - specs terse and self-documenting. -- `init.sh` new-repo skeleton now emits two sample smoke assertions - (`entrypoint.sh is installed and executable`, `bash is available on - PATH`) demonstrating the shared helpers, instead of one bare - `[ -x /entrypoint.sh ]` assertion. -- `test/unit/ci_spec.bats` (5 tests): covers `script/ci/ci.sh` - `_install_deps` — happy path plus the three explicit error branches - for `apt-get update` / `apt-get install` / `git clone bats-mock`. -- `test/unit/smoke_helper_spec.bats` (19 tests): unit coverage for every - runtime assertion helper above, including failure paths. -- `test/unit/setup_spec.bats`: 3 new `detect_ws_path` cases — explicit - ERROR on missing `base_path`, and path-normalization coverage for - strategies 1 and 3 when the input contains `..` segments. -- `test/unit/init_spec.bats` (15 tests): unit coverage for `init.sh` - helpers previously reachable only through the Level-1 integration - test — `_detect_template_version` (git-remote parsing, failure - paths, rc-tag filtering), `_create_version_file` (parameterized - version, `unknown` fallback, overwrite), `_create_new_repo` - (workflow `@ref` threading including empty-ref → `@main` fallback), - and `_create_symlinks` (full symlink set, stale-file replacement, - custom `.hadolint.yaml` preservation). -- `test/unit/ci_spec.bats`: 3 new `_run_shellcheck` tests — wired-file - regression guard, `script/docker/*.sh` discovery via `find`, and - strict-mode propagation on lint failure. - -### Fixed -- `init.sh`: stop hard-coding `v0.5.0` as the fallback version in the - generated `main.yaml`. Workflow refs now fall back to the `main` branch - (a valid git ref) when no tag is detected, instead of an arbitrary old - tag. Version detection is done once up-front and shared between - `.template_version` and the reusable-workflow `@ref`. -- `script/docker/setup.sh` `detect_ws_path`: normalize `base_path` with - `cd ... && pwd -P` before composing sibling/parent paths, so relative - or `..`-laden inputs do not produce surprising matches. Emits a clear - error when the base path does not exist. -- `script/docker/setup.sh`: use `${0:-}` consistently in the - `BASH_SOURCE == $0` guard (line 400) for parity with line 51. -- `script/ci/ci.sh` `_install_deps`: emit explicit error messages when - `apt-get update`, `apt-get install`, or `git clone bats-mock` fails, - instead of relying on `set -e` to exit silently. - -### Changed -- `script/ci/ci.sh`: guard `main "$@"` and `set -euo pipefail` behind a - `BASH_SOURCE == $0` check so the helpers (`_install_deps`, `_die`) can - be sourced by unit tests without executing the CI pipeline. Matches - the pattern already used in `script/docker/setup.sh`. -- `init.sh`: wrap top-level flow in `main()` + `BASH_SOURCE == $0` guard - so helpers (`_detect_template_version`, `_create_version_file`, - `_create_new_repo`, `_create_symlinks`) are sourceable from unit - tests without triggering a full `init.sh` run. Strict mode is also - gated so sourcing respects the caller's settings. Behaviour when - invoked directly is unchanged. - -## [v0.7.2] - 2026-04-14 - -### Changed -- Align `build.sh` / `run.sh` / `exec.sh` / `stop.sh` with Google Shell Style - Guide: wrap top-level logic in a `main()` function with `local` variables, - fix `case` indentation. Behavior unchanged. -- `config/pip/setup.sh`, `config/shell/tmux/setup.sh`, - `config/shell/terminator/setup.sh`: drop `-x` from strict mode - (`set -eux` → `set -euo pipefail`) so docker build logs stay quieter. - Tracing can still be enabled on demand via `bash -x`. -- `script/ci/ci.sh`: refactor kcov `--exclude-path` into a readable array - instead of one long comma-joined string. Behavior unchanged. -- Re-indent all `.bats` files under `test/smoke/`, `test/unit/`, and - `test/integration/` from 4-space to 2-space per Google Shell Style Guide. - Heredoc bodies untouched. Behavior unchanged; all 247 tests still pass. - -## [v0.7.1] - 2026-04-10 - -### Fixed -- `run.sh` foreground devel: `./run.sh` appeared to hang for ~10s after the - user typed `exit` because the cleanup trap ran `compose down` with the - default 10s SIGTERM grace period. Pass `-t 0` so the already-exited - interactive container is killed immediately. - -## [v0.7.0] - 2026-04-09 - -### Added -- `build.sh` / `run.sh` / `exec.sh` / `stop.sh`: `--dry-run` flag prints the - `docker` / `docker compose` commands that would run instead of executing them. - Useful for debugging compose / env / instance resolution without side effects. -- `exec.sh`: precheck refuses with a friendly error pointing at `./run.sh` - (and `--instance NAME` if applicable) when the target container is not running, - instead of letting `compose exec` print the cryptic `service "devel" is not running`. - -### Changed -- Refactor: extracted shared helpers (`_LANG` setup, `_load_env`, `_compute_project_name`, - `_compose`, `_compose_project`) into `template/script/docker/_lib.sh`. `build.sh`, - `run.sh`, `exec.sh`, and `stop.sh` now source `_lib.sh` and call the helpers instead - of duplicating the same i18n / env-loading / compose-flag boilerplate. -- `exec.sh`: passes the user command as a positional array (`"$@"`) to `compose exec`, - so arguments containing whitespace are preserved instead of being word-split. -- `run.sh`: trap is now `trap _devel_cleanup EXIT` (calls a named function) instead of - an inline string-expanded command, matching `build.sh`'s style. - -## [v0.6.8] - 2026-04-09 - -### Added -- `run.sh` / `exec.sh` / `stop.sh`: `--instance NAME` flag for parallel container instances - - `./run.sh --instance dev2` starts a parallel container alongside the default - - `./exec.sh --instance dev2 [cmd]` enters that named instance - - `./stop.sh --instance dev2` stops only that one - - `./stop.sh --all` stops the default + every named instance for this image -- Project name and container name now include `${INSTANCE_SUFFIX}` so each - instance has isolated docker compose project (own network/volumes) -- `init.sh`-generated `compose.yaml` uses - `container_name: ${IMAGE_NAME}${INSTANCE_SUFFIX:-}` - - Default invocation (no `--instance`) keeps the clean name `${IMAGE_NAME}` — - backward-compatible with external tools that grep `docker exec ${IMAGE_NAME}` - -### Changed -- `run.sh`: foreground devel now refuses to start if a container with the - default name is already running. Use `./stop.sh` first or pass - `--instance NAME` to start a parallel one. - -### Note -- Existing 17 consumer repos must update their `compose.yaml` to use - `container_name: ${IMAGE_NAME}${INSTANCE_SUFFIX:-}` (one-line edit) before - `--instance` works there. Default behavior unchanged until they upgrade. - -## [v0.6.7] - 2026-04-09 - -### Added -- `test/integration/init_new_repo_spec.bats`: 21 Level-1 integration tests - - Verifies `init.sh` produces a complete repo skeleton in an empty dir - (Dockerfile, compose.yaml, .env.example, symlinks, doc tree, .github/workflows, etc.) - - Runs inside the existing `make -f Makefile.ci test` container — no Docker needed - - Total tests: 180 → 201 (180 unit + 21 integration) -- `.github/workflows/self-test.yaml`: new `integration-e2e` job (Level 2) - - Runs `init.sh` → `build.sh test` → `build.sh` → `run.sh -d` → `exec.sh` → `stop.sh` - on a synthetic temp repo, on a real GitHub runner with Docker daemon - - `release` job now depends on both `test` and `integration-e2e` -- `script/ci/ci.sh`: now also runs `bats test/integration/` alongside `test/unit/` - -## [v0.6.6] - 2026-04-09 - -### Fixed -- `run.sh`: foreground `devel` mode could not be entered via `./exec.sh` from another terminal - - Symptom: `service "devel" is not running` even though `docker ps` showed it - - Root cause: foreground used `compose run --name`, which creates a one-off container - invisible to `compose exec` (the underlying mechanism behind `./exec.sh`) - - Fix: foreground `devel` now uses `compose up -d` + `compose exec devel bash` - + a `trap … down EXIT` to preserve the original "exit shell = container gone" semantic - - Other targets (`test`, `runtime`, ...) still use `compose run --rm` (one-shot stages - that don't need exec) - - `compose.yaml` `container_name: ${IMAGE_NAME}` is unchanged, so external scripts - that do `docker exec ${IMAGE_NAME}` (e.g. local CI helpers) continue to work - -### Removed -- `stop.sh`: orphan-container cleanup `docker rm -f "${IMAGE_NAME}"` no longer needed - (no more orphan from `compose run --name`) - -## [v0.6.5] - 2026-04-09 - -### Fixed -- `build.sh`/`run.sh`/`exec.sh`/`stop.sh`: graceful fallback when `i18n.sh` is missing - - v0.6.1 added `source template/script/docker/i18n.sh` but consumer Dockerfile - `test` stages do `COPY *.sh /lint/` without the template tree, so the source - failed and broke smoke tests in all consumer repos - - Fix: each script checks for i18n.sh and falls back to inline `_detect_lang` - if missing — no Dockerfile changes required in consumer repos - -## [v0.6.4] - 2026-04-09 - -### Fixed -- `upgrade.sh`: greedy sed pattern clobbered `release-worker.yaml@` reference, - replacing it with `build-worker.yaml@` and breaking release CI in consumer repos - - Root cause: `s|template/\.github/workflows/.*@v[0-9.]*|...build-worker.yaml@...|` - matched both worker references; the dedicated `release-worker` line that follows - only worked when the first sed didn't already overwrite it - - Fix: drop the greedy first sed, keep only the per-worker-name targeted seds - -## [v0.6.3] - 2026-04-09 - -### Added -- `upgrade.sh`: `--gen-image-conf` flag (delegates to `init.sh --gen-image-conf`) - - Lets users copy `image_name.conf` to repo root for per-repo customization - without needing to remember the init.sh path - -## [v0.6.2] - 2026-04-09 - -### Changed -- Remove all `# LCOV_EXCL_*` markers from shell scripts to expose real coverage - - Coverage now reflects actual instrumented lines (95.76% vs prior masked 100%) - - 2 new direct-run tests for `tmux/setup.sh` and `terminator/setup.sh` (171 total) - - Remaining 10 uncovered lines in `setup.sh` are kcov bash backend limitations - (case `;;` arms, `done` redirect close, child-bash guards) - -## [v0.6.1] - 2026-04-08 - -### Added -- `build.sh`: `--clean-tools` flag to remove `test-tools:local` image after build -- `script/docker/i18n.sh`: shared `_detect_lang()` and `_LANG` initialization - - Sourced by build.sh, run.sh, exec.sh, stop.sh, setup.sh - - Eliminates ~28 lines of duplication across 5 scripts - - Adding a new language now requires editing only one file -- `dockerfile/Dockerfile.test-tools`: include `bats-mock` (jasonkarns v1.2.5) - - Other repos' smoke tests can now use `stub`/`unstub` for command mocking - -### Changed -- `build.sh`: keep `test-tools:local` image by default (was removed on EXIT) - - Avoids race conditions in parallel builds - - Subsequent builds skip the test-tools build (Docker layer cache) - - Use `--clean-tools` to restore old behavior - -## [v0.6.0] - 2026-04-01 - -### Added -- `build.sh`: `--no-cache` flag for force rebuild (passes to both - test-tools image build and docker compose build) -- `config/image_name.conf`: rule-driven IMAGE_NAME detection - - Rule types: `prefix:`, `suffix:`, `@env_example`, `@basename`, `@default:` - - Per-repo override: place `image_name.conf` in repo root - - Default rules: `@env_example` → `prefix:docker_` → `suffix:_ws` → `@default:unknown` -- `init.sh --gen-image-conf`: copy template's image_name.conf to repo root - for per-repo customization - -### Changed -- `detect_image_name`: refactored to read rules from `image_name.conf` instead - of hardcoded logic -- **BREAKING**: `image_name.conf` keywords now require `@` prefix - (`env_example` → `@env_example`, `basename` → `@basename`) to distinguish - from user-defined values -- Default conf order: `@env_example` → `prefix:docker_` → `suffix:_ws` → `@default:unknown` - (`.env.example` highest priority; `@default:unknown` as final fallback - prints INFO log so users know to set IMAGE_NAME explicitly) -- New `@default:` keyword: explicit fallback value with INFO log -- WARNING only when no rule matches AND no `@default:` set (custom conf scenario) - -### Fixed -- `stop.sh`: remove orphan container left by `docker compose run --name` - (`docker compose down` only cleans up `up`-mode containers, not `run`-mode) -- `upgrade.sh`: re-run `init.sh` after subtree pull to sync symlinks - (avoids stale symlinks when template directory structure changes) - -### Removed -- Stale comments referencing `get_param.sh` (historical, no longer relevant) - -## [v0.5.0] - 2026-03-31 - -### Added -- `setup.sh`: add `APT_MIRROR_UBUNTU` and `APT_MIRROR_DEBIAN` to `.env` - - Default: `tw.archive.ubuntu.com` (Ubuntu), `mirror.twds.com.tw` (Debian) - - Preserves existing values from `.env` on re-run -- `setup.sh`: warn when `IMAGE_NAME` cannot be detected and `.env.example` not found -- `display_env.bats`: auto-skip GUI tests for headless repos -- `dockerfile/Dockerfile.test-tools`: pre-built test tools image (ShellCheck + Hadolint + Bats) -- `dockerfile/Dockerfile.example`: Dockerfile template for new repos -- `init.sh`: support creating new repo with full project structure -- `build.sh`: auto-build `test-tools:local` before compose build -- 5 new tests (137 total) - -### Changed -- **BREAKING**: Directory restructure - - `build.sh`, `run.sh`, `exec.sh`, `stop.sh`, `Makefile`, `setup.sh` → `script/docker/` - - `ci.sh` → `script/ci/` - - `init.sh`, `upgrade.sh` → template root (user-facing) -- Other repos symlink path: `template/build.sh` → `template/script/docker/build.sh` - -## [v0.4.2] - 2026-03-30 - -### Fixed -- `run.sh`: set `--name "${IMAGE_NAME}"` in foreground mode (`docker compose run`) so container name matches `container_name` in compose.yaml - -### Removed -- `script/migrate.sh`: all repos migrated, no longer needed -- i18n translations for TEST.md and CHANGELOG.md (keep English only) - -## [v0.4.1] - 2026-03-29 - -### Changed -- Rename `test/smoke_test/` → `test/smoke/` -- Fix README.md TOC anchor and add missing Tests section - -## [v0.4.0] - 2026-03-29 - -### Changed -- Move `config/` back to root level (was `script/config/` in v0.3.0) — configs are not scripts -- Fix `self-test.yaml` release archive: remove stale root `setup.sh` reference -- Fix mermaid architecture diagrams: `setup.sh` shown in correct `script/` box -- Add Table of Contents to zh-TW and zh-CN READMEs -- Add `Makefile.ci` entry to "What's included" table (all translations) -- Fix "Running Tests" section to use `make -f Makefile.ci` (all translations) -- Rename `test/smoke_test/` → `test/smoke/` - -## [v0.3.0] - 2026-03-29 - -### Changed -- **BREAKING**: Rename repo `docker_template` → `template` -- **BREAKING**: Move `setup.sh` → `script/setup.sh` -- **BREAKING**: Move `config/` → `script/config/` (reverted in v0.4.0) -- Apply Google Shell Style Guide to all shell scripts -- Split `Makefile` into `Makefile` (repo entry) + `Makefile.ci` (CI entry) -- Fix directory structure, test counts, bashrc style in documentation -- 132 tests (was 124) - -### Migration notes -- Other repos: subtree prefix changes from `docker_template/` to `template/` -- `CONFIG_SRC` path in Dockerfile: `docker_template/config` → `template/config` -- Symlinks: `docker_template/*.sh` → `template/*.sh` - -## [v0.2.0] - 2026-03-28 - -### Added -- `script/ci.sh`: CI pipeline script (local + remote) -- `Makefile`: unified command entry -- Restructured `test/unit/` and `test/smoke_test/` -- Restructured `doc/` with i18n (readme/, test/, changelog/) -- Coverage permissions fix (chown with HOST_UID/HOST_GID) - -### Changed -- `smoke_test/` moved to `test/smoke_test/` (**BREAKING**: Dockerfile COPY path change) -- `compose.yaml` calls `script/ci.sh --ci` instead of inline bash -- `self-test.yaml` calls `script/ci.sh` instead of docker compose directly - -## [v0.1.0] - 2026-03-28 - -### Added -- **Shared shell scripts**: `build.sh`, `run.sh` (with X11/Wayland support), `exec.sh`, `stop.sh` -- **setup.sh**: `.env` generator merged from `docker_setup_helper` (auto-detect UID/GID, GPU, workspace path, image name) -- **Config files**: bashrc, tmux, terminator, pip configs from `docker_setup_helper` -- **Shared smoke tests** (`smoke_test/`): - - `script_help.bats` — 16 tests for script help/usage - - `display_env.bats` — 10 tests for X11/Wayland environment (GUI repos) - - `test_helper.bash` — unified bats loader -- **Template self-tests** (`test/`): 114 tests with ShellCheck + Bats + Kcov coverage -- **CI reusable workflows**: - - `build-worker.yaml` — parameterized Docker build + smoke test - - `release-worker.yaml` — parameterized GitHub Release - - `self-test.yaml` — template's own CI -- **`migrate.sh`**: batch migration script for converting repos from `docker_setup_helper` to `template` -- `.hadolint.yaml`: shared Hadolint rules -- `.codecov.yaml`: coverage configuration -- Documentation: README (English), README.zh-TW.md, README.zh-CN.md, README.ja.md, TEST.md - -### Changed -- `setup.sh` default `_base_path` traverses 1 level up (`/..`) instead of 2 (`/../..`) to match new `template/setup.sh` location - -### Migration notes -- Replace `docker_setup_helper/` subtree with `template/` subtree -- Shell scripts at root become symlinks to `template/` -- Local `build-worker.yaml` / `release-worker.yaml` replaced by reusable workflow calls in `main.yaml` -- Dockerfile `CONFIG_SRC` path: `docker_setup_helper/src/config` → `template/config` -- Shared smoke tests loaded via `COPY template/smoke_test/` in Dockerfile (not symlinks) - -[v0.6.8]: https://github.com/ycpss91255-docker/template/compare/v0.6.7...v0.6.8 -[v0.6.7]: https://github.com/ycpss91255-docker/template/compare/v0.6.6...v0.6.7 -[v0.6.6]: https://github.com/ycpss91255-docker/template/compare/v0.6.5...v0.6.6 -[v0.6.5]: https://github.com/ycpss91255-docker/template/compare/v0.6.4...v0.6.5 -[v0.6.4]: https://github.com/ycpss91255-docker/template/compare/v0.6.3...v0.6.4 -[v0.6.3]: https://github.com/ycpss91255-docker/template/compare/v0.6.2...v0.6.3 -[v0.6.2]: https://github.com/ycpss91255-docker/template/compare/v0.6.1...v0.6.2 -[v0.6.1]: https://github.com/ycpss91255-docker/template/compare/v0.6.0...v0.6.1 -[v0.6.0]: https://github.com/ycpss91255-docker/template/compare/v0.5.0...v0.6.0 -[v0.5.0]: https://github.com/ycpss91255-docker/template/compare/v0.4.2...v0.5.0 -[v0.4.2]: https://github.com/ycpss91255-docker/template/compare/v0.4.1...v0.4.2 -[v0.4.1]: https://github.com/ycpss91255-docker/template/compare/v0.4.0...v0.4.1 -[v0.4.0]: https://github.com/ycpss91255-docker/template/compare/v0.3.0...v0.4.0 -[v0.3.0]: https://github.com/ycpss91255-docker/template/compare/v0.2.0...v0.3.0 -[v0.2.0]: https://github.com/ycpss91255-docker/template/compare/v0.1.0...v0.2.0 -[v0.1.0]: https://github.com/ycpss91255-docker/template/releases/tag/v0.1.0 diff --git a/template/doc/test/TEST.md b/template/doc/test/TEST.md deleted file mode 100644 index ca86eda..0000000 --- a/template/doc/test/TEST.md +++ /dev/null @@ -1,550 +0,0 @@ -# TEST.md - -Template self-tests: **632 tests** total (589 unit + 43 integration). - -## Test Files - -### test/unit/lib_spec.bats (34) - -| Test | Description | -|------|-------------| -| `_lib.sh sets _LANG to 'en' when LANG is unset` | Default language | -| `_lib.sh sets _LANG to 'zh-TW' for zh_TW.UTF-8` | Traditional Chinese | -| `_lib.sh sets _LANG to 'zh-CN' for zh_CN.UTF-8` | Simplified Chinese | -| `_lib.sh sets _LANG to 'zh-CN' for zh_SG (Singapore)` | Singapore variant | -| `_lib.sh sets _LANG to 'ja' for ja_JP.UTF-8` | Japanese | -| `_lib.sh honors SETUP_LANG override` | Env override | -| `_lib.sh is idempotent when sourced twice` | Double-source guard | -| `_load_env exports variables from a .env file` | Env loader works | -| `_load_env errors when no path is given` | Required arg check | -| `_compute_project_name with empty instance produces clean PROJECT_NAME` | Default instance | -| `_compute_project_name with named instance suffixes both` | Named instance | -| `_compute_project_name exports INSTANCE_SUFFIX so child processes see it` | Export propagation | -| `_compose with DRY_RUN=true prints command instead of running` | DRY_RUN path | -| `_compose without DRY_RUN tries to invoke docker compose (sanity)` | Real-call branch | -| `_compose_project pre-fills -p / -f / --env-file from PROJECT_NAME and FILE_PATH` | Project wrapper | -| `_sanitize_lang accepts en / zh-TW / zh-CN / ja unchanged` | Lang validator pass-through | -| `_sanitize_lang warns and falls back to 'en' for unsupported values` | Unknown lang fallback | -| `_sanitize_lang warns for the old bare 'zh' code (post zh→zh-TW rename)` | Legacy lang rejection | -| `_dump_conf_section extracts keys from the named section` | INI section dump | -| `_dump_conf_section stops at the next section header` | Section boundary | -| `_dump_conf_section returns silent empty for missing file` | Missing file | -| `_dump_conf_section returns silent empty for unknown section` | Missing section | -| `_print_config_summary prints files, identity, all populated sections, resolved` | Full config dump | -| `_print_config_summary hides sections that are empty in setup.conf` | Empty-section skip | -| `_print_config_summary warns when setup.conf is missing` | Missing-conf hint | - -### test/unit/setup_spec.bats (105) - -Covers core detection (user/hardware/docker/GPU/GUI), the INI parser -(`_parse_ini_section`), setup.conf section merging (`_load_setup_conf` -with replace strategy), image_name rule engine via `[image] rules`, -resolvers (`_resolve_gpu`, `_resolve_gui`), workspace path detection, -conf hash computation, drift detection, `write_env` (now including -runtime values + SETUP_* metadata), the `main()` CLI, and workspace -writeback (first-time bootstrap / user-edit respect / opt-out). - -| Category | Tests | -|----------|-------| -| `detect_user_info` / `detect_hardware` / `detect_docker_hub_user` / `detect_gpu` / `detect_gui` | 11 | -| `_parse_ini_section` (section isolation, comments, trim, missing) | 6 | -| `_load_setup_conf` (SETUP_CONF env, per-repo, template, replace) | 4 | -| `_get_conf_value` / `_get_conf_list_sorted` (incl. empty-value skip) | 5 | -| `_resolve_gpu` / `_resolve_gui` | 7 | -| `detect_image_name` (template default, per-repo rules, @default, order) | 7 | -| `detect_ws_path` (strategies 1/2/3 + missing base_path) | 5 | -| `_compute_conf_hash` | 2 | -| `write_env` (all fields + SETUP_* metadata) | 1 | -| `_check_setup_drift` (no-op, silent, conf drift, GPU drift) | 4 | -| `main` (unknown arg, --base-path / --lang missing value) | 3 | -| `_msg` / `_detect_lang` i18n | 6 | -| `[build]` apt_mirror (empty fallback, override) | 2 | -| Workspace writeback (first-time, respect user edit, opt-out) | 3 | - -### test/unit/tui_spec.bats (73) - -Pure-logic unit tests for the TUI support libraries (`_tui_conf.sh`). -No dialog/whiptail invocations here — strictly validators, mount-string -parsers, and setup.conf round-trip. - -| Category | Tests | -|----------|-------| -| `_validate_mount` (valid forms, env-var expansion, reject missing/extra colons, invalid mode) | 8 | -| `_validate_gpu_count` ('all', positive int, reject 0/negative/non-numeric/empty) | 6 | -| `_validate_enum` (match, non-match, empty) | 3 | -| `_mount_host_path` (plain, with mode, with env-var host) | 3 | -| `_load_setup_conf_full` + `_write_setup_conf` (section order, kv, comment preservation, untouched keys, round-trip) | 5 | -| `_upsert_conf_value` (updates existing, leaves other sections untouched) | 2 | - -### test/unit/tui_backend_spec.bats (23) - -Backend detection and wrapper-level arg forwarding. Uses a stub -`dialog` / `whiptail` binary installed on PATH that logs argv and echoes -a canned response; exercised with `TUI_STUB_RESPONSE` / `TUI_STUB_EXIT`. - -| Category | Tests | -|----------|-------| -| `_backend_detect` (prefers dialog, falls back to whiptail, prints install hint when neither) | 3 | -| `_tui_guard` (rejects empty backend) | 1 | -| `_tui_inputbox` (forwards title/prompt/initial, returns canned response, propagates non-zero on cancel) | 2 | -| `_tui_menu` (computes item count, forwards tag/label pairs) | 1 | -| `_tui_radiolist` (forwards tag/label/state triples) | 1 | -| `_tui_checklist` (passes `--separate-output`) | 1 | -| `_tui_msgbox` / `_tui_yesno` (correct flags, propagates exit code) | 2 | - -### test/unit/build_sh_spec.bats (32) - -Unit tests for `build.sh` argument handling and control flow. Uses a -sandbox tree mirroring the expected layout (build.sh + `template/` subtree -with real `_lib.sh` / `i18n.sh`, mock `setup.sh`). `docker` is PATH-shimmed -so the stub captures argv; `build.sh` is symlinked (not copied) so kcov -attributes coverage to the real source file. - -Covers: `--help` (en/zh/zh-CN/ja), `--setup`/`-s`, auto-bootstrap on -missing `.env` / `setup.conf` / `compose.yaml`, drift-check path when -all three are present, bootstrap staying non-interactive (setup.sh -direct, not `setup_tui.sh`), defensive guard when setup produces no -`.env`, TARGETARCH build-arg forwarding, `--no-cache`, `--clean-tools`, -positional `TARGET`, `--lang` argument validation, fallback -`_detect_lang` branches (zh_TW/zh_CN/ja), real (non-dry-run) docker -build invocation, and **runtime log-line i18n** (bootstrap / -drift-regen / err_no_env messages translate in all four languages via -the local `_msg()` table; English remains the default). - -### test/unit/run_sh_spec.bats (30) - -Unit tests for `run.sh`. Mirrors the build_sh_spec.bats harness; -`docker ps` reads from a controllable stub file so tests can simulate -"container already running" scenarios. - -Covers: `--help` (en/zh/zh-CN/ja), `--setup`/`-s`, bootstrap on -missing `.env` / `setup.conf` / `compose.yaml`, drift-check path, -bootstrap staying non-interactive (setup.sh, not TUI), defensive guard -when setup produces no `.env`, `--detach`, devel vs non-devel TARGET -routing, `--instance`, already-running guard, Wayland xhost path, -`--lang` / `--instance` argument validation, fallback `_detect_lang` -branches, and **runtime log-line i18n** (bootstrap + already-running -error translate in all four languages via the local `_msg()` table). - -### test/unit/exec_sh_spec.bats (18) - -Unit tests for `exec.sh` argument parsing, the container-running -precheck, and i18n. Sandbox tree mirrors build_sh_spec.bats; -`docker ps` reads from a controllable stub file so tests can toggle -"container running" state without a real docker daemon. `.env` is -pre-seeded so `_load_env` / `_compute_project_name` succeed without a -bootstrap step. - -Covers: `--help` (en/zh/zh-CN/ja), `--lang` / `--target` / `--instance` -value validation, English-default not-running error, Chinese / -Simplified Chinese / Japanese not-running error text, instance-specific -vs default start hints, `--dry-run` bypassing the guard, compose exec -routing when container is running, and fallback `_detect_lang` -branches when `template/` is absent. - -### test/unit/stop_sh_spec.bats (16) - -Unit tests for `stop.sh` argument parsing, the `--all` multi-instance -teardown, and i18n. `docker ps -a` output is PATH-shimmed via -`${DOCKER_PS_A_FILE}` so tests can seed the project list for the `--all` -branch. - -Covers: `--help` (en/zh/zh-CN/ja), `--lang` / `--instance` value -validation, default teardown via `docker compose down`, named-instance -suffix in project name, `--all` no-instances English message, -Chinese / Simplified Chinese / Japanese translations of the -no-instances message, `--all` multi-project teardown loop, and -fallback `_detect_lang` branches. - -### test/unit/compose_gen_spec.bats (38) - -Covers `generate_compose_yaml` conditional output: AUTO-GENERATED -header, baseline workspace volume, network/ipc/privileged env-var -references, `test` service presence, image name threading, and -conditional GPU deploy block + GUI env/volumes + extra volumes from -`[volumes]` section. - -| Test | Description | -|------|-------------| -| `outputs AUTO-GENERATED header` | Header check | -| `always emits workspace volume` | Baseline | -| `emits network_mode/ipc/privileged via env var` | env-var baked | -| `emits test service with profiles: [test]` | test service | -| `image field contains repo name` | Image name | -| `does NOT emit /dev:/dev by default (not in baseline)` | Baseline scope | -| `GPU enabled => deploy block present` | GPU on | -| `GPU disabled => no deploy block` | GPU off | -| `GPU with specific count and capabilities` | GPU args | -| `GUI enabled => DISPLAY env + X11 volumes present` | GUI on | -| `GUI disabled => no DISPLAY env + no X11 volumes` | GUI off | -| `extra volumes appended after baseline` | volumes list | -| `empty extras => no extra mount lines` | empty list | -| `with GUI+GPU+extras => all sections present` | fully loaded | - -### test/unit/template_spec.bats (102) - -| Test | Description | -|------|-------------| -| `build.sh exists and is executable` | File check | -| `run.sh exists and is executable` | File check | -| `exec.sh exists and is executable` | File check | -| `stop.sh exists and is executable` | File check | -| `setup.sh exists and is executable` | File check | -| `ci.sh exists and is executable` | File check | -| `ci.sh uses set -euo pipefail` | Shell convention | -| `Makefile exists (repo entry)` | File check | -| `Makefile has build target` | Makefile target | -| `Makefile.ci exists (template CI)` | File check | -| `Makefile.ci has test target` | Makefile target | -| `Makefile.ci has lint target` | Makefile target | -| `test/smoke/test_helper.bash exists` | Directory structure | -| `test/smoke/script_help.bats exists` | Directory structure | -| `test/smoke/display_env.bats exists` | Directory structure | -| `test/unit/ directory exists` | Directory structure | -| `doc/readme/ directory exists` | Directory structure | -| `doc/test/ directory exists` | Directory structure | -| `doc/changelog/ directory exists` | Directory structure | -| `build.sh references template/script/docker/setup.sh` | Path reference | -| `run.sh references template/script/docker/setup.sh` | Path reference | -| `build.sh uses set -euo pipefail` | Shell convention | -| `build.sh supports --no-cache flag` | Force rebuild flag | -| `build.sh passes --no-cache to docker compose build when set` | NO_CACHE forwarded | -| `build.sh keeps test-tools image by default (cleanup gated by CLEAN_TOOLS)` | Default keep tools | -| `build.sh supports --clean-tools flag` | Clean tools flag | -| `build.sh removes test-tools image when --clean-tools is set` | CLEAN_TOOLS forwarded | -| `run.sh uses set -euo pipefail` | Shell convention | -| `exec.sh uses set -euo pipefail` | Shell convention | -| `stop.sh uses set -euo pipefail` | Shell convention | -| `_lib.sh derives PROJECT_NAME from DOCKER_HUB_USER and IMAGE_NAME` | Shared derivation | -| `_lib.sh _compose_project wraps -p with PROJECT_NAME` | Shared compose wrapper | -| `_lib.sh defines _load_env helper` | Shared env loader | -| `_lib.sh defines _compute_project_name helper` | Shared helper | -| `_lib.sh defines _compose wrapper` | Shared compose wrapper | -| `build.sh routes compose call through _compose_project` | Uses shared lib | -| `run.sh routes compose calls through _compose_project` | Uses shared lib | -| `exec.sh routes compose call through _compose_project` | Uses shared lib | -| `stop.sh routes compose call through _compose_project` | Uses shared lib | -| `exec.sh loads .env via _load_env helper` | Uses shared lib | -| `stop.sh loads .env via _load_env helper` | Uses shared lib | -| `stop.sh no longer needs orphan cleanup (run.sh devel uses up not run)` | No more orphan | -| `run.sh devel target uses compose up -d (not compose run --name)` | up + exec model | -| `run.sh devel branch uses compose exec to enter shell` | up + exec model | -| `run.sh devel branch installs trap to auto-down on exit` | Auto cleanup | -| `run.sh _devel_cleanup uses short timeout to avoid 10s grace period` | Fast exit | -| `run.sh non-devel TARGET still uses compose run --rm` | One-shot stages | -| `run.sh devel branch does not use 'compose run --name'` | Old pattern gone | -| `run.sh supports --instance flag` | --instance | -| `exec.sh supports --instance flag` | --instance | -| `stop.sh supports --instance flag` | --instance | -| `stop.sh supports --all flag` | --all | -| `run.sh exports INSTANCE_SUFFIX env var to compose` | env passing | -| `exec.sh exports INSTANCE_SUFFIX env var to compose` | env passing | -| `stop.sh exports INSTANCE_SUFFIX env var to compose` | env passing | -| `run.sh refuses when default container already running and no --instance` | collision | -| `init.sh-generated compose.yaml uses parameterized container_name` | template gen | -| `run.sh -h shows --instance in help` | help text | -| `exec.sh -h shows --instance in help` | help text | -| `stop.sh -h shows --instance in help` | help text | -| `build.sh supports --dry-run flag` | --dry-run | -| `run.sh supports --dry-run flag` | --dry-run | -| `exec.sh supports --dry-run flag` | --dry-run | -| `stop.sh supports --dry-run flag` | --dry-run | -| `build.sh -h shows --dry-run in help` | --dry-run help | -| `run.sh -h shows --dry-run in help` | --dry-run help | -| `exec.sh -h shows --dry-run in help` | --dry-run help | -| `stop.sh -h shows --dry-run in help` | --dry-run help | -| `exec.sh checks container is running before exec` | precheck | -| `exec.sh precheck error mentions run.sh hint` | friendly hint | -| `exec.sh exits non-zero with friendly hint when container not running` | precheck e2e | -| `exec.sh --dry-run skips precheck and prints compose command` | dry-run e2e | -| `script/docker/i18n.sh exists` | i18n module exists | -| `Dockerfile.test-tools includes bats-mock` | bats-mock available in test image | -| `i18n.sh defines _detect_lang function` | _detect_lang in i18n.sh | -| `build.sh sources _lib.sh` | build.sh uses shared lib | -| `run.sh sources _lib.sh` | run.sh uses shared lib | -| `exec.sh sources _lib.sh` | exec.sh uses shared lib | -| `stop.sh sources _lib.sh` | stop.sh uses shared lib | -| `_lib.sh sources i18n.sh (delegates language detection)` | _lib delegates i18n | -| `setup.sh sources i18n.sh` | setup.sh uses shared i18n | -| `build.sh -h works when i18n.sh is missing (consumer Dockerfile /lint scenario)` | i18n fallback | -| `run.sh -h works when i18n.sh is missing` | i18n fallback | -| `exec.sh -h works when i18n.sh is missing` | i18n fallback | -| `stop.sh -h works when i18n.sh is missing` | i18n fallback | -| `setup.sh does not redefine _detect_lang` | No duplication | -| `VERSION file exists in template root` | Version file check | -| `upgrade.sh reads version from template/VERSION` | VERSION path | -| `upgrade.sh does not write .template_version` | No legacy write | -| `upgrade.sh runs init.sh after subtree pull` | Sync symlinks | -| `upgrade.sh cleans up legacy .template_version` | Legacy cleanup | -| `upgrade.sh supports --gen-conf flag` | Flag exists | -| `upgrade.sh --gen-conf delegates to init.sh --gen-conf` | Delegation | -| `upgrade.sh --help mentions --gen-conf` | Help text | -| `upgrade.sh updates main.yaml @tag without clobbering release-worker.yaml` | sed regression | -| `run.sh contains XDG_SESSION_TYPE check` | X11/Wayland branch | -| `run.sh contains xhost +SI:localuser for wayland` | Wayland xhost | -| `run.sh contains xhost +local: for X11` | X11 xhost | -| `setup.sh default _base_path uses /..` | Path resolution | -| `setup.sh default _base_path uses double parent traversal` | Repo root traversal | - -### test/unit/bashrc_spec.bats (14) - -| Test | Description | -|------|-------------| -| `defines alias_func` | Function definition | -| `defines swc` | Function definition | -| `defines color_git_branch` | Function definition | -| `defines ros_complete` | Function definition | -| `defines ros_source` | Function definition | -| `defines ebc alias` | Alias definition | -| `defines sbc alias` | Alias definition | -| `alias_func is called` | Function call | -| `color_git_branch is called` | Function call | -| `ros_complete is called` | Function call | -| `ros_source is called` | Function call | -| `swc searches for catkin devel/setup.bash` | Catkin search | -| `ros_source references ROS_DISTRO` | ROS env var | -| `color_git_branch sets PS1` | PS1 setting | - -### test/unit/pip_setup_spec.bats (3) - -| Test | Description | -|------|-------------| -| `pip setup.sh runs pip install with requirements.txt` | pip install | -| `pip setup.sh sets PIP_BREAK_SYSTEM_PACKAGES=1` | Break system packages | -| `pip setup.sh fails when pip is not available` | Missing pip error | - -### test/unit/ci_spec.bats (8) - -| Test | Description | -|------|-------------| -| `_install_deps: skips apt-get and git when bats is already installed` | No-op fast path | -| `_install_deps: dies with clear error when apt-get update fails` | Explicit `apt-get update` error | -| `_install_deps: dies with clear error when apt-get install fails` | Explicit `apt-get install` error | -| `_install_deps: dies with clear error when git clone bats-mock fails` | Explicit `git clone` error | -| `_install_deps: happy path succeeds when bats absent and all deps install cleanly` | Full install path | -| `_run_shellcheck: invokes shellcheck against every expected script` | Wired-file regression guard | -| `_run_shellcheck: picks up every .sh file in script/docker/` | `find` covers new scripts | -| `_run_shellcheck: exits non-zero when shellcheck fails on any script` | Strict-mode propagation | - -### test/unit/init_spec.bats (13) - -Unit coverage for `init.sh` helpers that previous rounds exercised only -through the Level-1 integration test. Complements -`test/integration/init_new_repo_spec.bats` by locking edge cases that -are hard to trigger from a real `bash template/init.sh` invocation -(network-down version detection, main.yaml `@ref` fallback, -`_create_version_file` with no argument). - -| Test | Description | -|------|-------------| -| `_detect_template_version: parses newest vX.Y.Z tag from git ls-remote` | Happy path + head -1 | -| `_detect_template_version: returns empty when git ls-remote fails` | Network-down fallback | -| `_detect_template_version: returns empty when no v*.*.* tags exist` | Nothing to match | -| `_detect_template_version: ignores non-semver tags (e.g. rc suffixes)` | Regex filters rc / pre-release | -| `_detect_template_version: reads VERSION file when present (no network)` | VERSION file priority | -| `_detect_template_version: VERSION file takes priority over git ls-remote` | Local-first resolution | -| `init.sh removes legacy .template_version when present` | Legacy cleanup | -| `init.sh succeeds when no legacy .template_version exists` | Clean state | -| `_create_new_repo: main.yaml uses given ref in workflow @ref` | Ref threading | -| `_create_new_repo: main.yaml falls back to @main when ref arg omitted` | Default ref | -| `_create_new_repo: main.yaml falls back to @main when ref arg is empty` | Empty-string → `@main` | -| `_create_new_repo: generates .env.example with IMAGE_NAME=` | Fallback image name | -| `_create_symlinks: produces all five docker-script symlinks` | Symlink set | -| `_create_symlinks: replaces a stale file at the symlink path` | Re-init over existing files | -| `_create_symlinks: keeps custom .hadolint.yaml when it differs` | Custom-hadolint preservation | - -### test/unit/smoke_helper_spec.bats (19) - -Exercises the runtime assertion helpers shipped in -`test/smoke/test_helper.bash` (used by downstream-repo smoke specs via -`load "${BATS_TEST_DIRNAME}/test_helper"`). - -| Test | Description | -|------|-------------| -| `assert_cmd_installed passes when cmd is on PATH` | Happy path | -| `assert_cmd_installed fails with descriptive message when cmd missing` | Missing cmd | -| `assert_cmd_installed errors when cmd arg missing` | Required arg check | -| `assert_cmd_runs passes when cmd exits 0` | Happy path | -| `assert_cmd_runs uses custom version flag when given` | Custom flag | -| `assert_cmd_runs fails when cmd exits non-zero` | Broken binary | -| `assert_cmd_runs fails when cmd is not installed` | Missing cmd | -| `assert_file_exists passes when file is a regular file` | Happy path | -| `assert_file_exists fails when path is missing` | Missing path | -| `assert_file_exists fails when path is a directory` | Type check | -| `assert_dir_exists passes when path is a directory` | Happy path | -| `assert_dir_exists fails when path is missing` | Missing path | -| `assert_dir_exists fails when path is a file` | Type check | -| `assert_file_owned_by passes when owner matches` | Happy path | -| `assert_file_owned_by fails with owner diff when user mismatches` | Owner mismatch | -| `assert_file_owned_by fails when path missing` | Missing path | -| `assert_pip_pkg passes when pip show returns 0` | Package installed | -| `assert_pip_pkg fails when pip show returns non-zero` | Package missing | -| `assert_pip_pkg fails when pip is not installed` | pip itself missing | - -### test/unit/terminator_config_spec.bats (10) - -| Test | Description | -|------|-------------| -| `has [global_config] section` | Config section | -| `has [keybindings] section` | Config section | -| `has [profiles] section` | Config section | -| `has [layouts] section` | Config section | -| `has [plugins] section` | Config section | -| `profiles has [[default]]` | Default profile | -| `default profile disables system font` | Font setting | -| `default profile has infinite scrollback` | Scrollback setting | -| `layouts has Window type` | Window layout | -| `layouts has Terminal type` | Terminal layout | - -### test/unit/terminator_setup_spec.bats (8) - -| Test | Description | -|------|-------------| -| `check_deps returns 0 when terminator is installed` | Dependency check | -| `check_deps fails when terminator is not installed` | Missing dep | -| `_entry_point calls main when deps pass` | Entry point | -| `_entry_point fails when deps missing` | Entry point fail | -| `main creates terminator config directory` | Config dir | -| `main copies terminator config file` | Config copy | -| `main calls chown with correct user and group` | Permissions | -| `script runs entry_point when executed directly` | Direct-run guard | - -### test/unit/tmux_conf_spec.bats (12) - -| Test | Description | -|------|-------------| -| `defines prefix key` | tmux prefix | -| `sets default shell to bash` | Shell setting | -| `sets default terminal` | Terminal setting | -| `enables mouse support` | Mouse | -| `enables vi status-keys` | vi mode | -| `enables vi mode-keys` | vi mode | -| `defines split-window bindings` | Split bindings | -| `defines reload config binding` | Reload binding | -| `enables status bar` | Status bar | -| `sets status bar position` | Status bar position | -| `declares tpm plugin` | tpm plugin | -| `initializes tpm at end of file` | tpm init | - -### test/unit/tmux_setup_spec.bats (9) - -| Test | Description | -|------|-------------| -| `check_deps returns 0 when tmux and git are installed` | Dependency check | -| `check_deps fails when tmux is not installed` | Missing tmux | -| `check_deps fails when git is not installed` | Missing git | -| `_entry_point calls main when deps pass` | Entry point | -| `_entry_point fails when deps missing` | Entry point fail | -| `main clones tpm repository` | tpm clone | -| `main creates tmux config directory` | Config dir | -| `main copies tmux.conf to config directory` | Config copy | -| `script runs entry_point when executed directly` | Direct-run guard | - -### test/unit/upgrade_spec.bats (20) - -Unit tests for `upgrade.sh` helpers. Uses the sed-range pattern to extract -one function at a time into a minimal harness (with `_log` / `_error` -stubs), so each helper runs in a sandboxed git repo without needing to -source the full `upgrade.sh` (which would trigger its top-level -`cd REPO_ROOT`). - -Covers: `_warn_config_drift` (silent / fires on drift / diff hint), -the three safety guards added after the v0.9.7 Jetson incident -(`_require_git_identity`, `_require_clean_merge_state`, -`_verify_subtree_intact` with rollback), and structural invariants that -pin call-ordering in `_upgrade` (identity check runs before subtree -pull, integrity verification runs after, pre-pull HEAD is snapshotted -for rollback). - -| Test | Description | -|------|-------------| -| `_warn_config_drift silent when no template/config in HEAD` | Initial setup | -| `_warn_config_drift silent when pre and post hashes match` | No drift | -| `_warn_config_drift prints WARNING + diff hint when hashes differ` | Drift reported | -| `upgrade.sh defines _warn_config_drift` | Helper present | -| `upgrade.sh invokes _warn_config_drift after subtree pull` | Call site present | -| `upgrade.sh captures pre-pull template/config tree hash` | Snapshot taken | -| `_require_git_identity succeeds when name + email are set` | Happy path | -| `_require_git_identity fails when user.email is unset` | Email guard | -| `_require_git_identity fails when user.name is unset` | Name guard | -| `_require_clean_merge_state succeeds in clean repo` | Happy path | -| `_require_clean_merge_state fails when MERGE_HEAD exists` | Mid-merge guard | -| `_require_clean_merge_state fails when rebase-merge dir exists` | Mid-rebase guard | -| `_verify_subtree_intact succeeds when all markers present` | Happy path | -| `_verify_subtree_intact rolls back when template/.version is missing` | Destructive-FF rollback | -| `_verify_subtree_intact rolls back when template/script/docker/setup.sh is missing` | Marker rollback | -| `upgrade.sh calls _require_git_identity before subtree pull` | Pre-flight ordering | -| `upgrade.sh calls _verify_subtree_intact after subtree pull` | Post-flight ordering | -| `upgrade.sh snapshots pre-pull HEAD for rollback` | Rollback anchor | - -### test/integration/init_new_repo_spec.bats (35) - -End-to-end verification that `init.sh` produces a complete repo skeleton in -an empty directory. **Level 1** (file generation only, no Docker). The -**Level 2** equivalent (real `build.sh` / `run.sh` / `exec.sh` / `stop.sh`) -runs as the `integration-e2e` job in `.github/workflows/self-test.yaml`, -which has access to a Docker daemon on the host runner. - -| Test | Description | -|------|-------------| -| `init.sh detects empty dir and creates new repo skeleton` | Smoke | -| `new repo: Dockerfile is copied from template` | Dockerfile gen | -| `new repo: compose.yaml exists and references the repo name` | compose gen | -| `new repo: .env.example contains IMAGE_NAME=` | env fallback | -| `new repo: script/entrypoint.sh exists and is executable` | entrypoint gen | -| `new repo: smoke test skeleton exists for the repo` | smoke skeleton | -| `new repo: .github/workflows/main.yaml exists with reusable workflow ref` | CI gen | -| `new repo: .gitignore exists` | gitignore | -| `new repo: doc/ tree exists with README translations` | i18n docs | -| `new repo: doc/test/TEST.md exists` | TEST.md gen | -| `new repo: doc/changelog/CHANGELOG.md exists` | CHANGELOG gen | -| `new repo: build.sh symlink → template/script/docker/build.sh` | symlink target | -| `new repo: run.sh / exec.sh / stop.sh / Makefile symlinks correct` | symlink set | -| `new repo: template/VERSION exists (no legacy .template_version)` | version file | -| `new repo: re-running init.sh on the result is idempotent` | idempotent | -| `new repo: init.sh creates setup_tui.sh symlink (not legacy tui.sh)` | post-rename symlink | -| `new repo: init.sh removes stale tui.sh symlink from earlier versions` | upgrade cleanup | -| `new repo: build.sh -h works against the generated symlink` | smoke build.sh | -| `new repo: run.sh -h works against the generated symlink` | smoke run.sh | -| `new repo: exec.sh -h works against the generated symlink` | smoke exec.sh | -| `new repo: stop.sh -h works against the generated symlink` | smoke stop.sh | -| `init.sh --gen-conf copies setup.conf to repo root` | setup.conf gen | -| `init.sh --gen-conf refuses to overwrite existing setup.conf` | overwrite safety | -| `new repo: .gitignore contains compose.yaml (derived artifact)` | gitignore compose.yaml | -| `new repo: .gitignore contains .env (derived artifact)` | gitignore .env | -| `new repo: compose.yaml has AUTO-GENERATED header (produced by setup.sh)` | setup.sh generated compose.yaml | -| `new repo: per-repo setup.conf not created by default` | template default usage | - -### test/integration/fresh_clone_portability_spec.bats (2) - -End-to-end verification for the fresh-clone-on-a-different-machine scenario: -the consumer repo's `setup.conf` has already been committed by another -contributor and carries either a stale absolute `mount_1` path (the Jetson -bug) or the portable `${WS_PATH}` form. Runs the real `build.sh` + -`setup.sh` (no mocks) and asserts the auto-migration / per-machine detection -pipeline lands a valid `.env` + `compose.yaml`. **Level 1** (no Docker -invocation — `build.sh --dry-run`). - -| Test | Description | -|------|-------------| -| `fresh clone with stale absolute mount_1: build.sh auto-migrates + generates local .env` | Stale-path auto-migrate | -| `fresh clone with portable ${WS_PATH} mount_1: no warning, .env gets local path` | Happy path round-trip | - -### test/integration/upgrade_spec.bats (6) - -End-to-end verification for `upgrade.sh` driving a real subtree update -against a fake template remote (bare repo with `v0.9.5` / `v0.9.7` tags -on a minimal subtree layout) attached to a sandbox downstream repo. -**Level 1** (no Docker). Exercises the happy path, the pre-flight -guards, and — most importantly — the destructive-FF rollback path added -after the Jetson v0.9.7 incident (stubs `git-subtree pull` via -`GIT_EXEC_PATH` to simulate the bug and asserts the repo is restored). - -| Test | Description | -|------|-------------| -| `upgrade.sh v0.9.7: bumps template/.version, pulls new content, updates main.yaml` | Happy path | -| `upgrade.sh v0.9.7 is idempotent on a second run` | Re-run is no-op | -| `upgrade.sh --check reports update available from v0.9.5 → v0.9.7` | --check flag | -| `upgrade.sh fails fast when git identity is missing` | Pre-flight identity guard | -| `upgrade.sh fails fast when MERGE_HEAD is present` | Pre-flight merge-state guard | -| `upgrade.sh rolls back when git-subtree does a destructive fast-forward` | Destructive-FF rollback | diff --git a/template/setup.conf b/template/setup.conf deleted file mode 100644 index 9006b70..0000000 --- a/template/setup.conf +++ /dev/null @@ -1,334 +0,0 @@ -# setup.conf - runtime configuration consumed by setup.sh. -# -# setup.sh reads this file + system detection → generates .env + compose.yaml. -# Template default lives at