From b836174d5ad5e4ea36a5f456061a00862317375f Mon Sep 17 00:00:00 2001 From: yyamashita Date: Sun, 17 May 2026 11:02:19 +0900 Subject: Add Claude Code session management via post-receive hook Root's post-receive hook now calls scripts/sync-claude-services.sh to manage yyamashita's systemd user services on git push. Sessions are configured in claude-code-sessions.txt (user:repo format). Co-Authored-By: Claude Sonnet 4.6 --- Deploy.md | 40 +++++++++++++--------- claude-code-sessions.txt | 6 ++++ scripts/sync-claude-services.sh | 70 +++++++++++++++++++++++++++++++++++++++ systemd/user/claude-code@.service | 17 ++++++++++ 4 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 claude-code-sessions.txt create mode 100755 scripts/sync-claude-services.sh create mode 100644 systemd/user/claude-code@.service diff --git a/Deploy.md b/Deploy.md index 2a50159..3c6e89b 100644 --- a/Deploy.md +++ b/Deploy.md @@ -60,29 +60,37 @@ ssh golive 'docker logs whois-app -f' 4. `git remote add hetzner golive:/var/git/.git` 5. `git push hetzner master` -## Claude Code リモートセッション(デーモン) +## Claude Code セッション管理 -各リポジトリに対して Claude Code を常駐プロセスとして起動する。 +`git push` 時に post-receive フック(root)が `yyamashita` ユーザーの systemd user service を自動同期する。 -```bash -scp scripts/claude-daemon-setup.sh golive:~/ -ssh golive 'bash claude-daemon-setup.sh' -``` +| ファイル | 役割 | +|---|---| +| `systemd/user/claude-code@.service` | サービステンプレート | +| `claude-code-sessions.txt` | 有効にするセッション一覧(`user:repo` 形式) | +| `scripts/sync-claude-services.sh` | root が user service を同期するスクリプト | -起動後は [https://claude.ai/code](https://claude.ai/code) から各リポジトリのセッションに接続できる。 +### セッション追加・削除 -| サービス名 | 対象ディレクトリ | -|---|---| -| `claude-infra` | `/app/infra` | -| `claude-tokyo` | `/app` | -| `claude-whoisband` | `/app/whois-band` | +`claude-code-sessions.txt` を編集して `git push hetzner master`: + +``` +# 形式: user:repo +yyamashita:hetzner-infra +yyamashita:new-repo +``` + +### 手動同期(サーバー上で) ```bash -# 状態確認 -ssh golive 'systemctl status claude-tokyo' +ssh root@localhost 'bash /app/infra/scripts/sync-claude-services.sh' +``` -# ログ確認 -ssh golive 'journalctl -u claude-tokyo -f' +### サービス確認 + +```bash +systemctl --user status 'claude-code@*.service' +tmux ls ``` ## 初回サーバーセットアップ(再構築時) diff --git a/claude-code-sessions.txt b/claude-code-sessions.txt new file mode 100644 index 0000000..250c3e8 --- /dev/null +++ b/claude-code-sessions.txt @@ -0,0 +1,6 @@ +# Claude Code セッション設定 +# 形式: : +# repo は /home//workspaces/repos/ が存在すること +yyamashita:hetzner-infra +yyamashita:tokyo-livehouse-events +yyamashita:whois-band diff --git a/scripts/sync-claude-services.sh b/scripts/sync-claude-services.sh new file mode 100755 index 0000000..a9a76fb --- /dev/null +++ b/scripts/sync-claude-services.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# サーバー側で root として実行し、各ユーザーの Claude Code systemd user service を同期する +set -euo pipefail + +APP_DIR="$(cd "$(dirname "$0")/.." && pwd)" +TEMPLATE_SRC="$APP_DIR/systemd/user/claude-code@.service" +SESSIONS_FILE="$APP_DIR/claude-code-sessions.txt" + +# sessions.txt をパースして user -> repos のマップを構築 +declare -A user_repos + +while IFS= read -r line; do + [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue + user="${line%%:*}" + repo="${line#*:}" + user_repos["$user"]+=" $repo" +done < "$SESSIONS_FILE" + +for user in "${!user_repos[@]}"; do + uid=$(id -u "$user" 2>/dev/null) || { echo "WARNING: user $user not found, skipping"; continue; } + runtime_dir="/run/user/$uid" + systemd_dir="/home/$user/.config/systemd/user" + + mkdir -p "$systemd_dir" + chown "$user:$user" "$systemd_dir" + + # テンプレートを更新(差分があれば) + if ! diff -q "$TEMPLATE_SRC" "$systemd_dir/claude-code@.service" >/dev/null 2>&1; then + echo "[$user] Updating claude-code@.service template" + cp "$TEMPLATE_SRC" "$systemd_dir/claude-code@.service" + chown "$user:$user" "$systemd_dir/claude-code@.service" + runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \ + systemctl --user daemon-reload + fi + + # sessions.txt で指定されたリポジトリを enable & start + desired=() + for repo in ${user_repos[$user]}; do + [[ -z "$repo" ]] && continue + desired+=("$repo") + repo_path="/home/$user/workspaces/repos/$repo" + if [[ ! -d "$repo_path" ]]; then + echo "[$user] WARNING: $repo_path not found, skipping $repo" + continue + fi + runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \ + systemctl --user enable "claude-code@${repo}.service" 2>/dev/null || true + if ! runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \ + systemctl --user is-active "claude-code@${repo}.service" >/dev/null 2>&1; then + echo "[$user] Starting claude-code@${repo}.service" + runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \ + systemctl --user start "claude-code@${repo}.service" || true + fi + done + + # sessions.txt にないが enabled になっているインスタンスを無効化 + while IFS= read -r unit; do + instance="${unit#claude-code@}" + instance="${instance%.service}" + if ! printf '%s\n' "${desired[@]}" | grep -qx "$instance"; then + echo "[$user] Disabling claude-code@${instance}.service (not in sessions.txt)" + runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \ + systemctl --user disable --now "claude-code@${instance}.service" || true + fi + done < <(runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \ + systemctl --user list-unit-files --plain --no-legend 'claude-code@*.service' 2>/dev/null \ + | awk '$2 == "enabled" {print $1}') +done + +echo "Claude Code session sync complete." diff --git a/systemd/user/claude-code@.service b/systemd/user/claude-code@.service new file mode 100644 index 0000000..e3983b6 --- /dev/null +++ b/systemd/user/claude-code@.service @@ -0,0 +1,17 @@ +[Unit] +Description=Claude Code Remote Session (%i) +After=network.target + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/home/yyamashita/workspaces/repos/%i +ExecStart=/usr/bin/tmux new-session -d -s claude-%i -c /home/yyamashita/workspaces/repos/%i /usr/local/bin/claude --remote-control %i --dangerously-skip-permissions +ExecStop=/usr/bin/tmux kill-session -t claude-%i +Environment=HOME=/home/yyamashita +Environment=USER=yyamashita +Environment=XDG_RUNTIME_DIR=/run/user/1000 +Environment=TMUX_TMPDIR=/tmp + +[Install] +WantedBy=default.target -- cgit v1.2.3