Files
claude-code/files/session-entrypoint.sh
claude 1b74e230ad
Helm Chart Release / release-chart (push) Successful in 7s
Build Session Image / build-push (push) Successful in 2m3s
feat: 失敗セッションの調査性を改善(終了Pod保持 + ログAPI + UI)(v2.1.263-d)
セッション Pod がブート失敗で Failed になると reaper が即削除し kubectl logs で
死因を追えなかった。「開く」で 502 になるだけで原因不明だった。

- session.reaper.terminatedRetentionSeconds(既定 900): 終了済み Pod を
  finishedAt からこの秒数は回収しない。manager 回収ループと reaper CronJob 両方が尊重
- GET /api/sessions/<id>/log: セッション Pod のログを text で返す(pods/log get のみ、追加権限なし)
- UI: 「ログ」ボタン、状態の色分け、Failed 行に reason/exitCode/message 併記、
  Running 以外は「開く」非表示
- session-entrypoint.sh: URL スキームと identity 型の不一致を警告。
  git clone を --progress ... 2>&1 にして git/ssh の出力を Pod ログへ
- helm lint / template 検証済み
2026-09-08 21:14:56 +09:00

267 lines
12 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# ==========================================================================
# claude-code セッションブート(使い捨てコンテナ内で1回だけ実行)
#
# 1. git 認証(SSH 鍵 / HTTPS トークン)を $HOME にセットアップ
# 2. 許可済みリポジトリを $WORKSPACE_DIR へ clone
# 3. `claude` を PATH に用意(無ければ非 root で $HOME にインストール)
# 4. ttyd でブラウザにシェル(+ 任意で claude 自動起動)を提供
# 5. アイドルウォッチドッグ:無通信が続いたら自身を終了 → Pod 破棄
#
# 前提: 非 root(uid=10001) / readOnlyRootFilesystem。
# 書き込み可能なのは $HOME, $WORKSPACE_DIR, /tmp のみ。
# git / ttyd / node は事前ビルドイメージに同梱されている想定
# (files/Dockerfile.session)。未同梱時は下記フォールバックが走る。
# ==========================================================================
set -eu
log() { printf '\033[36m[boot]\033[0m %s\n' "$*"; }
err() { printf '\033[31m[boot:ERROR]\033[0m %s\n' "$*" >&2; }
: "${HOME:=/home/claude}"
: "${WORKSPACE_DIR:=/workspace}"
: "${TTYD_PORT:=7681}"
: "${IDLE_TIMEOUT_SECONDS:=1800}"
: "${AUTOSTART_CLAUDE:=true}"
: "${REPO_REF:=main}"
export HOME
export NPM_CONFIG_CACHE="$HOME/.npm"
export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"
mkdir -p "$HOME/.ssh" "$WORKSPACE_DIR" "$HOME/.npm-global"
chmod 700 "$HOME/.ssh"
# --------------------------------------------------------------------------
# 1. git 認証セットアップ
# --------------------------------------------------------------------------
git config --global user.name "${GIT_AUTHOR_NAME:-Claude}"
git config --global user.email "${GIT_AUTHOR_EMAIL:-claude@cafepieters.com}"
git config --global safe.directory '*'
git config --global advice.detachedHead false
git config --global credential.helper ''
ASKPASS_DISABLE() { export GIT_TERMINAL_PROMPT=0; }
ASKPASS_DISABLE
case "${GIT_IDENTITY_TYPE:-}" in
ssh)
if [ ! -r /git-identity/id ]; then err "SSH 鍵がマウントされていません"; exit 1; fi
install -m 600 /git-identity/id "$HOME/.ssh/id_session"
# known_hosts を必須にし、ホスト鍵の TOFU を無効化する
if [ -r /git-identity/known_hosts ]; then
install -m 644 /git-identity/known_hosts "$HOME/.ssh/known_hosts"
else
err "known_hosts がありません(StrictHostKeyChecking を維持するため必須)"; exit 1
fi
cat > "$HOME/.ssh/config" <<EOF
Host *
IdentityFile $HOME/.ssh/id_session
IdentitiesOnly yes
StrictHostKeyChecking yes
UserKnownHostsFile $HOME/.ssh/known_hosts
PasswordAuthentication no
EOF
chmod 600 "$HOME/.ssh/config"
export GIT_SSH_COMMAND="ssh -F $HOME/.ssh/config"
log "SSH 認証を構成しました"
;;
https-token)
if [ ! -r /git-identity/token ]; then err "HTTPS トークンがマウントされていません"; exit 1; fi
_u="${GIT_HTTPS_USERNAME:-x-access-token}"
_t="$(cat /git-identity/token)"
# トークンをプロセス環境にもファイルにも平文で残さず、credential helper で都度供給
cat > "$HOME/.git-credentials" <<EOF
https://$(printf '%s' "$_u" | sed 's/@/%40/g'):$(printf '%s' "$_t" | sed 's/@/%40/g')@$(printf '%s\n' "$REPO_URL" | sed -E 's#^https://([^/]+)/.*#\1#')
EOF
chmod 600 "$HOME/.git-credentials"
git config --global credential.helper "store --file=$HOME/.git-credentials"
unset _t
log "HTTPS トークン認証を構成しました"
;;
*)
log "git 認証なし(公開リポジトリのみ clone 可能)"
;;
esac
# --------------------------------------------------------------------------
# 2. clone
# --------------------------------------------------------------------------
# URL のスキームと identity の型が食い違っていないか確認(よくある詰まり)
case "$REPO_URL" in
https://*)
if [ "${GIT_IDENTITY_TYPE:-}" = "ssh" ]; then
err "HTTPS の URL ($REPO_URL) に SSH identity が選ばれています。"
err "→ プライベートリポジトリなら https-token の identity を選ぶか、URL を ssh://git@<host>/<org>/<repo>.git 形式にしてください。"
fi ;;
git@*|ssh://*)
if [ "${GIT_IDENTITY_TYPE:-}" != "ssh" ]; then
err "SSH の URL ($REPO_URL) に SSH identity が選ばれていません(identity=${GIT_IDENTITY_TYPE:-none})。"
err "→ ssh 型の identity(秘密鍵 + known_hosts)を選んでください。"
fi ;;
esac
REPO_DIR="$WORKSPACE_DIR/$(basename "${REPO_URL%.git}")"
if [ -d "$REPO_DIR/.git" ]; then
log "既存チェックアウトを再利用: $REPO_DIR"
else
log "clone 中: $REPO_URL ($REPO_REF)"
# 進捗と失敗理由をそのまま Pod ログに出す(git のメッセージが死因の手がかり)
if ! GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh} -v" git clone --progress --filter=blob:none --no-tags "$REPO_URL" "$REPO_DIR" 2>&1; then
err "clone に失敗しました(上の git / ssh の出力を参照)。"
err "よくある原因: 認証情報の不足・権限なし / known_hosts 不一致 / NetworkPolicy による egress 遮断(接続タイムアウト)。"
exit 1
fi
fi
cd "$REPO_DIR"
git checkout "$REPO_REF" 2>/dev/null || git checkout -b "$REPO_REF" 2>/dev/null || true
log "作業ディレクトリ: $REPO_DIR ($(git rev-parse --short HEAD 2>/dev/null || echo '?'))"
# --------------------------------------------------------------------------
# 3. claude CLI を用意
# --------------------------------------------------------------------------
if ! command -v claude >/dev/null 2>&1; then
log "claude が未同梱のため $HOME にインストールします(初回は時間がかかります)"
npm config set prefix "$HOME/.npm-global" >/dev/null 2>&1 || true
_ver="${CLAUDE_CODE_VERSION:-}"
_pkg="@anthropic-ai/claude-code${_ver:+@$_ver}"
if ! npm install -g --no-fund --no-audit "$_pkg"; then
err "claude-code のインストールに失敗しました。事前ビルドイメージの利用を推奨します。"
exit 1
fi
fi
log "claude: $(claude --version 2>/dev/null || echo 'unknown')"
# --------------------------------------------------------------------------
# 3b. Claude 認証情報(credentials モード)
# --------------------------------------------------------------------------
# Secret からマウントした .credentials.json を書き込み可能な $HOME/.claude へコピーする。
# claude はアクセストークン失効時に refreshToken でこのファイルを自己更新する
# (セッション中のみ有効。クラスタ全体の更新は refresher CronJob が担当)。
if [ -n "${SEED_CREDENTIALS_FILE:-}" ] && [ -r "$SEED_CREDENTIALS_FILE" ]; then
mkdir -p "$HOME/.claude"
chmod 700 "$HOME/.claude"
install -m 600 "$SEED_CREDENTIALS_FILE" "$HOME/.claude/.credentials.json"
if command -v jq >/dev/null 2>&1; then
_exp="$(jq -r '.claudeAiOauth.expiresAt // empty' "$HOME/.claude/.credentials.json" 2>/dev/null)"
[ -n "$_exp" ] && log "Claude 認証情報を配置(accessToken 期限: $(date -u -d "@$(( _exp / 1000 ))" 2>/dev/null || echo "$_exp"))"
else
log "Claude 認証情報を $HOME/.claude/.credentials.json に配置しました"
fi
unset ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN
fi
# 追加 apk(root 権限がある事前ビルドイメージのみ有効。非 root では警告のみ)
if [ -n "${EXTRA_APK_PACKAGES:-}" ]; then
if [ "$(id -u)" = "0" ]; then
apk add --no-cache $EXTRA_APK_PACKAGES || err "apk add に一部失敗"
else
err "EXTRA_APK_PACKAGES は非 root では無視されます: $EXTRA_APK_PACKAGES"
fi
fi
# --------------------------------------------------------------------------
# 4. 補助コマンド(claude-verify / claude-push)
# --------------------------------------------------------------------------
mkdir -p "$HOME/.local/bin"
cat > "$HOME/.local/bin/claude-verify" <<'EOF'
#!/bin/sh
# セッション設定の検証コマンドを実行する
if [ -z "${VERIFY_COMMAND:-}" ]; then echo "verifyCommand は未設定です"; exit 0; fi
echo "+ $VERIFY_COMMAND"
exec sh -c "$VERIFY_COMMAND"
EOF
chmod +x "$HOME/.local/bin/claude-verify"
cat > "$HOME/.local/bin/claude-push" <<'EOF'
#!/bin/sh
# 検証 → 安全なブランチへ push(デフォルト/保護ブランチ・--force は禁止)
set -eu
if [ "${AUTOPUSH_ENABLED:-false}" != "true" ]; then
echo "autoPush は無効です(values: session.autoPush.enabled)。手動 push は 'git push' を使ってください。" >&2
exit 1
fi
cur="$(git rev-parse --abbrev-ref HEAD)"
prefix="${AUTOPUSH_BRANCH_PREFIX:-claude/}"
protected="${AUTOPUSH_PROTECTED:-main,master,develop,release}"
case ",$protected," in *",$cur,"*)
echo "現在のブランチ '$cur' は保護対象です。作業ブランチへ切り替えてください。" >&2; exit 1;;
esac
case "$cur" in "$prefix"*) target="$cur" ;; *) target="${prefix}${cur}" ;; esac
if [ -n "${VERIFY_COMMAND:-}" ]; then
echo "== verify: $VERIFY_COMMAND"
sh -c "$VERIFY_COMMAND" || { echo "検証に失敗したため push を中止します。" >&2; exit 1; }
fi
if [ -n "$(git status --porcelain)" ]; then
git add -A
git commit -m "${1:-chore: work from claude-code session $SESSION_ID}"
fi
[ "$target" = "$cur" ] || git branch -f "$target" "$cur"
echo "== push origin $target(--force なし)"
exec git push origin "HEAD:$target"
EOF
chmod +x "$HOME/.local/bin/claude-push"
# --------------------------------------------------------------------------
# 5. アイドルウォッチドッグ
# --------------------------------------------------------------------------
idle_watchdog() {
# ttyd への ESTABLISHED 接続が IDLE_TIMEOUT_SECONDS の間ゼロなら Pod を終了させる
last_seen="$(date +%s)"
while true; do
sleep 30
conns=0
if command -v ss >/dev/null 2>&1; then
conns="$(ss -Htn "sport = :$TTYD_PORT" state established 2>/dev/null | wc -l | tr -d ' ')"
elif [ -r /proc/net/tcp ]; then
hexport="$(printf '%04X' "$TTYD_PORT")"
conns="$(awk -v p=":$hexport" '$2 ~ p"$" && $4=="01"{n++} END{print n+0}' /proc/net/tcp)"
fi
now="$(date +%s)"
if [ "${conns:-0}" -gt 0 ]; then
last_seen="$now"
elif [ $(( now - last_seen )) -ge "$IDLE_TIMEOUT_SECONDS" ]; then
err "アイドル ${IDLE_TIMEOUT_SECONDS}s を超過。セッションを終了します。"
kill -TERM "$TTYD_PID" 2>/dev/null || true
exit 0
fi
done
}
# --------------------------------------------------------------------------
# 6. ttyd 起動
# --------------------------------------------------------------------------
if ! command -v ttyd >/dev/null 2>&1; then
err "ttyd が見つかりません。事前ビルドイメージ(files/Dockerfile.session)を利用してください。"
exit 1
fi
SHELL_CMD="/bin/bash"
command -v bash >/dev/null 2>&1 || SHELL_CMD="/bin/sh"
cat > "$HOME/.session-shell" <<EOF
#!/bin/sh
cd "$REPO_DIR" || cd "$WORKSPACE_DIR"
export PATH="$PATH"
echo "── Claude Code セッション $SESSION_ID ──────────────────────────"
echo "リポジトリ : $REPO_URL ($REPO_REF)"
echo "検証コマンド: ${VERIFY_COMMAND:-(未設定)} 補助: claude-verify / claude-push"
echo "─────────────────────────────────────────────────────────────"
if [ "${AUTOSTART_CLAUDE}" = "true" ] && command -v claude >/dev/null 2>&1; then
exec claude
fi
exec $SHELL_CMD -l
EOF
chmod +x "$HOME/.session-shell"
log "ttyd を 0.0.0.0:$TTYD_PORT で起動します"
ttyd --port "$TTYD_PORT" --interface 0.0.0.0 --writable \
--client-option fontSize=14 --client-option 'theme={"background":"#1e1e1e"}' \
--terminal-type xterm-256color \
"$HOME/.session-shell" &
TTYD_PID=$!
idle_watchdog &
wait "$TTYD_PID"