From 9cfec4cfdf17d971a2387d91a376ec19ea5c5ae7 Mon Sep 17 00:00:00 2001 From: temeteke Date: Sat, 17 Oct 2026 14:47:12 +0900 Subject: [PATCH 1/5] feat: add tmux and Vim dev container features with read-only host config --- .github/workflows/validate.yaml | 13 ++++++++++ README.md | 19 ++++++++++++++- src/tmux/README.md | 37 ++++++++++++++++++++++++++++ src/tmux/devcontainer-feature.json | 15 ++++++++++++ src/tmux/install.sh | 15 ++++++++++++ src/tmux/setup-persistence.sh | 34 ++++++++++++++++++++++++++ src/vim/README.md | 37 ++++++++++++++++++++++++++++ src/vim/devcontainer-feature.json | 15 ++++++++++++ src/vim/install.sh | 15 ++++++++++++ src/vim/setup-persistence.sh | 34 ++++++++++++++++++++++++++ test/persistence.sh | 39 ++++++++++++++++++++++++++++++ test/validate.sh | 2 ++ 12 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/validate.yaml create mode 100644 src/tmux/README.md create mode 100644 src/tmux/devcontainer-feature.json create mode 100755 src/tmux/install.sh create mode 100755 src/tmux/setup-persistence.sh create mode 100644 src/vim/README.md create mode 100644 src/vim/devcontainer-feature.json create mode 100755 src/vim/install.sh create mode 100755 src/vim/setup-persistence.sh create mode 100755 test/persistence.sh mode change 100644 => 100755 test/validate.sh diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 0000000..e802ece --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,13 @@ +name: Validate Dev Container Features + +on: + pull_request: + workflow_dispatch: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate metadata, shell scripts and configuration links + run: bash test/validate.sh diff --git a/README.md b/README.md index 25aa7d1..23d15f1 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ agent-specific repository. It provides the following features: - Roo Code - Playwright Agent CLI - GitLab CLI +- tmux +- Vim **Claude Code** has an [official Dev Container Feature](https://github.com/anthropics/devcontainer-features). This repository provides a companion Feature that adds persistence on top of the official installation. @@ -35,7 +37,9 @@ This keeps the setting out of each project's `.devcontainer/devcontainer.json`. "ghcr.io/temeteke/devcontainer-features/cline:0": {}, "ghcr.io/temeteke/devcontainer-features/roo-code:0": {}, "ghcr.io/temeteke/devcontainer-features/playwright:0": {}, - "ghcr.io/temeteke/devcontainer-features/gitlab-cli:0": {} + "ghcr.io/temeteke/devcontainer-features/gitlab-cli:0": {}, + "ghcr.io/temeteke/devcontainer-features/tmux:0": {}, + "ghcr.io/temeteke/devcontainer-features/vim:0": {} } } ``` @@ -87,6 +91,8 @@ other sensitive data. Since they are stored directly in your host home directory | `playwright` | AI agent tool: Playwright Agent CLI and browser runtime only | none | | `gitlab-cli` | Coding tool: `glab` CLI from GitLab releases | `~/.config/glab-cli` | | `agents` | Shared feature for agent skills/state only | `~/.agents` | +| `tmux` | `tmux` via apt (Debian/Ubuntu) | read-only `~/.config/tmux` | +| `vim` | `vim` via apt (Debian/Ubuntu) | read-only `~/.vim` | ## Publishing @@ -99,3 +105,14 @@ fork the repository. This repository is an unofficial collection of Dev Container Features. It is not affiliated with, endorsed by, or maintained by OpenAI, Anthropic, Roo Code, Cline, OpenCode, or GitLab. + +## Terminal Features + +The optional `tmux` and `vim` Features share host settings read-only through +the standard directory-based configurations. Before creating a container, run +`mkdir -p ~/.config/tmux ~/.vim` on the host. If you use `~/.tmux.conf` or +`~/.vimrc`, migrate those files to `~/.config/tmux/tmux.conf` and +`~/.vim/vimrc` respectively. These legacy dotfiles are **not** mounted. +The features do not mount the host HOME directory. See +[`src/tmux/README.md`](src/tmux/README.md) and +[`src/vim/README.md`](src/vim/README.md). diff --git a/src/tmux/README.md b/src/tmux/README.md new file mode 100644 index 0000000..eee52f8 --- /dev/null +++ b/src/tmux/README.md @@ -0,0 +1,37 @@ +# tmux Feature + +Installs tmux on Debian/Ubuntu and shares the **host** `~/.config/tmux` directory +with the container **read-only**. The container's `~/.config/tmux` is a symlink +to that bind mount. tmux reads `~/.config/tmux/tmux.conf` natively. + +## One-time host setup + +Run this on the host **before** creating the dev container (Docker bind mounts +require their source directory to exist): + +```sh +mkdir -p ~/.config/tmux +``` + +If you currently use `~/.tmux.conf`, move it to +`~/.config/tmux/tmux.conf` after checking the destination does not already +exist. The legacy dotfile itself is deliberately *not* bind-mounted: a bind mount +of a missing host file can fail, and mounting the host HOME would expose unrelated +data. If your configuration references plugins in `~/.tmux`, relocate or configure +those references under `~/.config/tmux` as well. + +## Usage + +```json +{ + "features": { + "ghcr.io/temeteke/devcontainer-features/tmux:0": {} + } +} +``` + +Only configuration is shared: tmux servers, sockets and sessions are not +persisted when the container is removed. Host configuration is read-only from +inside the container; plugins that need to update themselves must be managed on +the host. If the image already has a nonempty `~/.config/tmux`, setup fails +rather than destroying that container-side data. diff --git a/src/tmux/devcontainer-feature.json b/src/tmux/devcontainer-feature.json new file mode 100644 index 0000000..c640e59 --- /dev/null +++ b/src/tmux/devcontainer-feature.json @@ -0,0 +1,15 @@ +{ + "id": "tmux", + "version": "0.1.0", + "name": "tmux", + "description": "Installs tmux and makes the host ~/.config/tmux configuration available read-only.", + "mounts": [ + { + "type": "bind", + "source": "${localEnv:HOME}/.config/tmux", + "target": "/mnt/devcontainer-features/tmux/config", + "readonly": true + } + ], + "postCreateCommand": "/usr/local/share/devcontainer-features/tmux/setup-persistence.sh" +} diff --git a/src/tmux/install.sh b/src/tmux/install.sh new file mode 100755 index 0000000..47346db --- /dev/null +++ b/src/tmux/install.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v apt-get >/dev/null 2>&1; then + echo "[tmux] Debian/Ubuntu (apt-get) is required." >&2 + exit 1 +fi + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y --no-install-recommends tmux +rm -rf /var/lib/apt/lists/* + +mkdir -p "/usr/local/share/devcontainer-features/tmux" +install -m 0755 "${0%/*}/setup-persistence.sh" "/usr/local/share/devcontainer-features/tmux/setup-persistence.sh" diff --git a/src/tmux/setup-persistence.sh b/src/tmux/setup-persistence.sh new file mode 100755 index 0000000..d7cf0d0 --- /dev/null +++ b/src/tmux/setup-persistence.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Test overrides are only used when running the repository's smoke tests. +SOURCE="${TMUX_FEATURE_CONFIG_DIR:-/mnt/devcontainer-features/tmux/config}" +: "${HOME:?HOME must be set}" +TARGET="$HOME/.config/tmux" + +if [[ ! -d "$SOURCE" ]]; then + echo "[tmux] Shared config directory is missing: $SOURCE" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$TARGET")" + +if [[ -L "$TARGET" ]]; then + if [[ "$(readlink "$TARGET")" == "$SOURCE" ]]; then + exit 0 + fi + echo "[tmux] Refusing to replace existing symlink: $TARGET" >&2 + exit 1 +elif [[ -d "$TARGET" ]]; then + if [[ -n "$(ls -A "$TARGET")" ]]; then + echo "[tmux] Refusing to replace nonempty directory: $TARGET" >&2 + exit 1 + fi + rmdir "$TARGET" +elif [[ -e "$TARGET" ]]; then + echo "[tmux] Refusing to replace existing file: $TARGET" >&2 + exit 1 +fi + +ln -s "$SOURCE" "$TARGET" +echo "[tmux] Linked $TARGET -> $SOURCE (read-only bind mount)" diff --git a/src/vim/README.md b/src/vim/README.md new file mode 100644 index 0000000..83ed9f5 --- /dev/null +++ b/src/vim/README.md @@ -0,0 +1,37 @@ +# Vim Feature + +Installs the full `vim` package (not `vim-tiny`) on Debian/Ubuntu and shares +the **host** `~/.vim` directory with the container **read-only**. The +container's `~/.vim` is a symlink to that bind mount. + +Vim reads `~/.vim/vimrc` and loads plugins from this directory. + +## One-time host setup + +Run this on the host **before** creating the dev container (Docker bind mounts +require their source directory to exist): + +```sh +mkdir -p ~/.vim +``` + +If you currently use `~/.vimrc`, move it to `~/.vim/vimrc` after checking +that the destination does not already exist. The legacy dotfile itself is +deliberately *not* bind-mounted: a bind mount of a missing host file can fail, +and mounting the host HOME would expose unrelated data. + +## Usage + +```json +{ + "features": { + "ghcr.io/temeteke/devcontainer-features/vim:0": {} + } +} +``` + +Configuration and installed plugins under `~/.vim` are shared read-only. +Buffer files in a mounted workspace persist normally; container-local runtime +state is discarded with the container. Installing plugins or writing state +**inside** `~/.vim` is not supported; do those operations on the host. If the +image already has a nonempty `~/.vim`, setup fails instead of overwriting it. diff --git a/src/vim/devcontainer-feature.json b/src/vim/devcontainer-feature.json new file mode 100644 index 0000000..470d887 --- /dev/null +++ b/src/vim/devcontainer-feature.json @@ -0,0 +1,15 @@ +{ + "id": "vim", + "version": "0.1.0", + "name": "Vim", + "description": "Installs vim and makes the host ~/.vim configuration available read-only.", + "mounts": [ + { + "type": "bind", + "source": "${localEnv:HOME}/.vim", + "target": "/mnt/devcontainer-features/vim/config", + "readonly": true + } + ], + "postCreateCommand": "/usr/local/share/devcontainer-features/vim/setup-persistence.sh" +} diff --git a/src/vim/install.sh b/src/vim/install.sh new file mode 100755 index 0000000..9a3657e --- /dev/null +++ b/src/vim/install.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v apt-get >/dev/null 2>&1; then + echo "[vim] Debian/Ubuntu (apt-get) is required." >&2 + exit 1 +fi + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y --no-install-recommends vim +rm -rf /var/lib/apt/lists/* + +mkdir -p "/usr/local/share/devcontainer-features/vim" +install -m 0755 "${0%/*}/setup-persistence.sh" "/usr/local/share/devcontainer-features/vim/setup-persistence.sh" diff --git a/src/vim/setup-persistence.sh b/src/vim/setup-persistence.sh new file mode 100755 index 0000000..7e77d8b --- /dev/null +++ b/src/vim/setup-persistence.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Test overrides are only used when running the repository's smoke tests. +SOURCE="${VIM_FEATURE_CONFIG_DIR:-/mnt/devcontainer-features/vim/config}" +: "${HOME:?HOME must be set}" +TARGET="$HOME/.vim" + +if [[ ! -d "$SOURCE" ]]; then + echo "[vim] Shared config directory is missing: $SOURCE" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$TARGET")" + +if [[ -L "$TARGET" ]]; then + if [[ "$(readlink "$TARGET")" == "$SOURCE" ]]; then + exit 0 + fi + echo "[vim] Refusing to replace existing symlink: $TARGET" >&2 + exit 1 +elif [[ -d "$TARGET" ]]; then + if [[ -n "$(ls -A "$TARGET")" ]]; then + echo "[vim] Refusing to replace nonempty directory: $TARGET" >&2 + exit 1 + fi + rmdir "$TARGET" +elif [[ -e "$TARGET" ]]; then + echo "[vim] Refusing to replace existing file: $TARGET" >&2 + exit 1 +fi + +ln -s "$SOURCE" "$TARGET" +echo "[vim] Linked $TARGET -> $SOURCE (read-only bind mount)" diff --git a/test/persistence.sh b/test/persistence.sh new file mode 100755 index 0000000..1f9f097 --- /dev/null +++ b/test/persistence.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(mktemp -d)" +trap 'rm -rf "$root"' EXIT + +for feature in tmux vim; do + src="$root/shared-$feature" + home="$root/home-$feature" + mkdir -p "$src" "$home" + if [[ "$feature" == "tmux" ]]; then + var="TMUX_FEATURE_CONFIG_DIR" + target="$home/.config/tmux" + else + var="VIM_FEATURE_CONFIG_DIR" + target="$home/.vim" + fi + + script="src/$feature/setup-persistence.sh" + env HOME="$home" "$var=$src" bash "$script" + [[ -L "$target" && "$(readlink "$target")" == "$src" ]] + env HOME="$home" "$var=$src" bash "$script" # idempotent + + printf 'shared\n' > "$src/marker" + [[ "$(cat "$target/marker")" == "shared" ]] + + # Do not replace an existing nonempty container-side config. + conflicting_home="$root/conflict-$feature" + conflicting_target="$conflicting_home/${target#"$home"/}" + mkdir -p "$conflicting_target" + printf 'keep\n' > "$conflicting_target/existing" + if env HOME="$conflicting_home" "$var=$src" bash "$script" >/dev/null 2>&1; then + echo "Expected conflicting $feature config to fail" >&2 + exit 1 + fi + [[ "$(cat "$conflicting_target/existing")" == "keep" ]] +done + +echo "Persistence smoke tests OK" diff --git a/test/validate.sh b/test/validate.sh old mode 100644 new mode 100755 index 81622ca..2217da7 --- a/test/validate.sh +++ b/test/validate.sh @@ -11,4 +11,6 @@ for s in src/*/*.sh; do bash -n "$s" done +bash test/persistence.sh + echo "OK" From 5d7cb496c70a623afe0156cffd3583cb2320da5d Mon Sep 17 00:00:00 2001 From: temeteke Date: Sat, 17 Oct 2026 14:57:43 +0900 Subject: [PATCH 2/5] fix: mount existing tmux and Vim dotfiles without migration --- README.md | 24 ++++---- src/tmux/README.md | 34 +++++------ src/tmux/devcontainer-feature.json | 6 +- src/tmux/setup-persistence.sh | 21 ++----- src/vim/README.md | 39 +++++++------ src/vim/devcontainer-feature.json | 12 +++- src/vim/setup-persistence.sh | 66 ++++++++++++++------- test/persistence.sh | 92 ++++++++++++++++++++---------- 8 files changed, 171 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 23d15f1..19faca4 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ other sensitive data. Since they are stored directly in your host home directory | `playwright` | AI agent tool: Playwright Agent CLI and browser runtime only | none | | `gitlab-cli` | Coding tool: `glab` CLI from GitLab releases | `~/.config/glab-cli` | | `agents` | Shared feature for agent skills/state only | `~/.agents` | -| `tmux` | `tmux` via apt (Debian/Ubuntu) | read-only `~/.config/tmux` | -| `vim` | `vim` via apt (Debian/Ubuntu) | read-only `~/.vim` | +| `tmux` | `tmux` via apt (Debian/Ubuntu) | read-only `~/.tmux.conf` | +| `vim` | `vim` via apt (Debian/Ubuntu) | read-only `~/.vimrc`, read-write `~/.vim` | ## Publishing @@ -108,11 +108,15 @@ Roo Code, Cline, OpenCode, or GitLab. ## Terminal Features -The optional `tmux` and `vim` Features share host settings read-only through -the standard directory-based configurations. Before creating a container, run -`mkdir -p ~/.config/tmux ~/.vim` on the host. If you use `~/.tmux.conf` or -`~/.vimrc`, migrate those files to `~/.config/tmux/tmux.conf` and -`~/.vim/vimrc` respectively. These legacy dotfiles are **not** mounted. -The features do not mount the host HOME directory. See -[`src/tmux/README.md`](src/tmux/README.md) and -[`src/vim/README.md`](src/vim/README.md). +`tmux` mounts the host's `~/.tmux.conf` file read-only. `vim` mounts +`~/.vimrc` read-only and `~/.vim` read-write so vim-jetpack can manage +plugins and undo files. This follows the existing +[tmux-config](https://github.com/temeteke/tmux-config) and +[vim-config](https://github.com/temeteke/vim-config) Makefile installations. +No migration to new dotfile locations is needed. + +Before launching a Dev Container, make sure these host files/directories +exist; bind mounting a missing file can fail before lifecycle hooks run. +No other host HOME paths are mounted by these two Features. +See [`src/tmux/README.md`](src/tmux/README.md) and +[`src/vim/README.md`](src/vim/README.md) for details. diff --git a/src/tmux/README.md b/src/tmux/README.md index eee52f8..c4b5ce9 100644 --- a/src/tmux/README.md +++ b/src/tmux/README.md @@ -1,24 +1,18 @@ # tmux Feature -Installs tmux on Debian/Ubuntu and shares the **host** `~/.config/tmux` directory -with the container **read-only**. The container's `~/.config/tmux` is a symlink -to that bind mount. tmux reads `~/.config/tmux/tmux.conf` natively. +Installs tmux on Debian/Ubuntu and exposes the **host** `~/.tmux.conf` +file in the container, read-only. A symlink at the container user's +`~/.tmux.conf` points to the mounted file. -## One-time host setup +## Host configuration -Run this on the host **before** creating the dev container (Docker bind mounts -require their source directory to exist): +This matches [temeteke/tmux-config](https://github.com/temeteke/tmux-config): +its Makefile assembles `~/.tmux.conf` and installs it in the host HOME. +There is **no need to migrate** to `~/.config/tmux/tmux.conf`. -```sh -mkdir -p ~/.config/tmux -``` - -If you currently use `~/.tmux.conf`, move it to -`~/.config/tmux/tmux.conf` after checking the destination does not already -exist. The legacy dotfile itself is deliberately *not* bind-mounted: a bind mount -of a missing host file can fail, and mounting the host HOME would expose unrelated -data. If your configuration references plugins in `~/.tmux`, relocate or configure -those references under `~/.config/tmux` as well. +Before building/starting the Dev Container, make sure `~/.tmux.conf` +is a regular file or a resolvable symlink to a file on the host. A missing +file bind mount cannot be skipped dynamically; the container may fail to start. ## Usage @@ -30,8 +24,6 @@ those references under `~/.config/tmux` as well. } ``` -Only configuration is shared: tmux servers, sockets and sessions are not -persisted when the container is removed. Host configuration is read-only from -inside the container; plugins that need to update themselves must be managed on -the host. If the image already has a nonempty `~/.config/tmux`, setup fails -rather than destroying that container-side data. +Only the configuration file is shared; tmux server/socket/session state +belongs to the temporary container. The container startup script refuses +to overwrite an existing `~/.tmux.conf` file or unrelated symlink. diff --git a/src/tmux/devcontainer-feature.json b/src/tmux/devcontainer-feature.json index c640e59..987327b 100644 --- a/src/tmux/devcontainer-feature.json +++ b/src/tmux/devcontainer-feature.json @@ -2,12 +2,12 @@ "id": "tmux", "version": "0.1.0", "name": "tmux", - "description": "Installs tmux and makes the host ~/.config/tmux configuration available read-only.", + "description": "Installs tmux and shares the host ~/.tmux.conf read-only.", "mounts": [ { "type": "bind", - "source": "${localEnv:HOME}/.config/tmux", - "target": "/mnt/devcontainer-features/tmux/config", + "source": "${localEnv:HOME}/.tmux.conf", + "target": "/mnt/devcontainer-features/tmux/tmux.conf", "readonly": true } ], diff --git a/src/tmux/setup-persistence.sh b/src/tmux/setup-persistence.sh index d7cf0d0..7397821 100755 --- a/src/tmux/setup-persistence.sh +++ b/src/tmux/setup-persistence.sh @@ -1,34 +1,25 @@ #!/usr/bin/env bash set -euo pipefail -# Test overrides are only used when running the repository's smoke tests. -SOURCE="${TMUX_FEATURE_CONFIG_DIR:-/mnt/devcontainer-features/tmux/config}" +SOURCE="${TMUX_FEATURE_CONFIG_FILE:-/mnt/devcontainer-features/tmux/tmux.conf}" : "${HOME:?HOME must be set}" -TARGET="$HOME/.config/tmux" +TARGET="$HOME/.tmux.conf" -if [[ ! -d "$SOURCE" ]]; then - echo "[tmux] Shared config directory is missing: $SOURCE" >&2 +if [[ ! -f "$SOURCE" ]]; then + echo "[tmux] Host ~/.tmux.conf mount is missing or is not a file: $SOURCE" >&2 exit 1 fi -mkdir -p "$(dirname "$TARGET")" - if [[ -L "$TARGET" ]]; then if [[ "$(readlink "$TARGET")" == "$SOURCE" ]]; then exit 0 fi echo "[tmux] Refusing to replace existing symlink: $TARGET" >&2 exit 1 -elif [[ -d "$TARGET" ]]; then - if [[ -n "$(ls -A "$TARGET")" ]]; then - echo "[tmux] Refusing to replace nonempty directory: $TARGET" >&2 - exit 1 - fi - rmdir "$TARGET" elif [[ -e "$TARGET" ]]; then - echo "[tmux] Refusing to replace existing file: $TARGET" >&2 + echo "[tmux] Refusing to overwrite existing container file: $TARGET" >&2 exit 1 fi ln -s "$SOURCE" "$TARGET" -echo "[tmux] Linked $TARGET -> $SOURCE (read-only bind mount)" +echo "[tmux] Linked $TARGET -> $SOURCE" diff --git a/src/vim/README.md b/src/vim/README.md index 83ed9f5..483baa5 100644 --- a/src/vim/README.md +++ b/src/vim/README.md @@ -1,24 +1,28 @@ # Vim Feature -Installs the full `vim` package (not `vim-tiny`) on Debian/Ubuntu and shares -the **host** `~/.vim` directory with the container **read-only**. The -container's `~/.vim` is a symlink to that bind mount. +Installs the full `vim` package on Debian/Ubuntu. Exposes the **host** +`~/.vimrc` **read-only** and `~/.vim/` **read-write** and links both +paths into the container user's HOME. -Vim reads `~/.vim/vimrc` and loads plugins from this directory. +## Host configuration -## One-time host setup +This matches [temeteke/vim-config](https://github.com/temeteke/vim-config): +its Makefile installs `~/.vimrc` and populates `~/.vim/` with scripts, +plugin configuration and an undo directory. No configuration migration +is required. -Run this on the host **before** creating the dev container (Docker bind mounts -require their source directory to exist): +Make sure both `~/.vimrc` (file) and `~/.vim/` (directory) exist on the +host before starting a container. Missing bind sources may prevent startup. -```sh -mkdir -p ~/.vim -``` +**Why is `~/.vim` writable?** The current `vim-config/plugin.vim` +bootstraps vim-jetpack and synchronizes plugins on startup; the Makefile +also creates `~/.vim/undo`. Read-only sharing would break that workflow. +This means code running inside the container can modify host Vim plugins +and other files under `~/.vim`. Do not use this Feature with untrusted +agents unless you accept that exception to workspace isolation. -If you currently use `~/.vimrc`, move it to `~/.vim/vimrc` after checking -that the destination does not already exist. The legacy dotfile itself is -deliberately *not* bind-mounted: a bind mount of a missing host file can fail, -and mounting the host HOME would expose unrelated data. +The optional `~/.vimrc.local` is not mounted, so Vim skips it unless +another mount provides it. ## Usage @@ -30,8 +34,5 @@ and mounting the host HOME would expose unrelated data. } ``` -Configuration and installed plugins under `~/.vim` are shared read-only. -Buffer files in a mounted workspace persist normally; container-local runtime -state is discarded with the container. Installing plugins or writing state -**inside** `~/.vim` is not supported; do those operations on the host. If the -image already has a nonempty `~/.vim`, setup fails instead of overwriting it. +The setup script refuses to overwrite existing nonempty container-side +configuration. Use a base image with an empty/default Vim HOME. diff --git a/src/vim/devcontainer-feature.json b/src/vim/devcontainer-feature.json index 470d887..51bd734 100644 --- a/src/vim/devcontainer-feature.json +++ b/src/vim/devcontainer-feature.json @@ -2,13 +2,19 @@ "id": "vim", "version": "0.1.0", "name": "Vim", - "description": "Installs vim and makes the host ~/.vim configuration available read-only.", + "description": "Installs Vim and shares host ~/.vimrc (read-only) and ~/.vim (read-write).", "mounts": [ { "type": "bind", - "source": "${localEnv:HOME}/.vim", - "target": "/mnt/devcontainer-features/vim/config", + "source": "${localEnv:HOME}/.vimrc", + "target": "/mnt/devcontainer-features/vim/vimrc", "readonly": true + }, + { + "type": "bind", + "source": "${localEnv:HOME}/.vim", + "target": "/mnt/devcontainer-features/vim/vim", + "readonly": false } ], "postCreateCommand": "/usr/local/share/devcontainer-features/vim/setup-persistence.sh" diff --git a/src/vim/setup-persistence.sh b/src/vim/setup-persistence.sh index 7e77d8b..b4e1089 100755 --- a/src/vim/setup-persistence.sh +++ b/src/vim/setup-persistence.sh @@ -1,34 +1,58 @@ #!/usr/bin/env bash set -euo pipefail -# Test overrides are only used when running the repository's smoke tests. -SOURCE="${VIM_FEATURE_CONFIG_DIR:-/mnt/devcontainer-features/vim/config}" +CONFIG_FILE="${VIM_FEATURE_CONFIG_FILE:-/mnt/devcontainer-features/vim/vimrc}" +CONFIG_DIR="${VIM_FEATURE_CONFIG_DIR:-/mnt/devcontainer-features/vim/vim}" : "${HOME:?HOME must be set}" -TARGET="$HOME/.vim" -if [[ ! -d "$SOURCE" ]]; then - echo "[vim] Shared config directory is missing: $SOURCE" >&2 +if [[ ! -f "$CONFIG_FILE" ]]; then + echo "[vim] Host ~/.vimrc mount is missing or is not a file: $CONFIG_FILE" >&2 + exit 1 +fi +if [[ ! -d "$CONFIG_DIR" ]]; then + echo "[vim] Host ~/.vim mount is missing or is not a directory: $CONFIG_DIR" >&2 exit 1 fi -mkdir -p "$(dirname "$TARGET")" +link_config() { + local source="$1" target="$2" kind="$3" -if [[ -L "$TARGET" ]]; then - if [[ "$(readlink "$TARGET")" == "$SOURCE" ]]; then - exit 0 + if [[ -L "$target" ]]; then + if [[ "$(readlink "$target")" == "$source" ]]; then + return 0 + fi + echo "[vim] Refusing to replace existing symlink: $target" >&2 + return 1 fi - echo "[vim] Refusing to replace existing symlink: $TARGET" >&2 - exit 1 -elif [[ -d "$TARGET" ]]; then - if [[ -n "$(ls -A "$TARGET")" ]]; then - echo "[vim] Refusing to replace nonempty directory: $TARGET" >&2 + + if [[ "$kind" == dir && -d "$target" ]]; then + if [[ -n "$(ls -A "$target")" ]]; then + echo "[vim] Refusing to replace nonempty directory: $target" >&2 + return 1 + fi + rmdir "$target" + elif [[ -e "$target" ]]; then + echo "[vim] Refusing to overwrite existing container path: $target" >&2 + return 1 + fi + + ln -s "$source" "$target" + echo "[vim] Linked $target -> $source" +} + +# Check both destinations before modifying either to avoid partial migration. +for dest in "$HOME/.vimrc" "$HOME/.vim"; do + case "$dest" in + "$HOME/.vimrc") src="$CONFIG_FILE"; kind=file ;; + "$HOME/.vim") src="$CONFIG_DIR"; kind=dir ;; + esac + if [[ -L "$dest" && "$(readlink "$dest")" != "$src" ]] || + [[ ! -L "$dest" && -e "$dest" && + ( "$kind" == file || ! -d "$dest" || -n "$(ls -A "$dest")" ) ]]; then + echo "[vim] Refusing to replace pre-existing container configuration: $dest" >&2 exit 1 fi - rmdir "$TARGET" -elif [[ -e "$TARGET" ]]; then - echo "[vim] Refusing to replace existing file: $TARGET" >&2 - exit 1 -fi +done -ln -s "$SOURCE" "$TARGET" -echo "[vim] Linked $TARGET -> $SOURCE (read-only bind mount)" +link_config "$CONFIG_FILE" "$HOME/.vimrc" file +link_config "$CONFIG_DIR" "$HOME/.vim" dir diff --git a/test/persistence.sh b/test/persistence.sh index 1f9f097..81c705d 100755 --- a/test/persistence.sh +++ b/test/persistence.sh @@ -4,36 +4,66 @@ set -euo pipefail root="$(mktemp -d)" trap 'rm -rf "$root"' EXIT -for feature in tmux vim; do - src="$root/shared-$feature" - home="$root/home-$feature" - mkdir -p "$src" "$home" - if [[ "$feature" == "tmux" ]]; then - var="TMUX_FEATURE_CONFIG_DIR" - target="$home/.config/tmux" - else - var="VIM_FEATURE_CONFIG_DIR" - target="$home/.vim" - fi - - script="src/$feature/setup-persistence.sh" - env HOME="$home" "$var=$src" bash "$script" - [[ -L "$target" && "$(readlink "$target")" == "$src" ]] - env HOME="$home" "$var=$src" bash "$script" # idempotent - - printf 'shared\n' > "$src/marker" - [[ "$(cat "$target/marker")" == "shared" ]] - - # Do not replace an existing nonempty container-side config. - conflicting_home="$root/conflict-$feature" - conflicting_target="$conflicting_home/${target#"$home"/}" - mkdir -p "$conflicting_target" - printf 'keep\n' > "$conflicting_target/existing" - if env HOME="$conflicting_home" "$var=$src" bash "$script" >/dev/null 2>&1; then - echo "Expected conflicting $feature config to fail" >&2 - exit 1 - fi - [[ "$(cat "$conflicting_target/existing")" == "keep" ]] -done +tmux_src="$root/host.tmux.conf" +vimrc_src="$root/host.vimrc" +vim_dir="$root/host.vim" +printf 'set -g mouse on\n' > "$tmux_src" +printf 'set number\n' > "$vimrc_src" +mkdir -p "$vim_dir" +printf 'plugin state\n' > "$vim_dir/plugin.vim" + +tmux_home="$root/tmux-home" +vim_home="$root/vim-home" +mkdir -p "$tmux_home" "$vim_home" + +env HOME="$tmux_home" TMUX_FEATURE_CONFIG_FILE="$tmux_src" \ + bash src/tmux/setup-persistence.sh +[[ "$(readlink "$tmux_home/.tmux.conf")" == "$tmux_src" ]] +env HOME="$tmux_home" TMUX_FEATURE_CONFIG_FILE="$tmux_src" \ + bash src/tmux/setup-persistence.sh + +env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ + VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh +[[ "$(readlink "$vim_home/.vimrc")" == "$vimrc_src" ]] +[[ "$(readlink "$vim_home/.vim")" == "$vim_dir" ]] +[[ "$(cat "$vim_home/.vim/plugin.vim")" == "plugin state" ]] +env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ + VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh + +# Vim plugin state must be writable for current vim-config bootstrap. +printf 'updated\n' > "$vim_home/.vim/plugin.vim" +[[ "$(cat "$vim_dir/plugin.vim")" == "updated" ]] + +# Missing source fails, and no configuration is created. +if env HOME="$root/no-tmux" TMUX_FEATURE_CONFIG_FILE="$root/missing-file" \ + bash src/tmux/setup-persistence.sh > /dev/null 2>&1; then + echo "Missing tmux source unexpectedly accepted" >&2 + exit 1 +fi +if env HOME="$root/no-vim" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ + VIM_FEATURE_CONFIG_DIR="$root/missing-dir" \ + bash src/vim/setup-persistence.sh > /dev/null 2>&1; then + echo "Missing vim directory unexpectedly accepted" >&2 + exit 1 +fi + +# Conflicts preserve existing files and do not partially link. +mkdir -p "$root/conflict-tmux" "$root/conflict-vim/.vim" +printf 'keep\n' > "$root/conflict-tmux/.tmux.conf" +printf 'keep\n' > "$root/conflict-vim/.vim/existing" +if env HOME="$root/conflict-tmux" TMUX_FEATURE_CONFIG_FILE="$tmux_src" \ + bash src/tmux/setup-persistence.sh > /dev/null 2>&1; then + echo "tmux overwrote an existing config" >&2 + exit 1 +fi +if env HOME="$root/conflict-vim" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ + VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh \ + > /dev/null 2>&1; then + echo "vim overwrote an existing config" >&2 + exit 1 +fi +[[ "$(cat "$root/conflict-tmux/.tmux.conf")" == "keep" ]] +[[ "$(cat "$root/conflict-vim/.vim/existing")" == "keep" ]] +[[ ! -e "$root/conflict-vim/.vimrc" ]] echo "Persistence smoke tests OK" From 4a2f7f59c6fe24d516e3e09b158c025928f53c64 Mon Sep 17 00:00:00 2001 From: temeteke Date: Sat, 17 Oct 2026 15:05:39 +0900 Subject: [PATCH 3/5] docs: describe tmux and Vim mounts without personal dotfiles references --- README.md | 24 ++++++++++++++---------- src/tmux/README.md | 5 ++--- src/vim/README.md | 19 +++++++++---------- test/persistence.sh | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 19faca4..5defed7 100644 --- a/README.md +++ b/README.md @@ -108,15 +108,19 @@ Roo Code, Cline, OpenCode, or GitLab. ## Terminal Features -`tmux` mounts the host's `~/.tmux.conf` file read-only. `vim` mounts -`~/.vimrc` read-only and `~/.vim` read-write so vim-jetpack can manage -plugins and undo files. This follows the existing -[tmux-config](https://github.com/temeteke/tmux-config) and -[vim-config](https://github.com/temeteke/vim-config) Makefile installations. -No migration to new dotfile locations is needed. - -Before launching a Dev Container, make sure these host files/directories -exist; bind mounting a missing file can fail before lifecycle hooks run. -No other host HOME paths are mounted by these two Features. +The `tmux` Feature installs tmux and shares the host's `~/.tmux.conf` +file read-only. The `vim` Feature installs Vim and shares `~/.vimrc` +read-only and `~/.vim/` read-write. This supports standard dotfile layouts, +including Vim configurations that install plugins or write undo data under +`~/.vim/`. + +Before starting a Dev Container, ensure the corresponding host paths exist. +A missing bind-mount source may prevent the container from starting. +These Features do not mount the entire host HOME directory. + +The writable `~/.vim/` mount allows processes in the container to modify +host Vim configuration, plugins and state. Review this security tradeoff +before using these Features with untrusted code. + See [`src/tmux/README.md`](src/tmux/README.md) and [`src/vim/README.md`](src/vim/README.md) for details. diff --git a/src/tmux/README.md b/src/tmux/README.md index c4b5ce9..774a48c 100644 --- a/src/tmux/README.md +++ b/src/tmux/README.md @@ -6,9 +6,8 @@ file in the container, read-only. A symlink at the container user's ## Host configuration -This matches [temeteke/tmux-config](https://github.com/temeteke/tmux-config): -its Makefile assembles `~/.tmux.conf` and installs it in the host HOME. -There is **no need to migrate** to `~/.config/tmux/tmux.conf`. +The Feature uses tmux's conventional `~/.tmux.conf` configuration file. +It does not mount `~/.config/tmux/` or other host configuration paths. Before building/starting the Dev Container, make sure `~/.tmux.conf` is a regular file or a resolvable symlink to a file on the host. A missing diff --git a/src/vim/README.md b/src/vim/README.md index 483baa5..546c5d1 100644 --- a/src/vim/README.md +++ b/src/vim/README.md @@ -6,20 +6,19 @@ paths into the container user's HOME. ## Host configuration -This matches [temeteke/vim-config](https://github.com/temeteke/vim-config): -its Makefile installs `~/.vimrc` and populates `~/.vim/` with scripts, -plugin configuration and an undo directory. No configuration migration -is required. +The Feature uses Vim's conventional `~/.vimrc` and `~/.vim/` paths. +Configurations that use different locations must arrange those paths on +the host before starting the container. Make sure both `~/.vimrc` (file) and `~/.vim/` (directory) exist on the host before starting a container. Missing bind sources may prevent startup. -**Why is `~/.vim` writable?** The current `vim-config/plugin.vim` -bootstraps vim-jetpack and synchronizes plugins on startup; the Makefile -also creates `~/.vim/undo`. Read-only sharing would break that workflow. -This means code running inside the container can modify host Vim plugins -and other files under `~/.vim`. Do not use this Feature with untrusted -agents unless you accept that exception to workspace isolation. +**Why is `~/.vim` writable?** Some Vim configurations install or update +plugins and store undo or other runtime data inside `~/.vim/`. A read-only +mount would prevent these operations. This also means processes running +inside the container can modify host Vim configuration, plugins and state. +Review this exception to workspace isolation before using the Feature +with untrusted code. The optional `~/.vimrc.local` is not mounted, so Vim skips it unless another mount provides it. diff --git a/test/persistence.sh b/test/persistence.sh index 81c705d..5326268 100755 --- a/test/persistence.sh +++ b/test/persistence.sh @@ -30,7 +30,7 @@ env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh -# Vim plugin state must be writable for current vim-config bootstrap. +# Vim plugin/runtime state must be writable when the host directory is shared. printf 'updated\n' > "$vim_home/.vim/plugin.vim" [[ "$(cat "$vim_dir/plugin.vim")" == "updated" ]] From 3f85849ad0399e1e483362b8e24062024ce274f8 Mon Sep 17 00:00:00 2001 From: temeteke Date: Sat, 17 Oct 2026 15:11:40 +0900 Subject: [PATCH 4/5] test: clarify generic configuration link smoke tests --- test/{persistence.sh => config-links.sh} | 12 ++++++------ test/validate.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) rename test/{persistence.sh => config-links.sh} (87%) diff --git a/test/persistence.sh b/test/config-links.sh similarity index 87% rename from test/persistence.sh rename to test/config-links.sh index 5326268..49f1b1b 100755 --- a/test/persistence.sh +++ b/test/config-links.sh @@ -10,7 +10,7 @@ vim_dir="$root/host.vim" printf 'set -g mouse on\n' > "$tmux_src" printf 'set number\n' > "$vimrc_src" mkdir -p "$vim_dir" -printf 'plugin state\n' > "$vim_dir/plugin.vim" +printf 'sample data\n' > "$vim_dir/example.txt" tmux_home="$root/tmux-home" vim_home="$root/vim-home" @@ -26,13 +26,13 @@ env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh [[ "$(readlink "$vim_home/.vimrc")" == "$vimrc_src" ]] [[ "$(readlink "$vim_home/.vim")" == "$vim_dir" ]] -[[ "$(cat "$vim_home/.vim/plugin.vim")" == "plugin state" ]] +[[ "$(cat "$vim_home/.vim/example.txt")" == "sample data" ]] env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh -# Vim plugin/runtime state must be writable when the host directory is shared. -printf 'updated\n' > "$vim_home/.vim/plugin.vim" -[[ "$(cat "$vim_dir/plugin.vim")" == "updated" ]] +# Updates inside the shared directory must be reflected at the host source. +printf 'updated\n' > "$vim_home/.vim/example.txt" +[[ "$(cat "$vim_dir/example.txt")" == "updated" ]] # Missing source fails, and no configuration is created. if env HOME="$root/no-tmux" TMUX_FEATURE_CONFIG_FILE="$root/missing-file" \ @@ -66,4 +66,4 @@ fi [[ "$(cat "$root/conflict-vim/.vim/existing")" == "keep" ]] [[ ! -e "$root/conflict-vim/.vimrc" ]] -echo "Persistence smoke tests OK" +echo "Configuration link smoke tests OK" diff --git a/test/validate.sh b/test/validate.sh index 2217da7..5613573 100755 --- a/test/validate.sh +++ b/test/validate.sh @@ -11,6 +11,6 @@ for s in src/*/*.sh; do bash -n "$s" done -bash test/persistence.sh +bash test/config-links.sh echo "OK" From 36958038c71165ae81a38df61bfd93f5d1bde116 Mon Sep 17 00:00:00 2001 From: temeteke Date: Sat, 17 Oct 2026 15:16:40 +0900 Subject: [PATCH 5/5] refactor: remove config link smoke test and test-only overrides --- .github/workflows/validate.yaml | 2 +- src/tmux/setup-persistence.sh | 2 +- src/vim/setup-persistence.sh | 4 +- test/config-links.sh | 69 --------------------------------- test/validate.sh | 2 - 5 files changed, 4 insertions(+), 75 deletions(-) delete mode 100755 test/config-links.sh diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index e802ece..7aa0af5 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -9,5 +9,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Validate metadata, shell scripts and configuration links + - name: Validate metadata and shell script syntax run: bash test/validate.sh diff --git a/src/tmux/setup-persistence.sh b/src/tmux/setup-persistence.sh index 7397821..f00eae9 100755 --- a/src/tmux/setup-persistence.sh +++ b/src/tmux/setup-persistence.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -SOURCE="${TMUX_FEATURE_CONFIG_FILE:-/mnt/devcontainer-features/tmux/tmux.conf}" +SOURCE="/mnt/devcontainer-features/tmux/tmux.conf" : "${HOME:?HOME must be set}" TARGET="$HOME/.tmux.conf" diff --git a/src/vim/setup-persistence.sh b/src/vim/setup-persistence.sh index b4e1089..17124be 100755 --- a/src/vim/setup-persistence.sh +++ b/src/vim/setup-persistence.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -CONFIG_FILE="${VIM_FEATURE_CONFIG_FILE:-/mnt/devcontainer-features/vim/vimrc}" -CONFIG_DIR="${VIM_FEATURE_CONFIG_DIR:-/mnt/devcontainer-features/vim/vim}" +CONFIG_FILE="/mnt/devcontainer-features/vim/vimrc" +CONFIG_DIR="/mnt/devcontainer-features/vim/vim" : "${HOME:?HOME must be set}" if [[ ! -f "$CONFIG_FILE" ]]; then diff --git a/test/config-links.sh b/test/config-links.sh deleted file mode 100755 index 49f1b1b..0000000 --- a/test/config-links.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -root="$(mktemp -d)" -trap 'rm -rf "$root"' EXIT - -tmux_src="$root/host.tmux.conf" -vimrc_src="$root/host.vimrc" -vim_dir="$root/host.vim" -printf 'set -g mouse on\n' > "$tmux_src" -printf 'set number\n' > "$vimrc_src" -mkdir -p "$vim_dir" -printf 'sample data\n' > "$vim_dir/example.txt" - -tmux_home="$root/tmux-home" -vim_home="$root/vim-home" -mkdir -p "$tmux_home" "$vim_home" - -env HOME="$tmux_home" TMUX_FEATURE_CONFIG_FILE="$tmux_src" \ - bash src/tmux/setup-persistence.sh -[[ "$(readlink "$tmux_home/.tmux.conf")" == "$tmux_src" ]] -env HOME="$tmux_home" TMUX_FEATURE_CONFIG_FILE="$tmux_src" \ - bash src/tmux/setup-persistence.sh - -env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ - VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh -[[ "$(readlink "$vim_home/.vimrc")" == "$vimrc_src" ]] -[[ "$(readlink "$vim_home/.vim")" == "$vim_dir" ]] -[[ "$(cat "$vim_home/.vim/example.txt")" == "sample data" ]] -env HOME="$vim_home" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ - VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh - -# Updates inside the shared directory must be reflected at the host source. -printf 'updated\n' > "$vim_home/.vim/example.txt" -[[ "$(cat "$vim_dir/example.txt")" == "updated" ]] - -# Missing source fails, and no configuration is created. -if env HOME="$root/no-tmux" TMUX_FEATURE_CONFIG_FILE="$root/missing-file" \ - bash src/tmux/setup-persistence.sh > /dev/null 2>&1; then - echo "Missing tmux source unexpectedly accepted" >&2 - exit 1 -fi -if env HOME="$root/no-vim" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ - VIM_FEATURE_CONFIG_DIR="$root/missing-dir" \ - bash src/vim/setup-persistence.sh > /dev/null 2>&1; then - echo "Missing vim directory unexpectedly accepted" >&2 - exit 1 -fi - -# Conflicts preserve existing files and do not partially link. -mkdir -p "$root/conflict-tmux" "$root/conflict-vim/.vim" -printf 'keep\n' > "$root/conflict-tmux/.tmux.conf" -printf 'keep\n' > "$root/conflict-vim/.vim/existing" -if env HOME="$root/conflict-tmux" TMUX_FEATURE_CONFIG_FILE="$tmux_src" \ - bash src/tmux/setup-persistence.sh > /dev/null 2>&1; then - echo "tmux overwrote an existing config" >&2 - exit 1 -fi -if env HOME="$root/conflict-vim" VIM_FEATURE_CONFIG_FILE="$vimrc_src" \ - VIM_FEATURE_CONFIG_DIR="$vim_dir" bash src/vim/setup-persistence.sh \ - > /dev/null 2>&1; then - echo "vim overwrote an existing config" >&2 - exit 1 -fi -[[ "$(cat "$root/conflict-tmux/.tmux.conf")" == "keep" ]] -[[ "$(cat "$root/conflict-vim/.vim/existing")" == "keep" ]] -[[ ! -e "$root/conflict-vim/.vimrc" ]] - -echo "Configuration link smoke tests OK" diff --git a/test/validate.sh b/test/validate.sh index 5613573..81622ca 100755 --- a/test/validate.sh +++ b/test/validate.sh @@ -11,6 +11,4 @@ for s in src/*/*.sh; do bash -n "$s" done -bash test/config-links.sh - echo "OK"