diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a77963c..d240d87 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,15 @@ jobs: R2_ENDPOINT: ${{ vars.R2_ENDPOINT || secrets.R2_ENDPOINT }} R2_BUCKET: ${{ vars.R2_BUCKET || secrets.R2_BUCKET }} steps: + - uses: actions/create-github-app-token@v2 + id: registry-app-token + with: + app-id: ${{ secrets.REGISTRY_APP_ID }} + private-key: ${{ secrets.REGISTRY_APP_PRIVATE_KEY }} + - uses: actions/checkout@v7 + with: + token: ${{ steps.registry-app-token.outputs.token }} - uses: oven-sh/setup-bun@v2 @@ -144,12 +152,11 @@ jobs: # Canonical history: `git log registry.json` is the audit log of every # published version. Rollback = revert the commit. - name: Commit registry.json - uses: stefanzweifel/git-auto-commit-action@v7 - with: - file_pattern: registry.json - commit_message: "chore: publish ${{ inputs.recipe }} build" - commit_user_name: github-actions[bot] - commit_user_email: 41898282+github-actions[bot]@users.noreply.github.com + run: | + git config user.name "cofoundry-registry-writer[bot]" + git config user.email "cofoundry-registry-writer[bot]@users.noreply.github.com" + git add registry.json + git diff --cached --quiet || (git commit -m "chore: publish ${{ inputs.recipe }} build" && git push) # ── Mirror registry.json to R2 ───────────────────────────────────────── # Single fetch origin for consumers. Short TTL so rollbacks propagate diff --git a/.github/workflows/check-upstream.yml b/.github/workflows/check-upstream.yml index f034ff3..43c6d37 100644 --- a/.github/workflows/check-upstream.yml +++ b/.github/workflows/check-upstream.yml @@ -20,7 +20,15 @@ jobs: matrix: ${{ steps.check.outputs.matrix }} any_changed: ${{ steps.check.outputs.any_changed }} steps: + - uses: actions/create-github-app-token@v2 + id: registry-app-token + with: + app-id: ${{ secrets.REGISTRY_APP_ID }} + private-key: ${{ secrets.REGISTRY_APP_PRIVATE_KEY }} + - uses: actions/checkout@v7 + with: + token: ${{ steps.registry-app-token.outputs.token }} - uses: oven-sh/setup-bun@v2 @@ -44,8 +52,8 @@ jobs: - name: Commit updated checksums run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "cofoundry-registry-writer[bot]" + git config user.email "cofoundry-registry-writer[bot]@users.noreply.github.com" git add upstream-checksums.json git diff --staged --quiet || (git commit -m "chore: update upstream checksums" && git push) diff --git a/CLAUDE.md b/CLAUDE.md index 0450402..eef4bd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,17 +1 @@ -# Claude Notes - -See [AGENTS.md](AGENTS.md) for agent-facing guidance on disk sizing, Windows Server 2025 requirements, and preseed key injection behavior. - -## Project overview - -This repo builds Proxmox VM templates via Packer and exports them as vzdump `.vma.zst` artifacts. Packer runs **on the Proxmox node** (not locally) — `src/build.ts` rsyncs the repo to the node and invokes packer over SSH. - -## Key files - -- `src/cli.ts` — CLI entry point (`tb build`, `tb list`, etc.) -- `src/build.ts` — rsync + remote packer orchestration -- `src/config.ts` — recipe metadata parsing (reads HCL comment headers) -- `builds/.pkr.hcl` — one file per recipe -- `builds/_shared/post/vzdump-and-cleanup.sh` — post-processor: vzdump, move artifact, write sidecar JSON -- `scripts/inject-placeholders.sh` — generates ephemeral SSH keypair, injects into preseed -- `.env` — local secrets (never commit) +@AGENTS.md \ No newline at end of file diff --git a/builds/_shared/post/vzdump-and-cleanup.sh b/builds/_shared/post/vzdump-and-cleanup.sh index b65f0ae..d90e6c6 100755 --- a/builds/_shared/post/vzdump-and-cleanup.sh +++ b/builds/_shared/post/vzdump-and-cleanup.sh @@ -28,6 +28,12 @@ set -euo pipefail : "${CF_ARCH:?}" : "${CF_GROUP:?}" +# CF_BUILT_VMID is the slot-derived build id (recipe base * 100 + slot index) +# for parallel builds. The sidecar's suggested_vmid should advertise the recipe +# BASE, which cf exports as CF_RECIPE_BASE_VMID. Plain non-slot builds set only +# CF_BUILT_VMID, where the two are equal. +BASE_VMID="${CF_RECIPE_BASE_VMID:-$CF_BUILT_VMID}" + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOCAL_FILE="$CF_OUT_DIR/${CF_RECIPE_NAME}-${CF_ARCH}.vma.zst" @@ -142,7 +148,7 @@ cat >"$SIDECAR.tmp" < -# wait for completion, then on the PVE node: -qm clone 9999 --full && qm start 9999 && qm terminal 9999 -``` - -For each recipe, confirm: - -- Build completes without bootloop (the bigger risk for Windows — DriverPath - letter or vioscsi dir name). -- Cloned VM boots and reaches login. -- Linux: `lsblk` shows `/dev/sda*` (not vda), `systemctl status qemu-guest-agent` - is active, `qm terminal` produces output (serial console works). -- Windows: Device Manager shows "Red Hat VirtIO SCSI controller" and "Red Hat - VirtIO Ethernet Adapter"; VirtIO tools service running. - -## Known risks - -- **windows-server-2025 driver path**: virtio-win-0.1.248 may not ship a - `2k25` directory. The recipe currently uses `2k22` as a fallback per - Microsoft's compat guidance. Verify the directory exists in the mounted - ISO; if 2k25 is present, change `E:\vioscsi\2k22\amd64` → - `E:\vioscsi\2k25\amd64` in `builds/windows-server-2025/autounattend.xml` - (same for NetKVM). -- **CD drive letter (E:)**: Packer attaches the virtio ISO via - `additional_iso_files` (sata) and the answerfiles CD via the second - `additional_iso_files` (ide). Windows typically assigns the SATA CD-ROM - first (D:) and the IDE CD second (E:) — but the plan and autounattend - reference `E:\vioscsi`. If installation can't find a disk to install on, - the driver path is wrong; try `D:\vioscsi\...` instead. -- **Linux LVM on /dev/sda**: Debian preseed now installs to `/dev/sda` and - grub goes to `/dev/sda`. If `grub-install` fails, check that the boot disk - is the first SCSI disk (`scsi0`); the build VM should not have extra - attached disks. -- **Serial console**: kernel cmdline is `console=tty0 console=ttyS0,115200` - — last `console=` wins for the kernel's primary console, so logs go to - the serial port. noVNC still works because `console=tty0` is listed first. diff --git a/scripts/cf-cluster-templates.sh b/scripts/cf-cluster-templates.sh index 4675c22..218029e 100755 --- a/scripts/cf-cluster-templates.sh +++ b/scripts/cf-cluster-templates.sh @@ -9,10 +9,14 @@ # CF_UPLOAD_CMD=bash $PVE_DUMP_DIR/cofoundry-work/scripts/cf-cluster-templates.sh {{file}} # # Reads from the environment (set by the recipe's post-processor): -# CF_BUILT_VMID (required), CF_RECIPE_NAME, CF_ARCH +# CF_RECIPE_BASE_VMID or CF_BUILT_VMID (required), CF_RECIPE_NAME, CF_ARCH # -# Per-node VMID = node_id * OFFSET + CF_BUILT_VMID (OFFSET default 10000) -# build_vmid 4001 -> node1=14001, node2=24001, node3=34001 +# CF_BUILT_VMID is the slot-derived build id (recipe base * 100 + slot index) +# for parallel builds; CF_RECIPE_BASE_VMID is the recipe base cf exports for +# the per-node template numbering. Plain builds set only CF_BUILT_VMID = base. +# +# Per-node VMID = node_id * OFFSET + BASE_VMID (OFFSET default 10000) +# base 4001 -> node1=14001, node2=24001, node3=34001 # # LOCAL/cluster convenience — not part of the upstream recipes. @@ -21,7 +25,11 @@ set -uo pipefail ARTIFACT="${1:?usage: cf-cluster-templates.sh }" -BASE_VMID="${CF_BUILT_VMID:?CF_BUILT_VMID not set}" +# cf exports the recipe BASE directly. CF_BUILT_VMID is the slot-derived build +# id (recipe base * 100 + slot index) for parallel builds; the per-node template +# numbering needs the base, so prefer CF_RECIPE_BASE_VMID. A plain (non-slot) +# build doesn't set it — CF_BUILT_VMID is then the base itself. +BASE_VMID="${CF_RECIPE_BASE_VMID:-${CF_BUILT_VMID:?CF_BUILT_VMID or CF_RECIPE_BASE_VMID not set}}" DUMP_DIR="${PVE_DUMP_DIR:-/var/lib/vz/dump}" # --- knobs (edit to taste) ------------------------------------------------- @@ -34,7 +42,7 @@ OFFSET="${CF_TEMPLATE_VMID_OFFSET:-10000}" # per-node VMID spacing # Adjacent nodes collide if BASE_VMID >= OFFSET (e.g. node1+14001 == node2+4001). if [ "$BASE_VMID" -ge "$OFFSET" ]; then - echo "cf-cluster-templates: CF_BUILT_VMID ($BASE_VMID) must be < CF_TEMPLATE_VMID_OFFSET ($OFFSET)" >&2 + echo "cf-cluster-templates: derived base VMID ($BASE_VMID) must be < CF_TEMPLATE_VMID_OFFSET ($OFFSET)" >&2 exit 1 fi diff --git a/src/build.ts b/src/build.ts index abb6d1e..873a294 100644 --- a/src/build.ts +++ b/src/build.ts @@ -382,7 +382,8 @@ export const buildPhase = async ( remoteTmpDir, recipe.arch, recipe.group ?? '', - recipe.finalDiskSize + recipe.finalDiskSize, + recipe.buildVmid ) const unregisterVmCleanup = diff --git a/src/build/packer.ts b/src/build/packer.ts index dc11e50..e2513ec 100644 --- a/src/build/packer.ts +++ b/src/build/packer.ts @@ -65,7 +65,8 @@ export const buildRemoteEnv = ( remoteTmpDir: string, arch: string, group: string, - finalDiskSize?: string + finalDiskSize?: string, + baseVmid?: number ): string => { // Packer runs on the PVE node, so SSH_TARGET=local tells the post-processor // to run vzdump directly instead of SSHing back to itself. @@ -78,6 +79,12 @@ export const buildRemoteEnv = ( TMPDIR: remoteTmpDir, PACKER_CACHE_DIR: '/var/lib/vz/template/iso', } + // The post-processor's CF_BUILT_VMID is the slot-derived id (base*100+slot) + // for parallel builds; export the recipe BASE so downstream consumers + // (e.g. cf-cluster-templates.sh) get it directly instead of reverse- + // engineering it. Inherited by the shell-local post-processor and any + // CF_UPLOAD_CMD it spawns. + if (baseVmid !== undefined) pairs.CF_RECIPE_BASE_VMID = String(baseVmid) // Opt-in: when set, the post-processor shrinks the OS disk to this size // before vzdump (see builds/_shared/post/shrink-disk.sh). if (finalDiskSize) pairs.CF_FINAL_DISK_SIZE = finalDiskSize