Skip to content

Commit 24cf9cf

Browse files
takemi-ohamaclaude
andcommitted
feat(editor): Remote-SSH 跨ホストの attach 対応 + up 時のターミナル自動表示 (PLAN31_3)
Windows VS Code → Remote-SSH(Mac) → Mac の Docker 上コンテナ、という構成で devbase up の自動オープンが「コンテナが存在しません」で失敗する問題を修正。 実機検証の結果、ネスト authority attached-container+...@ssh-remote+<host> は 実際にサポートされており、これを使うと docker ルックアップが ssh 先(コンテナの ある側)で行われ解決できる(PLAN31_3 §2.3/§2.4 の当初想定を訂正)。 - build_attach_uri に ssh_host / docker_context を追加しネスト URI を生成 - DEVBASE_EDITOR_SSH_HOST(明示) / DEVBASE_EDITOR_DOCKER_CONTEXT(既定 docker context show) ※ ssh ホスト別名は VS Code が ssh 先端末 env に渡さず自動取得不可のため明示必須 - DEVBASE_OPEN_TERMINAL(既定 ON): up 時に folderOpen タスク .vscode/tasks.json を docker exec で配置し、フォルダを開くと統合ターミナルを自動表示(既存はスキップ) - CLI: --open-terminal / --no-open-terminal を追加 - docs / PLAN31_3 を実態へ更新、テスト追加 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f235d4b commit 24cf9cf

8 files changed

Lines changed: 467 additions & 33 deletions

File tree

docs/user/environment-variables.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,20 +149,48 @@ devbase はホストマシンの認証情報を自動収集し、コンテナ内
149149
| `DEVBASE_OPEN_EDITOR` | 真(`1`/`true`/`yes`/`on`)で `up` 後にエディタを開く(既定: OFF) |
150150
| `DEVBASE_EDITOR` | 起動コマンド(既定: `code`)。`cursor` / `code-insiders` 等も可 |
151151
| `DEVBASE_OPEN_INDEX` | scale 時に開く dev インスタンス番号(既定: `1`|
152+
| `DEVBASE_EDITOR_SSH_HOST` | Remote-SSH 跨ホスト構成での ssh-remote ホスト名(例 `mac2`)。下記「跨ホスト」参照 |
153+
| `DEVBASE_EDITOR_DOCKER_CONTEXT` | 跨ホスト時に ssh 先で使う docker context(既定: ホストの `docker context show`|
154+
| `DEVBASE_OPEN_TERMINAL` | 真で `up` 後に folderOpen ターミナル用 `.vscode/tasks.json` を配置(**既定: ON**|
152155

153-
都度の上書きは CLI フラグで行います: `devbase up --open` / `devbase up --no-open` / `devbase up --open-index N`(env より優先)。
156+
都度の上書きは CLI フラグで行います: `devbase up --open` / `--no-open` / `--open-index N` / `--open-terminal` / `--no-open-terminal`(env より優先)。
157+
158+
### 起動時に統合ターミナルを自動表示(`DEVBASE_OPEN_TERMINAL`
159+
160+
`up` 時に、開く dev コンテナのワークスペース(`/work/$GIT_REPO`)へ folderOpen タスク(`.vscode/tasks.json`)を `docker exec` で配置します(既存があれば変更しません)。VS Code はフォルダを開いた時にこのタスクで統合ターミナルを前面表示します。
161+
162+
VS Code 公式には「起動時にターミナルを開く」専用設定が無く、folderOpen タスクが唯一の方法です。なお自動実行には次の 2 つの **VS Code クライアント側ユーザー設定**が関わり、devbase からは制御できません(いずれも application/user スコープ専用):
163+
164+
- **Workspace Trust**: 信頼していないフォルダではタスクは自動実行されません(初回は「フォルダを信頼」が必要)。
165+
- **`task.allowAutomaticTasks`**: 既定 `off` ではフォルダごとに 1 回「自動タスクを許可」を尋ねます。`on` にするとプロンプト無しで実行されます。
166+
167+
→ 実際は「初回のみ信頼(+許可)クリック、以降は自動でターミナルが開く」挙動になります。無効化は `DEVBASE_OPEN_TERMINAL=0` または `devbase up --no-open-terminal`
154168

155169
### 実行コンテキスト別の挙動
156170

157171
| コンテキスト | 挙動 |
158172
|------|------|
159173
| ローカル端末(Mac/Linux) | ローカル VS Code が開く |
160174
| WSL 端末 | Windows 側 VS Code が開く(`code` ラッパ経由) |
161-
| VS Code の Remote-SSH 統合ターミナル | **クライアント側(手元)の VS Code** が開く(`code` シムが委譲) |
175+
| VS Code の Remote-SSH 統合ターミナル(同一ホストの Docker) | **クライアント側(手元)の VS Code** が開く(`code` シムが委譲) |
176+
| VS Code の Remote-SSH 統合ターミナル(**跨ホスト**: ssh 先の Docker にコンテナ) | `DEVBASE_EDITOR_SSH_HOST` 設定時にネスト URI で開く(下記「跨ホスト」参照) |
162177
| 手元から素の SSH(VS Code 外)で接続中 | クライアントへ自動で開く公式手段が無いため、手元で実行する `code --folder-uri ...` コマンドを提示 |
163178
| CI / 非対話(非 TTY) / `code` 不在 | 理由を表示してスキップ(`up` 自体は成功) |
164179

165-
> SSH 越しに「手元の VS Code」を自動で開きたい場合は、手元の VS Code から **Remote-SSH で接続した統合ターミナル内**`devbase up` を実行してください。そのターミナルの `code` はクライアント側 VS Code に委譲するため、リモートホスト上のコンテナへ接続した窓が手元に開きます。
180+
#### 跨ホスト(Windows VS Code → Remote-SSH → Mac のコンテナ)
181+
182+
手元(例 Windows)の VS Code から Remote-SSH で別ホスト(例 Mac)へ入り、その統合ターミナルで `devbase up` を実行する構成では、コンテナは **ssh 先(Mac)の Docker** 上にあります。このとき `code` の開く要求はクライアント(Windows)へ委譲されるため、フラットな attach URI のままだと **クライアント側の Docker** を見に行きコンテナが見つかりません(「コンテナーにアタッチできません。すでに存在しません」)。
183+
184+
これを解決するには、ssh-remote ホスト名(手元 `~/.ssh/config``Host` 別名。VS Code はこの別名を ssh 先の端末 env に渡さないため自動取得不可)を明示します:
185+
186+
```sh
187+
# $DEVBASE_ROOT/env など(全プロジェクト共通にしたい場合)
188+
DEVBASE_EDITOR_SSH_HOST=mac2
189+
```
190+
191+
これで devbase は `vscode-remote://attached-container+<hex>@ssh-remote+mac2/work/...`(必要に応じ payload に `settings.context` を埋める)というネスト URI を生成し、docker ルックアップが ssh 先(コンテナのある Mac)で行われて正しくアタッチします。docker context は `docker context show` から自動取得し、`DEVBASE_EDITOR_DOCKER_CONTEXT` で上書きできます。
192+
193+
> 同一ホスト構成(手元 Mac/Linux で直接、または ssh 先の Docker にコンテナが無い場合)では `DEVBASE_EDITOR_SSH_HOST` は不要で、従来どおりフラット URI で開きます。
166194
167195
## ソースファイル変更検出
168196

issues/PLAN31_3_up-open-editor.md

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,41 @@ vscode-remote://attached-container+<hex>/work/$GIT_REPO
4141
`<hex>`**`{"containerName":"/<実コンテナ名>"}` を UTF-8 hex 化**した文字列。
4242
(単純な名前の hex ではない点に注意。Docker 内部のコンテナ名は先頭 `/` 付き。)
4343

44-
### 2.3 ネスト authority は公式未サポート
44+
### 2.3 ネスト authority**実機で動作することを確認・当初想定を訂正**
4545

46-
`ssh-remote+<host>``attached-container+...` を 1 本の URI に合成する記法は
47-
**存在しない**(microsoft/vscode#242489 は *Closed as not planned*)。
48-
→ 「リモートホスト上のコンテナ」を単発 `code` で直接指定する手段は無い。
46+
> ⚠️ 訂正(2026-06-13 実装時の実機検証)。当初は「合成記法は存在しない
47+
> (microsoft/vscode#242489 *not planned*)」と記載していたが、**誤り**だった。
48+
49+
`attached-container+<hex>@ssh-remote+<host>` という**ネスト authority は実際に
50+
サポートされており動作する**(VS Code 1.124.2 / Dev Containers 0.459.1 で確認)。
51+
正常動作中の窓の resource URI を採取したところ:
52+
53+
```
54+
vscode-remote://attached-container+<hex>@ssh-remote+mac2/work/...
55+
hex = {"containerName":"/<name>","settings":{"context":"desktop-linux"}}
56+
```
57+
58+
`@ssh-remote+<host>` を付けると docker ルックアップが **ssh 先(コンテナのある
59+
ホスト)** で行われるため、跨ホスト(手元 Windows VS Code → ssh → Mac のコンテナ)
60+
でも単発 `code --folder-uri` で直接アタッチできる。`settings.context` は ssh 先で
61+
使う docker context を指定する。
4962

5063
### 2.4 結論(実行コンテキスト別マトリクス)
5164

5265
| コンテキスト | 自動オープン | 機構 / 根拠 |
5366
|---|---|---|
5467
| Mac/Linux ローカル端末 || ローカル `code` が attach URI を解決 |
5568
| WSL 端末 | ✓ (Windows VS Code) | `code` ラッパ→`code.exe`、Docker Desktop のコンテナへ attach |
56-
| VS Code **Remote-SSH 統合端末**(リモート=Mac) | ✓ (クライアント側) | `code` シム + `VSCODE_IPC_HOOK_CLI`。シムは既にクライアントへ接続済みなのでネスト URI 不要で attached-container を解決し、**クライアント(Windows)に窓が開く** |
57-
| plain SSH(WSL→ssh→Mac 等、VS Code 外) | ✗ → コマンド表示 | IPC hook 無し。公式にクライアントへ push 不可(§2.3)。手元で叩く `code` コマンドを提示するのが上限 |
69+
| VS Code **Remote-SSH 統合端末**(リモート=Mac・**同一ホストの Docker**| ✓ (クライアント側) | `code` シムが委譲。同一ホストの Docker にコンテナがある場合はフラット URI で解決 |
70+
| VS Code **Remote-SSH 統合端末****跨ホスト**: ssh 先 Mac の Docker にコンテナ) | ✓ (要 `DEVBASE_EDITOR_SSH_HOST`) | フラット URI だとクライアント(Windows)の Docker を見て失敗。**ネスト URI `@ssh-remote+<host>`(§2.3)で ssh 先の Docker を解決**。ssh ホスト名は env から取得不可のため明示設定が要る |
71+
| plain SSH(WSL→ssh→Mac 等、VS Code 外) | ✗ → コマンド表示 | IPC hook 無し。手元で叩く `code` コマンドを提示するのが上限 |
5872
| CI / 非TTY / `code` 不在 | ✗ → info スキップ | エディタ起動の前提を満たさない |
5973

60-
→ ユーザ理想チェーンは **「手元 VS Code で Remote-SSH→Mac に入った統合ターミナルで
61-
`devbase up`」の場合に自動成立**。plain ssh の場合は正直にコマンド提示で degrade する。
74+
→ 跨ホスト(手元 Windows VS Code → Remote-SSH→Mac で `devbase up`、コンテナは Mac の
75+
Docker)が最頻ユースケース。**`DEVBASE_EDITOR_SSH_HOST`(例 `mac2`)の設定で自動成立**
76+
ssh ホスト名(クライアント `~/.ssh/config` の Host 別名)は VS Code が ssh 先端末 env に
77+
渡さない(`SSH_CONNECTION` は IP のみ)ため自動取得できず、明示が必須(実機調査で確認)。
78+
plain ssh はコマンド提示で degrade。
6279

6380
## 3. 既存コード調査結果
6481

@@ -156,12 +173,19 @@ env 解釈は既存 `_parse_env_assignment`(`container.py:121`)に合わせ
156173
`--no-open`/`DEVBASE_OPEN_EDITOR=0` で呼ばれないこと
157174
- 既存 706 passed を維持
158175

159-
## 8. リスク・未確定
160-
161-
- **plain SSH では自動オープン不可**(§2.3 公式未サポート)。コマンド提示で degrade。
162-
この制約は README に明記する
163-
- VS Code Remote-SSH 統合端末でのクライアント側 attach は実機検証が必要
164-
`/ndf:investigation-rules`: 実機未検証の挙動は「推定」と明示)
176+
## 8. リスク・未確定(実機検証で更新)
177+
178+
- ~~VS Code Remote-SSH 統合端末でのクライアント側 attach は実機検証が必要~~
179+
**検証済み(2026-06-13)**。跨ホストではフラット URI だと失敗し、ネスト URI
180+
`@ssh-remote+<host>` + `settings.context` で成立することを確認(§2.3/§2.4 を訂正)。
181+
- ssh ホスト名(`DEVBASE_EDITOR_SSH_HOST`)は env 自動取得不可のため**ユーザ明示が前提**
182+
未設定の跨ホストではフラット URI にフォールバックし、従来同様アタッチ失敗ダイアログが出る
183+
(実害は無いが体験は劣化)。`$DEVBASE_ROOT/env` への 1 行設定を案内する。
184+
- **plain SSH(VS Code 外)では自動オープン不可**。コマンド提示で degrade(変更なし)。
185+
- 統合ターミナル自動表示は `.vscode/tasks.json`(folderOpen) 配置で実現。VS Code 公式に
186+
起動時ターミナル設定は無く(`hideOnStartup` は復元セッションの表示制御のみ)folderOpen
187+
が唯一。自動実行は Workspace Trust と `task.allowAutomaticTasks`(共に user スコープ専用・
188+
devbase 制御外)に依存し、初回のみ承認クリックが要る。
165189
- `code` ラッパの非ブロッキング起動が `up` プロセス終了をブロックしないこと確認
166190

167191
## 9. 参考(一次情報)

lib/devbase/cli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ def _add_open_args(parser):
112112
parser.add_argument('--open-index', dest='open_index', type=int, default=None,
113113
metavar='N',
114114
help='Container index to open (default: 1)')
115+
parser.add_argument('--open-terminal', dest='open_terminal', action='store_true',
116+
default=None,
117+
help='Place .vscode/tasks.json so the integrated terminal '
118+
'auto-opens on folder open (overrides DEVBASE_OPEN_TERMINAL)')
119+
parser.add_argument('--no-open-terminal', dest='open_terminal', action='store_false',
120+
help='Do not place the folderOpen terminal tasks.json '
121+
'(overrides DEVBASE_OPEN_TERMINAL)')
115122
return parser
116123

117124

lib/devbase/commands/container.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ def _dispatch_lifecycle(args) -> int:
290290
'up': lambda: cmd_up(project_name=project_name,
291291
scale=getattr(args, 'scale', None),
292292
open_editor=getattr(args, 'open_editor', None),
293-
open_index=getattr(args, 'open_index', None)),
293+
open_index=getattr(args, 'open_index', None),
294+
open_terminal=getattr(args, 'open_terminal', None)),
294295
'down': lambda: cmd_down(),
295296
'login': lambda: cmd_login(index=getattr(args, 'index', '1')),
296297
'ps': lambda: cmd_ps(all_containers=getattr(args, 'all', False)),
@@ -412,9 +413,72 @@ def _maybe_open_editor(project_name: str, open_flag: Optional[bool],
412413
logger.warning("エディタの自動オープンに失敗しましたがデプロイは成功しています: %s", e)
413414

414415

416+
def _maybe_place_terminal_task(project_name: str, open_flag: Optional[bool],
417+
open_index: Optional[int], scale: int,
418+
compose_file=None) -> None:
419+
"""`up` 後、開く dev コンテナの作業ディレクトリへ folderOpen ターミナル tasks.json を配置。
420+
421+
フォルダを開いた時に統合ターミナルを自動表示するための ``.vscode/tasks.json`` を、
422+
対象 dev インスタンスのワークスペース (``/work/$GIT_REPO``) に置く。作業ディレクトリは
423+
コンテナ内 (named volume) のためホストから直接書けず、起動済みコンテナへ ``docker exec``
424+
で書き込む。**既存の ``.vscode/tasks.json`` があれば一切触らない**。
425+
426+
有効判定は ``open_flag`` (CLI ``--open-terminal``/``--no-open-terminal``) が優先、None なら
427+
env ``DEVBASE_OPEN_TERMINAL`` (既定 ON)。配置失敗は warning に握り潰し ``up`` を倒さない。
428+
``open_index`` は開くインスタンスに合わせる (範囲外は 1 へフォールバック)。
429+
"""
430+
from devbase.editor import opener
431+
432+
enabled = open_flag if open_flag is not None else opener.is_open_terminal_enabled()
433+
if not enabled:
434+
return
435+
436+
if open_index is None:
437+
raw = os.environ.get('DEVBASE_OPEN_INDEX')
438+
try:
439+
open_index = int(raw) if raw else 1
440+
except ValueError:
441+
open_index = 1
442+
if not (1 <= open_index <= scale):
443+
open_index = 1
444+
445+
if compose_file is None and _SCALE_COMPOSE_FILE.exists():
446+
compose_file = _SCALE_COMPOSE_FILE
447+
448+
dev_service_name = get_dev_service_name()
449+
container = opener.resolve_container_name(dev_service_name, project_name,
450+
open_index, compose_file=compose_file)
451+
workdir = opener.resolve_workdir(os.environ, project_name)
452+
content = opener.build_folder_open_tasks_json()
453+
454+
# 既存があれば書かず、無ければ stdin から書き込む (冪等)。workdir は引数で渡し
455+
# シェル内クォートを避ける ($1)。
456+
script = (
457+
'set -e; d="$1/.vscode"; mkdir -p "$d"; '
458+
'if [ -e "$d/tasks.json" ]; then echo keep; '
459+
'else cat > "$d/tasks.json"; echo placed; fi'
460+
)
461+
try:
462+
proc = subprocess.run(
463+
["docker", "exec", "-i", container, "sh", "-c", script, "_", workdir],
464+
input=content, text=True, capture_output=True, timeout=15,
465+
)
466+
except Exception as e: # noqa: BLE001 - 配置失敗で up を倒さない
467+
logger.warning("ターミナル用 tasks.json の配置に失敗しましたが続行します: %s", e)
468+
return
469+
if proc.returncode != 0:
470+
logger.warning("ターミナル用 tasks.json の配置に失敗しましたが続行します: %s",
471+
(proc.stderr or "").strip())
472+
return
473+
if (proc.stdout or "").strip() == "placed":
474+
logger.info("[6/6] 統合ターミナル自動表示用 tasks.json を配置: %s/.vscode/tasks.json",
475+
workdir)
476+
477+
415478
def cmd_up(project_name: str = None, scale: int = None,
416479
open_editor: Optional[bool] = None,
417-
open_index: Optional[int] = None) -> int:
480+
open_index: Optional[int] = None,
481+
open_terminal: Optional[bool] = None) -> int:
418482
"""Deploy containers with specified scale"""
419483
if project_name is None:
420484
project_name = get_project_name()
@@ -481,6 +545,9 @@ def cmd_up(project_name: str = None, scale: int = None,
481545
if deploy_script.exists() and deploy_script.is_file():
482546
_run_deploy_script_for_instances(deploy_script, range(1, scale + 1))
483547

548+
# エディタを開く前に tasks.json を置く (開いた瞬間に folderOpen が効くように)。
549+
_maybe_place_terminal_task(project_name, open_terminal, open_index, scale,
550+
compose_file=override_file)
484551
_maybe_open_editor(project_name, open_editor, open_index, scale,
485552
compose_file=override_file)
486553

0 commit comments

Comments
 (0)