From d34cf90677df95205b46c843b8b8297953c8751e Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 17:58:28 +0900 Subject: [PATCH 1/5] chore(devcontainer): add Dev Container config for Claude Code isolated execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code をホスト直接実行するのではなく、devcontainer 内で動かすための設定を 追加する。社内ガイドライン (NaCl Claude Code 利用ガイドライン v0.2) の階層 B (OSS / 自社開発) の隔離環境推奨に準拠する。 ## 設計方針 - 既存の `docker-compose.yml` の `app` サービスを再利用する override 構成 - 物理的にアクセスできる範囲をホワイトリスト方式で制限: - `` → `/app` (既存通り) - `~/ghq` → `/ghq` (submodule の origin/upstream、関連 OSS 参照) - `~/.gitconfig` → `/root/.gitconfig` (ro) - `~/.config/gh` → `/root/.config/gh` (fine-grained PAT) - 以下は意図的にマウントしない: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library` - VS Code Dev Containers / devpod / docker compose のいずれからでも起動可能 ## ファイル - `.devcontainer/devcontainer.json` — devcontainer 設定本体。claude-code feature と github-cli feature を追加 - `.devcontainer/docker-compose.devcontainer.yml` — 既存 compose への override (sleep infinity で長寿命化 + 追加マウント) - `.devcontainer/README.md` — 起動方法、設計方針、トラブルシューティング 既存の `docker compose run --rm app ...` ワークフローには影響しない。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 80 +++++++++++++++++++ .devcontainer/devcontainer.json | 25 ++++++ .devcontainer/docker-compose.devcontainer.yml | 15 ++++ 3 files changed, 120 insertions(+) create mode 100644 .devcontainer/README.md create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.devcontainer.yml diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 00000000000..cb941b43841 --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,80 @@ +# .devcontainer + +Claude Code をホスト直接実行せず、隔離されたコンテナ内で動かすための devcontainer 設定です。 +NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** に準拠します。 + +## 設計方針 + +- **既存の `docker-compose.yml` の `app` サービスを再利用** する。`docker-compose.devcontainer.yml` で mount のみ追加する override 構成 +- ホスト全体ではなく、**作業に必要なディレクトリのみ** をマウントする(物理アクセス範囲の制限) +- 既存の `bin/dx` / `bin/setup-worktree` / git worktree 運用と共存する +- VS Code Dev Containers と devpod の両方で動作する標準準拠 + +## マウント一覧 + +| ホスト側 | コンテナ内 | 用途 | +|---|---|---| +| `` | `/app` | 作業ディレクトリ(既存 compose と同じ) | +| `~/ghq` | `/ghq` | submodule の origin/upstream、関連 OSS の参照・push | +| `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー名・コミット署名 | +| `~/.config/gh` | `/root/.config/gh` | fine-grained PAT を含む gh CLI 認証情報 | + +**マウントしないもの**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library`, 他案件ディレクトリ。 + +## 起動方法 + +### VS Code Dev Containers + +1. VS Code に「Dev Containers」拡張をインストール +2. リポジトリを VS Code で開く +3. コマンドパレットから **Dev Containers: Reopen in Container** + +### devpod (CLI) + +```bash +# 初回のみ +brew install devpod +devpod provider add docker + +# 起動 +cd +devpod up . + +# Claude Code をコンテナ内で起動 +devpod ssh smalruby3-editor -- claude + +# 停止 +devpod stop smalruby3-editor +``` + +### docker compose 直接 (CLI only) + +```bash +# devcontainer のオーバーレイを適用して起動 +docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontainer.yml up -d app + +# コンテナに入る +docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontainer.yml exec app bash +``` + +## 既存ワークフローへの影響 + +| 既存仕組み | 影響 | +|---|---| +| `docker compose run --rm app ...` | 変更なし。devcontainer を立てなくても従来通り動く | +| `bin/dx` | 変更なし(devcontainer 内では二重 docker を避けるラッパーを後続フェーズで検討) | +| `bin/setup-worktree` | `postCreateCommand` で自動実行(`|| true` で初回失敗を許容) | +| git worktree | compose の `name: smalruby3-editor` 固定で named volume を共有 | + +## AWS CDK deploy について + +`~/.aws` はマウントしません。`cdk deploy` は人間が diff を見て発動する操作なので、 +**ホスト側で実行** することを推奨します。コンテナ内では `cdk synth` / `cdk diff` まで(AWS 認証不要)。 + +必要時のみ短命 STS token を環境変数で注入する運用も可能(要検討)。 + +## トラブルシューティング + +- **gh auth が効かない**: `~/.config/gh/hosts.yml` がホスト側に存在するか確認。fine-grained PAT は keychain ではなく hosts.yml に書かれるケースもある +- **`/ghq` が空**: `ghq root` の出力が `~/ghq` 以外を指していないか確認 +- **ポート 8601 が見えない**: VS Code は `forwardPorts` で自動転送。devpod / 直接 compose では `docker-compose.yml` の `ports` 設定で `localhost:8601` に公開される diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..9458b0d8c7a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +{ + "name": "smalruby3-editor (Claude Code)", + "dockerComposeFile": ["../docker-compose.yml", "docker-compose.devcontainer.yml"], + "service": "app", + "workspaceFolder": "/app", + "overrideCommand": false, + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + "remoteEnv": { + "GHQ_ROOT": "/ghq" + }, + "forwardPorts": [8601], + "postCreateCommand": "bin/setup-worktree || true", + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] + } + } +} diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml new file mode 100644 index 00000000000..f022cbf4e5c --- /dev/null +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -0,0 +1,15 @@ +services: + app: + # devcontainer 用に長寿命化(既存 compose は `npm start` で短命) + command: sleep infinity + volumes: + - type: bind + source: ${HOME}/ghq + target: /ghq + - type: bind + source: ${HOME}/.gitconfig + target: /root/.gitconfig + read_only: true + - type: bind + source: ${HOME}/.config/gh + target: /root/.config/gh From 963896abb4eff904a8a822a3474df32b134300ee Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 22:34:45 +0900 Subject: [PATCH 2/5] fix(devcontainer): inject GH_TOKEN from host shell for macOS gh keychain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS の `gh` は PAT を Keychain に保存するため、~/.config/gh を bind mount しても oauth_token がコンテナに渡らない。実機検証で発覚した 問題への対処。 - devcontainer.json の remoteEnv に GH_TOKEN: ${localEnv:GH_TOKEN} を追加 - initialize.sh を新設し、GH_TOKEN 未設定時に警告を出す - README に「ホスト側で `export GH_TOKEN=$(gh auth token)` してから起動」 の手順を明記 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 18 ++++++++++++++-- .devcontainer/devcontainer.json | 4 +++- .devcontainer/initialize.sh | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100755 .devcontainer/initialize.sh diff --git a/.devcontainer/README.md b/.devcontainer/README.md index cb941b43841..88992892556 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -17,10 +17,24 @@ NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** | `` | `/app` | 作業ディレクトリ(既存 compose と同じ) | | `~/ghq` | `/ghq` | submodule の origin/upstream、関連 OSS の参照・push | | `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー名・コミット署名 | -| `~/.config/gh` | `/root/.config/gh` | fine-grained PAT を含む gh CLI 認証情報 | +| `~/.config/gh` | `/root/.config/gh` | gh CLI の設定 (ホスト名・ユーザー名) | **マウントしないもの**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library`, 他案件ディレクトリ。 +### gh CLI の認証トークンについて (macOS 必須手順) + +macOS の `gh` は PAT を **Keychain** に保存するため、`~/.config/gh` を +bind mount しても `oauth_token` がコンテナに渡らない。devcontainer 起動前に +ホスト側のシェルで以下を実行して `GH_TOKEN` 環境変数として注入する: + +```bash +export GH_TOKEN=$(gh auth token) +``` + +devcontainer.json の `remoteEnv.GH_TOKEN: "${localEnv:GH_TOKEN}"` 経由で +コンテナ内 `gh` が PAT を読み取れるようになる。未設定で devcontainer を +起動した場合、`initialize.sh` が警告を出す (devcontainer 自体は起動する)。 + ## 起動方法 ### VS Code Dev Containers @@ -75,6 +89,6 @@ docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontaine ## トラブルシューティング -- **gh auth が効かない**: `~/.config/gh/hosts.yml` がホスト側に存在するか確認。fine-grained PAT は keychain ではなく hosts.yml に書かれるケースもある +- **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する (上記「gh CLI の認証トークンについて」参照) - **`/ghq` が空**: `ghq root` の出力が `~/ghq` 以外を指していないか確認 - **ポート 8601 が見えない**: VS Code は `forwardPorts` で自動転送。devpod / 直接 compose では `docker-compose.yml` の `ports` 設定で `localhost:8601` に公開される diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9458b0d8c7a..e36161058ba 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,9 +9,11 @@ "ghcr.io/devcontainers/features/github-cli:1": {} }, "remoteEnv": { - "GHQ_ROOT": "/ghq" + "GHQ_ROOT": "/ghq", + "GH_TOKEN": "${localEnv:GH_TOKEN}" }, "forwardPorts": [8601], + "initializeCommand": ".devcontainer/initialize.sh", "postCreateCommand": "bin/setup-worktree || true", "customizations": { "vscode": { diff --git a/.devcontainer/initialize.sh b/.devcontainer/initialize.sh new file mode 100755 index 00000000000..0016f657fc3 --- /dev/null +++ b/.devcontainer/initialize.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# .devcontainer/initialize.sh +# +# Runs on the host before the devcontainer starts (devcontainer.json の +# initializeCommand から呼ばれる)。macOS の `gh` は PAT を keychain に +# 保存するため、~/.config/gh/hosts.yml を bind mount しただけでは +# コンテナ内 `gh` から認証できない。ホスト側で `gh auth token` を実行して +# 取得し、環境変数 GH_TOKEN として export することで、devcontainer.json の +# remoteEnv 経由でコンテナに渡す。 +# +# 注意: このスクリプトは export を親プロセス (devcontainer CLI / VS Code) に +# 反映できない。実際は devcontainer.json の `${localEnv:GH_TOKEN}` を +# 解釈するのは devcontainer ツール側なので、利用者は事前に手元で +# export GH_TOKEN=$(gh auth token) +# しておく必要がある。本スクリプトは GH_TOKEN が未設定だった場合に +# 警告を出し、ユーザーに手順を案内する。 + +set -euo pipefail + +if [[ -z "${GH_TOKEN:-}" ]]; then + if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then + cat <<'MSG' >&2 +warning: GH_TOKEN is not set in the host shell. +The devcontainer mounts ~/.config/gh, but on macOS the token is stored in +the keychain and is not available inside the container. + +To enable `gh` inside the devcontainer, run before launching: + + export GH_TOKEN=$(gh auth token) + +Then start/reopen the devcontainer. +MSG + else + echo "warning: host gh CLI not authenticated. devcontainer-internal gh will be unusable." >&2 + fi +fi + +exit 0 From 2ec764b094693206db29c013008e0194a5fb54a2 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 23:01:11 +0900 Subject: [PATCH 3/5] fix(devcontainer): skip setup-worktree on main checkout in postCreateCommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VS Code Reopen in Container を main checkout で実行したとき、 postCreateCommand の `bin/setup-worktree || true` が "this script is for worktrees, not the main checkout" を stderr に出力し、 VS Code のターミナルに "error" として表示されていた (exit code 0 だが UX 上は失敗に見える)。 post-create.sh を新設し、git-common-dir と git-dir を比較して worktree のときだけ bin/setup-worktree を実行するように修正。 main checkout では何もしない (env / node_modules / dist は既に揃っているため)。 加えて、Dev Containers CLI が `--experimental-lockfile` で生成した devcontainer-lock.json をコミット (features の digest を固定してサプライチェーンを保護)。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 2 +- .devcontainer/devcontainer-lock.json | 14 ++++++++++++ .devcontainer/devcontainer.json | 2 +- .devcontainer/post-create.sh | 32 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .devcontainer/devcontainer-lock.json create mode 100755 .devcontainer/post-create.sh diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 88992892556..309c74103df 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -77,7 +77,7 @@ docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontaine |---|---| | `docker compose run --rm app ...` | 変更なし。devcontainer を立てなくても従来通り動く | | `bin/dx` | 変更なし(devcontainer 内では二重 docker を避けるラッパーを後続フェーズで検討) | -| `bin/setup-worktree` | `postCreateCommand` で自動実行(`|| true` で初回失敗を許容) | +| `bin/setup-worktree` | `.devcontainer/post-create.sh` 経由で **worktree のときだけ** 実行(main checkout では skip) | | git worktree | compose の `name: smalruby3-editor` 固定で named volume を共有 | ## AWS CDK deploy について diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000000..93d4d3e6533 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,14 @@ +{ + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": { + "version": "1.0.5", + "resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a", + "integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a" + }, + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "1.1.0", + "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671", + "integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e36161058ba..33f6346e9cd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,7 @@ }, "forwardPorts": [8601], "initializeCommand": ".devcontainer/initialize.sh", - "postCreateCommand": "bin/setup-worktree || true", + "postCreateCommand": ".devcontainer/post-create.sh", "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 00000000000..26107b7ff3c --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# .devcontainer/post-create.sh +# +# Runs inside the container after creation (devcontainer.json の +# postCreateCommand から呼ばれる)。 +# +# - worktree であれば bin/setup-worktree を実行 (env コピー + npm install + build:dev) +# - main checkout であれば既存の node_modules / dist / .env がそのままあるので +# 何もしない (husky hooks も既にインストール済みのはず) + +set -euo pipefail + +cd /app + +GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null || echo "") +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || echo "") + +if [[ -z "${GIT_COMMON_DIR}" ]]; then + echo "post-create: not a git repository, skipping setup" + exit 0 +fi + +GIT_COMMON_ABS=$(cd "${GIT_COMMON_DIR}" && pwd) +GIT_DIR_ABS=$(cd "${GIT_DIR}" && pwd) + +if [[ "${GIT_COMMON_ABS}" == "${GIT_DIR_ABS}" ]]; then + echo "post-create: main checkout detected, no worktree setup needed" + exit 0 +fi + +echo "post-create: worktree detected, running bin/setup-worktree" +exec bin/setup-worktree From 1224014f49ef6c0e50eeb885969e05c17ffe09f1 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 23:15:22 +0900 Subject: [PATCH 4/5] feat(devcontainer): share ~/.claude auth/config, isolate other projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devcontainer 内で claude を起動するとログイン状態・設定・skills・memory が リセットされてしまう問題への対応。素朴に ~/.claude 全体をマウントすると 他プロジェクトの transcripts/sessions/history が container から見えてしまうため、 選択的マウントを採用。 ## マウントするもの (グローバルかつ共有して安全なもの) - ~/.claude.json — 認証 - ~/.claude/settings.json (ro) — グローバル設定 - ~/.claude/skills/ (ro) — 自作スキル - ~/.claude/plugins/ (ro) — プラグイン - ~/.claude/statusline-command.sh (ro) — カスタム statusline ## マウントしないもの (他プロジェクト漏れ防止) - ~/.claude/projects/ 全体, ~/.claude/sessions/, ~/.claude/history.jsonl - ~/.claude/file-history/, ~/.claude/shell-snapshots/, ~/.claude/session-env/ ## このプロジェクトの memory だけ共有する仕組み ホスト側の per-project ディレクトリ名はユーザーのパスに依存。 コンテナ内では workspaceFolder=/app 固定なので slug は常に `-app`。 initialize.sh がホスト側に `-app` シンボリックリンクを作成し、 ホスト本来の slug を指すようにする。compose は `~/.claude/projects/-app` を マウントし、コンテナの claude は透過的に同じ memory を読み書きできる。 他のユーザー / worktree / マシンに切り替えても、initialize.sh がそのときの host slug を計算してリンクを張り直す。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 14 +++++++++- .devcontainer/docker-compose.devcontainer.yml | 28 +++++++++++++++++++ .devcontainer/initialize.sh | 28 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 309c74103df..6d2b00cd05d 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -18,8 +18,20 @@ NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** | `~/ghq` | `/ghq` | submodule の origin/upstream、関連 OSS の参照・push | | `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー名・コミット署名 | | `~/.config/gh` | `/root/.config/gh` | gh CLI の設定 (ホスト名・ユーザー名) | +| `~/.claude.json` | `/root/.claude.json` | Claude Code 認証 | +| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 | +| `~/.claude/skills` | 同上 (ro) | 自作 skills | +| `~/.claude/plugins` | 同上 (ro) | Claude Code プラグイン | +| `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | +| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有の memory のみ (後述) | -**マウントしないもの**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library`, 他案件ディレクトリ。 +**マウントしないもの**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, `~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, `~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 + +### Claude Code の認証・設定・memory 共有について + +`~/.claude` のうち、共有して安全なもの(グローバル設定・自作 skills・認証)だけを bind mount し、**他プロジェクトの transcripts や履歴は意図的に隔離** する設計。 + +このプロジェクトのメモリ (`~/.claude/projects//memory/`) はホスト側スラッグがユーザーのパスに依存するため、`initialize.sh` がホスト側に **`~/.claude/projects/-app` → `~/.claude/projects/`** のシンボリックリンクを作成する。コンテナ内では workspace=`/app` のため slug=`-app` で固定され、リンク経由で同じ memory を読み書きできる。他のマシンや他の worktree に切り替えても、`initialize.sh` がそのときの host slug にリンクを張り直す。 ### gh CLI の認証トークンについて (macOS 必須手順) diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml index f022cbf4e5c..4d5e1d4e92c 100644 --- a/.devcontainer/docker-compose.devcontainer.yml +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -13,3 +13,31 @@ services: - type: bind source: ${HOME}/.config/gh target: /root/.config/gh + + # Claude Code: グローバル設定・認証のみ共有 (per-project な transcripts / + # sessions / history はマウントしないことで他プロジェクトの漏れを防ぐ) + - type: bind + source: ${HOME}/.claude.json + target: /root/.claude.json + - type: bind + source: ${HOME}/.claude/settings.json + target: /root/.claude/settings.json + read_only: true + - type: bind + source: ${HOME}/.claude/skills + target: /root/.claude/skills + read_only: true + - type: bind + source: ${HOME}/.claude/plugins + target: /root/.claude/plugins + read_only: true + - type: bind + source: ${HOME}/.claude/statusline-command.sh + target: /root/.claude/statusline-command.sh + read_only: true + # このプロジェクトの memory のみ共有 (他プロジェクトの転写は隔離)。 + # ホスト側の `-app` シンボリックリンクは initialize.sh が作成し、 + # ホスト本来の slug (`-Users-`) を指すようにする。 + - type: bind + source: ${HOME}/.claude/projects/-app + target: /root/.claude/projects/-app diff --git a/.devcontainer/initialize.sh b/.devcontainer/initialize.sh index 0016f657fc3..fac0d93b131 100755 --- a/.devcontainer/initialize.sh +++ b/.devcontainer/initialize.sh @@ -35,4 +35,32 @@ MSG fi fi +# Per-project Claude memory bridge: +# コンテナ内では workspaceFolder=/app なので claude が使う project slug は常に `-app`。 +# ホスト側の本来の slug は HOME と workspace のパスから生成される (例: +# `-Users-kouji-work-smalruby-smalruby3-editor`)。 +# ホスト側に `-app` シンボリックリンクを張って同じディレクトリを指すようにし、 +# compose が `${HOME}/.claude/projects/-app` をマウントできるようにする。 +# これにより、このプロジェクトの memory だけ container と host で透明に共有され、 +# 他プロジェクトの transcripts/sessions は隔離される。 +WORKSPACE_DIR="$(cd "$(dirname "$0")/.." && pwd)" +HOST_PROJECT_SLUG="$(printf '%s' "${WORKSPACE_DIR}" | sed 's:/:-:g')" +HOST_PROJECT_DIR="${HOME}/.claude/projects/${HOST_PROJECT_SLUG}" +ALIAS_DIR="${HOME}/.claude/projects/-app" + +mkdir -p "${HOST_PROJECT_DIR}/memory" + +if [[ -L "${ALIAS_DIR}" ]]; then + # 既存リンク。target が今回と違うなら張り替え (worktree 切替対応) + CURRENT_TARGET="$(readlink "${ALIAS_DIR}")" + if [[ "${CURRENT_TARGET}" != "${HOST_PROJECT_DIR}" ]]; then + rm "${ALIAS_DIR}" + ln -s "${HOST_PROJECT_DIR}" "${ALIAS_DIR}" + fi +elif [[ -e "${ALIAS_DIR}" ]]; then + echo "warning: ${ALIAS_DIR} exists as a non-symlink; not touching it" >&2 +else + ln -s "${HOST_PROJECT_DIR}" "${ALIAS_DIR}" +fi + exit 0 From c19dea477d2cce9d490ccfb4e36f3532e408a054 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 23:19:53 +0900 Subject: [PATCH 5/5] refactor(devcontainer): split personal mounts into .local.yml override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit これまで docker-compose.devcontainer.yml に詰め込んでいた個人依存のマウント (ghq, ~/.claude*) を docker-compose.local.yml に分離。本ファイルは gitignore 対象で、各自が .example からコピーして自分の環境に合わせる。 ## ファイル構成 - docker-compose.devcontainer.yml (commit): 全員共通 (gitconfig, gh config) - docker-compose.local.yml.example (commit): テンプレート (ghq, ~/.claude*) - docker-compose.local.yml (gitignore): 各自カスタマイズ - initialize.sh: ファイル無ければ空の services スタブを自動生成 ## 理由 ghq の有無、Claude Code 利用の有無、~/ がどう構成されているかは開発者ごとに 異なる。共有 compose ファイルに個人依存のパスが残ると、他開発者の Reopen in Container が起動失敗する (bind mount のソース不在で fail)。 ## 動作 - Claude Code を使わない人: 何もしなくても起動する (空 stub だけが生成される) - 個人 mount を有効化したい人: cp .example → .local.yml で開始 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 40 ++++++++-- .devcontainer/devcontainer.json | 6 +- .devcontainer/docker-compose.devcontainer.yml | 33 +------- .../docker-compose.local.yml.example | 55 +++++++++++++ .devcontainer/initialize.sh | 77 +++++++++++-------- .gitignore | 3 + 6 files changed, 143 insertions(+), 71 deletions(-) create mode 100644 .devcontainer/docker-compose.local.yml.example diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 6d2b00cd05d..90bcce00f11 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -10,22 +10,50 @@ NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** - 既存の `bin/dx` / `bin/setup-worktree` / git worktree 運用と共存する - VS Code Dev Containers と devpod の両方で動作する標準準拠 -## マウント一覧 +## マウント構成 + +devcontainer のマウントは **共有用** と **個人用** を 2 つの compose ファイルに +分けている。 + +### `docker-compose.devcontainer.yml` (commit、全員共通) | ホスト側 | コンテナ内 | 用途 | |---|---|---| | `` | `/app` | 作業ディレクトリ(既存 compose と同じ) | -| `~/ghq` | `/ghq` | submodule の origin/upstream、関連 OSS の参照・push | | `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー名・コミット署名 | -| `~/.config/gh` | `/root/.config/gh` | gh CLI の設定 (ホスト名・ユーザー名) | +| `~/.config/gh` | `/root/.config/gh` | gh CLI 設定 | + +### `docker-compose.local.yml` (`.gitignore`、各自で `*.example` からコピー) + +ghq の有無、Claude Code の利用有無などはユーザーによって違うため、個人ごとに +override する。`.devcontainer/docker-compose.local.yml.example` をコピーして +自分の環境に合わせる: + +```bash +cp .devcontainer/docker-compose.local.yml.example \ + .devcontainer/docker-compose.local.yml +# 不要な mount をコメントアウトして使う +``` + +template には以下がデフォルトで含まれる: + +| ホスト側 | コンテナ内 | 用途 | +|---|---|---| +| `~/ghq` | `/ghq` | 関連 OSS の参照・push (使わない人はコメントアウト) | | `~/.claude.json` | `/root/.claude.json` | Claude Code 認証 | | `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 | | `~/.claude/skills` | 同上 (ro) | 自作 skills | -| `~/.claude/plugins` | 同上 (ro) | Claude Code プラグイン | +| `~/.claude/plugins` | 同上 (ro) | プラグイン | | `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | -| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有の memory のみ (後述) | +| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有 memory | + +`docker-compose.local.yml` が無い場合は `initialize.sh` が空の stub を自動生成 +するので、Claude Code を使わない・ghq を使わない人は何もせずそのまま動作する。 -**マウントしないもの**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, `~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, `~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 +**マウントしないもの (全員)**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, +`~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, +`~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, +`~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 ### Claude Code の認証・設定・memory 共有について diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 33f6346e9cd..d81dc1f22da 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,10 @@ { "name": "smalruby3-editor (Claude Code)", - "dockerComposeFile": ["../docker-compose.yml", "docker-compose.devcontainer.yml"], + "dockerComposeFile": [ + "../docker-compose.yml", + "docker-compose.devcontainer.yml", + "docker-compose.local.yml" + ], "service": "app", "workspaceFolder": "/app", "overrideCommand": false, diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml index 4d5e1d4e92c..ca3d58072c3 100644 --- a/.devcontainer/docker-compose.devcontainer.yml +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -3,9 +3,8 @@ services: # devcontainer 用に長寿命化(既存 compose は `npm start` で短命) command: sleep infinity volumes: - - type: bind - source: ${HOME}/ghq - target: /ghq + # 全開発者で共通のマウント。個人依存のもの (~/ghq, ~/.claude 等) は + # docker-compose.local.yml に置く (gitignore、各自で .example からコピー)。 - type: bind source: ${HOME}/.gitconfig target: /root/.gitconfig @@ -13,31 +12,3 @@ services: - type: bind source: ${HOME}/.config/gh target: /root/.config/gh - - # Claude Code: グローバル設定・認証のみ共有 (per-project な transcripts / - # sessions / history はマウントしないことで他プロジェクトの漏れを防ぐ) - - type: bind - source: ${HOME}/.claude.json - target: /root/.claude.json - - type: bind - source: ${HOME}/.claude/settings.json - target: /root/.claude/settings.json - read_only: true - - type: bind - source: ${HOME}/.claude/skills - target: /root/.claude/skills - read_only: true - - type: bind - source: ${HOME}/.claude/plugins - target: /root/.claude/plugins - read_only: true - - type: bind - source: ${HOME}/.claude/statusline-command.sh - target: /root/.claude/statusline-command.sh - read_only: true - # このプロジェクトの memory のみ共有 (他プロジェクトの転写は隔離)。 - # ホスト側の `-app` シンボリックリンクは initialize.sh が作成し、 - # ホスト本来の slug (`-Users-`) を指すようにする。 - - type: bind - source: ${HOME}/.claude/projects/-app - target: /root/.claude/projects/-app diff --git a/.devcontainer/docker-compose.local.yml.example b/.devcontainer/docker-compose.local.yml.example new file mode 100644 index 00000000000..242d8a8332d --- /dev/null +++ b/.devcontainer/docker-compose.local.yml.example @@ -0,0 +1,55 @@ +# .devcontainer/docker-compose.local.yml +# +# 個人ごとに devcontainer を拡張するための override。`.gitignore` 対象。 +# 使い方: このファイルを `docker-compose.local.yml` にコピーし、自分の環境に +# 合わせて編集する。 +# +# cp .devcontainer/docker-compose.local.yml.example \ +# .devcontainer/docker-compose.local.yml +# +# devcontainer.json はこのファイルを dockerComposeFile に含めるため、Reopen +# in Container すると自動で適用される。空のままでも害はない。 +# +# 注意: +# - bind mount のソースが存在しないと devcontainer の起動が失敗するため、 +# 不要なエントリはコメントアウトしておく。 +# - Claude Code を使わない人は claude セクションを丸ごと省略してよい。 +# - ghq を使わない人は ghq セクションを省略してよい。 + +services: + app: + volumes: + # --- ghq (関連 OSS の参照) --- + # 自分の ghq root が ~/ghq でない場合は source を書き換える。 + - type: bind + source: ${HOME}/ghq + target: /ghq + + # --- Claude Code: グローバル設定・認証のみ共有 --- + # 他プロジェクトの transcripts / sessions / history は意図的に + # マウントしないことで漏れを防ぐ。詳細は README.md 参照。 + - type: bind + source: ${HOME}/.claude.json + target: /root/.claude.json + - type: bind + source: ${HOME}/.claude/settings.json + target: /root/.claude/settings.json + read_only: true + - type: bind + source: ${HOME}/.claude/skills + target: /root/.claude/skills + read_only: true + - type: bind + source: ${HOME}/.claude/plugins + target: /root/.claude/plugins + read_only: true + - type: bind + source: ${HOME}/.claude/statusline-command.sh + target: /root/.claude/statusline-command.sh + read_only: true + # このプロジェクトの memory のみ共有。initialize.sh が + # ${HOME}/.claude/projects/-app を host slug へのシンボリックリンクと + # して作成する。 + - type: bind + source: ${HOME}/.claude/projects/-app + target: /root/.claude/projects/-app diff --git a/.devcontainer/initialize.sh b/.devcontainer/initialize.sh index fac0d93b131..cbad9e744e1 100755 --- a/.devcontainer/initialize.sh +++ b/.devcontainer/initialize.sh @@ -2,21 +2,35 @@ # .devcontainer/initialize.sh # # Runs on the host before the devcontainer starts (devcontainer.json の -# initializeCommand から呼ばれる)。macOS の `gh` は PAT を keychain に -# 保存するため、~/.config/gh/hosts.yml を bind mount しただけでは -# コンテナ内 `gh` から認証できない。ホスト側で `gh auth token` を実行して -# 取得し、環境変数 GH_TOKEN として export することで、devcontainer.json の -# remoteEnv 経由でコンテナに渡す。 +# initializeCommand から呼ばれる)。以下を行う: # -# 注意: このスクリプトは export を親プロセス (devcontainer CLI / VS Code) に -# 反映できない。実際は devcontainer.json の `${localEnv:GH_TOKEN}` を -# 解釈するのは devcontainer ツール側なので、利用者は事前に手元で -# export GH_TOKEN=$(gh auth token) -# しておく必要がある。本スクリプトは GH_TOKEN が未設定だった場合に -# 警告を出し、ユーザーに手順を案内する。 +# 1. docker-compose.local.yml が無ければ空の stub を作る +# (devcontainer.json は常にこのファイルを参照するため) +# 2. GH_TOKEN が export されていなければ案内を出す +# (macOS の gh は PAT を keychain に保存するため、~/.config/gh を +# bind mount しただけではコンテナ内 gh から認証できない) +# 3. ホスト側に ~/.claude/projects/-app シンボリックリンクを作成 +# (Claude Code を使う人向けの per-project memory bridge) set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# --- 1) docker-compose.local.yml の stub 作成 ----------------------------- +LOCAL_COMPOSE="${SCRIPT_DIR}/docker-compose.local.yml" +if [[ ! -f "${LOCAL_COMPOSE}" ]]; then + cat >"${LOCAL_COMPOSE}" <<'EOF' +# Auto-generated by .devcontainer/initialize.sh. +# 個人ごとの mount を追加したい場合は +# .devcontainer/docker-compose.local.yml.example を参考に書き換える。 +# このファイルは .gitignore 対象なので、個人の変更は他開発者に共有されない。 +services: + app: {} +EOF + echo "initialize: created empty ${LOCAL_COMPOSE} (edit to add personal mounts)" +fi + +# --- 2) GH_TOKEN チェック ------------------------------------------------- if [[ -z "${GH_TOKEN:-}" ]]; then if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then cat <<'MSG' >&2 @@ -35,32 +49,29 @@ MSG fi fi -# Per-project Claude memory bridge: -# コンテナ内では workspaceFolder=/app なので claude が使う project slug は常に `-app`。 -# ホスト側の本来の slug は HOME と workspace のパスから生成される (例: -# `-Users-kouji-work-smalruby-smalruby3-editor`)。 -# ホスト側に `-app` シンボリックリンクを張って同じディレクトリを指すようにし、 -# compose が `${HOME}/.claude/projects/-app` をマウントできるようにする。 -# これにより、このプロジェクトの memory だけ container と host で透明に共有され、 -# 他プロジェクトの transcripts/sessions は隔離される。 -WORKSPACE_DIR="$(cd "$(dirname "$0")/.." && pwd)" -HOST_PROJECT_SLUG="$(printf '%s' "${WORKSPACE_DIR}" | sed 's:/:-:g')" -HOST_PROJECT_DIR="${HOME}/.claude/projects/${HOST_PROJECT_SLUG}" -ALIAS_DIR="${HOME}/.claude/projects/-app" +# --- 3) Claude Code per-project memory bridge ---------------------------- +# Claude Code をホスト側にインストールしていないユーザは何もしない。 +# このセクションは本人が docker-compose.local.yml で +# ~/.claude/projects/-app マウントを有効にしている場合に意味を持つ。 +if [[ -d "${HOME}/.claude/projects" ]]; then + WORKSPACE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + HOST_PROJECT_SLUG="$(printf '%s' "${WORKSPACE_DIR}" | sed 's:/:-:g')" + HOST_PROJECT_DIR="${HOME}/.claude/projects/${HOST_PROJECT_SLUG}" + ALIAS_DIR="${HOME}/.claude/projects/-app" -mkdir -p "${HOST_PROJECT_DIR}/memory" + mkdir -p "${HOST_PROJECT_DIR}/memory" -if [[ -L "${ALIAS_DIR}" ]]; then - # 既存リンク。target が今回と違うなら張り替え (worktree 切替対応) - CURRENT_TARGET="$(readlink "${ALIAS_DIR}")" - if [[ "${CURRENT_TARGET}" != "${HOST_PROJECT_DIR}" ]]; then - rm "${ALIAS_DIR}" + if [[ -L "${ALIAS_DIR}" ]]; then + CURRENT_TARGET="$(readlink "${ALIAS_DIR}")" + if [[ "${CURRENT_TARGET}" != "${HOST_PROJECT_DIR}" ]]; then + rm "${ALIAS_DIR}" + ln -s "${HOST_PROJECT_DIR}" "${ALIAS_DIR}" + fi + elif [[ -e "${ALIAS_DIR}" ]]; then + echo "warning: ${ALIAS_DIR} exists as a non-symlink; not touching it" >&2 + else ln -s "${HOST_PROJECT_DIR}" "${ALIAS_DIR}" fi -elif [[ -e "${ALIAS_DIR}" ]]; then - echo "warning: ${ALIAS_DIR} exists as a non-symlink; not touching it" >&2 -else - ln -s "${HOST_PROJECT_DIR}" "${ALIAS_DIR}" fi exit 0 diff --git a/.gitignore b/.gitignore index b2e5964b54c..26c1bdffe48 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,6 @@ infra/**/cdk.out.*/ infra/**/tmp/ infra/**/.cdk.staging/ infra/**/cdk-out-*/ + +# devcontainer の個人用 mount override (initialize.sh が空 stub を生成する) +.devcontainer/docker-compose.local.yml