Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/check-upstream.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)

Expand Down
18 changes: 1 addition & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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/<name>.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
8 changes: 7 additions & 1 deletion builds/_shared/post/vzdump-and-cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -142,7 +148,7 @@ cat >"$SIDECAR.tmp" <<JSON
"group": "$CF_GROUP",
"sha256": "$SHA256",
"size": $SIZE,
"suggested_vmid": ${CF_BUILT_VMID},
"suggested_vmid": ${BASE_VMID},
"url": "$PUBLIC_URL",
"built_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
Expand Down
55 changes: 0 additions & 55 deletions docs/phase-3-verification.md

This file was deleted.

18 changes: 13 additions & 5 deletions scripts/cf-cluster-templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -21,7 +25,11 @@
set -uo pipefail

ARTIFACT="${1:?usage: cf-cluster-templates.sh <artifact-path>}"
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) -------------------------------------------------
Expand All @@ -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

Expand Down
3 changes: 2 additions & 1 deletion src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ export const buildPhase = async (
remoteTmpDir,
recipe.arch,
recipe.group ?? '',
recipe.finalDiskSize
recipe.finalDiskSize,
recipe.buildVmid
)

const unregisterVmCleanup =
Expand Down
9 changes: 8 additions & 1 deletion src/build/packer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down