Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

## [Unreleased]

### Added
- **base イメージに `shellcheck` を入れました(PLAN67 / #249)。** base と、base を継ぐ派生
イメージ(`general` / `php` など)のコンテナで、Bash スクリプトの静的検査ができます。
bash-language-server などの言語サーバが返す Bash の診断もこれを使います。版は固定せず、
Ubuntu のアーカイブの版(2026-09 時点で 0.11.0)が入ります。入れ損ないはビルドの版の確認で
止まります。`lfm` / `snapshot` は base を継がないため入りません。
**反映には `devbase build base --no-cache` と、使っている派生イメージの建て直しが要ります。**

## [3.7.0] - 2026-09-23

### Added
Expand Down
7 changes: 5 additions & 2 deletions containers/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
fonts-crosextra-carlito fonts-crosextra-caladea \
# 文書を扱う軽量の道具 (#160)。PDF を画像にする・調べる、OOXML を壊さずに読み書きする。
# LibreOffice (展開 372〜459MB) と pip は入れない。Python パッケージは uv / uvx で賄う。
poppler-utils python3-pil python3-defusedxml python3-lxml; \
poppler-utils python3-pil python3-defusedxml python3-lxml \
# Bash の静的検査 (#249)。bash-language-server は診断を shellcheck に任せており、
# 無いとエラーも警告も出さずに診断が空になる。
shellcheck; \
# ロケール設定
locale-gen en_US.UTF-8; \
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8; \
Expand Down Expand Up @@ -184,7 +187,7 @@ RUN set -eux; \
bao version

# 確認
RUN gh --version && node --version && npm --version && aws --version && gcloud --version && session-manager-plugin --version
RUN gh --version && node --version && npm --version && aws --version && gcloud --version && session-manager-plugin --version && shellcheck --version

USER ${USERNAME}
WORKDIR /tmp
Expand Down
129 changes: 129 additions & 0 deletions docs/specifications/base-image-shellcheck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# base イメージの Bash の静的検査(shellcheck)

## 概要

base イメージは [ShellCheck](https://www.shellcheck.net/)(`shellcheck`)を同梱する。base と、
base を継ぐ派生イメージのコンテナの中で、Bash スクリプトの静的検査をそのまま走らせられる。
bash-language-server などの言語サーバは Bash の診断を `shellcheck` に任せており、無いとエラーも
警告も出さずに診断が空になる。base に置くことで、コンテナの中で動かす言語サーバも診断を返せる。

**shellcheck が入っていないイメージは建たない。** 入れ損ないはビルドの時点で止まる。

利用者向けの読み方は
[コンテナ操作ガイド: Bash の静的検査](../user/container-operations.md#bash-の静的検査base-以降)
にある。

## 対象範囲

- base イメージへの `shellcheck` の導入と、入れ損ないをビルドで止める仕組み
- base から派生するイメージ(`general` / `go` / `php` / `php85` / `bi-tools` / `latex` /
`trygroup`)への伝播の規則
- `containers/lfm` と `containers/snapshot` は base を継がないため対象に含まない
- bash-language-server 自体は同梱しない
- CI の ShellCheck ジョブは runner の shellcheck を使い、この仕様の対象ではない

## 構成要素

| 要素 | 置き場所 | 責務 |
| --- | --- | --- |
| 導入 | `containers/base/Dockerfile` の 1 つ目の `RUN` の 1 回目の `apt-get install` | `poppler-utils` などの行の後に、理由のコメントとともに `shellcheck` を置く |
| 入れ損ないの検出 | `containers/base/Dockerfile` の版の確認の `RUN` | `gh --version && ... && session-manager-plugin --version && shellcheck --version`。無ければ `shellcheck: command not found`(終了コード 127)でビルドが止まる |
| 形の検査 | `tests/containers/test_base_dockerfile_shellcheck.py` | Docker を起動せずに、上の 2 か所の形を固定する |

型(クラス)は持たない。Dockerfile の命令だけで構成する。

## 仕様

### 置き場所

`shellcheck` は Ubuntu の標準のアーカイブ(`universe`)にあり、外部のリポジトリを要さない。
そのため 1 つ目の `RUN` の**1 回目**の `apt-get install` の一覧に置く。2 回目の一覧は後から
足したリポジトリのパッケージ(`docker-ce` / `terraform` / `gh` / `nodejs` / `chromium-browser`)
のためにあり、標準のアーカイブのパッケージを混ぜない。

shellcheck のための `RUN` は立てない。立てると派生イメージ 7 つが積む層が 1 つ増え、
`apt-get update` とクリーンアップ(`apt-get clean` / `rm -rf`)をもう 1 か所に持つことになる。
Dockerfile の `apt-get update` は 2 回である。

### 入れ損ないを止める

版の確認の `RUN` は `USER ${USERNAME}` より前にあり root で走る。`/usr/bin/shellcheck` は
`PATH` にあるため、パスを書かずに呼ぶ。

**止める役はテストではなくビルドが持つ。** `apt-get install` はパッケージが一覧から消えても
失敗しないため、一覧の編集で `shellcheck` が落ちたときに止まるのは版の確認の側だけである。
テストは、版の確認の 1 語が消えないことを固定する。

### 版

版は固定せず、base を建てた時点で Ubuntu のアーカイブが配る版を入れる(2026-09 時点で
`0.11.0-2`、`shellcheck --version` は `version: 0.11.0`)。`gh` / `terraform` / `nodejs` と同じ
扱いである。`bao` のように `ARG` とチェックサムで固定しないのは、サーバの版と揃える制約が
無いためである。`shellcheck=<版>` と書かないのは、アーカイブが版を上げるとその版が消えて
ビルドが止まるためである。

## データ・設定

依存を含めて新しく入るパッケージは `shellcheck` と `libnuma1` の 2 つである(`libc6` /
`libffi8` / `libgmp10` は既に入っている)。置き場所は `/usr/bin/shellcheck`。

| 項目 | 値 |
| --- | --- |
| `Installed-Size`(`shellcheck`) | 24971 KB(arm64)/ 22867 KB(amd64、アーカイブの `Packages.gz` の値) |
| `/usr` の増分 | 約 25 MB(arm64 の実測) |

環境変数・設定ファイルは持たない。

## 運用

- 変更は**イメージを建て直すまで反映されない**。`devbase build base --no-cache` で base を
建て直し、使っている派生イメージ(いずれも `FROM devbase-base:latest`)も建て直し、稼働中の
コンテナは `devbase down` → `devbase up` で作り直す。`devbase up` だけでは反映されない
- **`devbase rebuild` はここでは使えない。** `devbase build --expires=7` のシノニム
(`lib/devbase/commands/container.py` の `cmd_rebuild`)で、期限内ならビルドを飛ばし、
コンテナも作り直さない
- `containers/lfm` は `FROM nvidia/cuda:...` で base を継がず、base からは `/usr/local` /
`/usr/bin/gh` / `/usr/bin/node` / `/opt` などを選んで `COPY` するだけのため、
`/usr/bin/shellcheck` は届かない。`containers/snapshot` は `FROM ubuntu:26.04` で base を
継がない。どちらかで要るようになったときは、そのイメージの `apt-get install` か `COPY` の
一覧へ足す
- 建てて確かめてあるのは arm64 である。amd64 はアーカイブに同じ版があり、依存も同じである
ことまで確かめている

## テスト観点

`tests/containers/test_base_dockerfile_shellcheck.py`(Docker を要さない):

- `shellcheck` が 1 つ目の `RUN` の 1 回目の `apt-get install` の一覧(最初の `;` まで)に
あること。2 回目の一覧に無いこと
- 2 つ目以降の `RUN` に `shellcheck` を入れる `apt-get install` が無く、`apt-get update` が
2 回のままであること
- 版の確認の `RUN` を `&&` で分けた命令に `shellcheck --version` があること

補助の関数(コメント行を除く本文・`RUN` ブロックへの分割・1 回目の一覧の切り出し)は
このファイルに持ち、`test_base_dockerfile_fonts.py` から import しない。テストのファイル
どうしを依存させない。

イメージの中の検査は Docker のテストにしない。入っていることはビルドが守る。
`test_base_image_font_matching.py` の期待値へ足すと、この変更より前に建てた base を持つ全員の
`pytest tests/` が赤くなり、赤の意味が「壊れている」と「イメージが古い」で混ざる。
「無ければ skip」の別のテストは「無い」を検査できない。

建てたイメージで手で確かめる観点:

- `docker run --rm --entrypoint /bin/bash devbase-base:latest -c 'shellcheck --version'` が
終了コード 0 で終わり、`version:` の行を出すこと。base を建て直した後に建てた
`devbase-general:latest` / `devbase-php:latest` でも同じであること
- `echo $foo` の 1 行を持つ Bash スクリプトへ `shellcheck` を走らせると、出力に `SC2086` を
含み、終了コード 1 で終わること(言語サーバが使うのはこの診断である)
- 新しく入るのが `shellcheck` と `libnuma1` の 2 パッケージで、
`dpkg-query -W -f='${Installed-Size}\n' shellcheck libnuma1` の合計が 30720 KB 以下であること。
`docker images` の前後の差では測らない(`--no-cache` の建て直しは他の取得物の版も入れ替える)

CI はイメージを建てるジョブを持たないため、イメージの中の観点は CI では確かめない。

## 関連リンク

- [コンテナ操作ガイド: Bash の静的検査](../user/container-operations.md#bash-の静的検査base-以降)
- [base イメージの文字の描画と、文書を扱う道具](base-image-rendering.md)
- [ShellCheck](https://www.shellcheck.net/)
26 changes: 25 additions & 1 deletion docs/user/container-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ graph TD

| イメージ | ベース | 主な内容 | 用途 |
|---------|-------|---------|------|
| **base** | Ubuntu 26.04 | Docker CLI、Python 3、日本語フォント、PDF / OOXML の道具 | 最小限の開発環境 |
| **base** | Ubuntu 26.04 | Docker CLI、Python 3、日本語フォント、PDF / OOXML の道具、shellcheck | 最小限の開発環境 |
| **general** | base | AWS CLI、gcloud、Terraform、Node.js 20、AI CLI | 汎用開発環境 |
| **php** | general | PHP 8.5、Composer、MySQL Shell | PHP 8.5 系 開発 |
| **php85** | general | PHP 8.5、Composer、MySQL Shell | PHP 8.5 系 開発 |
Expand Down Expand Up @@ -418,6 +418,30 @@ fontconfig は Chromium / Playwright のスクリーンショット、PDF の生
[base イメージの文字の描画と、文書を扱う道具](../specifications/base-image-rendering.md)
にあります。

### Bash の静的検査(base 以降)

base イメージには [ShellCheck](https://www.shellcheck.net/)(`shellcheck`)が入っています。
コンテナの中で Bash スクリプトを検査できます。

```bash
shellcheck path/to/script.sh
```

指摘は `SC2086` のような番号つきで出て、指摘があれば終了コード 1 で終わります。
bash-language-server などの言語サーバは Bash の診断を `shellcheck` に任せているため、
コンテナの中で言語サーバを動かすときもこれが使われます(言語サーバ自体は base に入っていません)。

版は固定しておらず、base を建てた時点の Ubuntu のアーカイブの版が入ります。
`containers/lfm` と `containers/snapshot` は base を継がないため入っていません。
置き場所・入れ損ないの止め方・版の扱いの仕様は
[base イメージの Bash の静的検査(shellcheck)](../specifications/base-image-shellcheck.md)
にあります。

> **`devbase build base --no-cache` で base を建て直すと反映されます。** `devbase up` だけでは
> 反映されません。派生イメージ(`general` / `php` など)を使っているプロジェクトは、その
> 派生イメージも建て直し、稼働中のコンテナは `devbase down` → `devbase up` で作り直して
> ください。`devbase rebuild` では建て直りません。

### AI CLI エイリアス

general イメージ以降のコンテナ内では、以下の AI CLI ツールがエイリアスとして利用可能です。
Expand Down
78 changes: 78 additions & 0 deletions issues/old/PLAN67_base-shellcheck-impl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# PLAN67: base イメージに shellcheck を入れる の実装計画

## 関連リンク

- 課題: devbasex/devbase#249
- 要求と受け入れ条件: [PLAN67_base-shellcheck.md](PLAN67_base-shellcheck.md)(受け入れ条件 1〜9)
- 設計: [PLAN67_base-shellcheck-design.md](PLAN67_base-shellcheck-design.md)(決定 1〜5)
- 設計の Pull Request: devbasex/devbase#250(マージ済み)

## モード

`standard`。base イメージが同梱するコマンドを変え、建て直した全員に届く(要求の文書の根拠のとおり)。

## 目的と非目的

達成したい状態:

- base と派生イメージのコンテナで `shellcheck` が使え、診断を返す
- shellcheck が入っていないイメージはビルドの時点で止まる

やらないこと(要求の文書の「含まない」のとおり):

- bash-language-server の導入、#247 の片付け、CI の変更、版の固定、lfm / snapshot への導入
- 「イメージの詳細」の表の、base 以外の行のベース列の訂正(実装中に見つけた。#251 として起票)

## 修正対象

- `containers/base/Dockerfile`
- `tests/containers/test_base_dockerfile_shellcheck.py`(新設)
- `docs/user/container-operations.md`
- `CHANGELOG.md`

## タスク分解

### Task 1: 1 回目の apt-get install の一覧へ shellcheck を足す

- **対象ファイル:** `tests/containers/test_base_dockerfile_shellcheck.py`、`containers/base/Dockerfile`
- **変更内容:** `poppler-utils ...;` の行の `;` を `\` に変え、理由のコメントと `shellcheck; \` を足す(設計「入出力の契約」)
- **満たす受け入れ条件:** 5
- **進め方:** 失敗するテスト(1 回目の一覧に `shellcheck` がある / `shellcheck` を入れる別の `RUN` が無く `apt-get update` が 2 回のまま)→ Dockerfile の変更 → 整理

### Task 2: 版の確認の RUN へ `shellcheck --version` を足す

- **対象ファイル:** 同上
- **変更内容:** `session-manager-plugin --version` の後へ `&& shellcheck --version`
- **満たす受け入れ条件:** 4
- **進め方:** 失敗するテスト(版の確認の `RUN` に `shellcheck --version` がある)→ Dockerfile の変更

### Task 3: 利用者向け文書と CHANGELOG

- **対象ファイル:** `docs/user/container-operations.md`、`CHANGELOG.md`
- **変更内容:** 「イメージの詳細」の表の base の行へ shellcheck、新しい小節「Bash の静的検査(base 以降)」、`[Unreleased]` の `### Added`。表以外の 2 か所に `devbase build base --no-cache` が要ることを書く
- **満たす受け入れ条件:** 9
- **進め方:** 文書のためテスト駆動を適用しない。差分の目視で確かめる

### Task 4: 手元で建てて確かめる

- **対象:** 手元の arm64 の Docker
- **変更内容:** 無し(証跡の採取)。変更前の `devbase-base:latest` で受け入れ条件 6 の `apt-get install -s` を採ってから、`devbase build base --no-cache` → `devbase-general` / `devbase-php` の建て直し → 受け入れ条件 1・2・3・6 のコマンド
- **満たす受け入れ条件:** 1・2・3・6・8。7 は `uv run --locked pytest tests/ -q`
- **進め方:** 出力を Pull Request 本文へ貼る。CI はイメージを建てない(要求の文書の前提 2)

## リスクと対処

| リスク | 対処 |
| --- | --- |
| Docker のビルドキャッシュが壊れた層を配る | `--no-cache` で建てる。0 バイトのファイルが出たら builder prune の後に建て直す |
| 変更前のイメージを建て直しで失う | 受け入れ条件 6 の `apt-get install -s` を建て直しの前に採る |
| pytest が実環境の `DEVBASE_ROOT` を継承する | 新しいテストは Dockerfile の文字列しか読まない |

## 切り戻し手順

要求の文書の「切り戻し手順」のとおり(revert → `devbase build base --no-cache` → 派生イメージの建て直し → コンテナの作り直し)。

## 完了の定義

- [ ] 受け入れ条件 1〜9 をすべて満たし、条件ごとの検証手段と結果を Pull Request 本文に載せた
- [ ] `uv run --locked pytest tests/ -q` が終了コード 0
File renamed without changes.
Loading
Loading