Files
claude-code/files/session-entrypoint.sh
T
claude 528a48b667
Helm Chart Release / release-chart (push) Successful in 9s
Build Session Image / build-push (push) Successful in 2m8s
feat: credentials 認証モードとトークン自動リフレッシュ CronJob を追加 (v2.1.263-b)
トークンが定期的に失効・ローテーションしても再デプロイ不要にする。

- anthropic.authMode: credentials(新既定)
  ~/.claude/.credentials.json 全体(refreshToken 込み)を Secret に格納。
  セッション Pod にはファイルとしてマウントし $HOME/.claude へコピー(書き込み可)、
  セッション内 claude が refreshToken で自己更新
- anthropic.refresher CronJob(credentials 時・既定有効, files/token-refresher.py)
  Secret 内 refreshToken で OAuth トークンを更新し Secret を書き戻す。
  専用 SA + resourceNames 限定 Role で該当 Secret の get/patch のみ。
  tokenUrl/clientId は Claude Code CLI の非公式値(values 上書き可)
- manager.py build_pod: credentials は envFrom を使わずファイルマウント +
  SEED_CREDENTIALS_FILE。apiKey/oauth は従来どおり envFrom
- Secret に helm.sh/resource-policy: keep(credentials 時)で refresher の書込を保持
- fix: values.schema.json の $schema 行を削除(一部の Helm で
  "no schema with key or ref https://json-schema.org/draft-07/schema#" エラー)
- helm lint / template(credentials/apiKey/oauth)検証済み
2026-09-08 15:18:21 +09:00

250 lines
11 KiB
Bash
Raw 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
# --------------------------------------------------------------------------
REPO_DIR="$WORKSPACE_DIR/$(basename "${REPO_URL%.git}")"
if [ -d "$REPO_DIR/.git" ]; then
log "既存チェックアウトを再利用: $REPO_DIR"
else
log "clone 中: $REPO_URL ($REPO_REF)"
if ! git clone --filter=blob:none --no-tags "$REPO_URL" "$REPO_DIR"; then
err "clone に失敗しました"; 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"