Compare commits

..

2 Commits

Author SHA1 Message Date
didericis a73aba935f fix(firecracker): read only BOT_BOTTLE_INFRA_ARTIFACT_TOKEN for the artifact
lint / lint (push) Successful in 2m19s
test / unit (pull_request) Successful in 1m21s
test / integration (pull_request) Successful in 24s
test / coverage (pull_request) Successful in 1m24s
Drop the fallback to the general-purpose BOT_BOTTLE_CLAUDE_GITEA_TOKEN so
the artifact pull/publish uses a dedicated, package-scoped token that can
be granted (or revoked) independently of the general Gitea token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoEZHDjv84ChoZbozQERhJ
2026-07-16 23:31:17 -04:00
didericis 948504bde2 feat(firecracker): pull the infra rootfs as a prebuilt artifact (PRD 0069 Stage 2)
lint / lint (push) Successful in 2m25s
test / unit (pull_request) Successful in 1m22s
test / integration (pull_request) Successful in 26s
test / coverage (pull_request) Successful in 1m23s
Stage 2 of the docker-free Firecracker backend (#348): stop building the
fixed infra image on the launch host. The infra VM's rootfs is host- and
bottle-agnostic (authorized_keys + guest IP ride the kernel cmdline, not the
rootfs), so it's built once off-host and published as a versioned, ready-to-
boot ext4; the launch host downloads + verifies + boots it — no Docker, no
image tooling, just HTTP + gunzip.

- infra_artifact.py: version = content hash of the rootfs inputs (the shipped
  bot_bottle package + the three Dockerfiles + the init), so a launch host
  pulls the artifact matching its code and a content change can't silently
  boot a stale rootfs. Pull + sha256-verify (fail-closed) + gunzip from a
  Gitea generic package; base/owner/token configurable, default this Gitea.
- infra_vm.ensure_built/boot default to the pull path; BOT_BOTTLE_INFRA_BUILD=
  local keeps the docker build-from-source path for iterating on Dockerfiles.
- publish_infra.py: the off-host half — builds the images with Docker, mke2fs
  the rootfs (with buildah slack), gzips, and PUTs it to the generic package.

Rollout note: default=pull means a launch 404s until an artifact is published;
until the Gitea packages endpoint is enabled + an artifact published, use
BOT_BOTTLE_INFRA_BUILD=local. Freeze/migrate's remaining docker use is a
separate PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoEZHDjv84ChoZbozQERhJ
2026-07-16 23:21:17 -04:00
3 changed files with 3 additions and 16 deletions
-10
View File
@@ -31,7 +31,6 @@ from __future__ import annotations
import dataclasses
import json
import secrets
import socket
import string
from dataclasses import dataclass
from pathlib import Path
@@ -88,14 +87,6 @@ def bottle_identity(agent_name: str) -> str:
return f"{slug}-{suffix}"
def globalize_slug(slug: str) -> str:
"""Return a globally-unique slug qualified with the current hostname.
Assumes slug is a value returned from mint_slug. Use wherever a slug
must be unique across hosts (e.g. deploy-key titles)."""
return f"{socket.gethostname()}-{slug}"
@dataclass(frozen=True)
class BottleMetadata:
"""Persistent record of how a bottle was launched, written at
@@ -349,7 +340,6 @@ __all__ = [
"BottleMetadata",
"agent_state_dir",
"bottle_identity",
"globalize_slug",
"bottle_state_dir",
"cleanup_state",
"clear_preserve_marker",
+1 -2
View File
@@ -13,7 +13,6 @@ import dataclasses
from pathlib import Path
from typing import TYPE_CHECKING
from .bottle_state import globalize_slug
from .errors import MissingEnvVarError
from .log import info
from .manifest import ManifestBottle, ManifestGitEntry
@@ -47,7 +46,7 @@ def _provision_dynamic_key(
owner_repo = entry.UpstreamPath
if owner_repo.endswith(".git"):
owner_repo = owner_repo[:-4]
title = f"bot-bottle:{globalize_slug(slug)}:{entry.Name}"
title = f"bot-bottle:{slug}:{entry.Name}"
info(f"provisioning deploy key for git-gate.repos[{entry.Name!r}]")
key_id, private_key_bytes = provisioner.create(owner_repo, title)
+2 -4
View File
@@ -6,7 +6,6 @@ Covers the pure `git_gate_render_gitconfig` renderer and the dynamic
from __future__ import annotations
import socket
import tempfile
import types
import unittest
@@ -127,9 +126,8 @@ class TestProvisionDynamicKey(unittest.TestCase):
self.assertEqual(b"PRIVATE-KEY-BYTES", key_file.read_bytes())
id_file = Path(d) / "repo-deploy-key-id"
self.assertEqual("kid123", id_file.read_text())
# owner_repo had .git stripped; title carries globalize_slug(slug) + name
hostname = socket.gethostname()
self.assertEqual([("o/r", f"bot-bottle:{hostname}-myslug:repo")], fake.created)
# owner_repo had .git stripped; title carries slug + name
self.assertEqual([("o/r", "bot-bottle:myslug:repo")], fake.created)
def test_missing_token_raises(self) -> None:
with tempfile.TemporaryDirectory() as d, \