# Tumpai Team VM bootstrapper for Windows PowerShell / PowerShell 7. # It prepares a local shortcut and one member workspace on the shared Ubuntu VM. $ErrorActionPreference = 'Stop' $VMHost = if ($env:TUMPAI_VM_HOST) { $env:TUMPAI_VM_HOST } else { 'parallels@192.168.50.126' } $VMPort = if ($env:TUMPAI_VM_PORT) { $env:TUMPAI_VM_PORT } else { '22' } $Shortcut = if ($env:TUMPAI_VM_SHORTCUT) { $env:TUMPAI_VM_SHORTCUT } else { 'tumpai-vm' } $TeamIds = @('cj', 'lj', 'rxp', 'lyx') function Write-Info([string]$Text) { Write-Host "ℹ $Text" -ForegroundColor Cyan } function Write-Ok([string]$Text) { Write-Host "✓ $Text" -ForegroundColor Green } function Write-Warn([string]$Text) { Write-Host "⚠ $Text" -ForegroundColor Yellow } function Stop-Fail([string]$Text) { Write-Host "✗ $Text" -ForegroundColor Red; exit 1 } function Require-Command([string]$Name) { if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { Stop-Fail "缺少命令: $Name。请先安装 Windows OpenSSH Client。" } } function Choose-Member { Write-Host '' Write-Host '请选择你的成员目录:' -ForegroundColor White Write-Host ' 1) cj' Write-Host ' 2) lj' Write-Host ' 3) rxp' Write-Host ' 4) lyx' while ($true) { $choice = Read-Host '输入 1/2/3/4' switch ($choice) { '1' { return 'cj' } '2' { return 'lj' } '3' { return 'rxp' } '4' { return 'lyx' } 'cj' { return 'cj' } 'lj' { return 'lj' } 'rxp' { return 'rxp' } 'lyx' { return 'lyx' } default { Write-Warn '只能选择 1、2、3、4,或直接输入 cj / lj / rxp / lyx。' } } } } function Ensure-LoginKey { $sshDir = Join-Path $HOME '.ssh' $loginKey = if ($env:TUMPAI_VM_LOGIN_KEY) { $env:TUMPAI_VM_LOGIN_KEY } else { Join-Path $sshDir 'id_ed25519' } $pubKey = "$loginKey.pub" if (-not (Test-Path $pubKey)) { Write-Info "本机还没有 SSH 登录公钥,正在生成: $loginKey" New-Item -ItemType Directory -Force -Path $sshDir | Out-Null & ssh-keygen.exe -t ed25519 -f $loginKey -C "$env:USERNAME@tumpai-vm-login" -N '' } return @{ Private = $loginKey; PublicText = (Get-Content -Raw $pubKey).Trim() } } function Authorize-AndCreateWorkspace([string]$TeamId, [string]$PublicKey) { Write-Info "现在会连接 ${VMHost}。首次执行会要求输入 VM 密码;只有密码正确才会创建 ~/${TeamId}。" $remote = 'umask 077; mkdir -p ~/.ssh ~/' + $TeamId + '; touch ~/.ssh/authorized_keys; IFS= read -r key; grep -qxF -- "$key" ~/.ssh/authorized_keys || printf "%s\n" "$key" >> ~/.ssh/authorized_keys; chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys; printf "REMOTE_READY=' + $TeamId + '\n"' $PublicKey | & ssh.exe -p $VMPort $VMHost $remote if ($LASTEXITCODE -ne 0) { Stop-Fail 'VM 密码错误、Tailscale 未连通,或 SSH 登录失败。请先确认 Ubuntu VM 已执行:sudo apt update && sudo apt install -y openssh-server tmux git && sudo systemctl enable --now ssh' } Write-Ok "VM 登录公钥已写入,成员目录 ~/${TeamId} 已准备好。" } function Configure-RemoteClaudeRuntime { $remote = @' set -e block_start="# >>> siyun claude update lock >>>" block_end="# <<< siyun claude update lock <<<" write_shell_env() { f="$1" touch "$f" tmp="$(mktemp)" awk -v start="$block_start" -v end="$block_end" '$0==start{skip=1; next} $0==end{skip=0; next} !skip{print}' "$f" > "$tmp" { cat "$tmp" printf '\n%s\nexport DISABLE_AUTOUPDATER=1\nexport DISABLE_UPDATES=1\n%s\n' "$block_start" "$block_end" } > "$f" rm -f "$tmp" } write_shell_env "$HOME/.bashrc" write_shell_env "$HOME/.profile" tmux_conf="$HOME/.tmux.conf" touch "$tmux_conf" tmp="$(mktemp)" awk -v start="$block_start" -v end="$block_end" '$0==start{skip=1; next} $0==end{skip=0; next} !skip{print}' "$tmux_conf" > "$tmp" { cat "$tmp" printf '\n%s\nset-environment -g DISABLE_AUTOUPDATER 1\nset-environment -g DISABLE_UPDATES 1\n%s\n' "$block_start" "$block_end" } > "$tmux_conf" rm -f "$tmp" '@ & ssh.exe -p $VMPort $VMHost $remote Write-Ok '已写入 VM Claude 运行环境:禁用自动更新提示并锁定当前安装。' } function Configure-RemoteGitInclude([string]$TeamId, [string]$GitName, [string]$GitEmail) { $cfg = "[user]`n`tname = $GitName`n" if ($GitEmail.Trim().Length -gt 0) { $cfg += "`temail = $GitEmail`n" } $cfg += "[core]`n`tsshCommand = ssh -i ~/.ssh/github_siyun_dev_ed25519 -o IdentitiesOnly=yes`n" $cfg | & ssh.exe -p $VMPort $VMHost "cat > ~/.gitconfig-$TeamId" & ssh.exe -p $VMPort $VMHost "if command -v git >/dev/null 2>&1; then git config --global --replace-all 'includeIf.gitdir:~/$TeamId/.path' '~/.gitconfig-$TeamId'; else printf '%s\n' 'WARN_NO_GIT'; fi" Write-Ok "已写入 Git 目录级配置:~/${TeamId} 下的仓库会使用 ~/.gitconfig-${TeamId}。" } function Configure-GitSshAliases { $remote = @' set -e umask 077 mkdir -p ~/.ssh touch ~/.ssh/config awk '/^[#] >>> siyun-dev github >>>$/ {skip=1; next} /^[#] <<< siyun-dev github <<<$/{skip=0; next} !skip {print}' ~/.ssh/config > ~/.ssh/config.tmp mv ~/.ssh/config.tmp ~/.ssh/config cat >> ~/.ssh/config <<'EOF' # >>> siyun-dev github >>> Host github-main HostName ssh.github.com Port 443 User git IdentityFile ~/.ssh/github_siyun_dev_ed25519 IdentitiesOnly yes # <<< siyun-dev github <<< EOF chmod 600 ~/.ssh/config ssh-keyscan -p 443 ssh.github.com >> ~/.ssh/known_hosts 2>/dev/null || true sort -u ~/.ssh/known_hosts -o ~/.ssh/known_hosts 2>/dev/null || true chmod 644 ~/.ssh/known_hosts 2>/dev/null || true '@ & ssh.exe -p $VMPort $VMHost $remote Write-Ok '已写入 SSH Host 别名:github-main。' } function Setup-GitKey([string]$TeamId) { Write-Host '' Write-Host 'GitHub 共享访问配置:' -ForegroundColor White Write-Info '正在 VM 内生成或复用 ~/.ssh/github_siyun_dev_ed25519' & ssh.exe -p $VMPort $VMHost "umask 077; mkdir -p ~/.ssh; if [ ! -f ~/.ssh/github_siyun_dev_ed25519 ]; then ssh-keygen -t ed25519 -N '' -C 'siyun-dev' -f ~/.ssh/github_siyun_dev_ed25519 >/dev/null; fi; chmod 600 ~/.ssh/github_siyun_dev_ed25519; chmod 644 ~/.ssh/github_siyun_dev_ed25519.pub; printf '\n请把下面这行公钥添加到你的 GitHub 账号 SSH Keys,Title 写 siyun-dev:\n'; cat ~/.ssh/github_siyun_dev_ed25519.pub" $gitName = Read-Host "Git 提交姓名 [$TeamId]" if ([string]::IsNullOrWhiteSpace($gitName)) { $gitName = $TeamId } $gitEmail = Read-Host "Git 提交邮箱 [$TeamId@example.com]" if ([string]::IsNullOrWhiteSpace($gitEmail)) { $gitEmail = "$TeamId@example.com" } Configure-GitSshAliases Configure-RemoteGitInclude -TeamId $TeamId -GitName $gitName -GitEmail $gitEmail } function Write-Shortcut([string]$TeamId) { $binDir = Join-Path $HOME 'bin' $scriptPath = Join-Path $binDir "$Shortcut.ps1" New-Item -ItemType Directory -Force -Path $binDir | Out-Null $helper = @" `$ErrorActionPreference = 'Stop' `$VMHost = '$VMHost' `$VMPort = '$VMPort' `$TeamId = '$TeamId' `$remote = 'export DISABLE_AUTOUPDATER=1 DISABLE_UPDATES=1; TEAM_ID="' + `$TeamId + '"; DIR="`$HOME/' + `$TeamId + '"; SESSION="' + `$TeamId + '-main"; mkdir -p "`$DIR"; cd "`$DIR"; if command -v tmux >/dev/null 2>&1; then exec tmux new-session -A -s "`$SESSION" -c "`$DIR"; fi; echo "VM 未安装 tmux,已进入 `$DIR。建议管理员执行: sudo apt install -y tmux"; exec "`${SHELL:-/bin/bash}" -l' & ssh.exe -p `$VMPort -t `$VMHost `$remote "@ Set-Content -Path $scriptPath -Value $helper -Encoding UTF8 $profileDir = Split-Path -Parent $PROFILE if ($profileDir) { New-Item -ItemType Directory -Force -Path $profileDir | Out-Null } $content = if (Test-Path $PROFILE) { Get-Content -Raw $PROFILE } else { '' } $content = [regex]::Replace($content, '(?s)# >>> tumpai-vm shortcut >>>.*?# <<< tumpai-vm shortcut <<<\s*', '') $block = @" # >>> tumpai-vm shortcut >>> function tumpai-vm { & "$scriptPath" } Set-Alias vm tumpai-vm Set-Alias vm-$TeamId tumpai-vm # <<< tumpai-vm shortcut <<< "@ Set-Content -Path $PROFILE -Value ($content.TrimEnd() + $block + "`r`n") -Encoding UTF8 try { Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force | Out-Null } catch {} Write-Ok "已创建快捷命令脚本: $scriptPath" Write-Ok "已写入 PowerShell Profile 别名: vm / vm-$TeamId。重开终端后可直接使用。" } Write-Host '' Write-Host '╔════════════════════════════════════════════╗' Write-Host '║ Tumpai · 团队 Ubuntu VM 快捷入口 ║' Write-Host '╚════════════════════════════════════════════╝' Require-Command 'ssh.exe' Require-Command 'scp.exe' Require-Command 'ssh-keygen.exe' $TeamId = Choose-Member $login = Ensure-LoginKey Authorize-AndCreateWorkspace -TeamId $TeamId -PublicKey $login.PublicText Configure-RemoteClaudeRuntime Setup-GitKey -TeamId $TeamId Write-Shortcut -TeamId $TeamId Write-Host '' Write-Host '处理完毕。' -ForegroundColor Green Write-Host "以后打开 PowerShell 执行:$Shortcut 或 vm" Write-Host "会自动进入 VM 的 ~/${TeamId},并附着到 tmux 会话 ${TeamId}-main。"