From 162ac93f81e9f60697baf6ae429b10c05696f09c Mon Sep 17 00:00:00 2001 From: mkorwel Date: Sat, 14 Mar 2026 11:59:41 -0700 Subject: [PATCH] feat(offload): implement continuous golden image pipeline and robust fork discovery --- .../offload/scripts/provision-worker.sh | 44 ++++++++++-------- .gemini/skills/offload/scripts/setup.ts | 46 +++++++++++-------- .github/workflows/offload-image-update.yml | 41 +++++++++++++++++ 3 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/offload-image-update.yml diff --git a/.gemini/skills/offload/scripts/provision-worker.sh b/.gemini/skills/offload/scripts/provision-worker.sh index be7edc51dd..f1fc1cf82b 100644 --- a/.gemini/skills/offload/scripts/provision-worker.sh +++ b/.gemini/skills/offload/scripts/provision-worker.sh @@ -6,11 +6,10 @@ export USER=${USER:-ubuntu} export HOME=/home/$USER export DEBIAN_FRONTEND=noninteractive -echo "šŸ› ļø Provisioning Gemini CLI Maintainer Worker for user: $USER" +echo "šŸ› ļø Provisioning High-Performance Gemini CLI Maintainer Worker..." # Wait for apt lock wait_for_apt() { - echo "Waiting for apt lock..." while sudo fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock >/dev/null 2>&1 ; do sleep 2 done @@ -18,9 +17,10 @@ wait_for_apt() { wait_for_apt -# 1. System Essentials +# 1. System Essentials (Inc. libraries for native node modules) apt-get update && apt-get install -y \ - curl git git-lfs tmux build-essential unzip jq gnupg cron + curl git git-lfs tmux build-essential unzip jq gnupg cron \ + libsecret-1-dev libkrb5-dev # 2. GitHub CLI if ! command -v gh &> /dev/null; then @@ -32,22 +32,28 @@ if ! command -v gh &> /dev/null; then fi # 3. Direct Node.js 20 Installation (NodeSource) -echo "Removing any existing nodejs/npm..." -wait_for_apt -apt-get purge -y nodejs npm || true -apt-get autoremove -y +if ! command -v node &> /dev/null; then + echo "Installing Node.js 20..." + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + wait_for_apt + apt-get install -y nodejs +fi -echo "Installing Node.js 20 via NodeSource..." -curl -fsSL https://deb.nodesource.com/setup_20.x | bash - -wait_for_apt -apt-get install -y nodejs +# 4. Global Maintenance Tooling +echo "Installing global developer tools..." +npm install -g tsx vitest @google/gemini-cli@nightly -# Verify installations -node -v -npm -v - -# 4. Install Gemini CLI (Nightly) -echo "Installing Gemini CLI..." -npm install -g @google/gemini-cli@nightly +# 5. Pre-warm Repository (Main Hub) +# We clone and build the main repo in the image so that new worktrees start with a warm cache +REMOTE_WORK_DIR="$HOME/dev/main" +mkdir -p "$HOME/dev" +if [ ! -d "$REMOTE_WORK_DIR" ]; then + echo "Pre-cloning and building repository..." + git clone --filter=blob:none https://github.com/google-gemini/gemini-cli.git "$REMOTE_WORK_DIR" + cd "$REMOTE_WORK_DIR" + npm install --no-audit --no-fund + npm run build +fi +chown -R $USER:$USER $HOME/dev echo "āœ… Provisioning Complete!" diff --git a/.gemini/skills/offload/scripts/setup.ts b/.gemini/skills/offload/scripts/setup.ts index b5a0349515..410b41349f 100644 --- a/.gemini/skills/offload/scripts/setup.ts +++ b/.gemini/skills/offload/scripts/setup.ts @@ -89,30 +89,40 @@ Host ${sshAlias} console.log('\nšŸ“ Configuring Security Fork...'); const upstreamRepo = 'google-gemini/gemini-cli'; - const forkCheck = spawnSync('gh', ['repo', 'view', '--json', 'parent,nameWithOwner'], { stdio: 'pipe' }); - let currentRepo = ''; - try { - const repoInfo = JSON.parse(forkCheck.stdout.toString()); - currentRepo = repoInfo.nameWithOwner; - } catch (e) {} + // Use API to find all forks owned by the user + const forksQuery = spawnSync('gh', ['api', 'user/repos', '--paginate', '-q', `.[] | select(.fork == true and .parent.full_name == "${upstreamRepo}") | .full_name`], { stdio: 'pipe' }); + const existingForks = forksQuery.stdout.toString().trim().split('\n').filter(Boolean); let userFork = ''; - if (currentRepo.includes(`${env.USER}/`) || currentRepo.includes('mattkorwel/')) { - userFork = currentRepo; - console.log(` āœ… Using existing fork: ${userFork}`); - } else { - console.log(` šŸ” No personal fork detected for ${upstreamRepo}.`); - if (await confirm(' Would you like to create a personal fork for autonomous work?')) { - const forkResult = spawnSync('gh', ['repo', 'fork', upstreamRepo, '--clone=false'], { stdio: 'inherit' }); - if (forkResult.status === 0) { - // Get the fork name (usually /gemini-cli) - const user = spawnSync('gh', ['api', 'user', '-q', '.login'], { stdio: 'pipe' }).stdout.toString().trim(); - userFork = `${user}/gemini-cli`; - console.log(` āœ… Created fork: ${userFork}`); + if (existingForks.length > 0) { + console.log(` šŸ” Found existing fork(s):`); + existingForks.forEach((f, i) => console.log(` ${i + 1}. ${f}`)); + + if (existingForks.length === 1) { + if (await confirm(` Use existing fork ${existingForks[0]}?`)) { + userFork = existingForks[0]; } + } else { + const choice = await prompt(` Select fork (1-${existingForks.length}) or type 'new'`, '1'); + if (choice !== 'new') userFork = existingForks[parseInt(choice) - 1]; } } + if (!userFork) { + console.log(` šŸ” No fork selected or detected.`); + if (await confirm(' Create a fresh personal fork?')) { + spawnSync('gh', ['repo', 'fork', upstreamRepo, '--clone=false'], { stdio: 'inherit' }); + const user = spawnSync('gh', ['api', 'user', '-q', '.login'], { stdio: 'pipe' }).stdout.toString().trim(); + userFork = `${user}/gemini-cli`; + } + } + + if (!userFork) { + console.error('āŒ A personal fork is required for autonomous offload tasks.'); + return 1; + } + console.log(` āœ… Using fork: ${userFork}`); + // Use the alias for remaining setup steps const remoteHost = sshAlias; const remoteHome = spawnSync(`ssh ${remoteHost} "pwd"`, { shell: true }).stdout.toString().trim(); diff --git a/.github/workflows/offload-image-update.yml b/.github/workflows/offload-image-update.yml new file mode 100644 index 0000000000..ffc5f06e9a --- /dev/null +++ b/.github/workflows/offload-image-update.yml @@ -0,0 +1,41 @@ +name: Offload Golden Image Update + +on: + push: + branches: + - main + paths: + - '.gemini/skills/offload/scripts/provision-worker.sh' + - '.gemini/skills/offload/scripts/fleet.ts' + workflow_dispatch: + +jobs: + update-image: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Dependencies + run: npm install + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WIP }} + service_account: ${{ secrets.GCP_SA }} + + - name: Setup GCloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Build New Golden Image + run: npm run offload:fleet create-image