feat: Alpine ベースの Claude Code リモート実行 Helm チャート初版 (v2.1.259)
gateway(nginx) + manager(python 標準ライブラリ) + セッションごとの使い捨て Pod + reaper CronJob の構成。ブラウザ(PC/スマホ)からアクセスし、セッション単位で 独立した Alpine コンテナに Git リポジトリ(GitHub/Gitea など)をチェックアウトして 編集・検証・プッシュする。無操作が続いたセッションは Pod ごと自動破棄。 - セキュリティ: セッション NS 分離 / restricted PodSecurity / RBAC はセッション NS の pods,services のみの Role / セッション Pod は SA トークン非マウント・非 root・ readOnlyRootFilesystem・drop ALL caps・seccomp / NetworkPolicy で egress を DNS+HTTPS+SSH に限定 / Git ホスト allow-list / ResourceQuota+LimitRange - ライフサイクル: idleTimeout(Pod 内ウォッチドッグ + manager 回収) / maxLifetime (activeDeadlineSeconds) / maxSessions / 予備の孤児掃除 CronJob - 認証: token(既定) / basic / forward(外部SSO) / none。WebSocket は Cookie トークン - Claude 認証: ANTHROPIC_API_KEY または CLAUDE_CODE_OAUTH_TOKEN - CI: helm-release.yaml(未リリース版のみ tag+release+publish) / image-update-and-release.yaml(週次 npm 追従) / session-image.yaml(multi-arch build) - files/ 実体を .Files.Get で ConfigMap 化。helm lint / template 検証済み
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
#!/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')"
|
||||
|
||||
# 追加 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"
|
||||
Reference in New Issue
Block a user