Skip to content

Commit 878cbe7

Browse files
committed
feat: GHCR token auto-login for public image pulls
Add GHCR_TOKEN support for anonymous-free image pulls: - lpb.py: auto-login GHCR before pull if token available - start.sh: bake login at container start - lpb.conf.env: GHCR_TOKEN placeholder for user config - lpb.stack.env: GHCR_TOKEN baked into container image How it works: 1. User creates read-only PAT: https://github.com/settings/tokens Scope: read:packages 2. User sets GHCR_TOKEN in .env or lpb.conf.env 3. lpb.py passes token to container 4. Container auto-logs in to GHCR 5. docker/podman pull works without manual login Future: replace with org account for true public access
1 parent b260d59 commit 878cbe7

4 files changed

Lines changed: 50 additions & 2 deletions

File tree

lpb.conf.env

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ LPB_AGENT_BROWSER_IDLE_TIMEOUT_MS=300000
6666
# Persist gh CLI auth to ~/.config/gh inside container
6767
LPB_PERSIST_GH_CONFIG=true
6868
LPB_PERSIST_GH_CONFIG=true
69+
70+
# ─── GHCR Token (image pulls) ──────────────────────────────────────────
71+
# Read-only PAT for GHCR pulls (personal account requires auth).
72+
# Create at: https://github.com/settings/tokens scope: read:packages
73+
# Leave empty to use GITHUB_TOKEN / LPB_GITHUB_TOKEN from environment.
74+
GHCR_TOKEN=
75+
GHCR_USERNAME=localpibox
6976
# ─── Agent Date/Time ──────────────────────────────────────────────────
7077
# Set by start.sh at runtime (via $(date)) so the agent always knows
7178
# the current date/time. Available as PI_DATE and PI_TIME environment

lpb.stack.env

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ LPB_CONFIG_REF=dev
2929
LPB_IMAGE_CLI=ghcr.io/localpibox/devstack:cli
3030
LPB_IMAGE_WEB=ghcr.io/localpibox/devstack:web
3131

32+
# ─── GHCR Token ───────────────────────────────────────────────────────
33+
# Read-only PAT for image pulls (baked into image for public access).
34+
# Create at: https://github.com/settings/tokens scope: read:packages
35+
# Leave empty to use GITHUB_TOKEN / LPB_GITHUB_TOKEN from environment.
36+
GHCR_TOKEN=
37+
GHCR_USERNAME=localpibox
38+
3239
# ─── Container Identity ─────────────────────────────────────────────────
3340
LPB_CONTAINER_NAME=localpibox
3441

scripts/lpb.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,8 +554,11 @@ def containers_logs(self, name: str, follow: bool = True, tail: int | None = Non
554554
return False
555555

556556
def images_pull(self, name):
557-
"""Pull image with full verbosity, no stdin, and no timeout.
558-
Uses Popen to stream output in real-time. No stdin to avoid blocking on slow connections."""
557+
"""Pull image with full verbosity, auto-login to GHCR if needed."""
558+
# Auto-login to GHCR for LocalPibox images
559+
if name.startswith("ghcr.io/localpibox/"):
560+
self._ghcr_login()
561+
559562
proc = subprocess.Popen(
560563
[self.cmd, "pull", name],
561564
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
@@ -569,6 +572,20 @@ def images_pull(self, name):
569572
rc = proc.wait()
570573
return rc
571574

575+
def _ghcr_login(self):
576+
"""Login to GHCR with read-only token if not already authenticated."""
577+
# Try existing auth first
578+
_, _, rc = run_cmd([self.cmd, "login", "ghcr.io", "--inspect"], timeout=10)
579+
if rc == 0:
580+
return # Already logged in
581+
582+
token = os.environ.get("GHCR_TOKEN") or os.environ.get("GITHUB_TOKEN") or os.environ.get("LPB_GITHUB_TOKEN", "")
583+
if not token:
584+
return # No token available, pull will fail with auth error
585+
586+
username = os.environ.get("GHCR_USERNAME", "localpibox")
587+
run_cmd([self.cmd, "login", "ghcr.io", "-u", username, "-p", token], timeout=30)
588+
572589

573590
def images_inspect(self, name):
574591
_, _, rc = run_cmd([self.cmd, "image", "inspect", name])
@@ -1283,6 +1300,12 @@ def cmd_run():
12831300
f"LPB_EXA_API_KEY={os.environ.get('LPB_EXA_API_KEY', os.environ.get('EXA_API_KEY', ''))}",
12841301
f"LPB_MAX_TOKENS_CONTEXT_RATIO={os.environ.get('LPB_MAX_TOKENS_CONTEXT_RATIO', _conf_cfg.get('LPB_MAX_TOKENS_CONTEXT_RATIO', '0.06'))}",
12851302
]
1303+
# GHCR token for image pulls (personal account requires auth)
1304+
ghcr_token = os.environ.get('GHCR_TOKEN') or os.environ.get('GITHUB_TOKEN') or os.environ.get('LPB_GITHUB_TOKEN', '')
1305+
ghcr_username = os.environ.get('GHCR_USERNAME', _conf_cfg.get('GHCR_USERNAME', 'localpibox'))
1306+
if ghcr_token:
1307+
env_vars.append(f"GHCR_TOKEN={ghcr_token}")
1308+
env_vars.append(f"GHCR_USERNAME={ghcr_username}")
12861309
for k in ("PI_WORKTREE_ID", "LPB_AGENT_BROWSER_ARGS", "LPB_AGENT_BROWSER_MAX_OUTPUT",
12871310
"LPB_AGENT_BROWSER_CONTENT_BOUNDARIES", "LPB_AGENT_BROWSER_CONFIRM_ACTIONS",
12881311
"LPB_AGENT_BROWSER_IDLE_TIMEOUT_MS", "LPB_AGENT_BROWSER_SESSION"):

support/start.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ if [[ "$FIRST_RUN" = "true" ]]; then
379379
warn "lpb-memory-config.json.template not found — extension uses defaults"
380380
fi
381381

382+
# ── GHCR login for image pulls ────────────────────────────────────
383+
if [[ -n "${GHCR_TOKEN:-}" ]]; then
384+
info "Logging in to GHCR..."
385+
if command -v podman &>/dev/null; then
386+
podman login ghcr.io -u "${GHCR_USERNAME:-localpibox}" -p "${GHCR_TOKEN}"
387+
elif command -v docker &>/dev/null; then
388+
docker login ghcr.io -u "${GHCR_USERNAME:-localpibox}" -p "${GHCR_TOKEN}"
389+
fi
390+
info " GHCR login complete (baked token)"
391+
fi
392+
382393
# ── Config repo: clone/fetch into ~/.pi/agent/ (runs every boot — see §4a)
383394
touch "${HOME_DIR}/.pi/.initialized"
384395

0 commit comments

Comments
 (0)