mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
feat(offload): implement continuous golden image pipeline and robust fork discovery
This commit is contained in:
@@ -6,11 +6,10 @@ export USER=${USER:-ubuntu}
|
|||||||
export HOME=/home/$USER
|
export HOME=/home/$USER
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
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 lock
|
||||||
wait_for_apt() {
|
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
|
while sudo fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock >/dev/null 2>&1 ; do
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
@@ -18,9 +17,10 @@ wait_for_apt() {
|
|||||||
|
|
||||||
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 \
|
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
|
# 2. GitHub CLI
|
||||||
if ! command -v gh &> /dev/null; then
|
if ! command -v gh &> /dev/null; then
|
||||||
@@ -32,22 +32,28 @@ if ! command -v gh &> /dev/null; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. Direct Node.js 20 Installation (NodeSource)
|
# 3. Direct Node.js 20 Installation (NodeSource)
|
||||||
echo "Removing any existing nodejs/npm..."
|
if ! command -v node &> /dev/null; then
|
||||||
wait_for_apt
|
echo "Installing Node.js 20..."
|
||||||
apt-get purge -y nodejs npm || true
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||||
apt-get autoremove -y
|
wait_for_apt
|
||||||
|
apt-get install -y nodejs
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Installing Node.js 20 via NodeSource..."
|
# 4. Global Maintenance Tooling
|
||||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
echo "Installing global developer tools..."
|
||||||
wait_for_apt
|
npm install -g tsx vitest @google/gemini-cli@nightly
|
||||||
apt-get install -y nodejs
|
|
||||||
|
|
||||||
# Verify installations
|
# 5. Pre-warm Repository (Main Hub)
|
||||||
node -v
|
# We clone and build the main repo in the image so that new worktrees start with a warm cache
|
||||||
npm -v
|
REMOTE_WORK_DIR="$HOME/dev/main"
|
||||||
|
mkdir -p "$HOME/dev"
|
||||||
# 4. Install Gemini CLI (Nightly)
|
if [ ! -d "$REMOTE_WORK_DIR" ]; then
|
||||||
echo "Installing Gemini CLI..."
|
echo "Pre-cloning and building repository..."
|
||||||
npm install -g @google/gemini-cli@nightly
|
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!"
|
echo "✅ Provisioning Complete!"
|
||||||
|
|||||||
@@ -89,30 +89,40 @@ Host ${sshAlias}
|
|||||||
console.log('\n🍴 Configuring Security Fork...');
|
console.log('\n🍴 Configuring Security Fork...');
|
||||||
const upstreamRepo = 'google-gemini/gemini-cli';
|
const upstreamRepo = 'google-gemini/gemini-cli';
|
||||||
|
|
||||||
const forkCheck = spawnSync('gh', ['repo', 'view', '--json', 'parent,nameWithOwner'], { stdio: 'pipe' });
|
// Use API to find all forks owned by the user
|
||||||
let currentRepo = '';
|
const forksQuery = spawnSync('gh', ['api', 'user/repos', '--paginate', '-q', `.[] | select(.fork == true and .parent.full_name == "${upstreamRepo}") | .full_name`], { stdio: 'pipe' });
|
||||||
try {
|
const existingForks = forksQuery.stdout.toString().trim().split('\n').filter(Boolean);
|
||||||
const repoInfo = JSON.parse(forkCheck.stdout.toString());
|
|
||||||
currentRepo = repoInfo.nameWithOwner;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
let userFork = '';
|
let userFork = '';
|
||||||
if (currentRepo.includes(`${env.USER}/`) || currentRepo.includes('mattkorwel/')) {
|
if (existingForks.length > 0) {
|
||||||
userFork = currentRepo;
|
console.log(` 🔍 Found existing fork(s):`);
|
||||||
console.log(` ✅ Using existing fork: ${userFork}`);
|
existingForks.forEach((f, i) => console.log(` ${i + 1}. ${f}`));
|
||||||
} else {
|
|
||||||
console.log(` 🔍 No personal fork detected for ${upstreamRepo}.`);
|
if (existingForks.length === 1) {
|
||||||
if (await confirm(' Would you like to create a personal fork for autonomous work?')) {
|
if (await confirm(` Use existing fork ${existingForks[0]}?`)) {
|
||||||
const forkResult = spawnSync('gh', ['repo', 'fork', upstreamRepo, '--clone=false'], { stdio: 'inherit' });
|
userFork = existingForks[0];
|
||||||
if (forkResult.status === 0) {
|
|
||||||
// Get the fork name (usually <user>/gemini-cli)
|
|
||||||
const user = spawnSync('gh', ['api', 'user', '-q', '.login'], { stdio: 'pipe' }).stdout.toString().trim();
|
|
||||||
userFork = `${user}/gemini-cli`;
|
|
||||||
console.log(` ✅ Created fork: ${userFork}`);
|
|
||||||
}
|
}
|
||||||
|
} 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
|
// Use the alias for remaining setup steps
|
||||||
const remoteHost = sshAlias;
|
const remoteHost = sshAlias;
|
||||||
const remoteHome = spawnSync(`ssh ${remoteHost} "pwd"`, { shell: true }).stdout.toString().trim();
|
const remoteHome = spawnSync(`ssh ${remoteHost} "pwd"`, { shell: true }).stdout.toString().trim();
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user