Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
adaff61
fix(entrypoint): 同時起動でもリンクの段が失敗しないようにする (#357)
takemi-ohama Oct 2, 2026
b937503
docs(plugin-dev): quickstart のアカウントグループを宣言必須の書き方へ直す (#215)
takemi-ohama Oct 2, 2026
6296b68
docs(plugin-dev): requires の契機表を正本にし、PR テンプレートへ 1 行足す手順を入れる (#215)
takemi-ohama Oct 2, 2026
c8fcdf4
Merge pull request #377 from devbasex/feat/issue-215-m7a
takemi-ohama Oct 2, 2026
ad0de31
fix(up): 起動の待ちで 1 台が落ちても残りを待ち、起動の結果を例外で返す (#224 #371)
takemi-ohama Oct 2, 2026
1ca4401
fix(up,scale): 起動の後の処理を 1 つの段にまとめ、起動できたインスタンスへ届ける (#224 #371)
takemi-ohama Oct 2, 2026
b20fa2b
feat(project): project post-start を登録する (#371)
takemi-ohama Oct 2, 2026
dbf9070
docs: scale の後処理・起動できなかったときの扱い・project post-start を書く (#224 #371)
takemi-ohama Oct 2, 2026
62d6653
Merge pull request #378 from devbasex/feat/issue-224-m7a
takemi-ohama Oct 2, 2026
897e115
fix(env): AWS の region と GCP の正規化の警告を取り込みの方針に従わせる (#334)
takemi-ohama Oct 2, 2026
af1275c
test(env): AWS の region と GCP の警告の回数を方針ごとに縛る (#334)
takemi-ohama Oct 2, 2026
b8ba103
docs: AWS の region と GCP の正規化の警告が取り込みの方針に従うことを書く (#334)
takemi-ohama Oct 2, 2026
5aacba3
Merge pull request #379 from devbasex/feat/issue-334-m7a
takemi-ohama Oct 2, 2026
930fa69
test(entrypoint): 同時起動のテストの所要を減らし、性能の比較を並列の負荷に揺れないようにする (#357)
takemi-ohama Oct 2, 2026
da2dc8c
Merge remote-tracking branch 'origin/sprint/m7a' into feat/issue-357-m7a
takemi-ohama Oct 2, 2026
d85f225
Merge pull request #380 from devbasex/feat/issue-357-m7a
takemi-ohama Oct 2, 2026
6607703
Test: 現状固定テストを足す — etc/devbase-completion.bash#_devbase_completions
takemi-ohama Oct 2, 2026
fb147a0
Refactor: extract_method — lib/devbase/commands/container.py#cmd_scale
takemi-ohama Oct 2, 2026
3fd5e18
Refactor: introduce_named_constant — lib/devbase/env/collectors/aws.p…
takemi-ohama Oct 2, 2026
3a6136a
Refactor: centralize_configuration — lib/devbase/commands/container.p…
takemi-ohama Oct 2, 2026
f19b899
Refactor: extract_method — etc/devbase-completion.bash#_devbase_compl…
takemi-ohama Oct 2, 2026
a756a31
fix: entrypoint の完了の印を起動ごとに消す
takemi-ohama Oct 2, 2026
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
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [ ] 影響を受けるコマンドの主要シナリオを手動で確認した
- [ ] 必要に応じてドキュメント (`docs/`, `README.md`) を更新した
- [ ] 利用者に見える変更なら `CHANGELOG.md` の `[Unreleased]` を更新した
- [ ] Plugin から使える機能を足したら、`docs/plugin-dev/plugin-yml-reference.md` の `requires` 契機表へ 1 行足した
- [ ] CI が green である

## スクリーンショット・ログ(任意)
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
- **base イメージに `python3-psutil` を入れました。** コンテナの中のシステムの `python3` で
`import psutil` が使えます(NDF のスクリプトがプロセスの一覧と資源の量を読むのに使います)。
反映には `devbase container build` でイメージを建て直してください。
- **`devbase project post-start [name]` を足しました(#371)。** 動いているコンテナへ、起動の後の処理
(clone できなかったリポジトリの報告・OpenBao の token の配布・VS Code の窓のタイトルの設定)を、
コンテナを作り直さずにやり直します。`./deploy` は走らせず、エディタも開きません。
`docker start` で再起動したコンテナの初期化の途中に前回の完了の印を読んで先へ進まないよう、
entrypoint が起動のたびに印を消してから作り直します(base イメージを建て直した後のコンテナから効きます)。

### Changed
- **`containers/lfm` のもとのイメージを `nvidia/cuda:13.4.1-cudnn-devel-ubuntu26.04` に上げました(#337)。**
Expand All @@ -20,6 +25,31 @@
差分が前回からの変更分だけになり、1 回の時間とディスクの使い方が大きく減ります(手元の測定で
フル 2.2GB・40 秒に対し、差分 1.5MB・2 秒)。直した後の最初の `devbase up` では、既存の世代へ
積まずに新しい世代(フルバックアップ)を 1 回作ります。既存の世代はそのまま復元できます。
- **同じアカウントグループのコンテナを同時に起動すると、1 つが `ln: Already exists` で落ちることがあったのを直しました**(#357)。
ホストの再起動の後の一斉起動や、`scale` を 2 以上にしたプロジェクトの `devbase up` で起きていました。
共有の設定のリンクを張る処理を 1 つずつ順に走らせ、ほかのコンテナが先に張っていても成功として
扱います。効くのは base イメージを建て直した後のコンテナからです(`devbase build base` の後、
派生イメージも建て直す)。
- **`devbase scale` で増やしたコンテナにも、`devbase up` と同じ起動の後の処理を行うようにしました(#224)。**
clone できなかったリポジトリの警告と VS Code の窓のタイトル(コンテナ名始まり)が、増やしたコンテナにも
届きます。処理の順は `up` と同じ(リポジトリの報告 → `./deploy` → token の配布 → 窓のタイトル)になり、
`scale` では `./deploy` が token の配布より先に走るようになりました。
- **`devbase up` / `devbase scale` で 1 台が起動できなくても、起動できたコンテナには起動の後の処理を
行うようにしました(#371)。** これまでは 1 台が落ちると、ほかのコンテナの窓のタイトル・token・
エディタの自動オープンも黙って欠けていました。起動できなかったすべてのコンテナの名前と理由
(終了した・見つからない・時間切れ)と、補う手順(`devbase project post-start <name>`)を出して、
終了コード 1 で終わります。1 台が落ちても残りのコンテナの起動は最長 60 秒まで待つため、失敗で終わる
までの時間はその分延びます。
- **`devbase env init` の AWS の `2` SSO Profile と `3` Access Key が、取り込みの方針によらず
`~/.aws/config` の region を `AWS_DEFAULT_REGION` へ書いていたのを直しました(#334)。** `skip` のグループと、
`3` で鍵の取り込みを断ったときは `~/.aws/config` を読まずに region を尋ねます(既定 `ap-northeast-1`)。
`ask` のグループで鍵の確認が無いとき(`2`、鍵の無い `3`)は、`~/.aws/config` の region を既定の値として
見せてから尋ねます。`import` のグループは今までどおり尋ねずに書きます。`2` の `skip` では
`AWS認証: 取り込まない設定のため飛ばしました` の 1 行が出るようになりました。
- **GCP の鍵ファイル名の正規化の警告(`プロファイル名 '…' を … に正規化しました`)と衝突の警告が、
取り込まない鍵にも、1 回の `env init` で 2 回ずつ出ていたのを直しました(#334)。** 取り込むと決めた鍵に
ついてだけ 1 回出し、`env sync` では出しません。正規化した名前が衝突したとき、`env sync` も `env init` と
同じくファイル名の昇順で先の鍵ファイルを引くようになりました。

## [4.0.0] - 2026-09-28

Expand Down
6 changes: 3 additions & 3 deletions bin/devbase
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ fi
# トップレベルシノニム <sub> <name> -> $2
#
# 重要 (PLAN06 codex 指摘対応): `project` グループでは parser が `name` positional を
# 持つサブコマンド (`up`/`down`/`ps`/`logs`/`scale`/`rebuild`/`open`) に限定して $3 を
# 持つサブコマンド (`up`/`down`/`ps`/`logs`/`scale`/`rebuild`/`open`/`post-start`) に限定して $3 を
# name 解決する。`project login` / `project build` は単一 positional が index / image
# (旧 container 互換) であり parser が name を受け付けない (cli.py の
# _add_login_subparser / _add_build_subparser 参照)。これらで $3 を name strip すると、
Expand All @@ -474,14 +474,14 @@ _DEVBASE_ARGS=("${@:2}")
# 同期注意 (メンテナンス性): 下記 2 リストは cli.py の parser 定義に対応する。
# _PROJECT_NAME_SUBCOMMANDS = `project` で `name` positional を受け付けるサブコマンド
# 集合。cli.py の _add_project_parser で `add_argument('name', ...)` を持つもの
# (up/down/ps/logs/scale/rebuild/open) と一致させる。login/build は index/image
# (up/down/ps/logs/scale/rebuild/open/post-start) と一致させる。login/build は index/image
# 互換のため意図的に除外 (上のコメント参照)。
# _NAME_RESOLVABLE_SHORTCUTS = トップレベルシノニムのうち「実在 project なら cd」
# を許すもの。cli.py の SHORTCUTS 経由で project サブコマンドへ写像される
# 集合 + shell 実装の build を含む。
# cli.py 側でサブコマンドを追加/削除した際は両リストの更新漏れに注意すること
# (cli.py の _add_project_parser / SHORTCUTS にも対の注記あり)。
_PROJECT_NAME_SUBCOMMANDS=" up down ps logs scale rebuild open "
_PROJECT_NAME_SUBCOMMANDS=" up down ps logs scale rebuild open post-start "
_NAME_RESOLVABLE_SHORTCUTS=" up down ps scale login build rebuild open "
case "$_resolved_cmd" in
project)
Expand Down
136 changes: 117 additions & 19 deletions containers/base/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -297,23 +297,63 @@ devbase_ensure_persistent_root() {
fi
}

# リンクの段のロックを待つ秒数の上限 (#357)。超えたら警告を出してロックなしで進む。
# 止まったまま終わらない持ち主が 1 つあるだけで、ほかのすべてのコンテナの起動が
# 止まるのを避ける。ロックなしでも、張り替えの試行と排他の作成で結果は壊れない。
DEVBASE_LINK_LOCK_WAIT=10

# 張り替えの試行の上限 (#357)。修正前のイメージのコンテナは 1 つにつき 1 回だけ
# 正しいリンクを消すため、同時起動 8 個なら試行は 9 回までに収まる。その 2 倍の余裕
DEVBASE_LINK_TRIES=20

# エントリを**無いときだけ**作る (排他の作成、#357)。
#
# 第 1 引数は file か dir。終了コードは 作れた 0 / 既にあった 1 / 作れない 2。
# 同時に呼んでも 0 を返すのは 1 つのプロセスだけで、そのプロセスだけが中身を書く。
# 通常のファイルは noclobber の `>` で作る。ディレクトリは perl から mkdir(2) を呼ぶ。
# base イメージの mkdir (uutils) は同時に呼ぶと 2 つ以上が 0 で終わり、作れた
# プロセスを 1 つに決められないため使わない。perl が無い環境では mkdir で作る。
devbase_create_entry() {
local kind="$1" path="$2" err

if [ "$kind" = "file" ]; then
err=$( { set -C; : > "$path"; } 2>&1 ) && return 0
elif command -v perl >/dev/null 2>&1; then
err=$(perl -e 'mkdir($ARGV[0]) or die "mkdir: $!\n"' "$path" 2>&1) && return 0
else
err=$(mkdir "$path" 2>&1) && return 0
fi
# 作る操作の終了コードではなく、読み直して決める
if [ -e "$path" ] || [ -L "$path" ]; then
return 1
fi
echo "ERROR: エントリを作れない: ${path} (${err})" >&2
return 2
}

# 実体が無ければプレースホルダを作る (親ディレクトリごと)。
devbase_ensure_entry() {
local path="$1"
local path="$1" rc=0

mkdir -p "$(dirname "$path")"
if [ -e "$path" ]; then
return 0
fi
if devbase_is_file_entry "$path"; then
if devbase_is_json_file_entry "$path"; then
devbase_create_entry file "$path" || rc=$?
# 作れたプロセスだけが中身を書く。ほかのプロセスが作ったものは触らない
if [ "$rc" -eq 0 ] && devbase_is_json_file_entry "$path"; then
printf '{}' > "$path"
else
: > "$path"
fi
else
mkdir -p "$path"
devbase_create_entry dir "$path" || rc=$?
fi
[ "$rc" -ne 2 ]
}

# <link_path> が <target_path> を指す symlink か判定する (何も出力しない)。
devbase_link_is_set() {
[ -L "$1" ] && [ "$(readlink "$1")" = "$2" ]
}

# <link_path> を <target_path> への symlink にする。
Expand All @@ -324,33 +364,52 @@ devbase_ensure_entry() {
#
# 既存の実体は `rm -rf` してから張り直す。symlink に対する `rm -rf` は
# **リンクだけ**を消すので、共通側の実体は巻き添えにならない。
#
# 同じグループのコンテナは同じ共有のリンクを同時に張り直す (#357)。成否は `ln` と
# `rm` の終了コードではなく、張った後に読み直した結果で決め、正しくなるまで
# DEVBASE_LINK_TRIES 回まで繰り返す。`ln` に -n を付けるのは、相手が先に張った
# ディレクトリへのリンクの中へ、入れ子のリンクを作らないためである。
devbase_link_setting() {
local link_path="$1" target_path="$2" owner="${3:-${USERNAME:-ubuntu}}"
local try=1 err=""

devbase_ensure_entry "$target_path"

if [ -L "$link_path" ] && [ "$(readlink "$link_path")" = "$target_path" ]; then
if devbase_link_is_set "$link_path" "$target_path"; then
echo " ✓ ${link_path} (symlink exists)"
return 0
fi

mkdir -p "$(dirname "$link_path")"
if [ -e "$link_path" ] || [ -L "$link_path" ]; then
echo " Removing existing ${link_path}..."
rm -rf "$link_path"
fi

echo " Creating symlink: ${link_path} -> ${target_path}"
ln -s "$target_path" "$link_path"
chown -h "${owner}:${owner}" "$link_path" 2>/dev/null || true
mkdir -p "$(dirname "$link_path")" 2>/dev/null || true
while :; do
if [ -e "$link_path" ] || [ -L "$link_path" ]; then
[ "$try" -eq 1 ] && echo " Removing existing ${link_path}..."
err=$(rm -rf "$link_path" 2>&1) || true
fi
[ "$try" -eq 1 ] && echo " Creating symlink: ${link_path} -> ${target_path}"
err=$(ln -sn "$target_path" "$link_path" 2>&1) || true
if devbase_link_is_set "$link_path" "$target_path"; then
chown -h "${owner}:${owner}" "$link_path" 2>/dev/null || true
return 0
fi
if [ "$try" -ge "$DEVBASE_LINK_TRIES" ]; then
echo "ERROR: symlink を張れない: ${link_path} -> ${target_path} (${err})" >&2
return 1
fi
try=$((try + 1))
done
}

# シード元から 1 エントリを**コピー**する (既にあれば何もしない)。
#
# 第 3 引数以降は「コピーしない直下の名前」。分類 A の共通資産をグループ側へ
# 複製しないために使う。
#
# コピー先は排他の作成で作り、作れたプロセスだけが中身をコピーする (#357)。
# 同時に走る 2 つが同じディレクトリへ重ねてコピーすると、子のディレクトリの中に
# 同じ名前のディレクトリがもう 1 つ残る。
devbase_seed_entry() {
local src="$1" dest="$2"
local src="$1" dest="$2" rc=0
shift 2

if [ -e "$dest" ]; then
Expand All @@ -363,12 +422,23 @@ devbase_seed_entry() {

mkdir -p "$(dirname "$dest")"
if [ ! -d "$src" ]; then
devbase_create_entry file "$dest" || rc=$?
case "$rc" in
0) ;;
1) return 0 ;;
*) return 1 ;;
esac
cp -a "$src" "$dest"
echo " seeded: $dest"
return 0
fi

mkdir -p "$dest"
devbase_create_entry dir "$dest" || rc=$?
case "$rc" in
0) ;;
1) return 0 ;;
*) return 1 ;;
esac
local child name excluded skip
# `.[!.]*` と `..?*` で隠しファイルも拾う (`.credentials.json` 等)。
for child in "$src"/* "$src"/.[!.]* "$src"/..?*; do
Expand Down Expand Up @@ -412,14 +482,35 @@ devbase_seed_image_claude_settings() {
# 第 4 引数はアカウントグループ名 (呼び出しの形を保つために受け取るが使わない)。
# グループ側は /persistent/ai から取り込まない (#315 I13)。取り込み元は PLAN39 より前の
# 置き場で、グループの実名化と合わせて初回シードを無くした。
#
# 同じボリュームを共有するコンテナは同時に起動する (#357)。共通のボリュームの
# ルートのディレクトリそのものに flock を掛け、リンクの段どうしを 1 つずつ順に
# 走らせる。ロックのためのファイルは作らない。ロックはサブシェルの fd でだけ持ち、
# サブシェルを抜ければ (成功でも失敗でも) 外れ、最後に exec するコマンドへ
# 引き継がない。flock が無い・待ち切れないときはロックなしで進む。
devbase_setup_ai_settings() {
local home_root="$1" ai_root="$2" group_root="$3"
local owner="${5:-${USERNAME:-ubuntu}}"
local entry

devbase_ensure_persistent_root "$ai_root" "$owner"
devbase_ensure_persistent_root "$group_root" "$owner"

(
if command -v flock >/dev/null 2>&1; then
exec 9<"$ai_root"
if ! flock -w "$DEVBASE_LINK_LOCK_WAIT" 9; then
echo "WARNING: リンクの段のロックを ${DEVBASE_LINK_LOCK_WAIT} 秒待っても取れないため、ロックなしで進める: ${ai_root}" >&2
fi
fi
devbase_apply_ai_settings "$home_root" "$ai_root" "$group_root" "$owner"
)
}

# リンクの段の本体。退避・ホームのリンク・共有のリンクをこの順で行う。
devbase_apply_ai_settings() {
local home_root="$1" ai_root="$2" group_root="$3" owner="$4"
local entry

# symlink を張る**前**にシードする。張ったあとに走らせると、共通側を指す
# symlink の中身へコピーしてしまう。
devbase_seed_image_claude_settings "$home_root" "$ai_root"
Expand Down Expand Up @@ -591,6 +682,13 @@ if [ -n "${DEVBASE_ENTRYPOINT_LIB_ONLY:-}" ]; then
return 0 2>/dev/null || exit 0
fi

# 完了の印は起動 1 回ごとの寿命にする。docker start で再起動したコンテナには前回の印が
# /tmp に残るため、消さないとホスト側 (post-start / wait_for_containers_ready) が
# 今回の初期化の途中でも完了と読んでしまう。値は lib/devbase/utils/docker.py の
# ENTRYPOINT_READY_FILE と揃える。
ENTRYPOINT_READY_FILE=/tmp/entrypoint-ready
rm -f "$ENTRYPOINT_READY_FILE"

# Setup authentication credentials from environment variables
USERNAME="${USERNAME:-ubuntu}"

Expand Down Expand Up @@ -785,6 +883,6 @@ devbase_write_workspace "$DEVBASE_WORK_ROOT"
devbase_enter_primary_dir "$DEVBASE_WORK_ROOT"

# Signal that entrypoint setup is complete
touch /tmp/entrypoint-ready
touch "$ENTRYPOINT_READY_FILE"

exec "$@"
17 changes: 12 additions & 5 deletions docs/plugin-dev/plugin-yml-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,21 @@ devbase 本体を更新してください (この警告を止める場合は DEV
```

> **`requires.devbase` は本体の版数と一緒に自動では上がりません。** 上げるのは、**Plugin が
> devbase の新しい機能に依存しはじめたタイミング**です。契機は次のとおりです。
> devbase の新しい機能に依存しはじめたタイミング**です。契機は次の表のとおりで、この表が正本です。
>
> | 契機 | 上げる版数 |
> | 契機 | Plugin の側ですること |
> | --- | --- |
> | Plugin が `project.yml` 形式へ移行した | その形式を読める版 |
> | Plugin のプロジェクトが `compose.yml` に `profiles:` を使う | `">=3.5.0"`([テスト用サーバを後から起動・停止する](compose-profiles.md#plugin-として配るなら-requiresdevbase-を-350-以上へ上げる)) |
> | Plugin が `project.yml` 形式へ移行した | `requires.devbase` を `">=3.0.0"` へ上げる |
> | Plugin のプロジェクトが `compose.yml` に `profiles:` を使う | `requires.devbase` を `">=3.5.0"` へ上げる([テスト用サーバを後から起動・停止する](compose-profiles.md#plugin-として配るなら-requiresdevbase-を-350-以上へ上げる)) |
> | Plugin のプロジェクトが、base イメージに後から足された道具を前提にする(`pdftoppm` などの `poppler-utils`・`python3-lxml` は 3.7.0、`shellcheck`・`tmux-go` などの tmux のセッションのコマンドは 3.8.0) | `requires.devbase` を、その道具を足した版以上へ上げる(どの版で足されたかは `CHANGELOG.md` の Added を見る) |
> | Plugin のプロジェクトが、コンテナの中で `~/.shellrc.d/`(環境変数 `DEVBASE_SHELLRC_DIR`)へシェルの設定を置く | `requires.devbase` を `">=3.8.0"` へ上げる |
> | devbase 4.0.0 以降で使われる Plugin のプロジェクト(アカウントグループの宣言が必須) | `requires.devbase` の下限ではなく、Plugin のプロジェクトの `env` に `DEVBASE_ACCOUNT_GROUP=<グループ>` を書く(`default` は予約語で使えない。[クイックスタート 2.3.1](quickstart.md#231-env-ファイル必須アカウントグループの宣言を書く))。宣言が無いと `devbase up` / `scale` が止まる |
>
> どちらも、その機能を使い始めた Pull Request で一緒に上げます。
> どれも、その機能を使い始めた Pull Request で一緒に行います。base イメージの道具とシェルの設定の置き場所は、
> 利用者がイメージを建て直すまで効きません。
>
> devbase の側で Plugin から使える機能(Plugin が書く設定・compose の書き方・イメージの道具・コンテナの中の置き場所など)を
> 足した Pull Request は、この表へ 1 行足します(Pull Request のテンプレートの「動作確認」に項目があります)。

### `priority`

Expand Down
Loading
Loading