Skip to content

Commit 971d946

Browse files
committed
fix: drive build from lpb.stack.env/lpb.conf.env, harden launcher + tests
lpb.py regressions from recent refactors (all now covered by tests): - cmd_remove called .is_dir() on the str from resolve_path() -> AttributeError - _load_stack_env/_load_conf_env only looked next to the script, silently ignoring lpb.stack.env/lpb.conf.env when run from a checkout - separator comment swallowed `def self_update()`, so cmd_update would NameError at runtime (body was absorbed into warn()) Canonical config now drives the build: - support/build.sh sources lpb.stack.env + lpb.conf.env and passes --build-arg for every value (CONFIG_FORK, CONFIG_REF, NODE_VERSION, VSCODIUM_VERSION, PI_*, LPB_VERSION, LPB_MAX_TOKENS_CONTEXT_RATIO, ...) - Dockerfile: new ARGs/ENVs, bakes CONFIG_FORK/REF as LPB_CONFIG_REMOTE/REF and copies lpb.stack.env into /opt/devstack/ - CI cli/web jobs pass PI_REF, PI_HEAD_SHA, LPB_VERSION and MAX_TOKENS and apply them on push Tests (scripts/test_lpb.py): 15 -> 27, incl. structural guards for swallowed defs, env-file discovery, host-path semantics, remove confirm/abort, update path, and web-mode env/volume assertions; mutation tests prove the guards catch the two historic regressions. CI runs the suite and gates image builds. Docs: fix stale /home/dev -> /home/lpb; add doc/LAUNCHER-INTERNALS.md.
1 parent 5d69a01 commit 971d946

12 files changed

Lines changed: 819 additions & 101 deletions

File tree

.github/workflows/build-and-publish.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ on:
3030
- 'lpb.stack.env'
3131
- 'lpb.conf.env'
3232
- '.github/workflows/*.yml'
33+
- 'scripts/**'
3334
pull_request:
3435
branches: [main]
3536
paths:
@@ -38,6 +39,7 @@ on:
3839
- 'lpb.stack.env'
3940
- 'lpb.conf.env'
4041
- '.github/workflows/*.yml'
42+
- 'scripts/**'
4143
schedule:
4244
- cron: '0 3 * * 1'
4345
workflow_dispatch:
@@ -59,10 +61,21 @@ env:
5961
IMAGE_NAME: ghcr.io/localpibox/devstack
6062

6163
jobs:
64+
test-lpb:
65+
name: Run lpb.py unit tests
66+
runs-on: ubuntu-latest
67+
steps:
68+
- name: Checkout
69+
uses: actions/checkout@v6
70+
71+
- name: Run test suite
72+
run: python3 scripts/test_lpb.py
73+
6274
build-cli:
6375
name: Build & publish cli image
6476
runs-on: ubuntu-latest
6577
timeout-minutes: 45
78+
needs: [test-lpb]
6679
steps:
6780
- name: Checkout
6881
uses: actions/checkout@v6
@@ -92,6 +105,9 @@ jobs:
92105
echo "CONFIG_REF=$LPB_CONFIG_REF" >> "$GITHUB_OUTPUT"
93106
echo "NODE_VERSION=$LPB_NODE_VERSION" >> "$GITHUB_OUTPUT"
94107
echo "VSCODIUM_VERSION=$LPB_VSCODIUM_VERSION" >> "$GITHUB_OUTPUT"
108+
echo "LPB_VERSION=$(cat VERSION 2>/dev/null || echo unknown)" >> "$GITHUB_OUTPUT"
109+
MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
110+
echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
95111
sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
96112
# Read stack version from config repo
97113
cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
@@ -116,6 +132,8 @@ jobs:
116132
NODE_VERSION=${{ steps.config.outputs.NODE_VERSION }}
117133
VSCODIUM_VERSION=${{ steps.config.outputs.VSCODIUM_VERSION }}
118134
PI_HEAD_SHA=${{ steps.config.outputs.sha }}
135+
LPB_VERSION=${{ steps.config.outputs.LPB_VERSION }}
136+
LPB_MAX_TOKENS_CONTEXT_RATIO=${{ steps.config.outputs.MAX_TOKENS }}
119137
no-cache: ${{ github.event.inputs.no_cache == 'true' }}
120138
tags: |
121139
${{ env.IMAGE_NAME }}:cli
@@ -162,6 +180,9 @@ jobs:
162180
echo "CONFIG_REF=$LPB_CONFIG_REF" >> "$GITHUB_OUTPUT"
163181
echo "NODE_VERSION=$LPB_NODE_VERSION" >> "$GITHUB_OUTPUT"
164182
echo "VSCODIUM_VERSION=$LPB_VSCODIUM_VERSION" >> "$GITHUB_OUTPUT"
183+
echo "LPB_VERSION=$(cat VERSION 2>/dev/null || echo unknown)" >> "$GITHUB_OUTPUT"
184+
MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
185+
echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
165186
sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
166187
# Read stack version from config repo
167188
cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
@@ -180,11 +201,14 @@ jobs:
180201
cache-to: type=gha,mode=max
181202
build-args: |
182203
PI_FORK=${{ steps.config.outputs.PI_FORK }}
204+
PI_REF=${{ steps.config.outputs.PI_REF }}
183205
CONFIG_FORK=${{ steps.config.outputs.CONFIG_FORK }}
184206
CONFIG_REF=${{ steps.config.outputs.CONFIG_REF }}
185207
NODE_VERSION=${{ steps.config.outputs.NODE_VERSION }}
186208
VSCODIUM_VERSION=${{ steps.config.outputs.VSCODIUM_VERSION }}
187209
PI_HEAD_SHA=${{ steps.config.outputs.sha }}
210+
LPB_VERSION=${{ steps.config.outputs.LPB_VERSION }}
211+
LPB_MAX_TOKENS_CONTEXT_RATIO=${{ steps.config.outputs.MAX_TOKENS }}
188212
no-cache: ${{ github.event.inputs.no_cache == 'true' }}
189213
tags: |
190214
${{ env.IMAGE_NAME }}:web

Dockerfile

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,25 @@
2121
# ═══════════════════════════════════════════════════════════════════════════
2222

2323
# Source fork configuration — ARG defaults come from lpb.stack.env
24+
# Instead of editing this file, edit lpb.stack.env (canonical) and either:
25+
# - run support/build.sh (reads lpb.stack.env + lpb.conf.env), or
26+
# - pass --build-arg per value.
2427
# NOTE: Docker ARG values can't reference source'd shell variables.
25-
# The ARGs below are the definitive defaults. CI sets them via
26-
# docker build --build-arg PI_FORK=... etc.
2728
ARG NODE_VERSION=24
2829
ARG VSCODIUM_VERSION=1.126.04524
2930
ARG PI_FORK=https://github.com/localpibox/pi.git
3031
ARG PI_REF=lpb
3132
ARG PI_HEAD_SHA=unknown
3233

34+
# Config preset repo — baked so start.sh clones the fork's config at boot.
35+
# (CI passes these from lpb.stack.env LPB_CONFIG_FORK / LPB_CONFIG_REF.)
36+
ARG CONFIG_FORK=https://github.com/localpibox/config.git
37+
ARG CONFIG_REF=main
38+
39+
# Runtime defaults baked from lpb.conf.env (start.sh can still override).
40+
ARG LPB_MAX_TOKENS_CONTEXT_RATIO=0.06
41+
ARG LPB_VERSION=unknown
42+
3343
# Provenance — set via --build-arg in CI, defaults to "unknown" for local builds
3444
ARG IMAGE_REVISION=unknown
3545
ARG IMAGE_BUILT=unknown
@@ -44,16 +54,23 @@ ARG VSCODIUM_VERSION
4454
ARG PI_FORK
4555
ARG PI_REF
4656
ARG PI_HEAD_SHA
57+
ARG CONFIG_FORK
58+
ARG CONFIG_REF
59+
ARG LPB_MAX_TOKENS_CONTEXT_RATIO
60+
ARG LPB_VERSION
4761
ARG IMAGE_REVISION
4862
ARG IMAGE_BUILT
4963

5064
ENV DEBIAN_FRONTEND=noninteractive
5165

52-
# Context window / max tokens ratio — baked into image.
53-
# 0.06 (6%) — reduced from 0.125 for Qwen thinking models.
66+
# Context window / max tokens ratio — baked into image from lpb.conf.env.
5467
# Container-level overrides take precedence via --env or -e at runtime.
55-
ENV LPB_MAX_TOKENS_CONTEXT_RATIO=0.06
56-
ENV LPB_VERSION=
68+
ENV LPB_MAX_TOKENS_CONTEXT_RATIO=${LPB_MAX_TOKENS_CONTEXT_RATIO}
69+
# Stack/version banner value (baked from lpb.conf.env).
70+
ENV LPB_VERSION=${LPB_VERSION}
71+
# Baked from lpb.stack.env/CI — start.sh reads these for the config repo clone.
72+
ENV LPB_CONFIG_REMOTE=${CONFIG_FORK}
73+
ENV LPB_CONFIG_REF=${CONFIG_REF}
5774

5875
# ── System packages ─────────────────────────────────────────────────────────
5976
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -158,6 +175,7 @@ COPY --chmod=755 support/validate.sh /opt/devstack/validate.sh
158175
COPY --chmod=755 support/install-openspec.sh /opt/pi-support/install-openspec.sh
159176
COPY --chmod=755 support/start.sh /opt/devstack/start.sh
160177
COPY lpb.conf.env /opt/devstack/lpb.conf.env
178+
COPY lpb.stack.env /opt/devstack/lpb.stack.env
161179
COPY --chmod=755 support/entrypoint-cli.sh /opt/devstack/entrypoint-cli.sh
162180

163181
# ── Shell PATH helper ───────────────────────────────────────────────────────

QUICK-START.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ podman run -d \
5151
--userns keep-id \
5252
-e LPB_ED_PORT=3000 \
5353
-e LPB_EDITOR_HOST=0.0.0.0 \
54-
-e LPB_DEVCONTAINER_WORKSPACE_DIR="/home/dev/workspace/$PROJECT_NAME" \
54+
-e LPB_DEVCONTAINER_WORKSPACE_DIR="/home/lpb/workspace/$PROJECT_NAME" \
5555
-e LPB_CONNECTION_TOKEN=devsession \
5656
-e LPB_EXA_API_KEY=your-exa-key \
57-
-v "$PROJECT_DIR:/home/dev/workspace/$PROJECT_NAME:Z" \
58-
-v "$STATE_DIR:/home/dev/.pi:Z" \
59-
-v "$BROWSER_DIR:/home/dev/.agent-browser:Z" \
57+
-v "$PROJECT_DIR:/home/lpb/workspace/$PROJECT_NAME:Z" \
58+
-v "$STATE_DIR:/home/lpb/.pi:Z" \
59+
-v "$BROWSER_DIR:/home/lpb/.agent-browser:Z" \
6060
ghcr.io/localpibox/devstack:latest
6161
```
6262
@@ -93,8 +93,8 @@ xdg-open http://localhost:3000
9393
## Architecture
9494

9595
```
96-
Host: ~/projects/myproject/ → Container: /home/dev/workspace/myproject/
97-
Host: ~/.localpibox/state/ → Container: /home/dev/.pi/
96+
Host: ~/projects/myproject/ → Container: /home/lpb/workspace/myproject/
97+
Host: ~/.localpibox/state/ → Container: /home/lpb/.pi/
9898
Host: (Lemonade on host) → Container: http://127.0.0.1:13305 (via --network host)
9999
```
100100

README.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ podman pull ghcr.io/localpibox/devstack:latest
3232

3333
# Run with a project folder
3434
podman run -d --name localpibox --network host --userns keep-id \
35-
-v /path/to/your/project:/home/dev/workspace/myproject:Z \
36-
-v ~/.localpibox/state:/home/dev/.pi:Z \
37-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
35+
-v /path/to/your/project:/home/lpb/workspace/myproject:Z \
36+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
37+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
3838
-e ED_PORT=3000 \
3939
ghcr.io/localpibox/devstack:latest
4040

@@ -46,9 +46,9 @@ podman run -d --name localpibox --network host --userns keep-id \
4646
```bash
4747
# Run and get a shell inside the container
4848
podman run -it --name localpibox --network host --userns keep-id \
49-
-v /path/to/your/project:/home/dev/workspace/myproject:Z \
50-
-v ~/.localpibox/state:/home/dev/.pi:Z \
51-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
49+
-v /path/to/your/project:/home/lpb/workspace/myproject:Z \
50+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
51+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
5252
-e ED_PORT=3000 \
5353
ghcr.io/localpibox/devstack:latest
5454
```
@@ -88,9 +88,9 @@ flowchart TB
8888
```
8989

9090
Mount structure:
91-
- Host: `$PROJECT → /home/dev/workspace/myproject/`
92-
- Host: `~/.localpibox/state → /home/dev/.pi` (persistent agent state)
93-
- Host: `~/.localpibox/agent-browser → /home/dev/.agent-browser` (browser sessions)
91+
- Host: `$PROJECT → /home/lpb/workspace/myproject/`
92+
- Host: `~/.localpibox/state → /home/lpb/.pi` (persistent agent state)
93+
- Host: `~/.localpibox/agent-browser → /home/lpb/.agent-browser` (browser sessions)
9494
- Host: `Lemonade (:13305) → 127.0.0.1:13305` (host network mode)
9595

9696
## Commands Available Inside Container
@@ -121,9 +121,9 @@ podman exec -it localpibox update --extensions
121121
```bash
122122
# Run with any project folder
123123
podman run -d --name localpibox --network host --userns keep-id \
124-
-v /home/user/projects/myproject:/home/dev/workspace/myproject:Z \
125-
-v ~/.localpibox/state:/home/dev/.pi:Z \
126-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
124+
-v /home/user/projects/myproject:/home/lpb/workspace/myproject:Z \
125+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
126+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
127127
ghcr.io/localpibox/devstack:latest
128128
```
129129

@@ -135,9 +135,9 @@ The project mounts at `/workspace/myproject/` so tools see the correct project n
135135
# First project
136136
podman run -d --name localpibox --network host --userns keep-id \
137137
-e ED_PORT=3000 \
138-
-v /path/to/project-a:/home/dev/workspace/project-a:Z \
139-
-v ~/.localpibox/state:/home/dev/.pi:Z \
140-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
138+
-v /path/to/project-a:/home/lpb/workspace/project-a:Z \
139+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
140+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
141141
ghcr.io/localpibox/devstack:latest
142142
# → http://localhost:3000
143143

@@ -147,9 +147,9 @@ podman rm localpibox
147147

148148
podman run -d --name localpibox --network host --userns keep-id \
149149
-e ED_PORT=3001 \
150-
-v /path/to/project-b:/home/dev/workspace/project-b:Z \
151-
-v ~/.localpibox/state:/home/dev/.pi:Z \
152-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
150+
-v /path/to/project-b:/home/lpb/workspace/project-b:Z \
151+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
152+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
153153
ghcr.io/localpibox/devstack:latest
154154
# → http://localhost:3001
155155
```
@@ -358,9 +358,9 @@ podman rm localpibox
358358
# Run with new port
359359
podman run -d --name localpibox --network host --userns keep-id \
360360
-e ED_PORT=8080 \
361-
-v /path/to/project:/home/dev/workspace/myproject:Z \
362-
-v ~/.localpibox/state:/home/dev/.pi:Z \
363-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
361+
-v /path/to/project:/home/lpb/workspace/myproject:Z \
362+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
363+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
364364
ghcr.io/localpibox/devstack:latest
365365
# → http://localhost:8080
366366
```
@@ -386,9 +386,9 @@ podman exec -it localpibox update --extensions
386386
podman pull ghcr.io/localpibox/devstack:latest
387387
podman stop localpibox && podman rm localpibox
388388
podman run -d --name localpibox --network host --userns keep-id \
389-
-v /path/to/project:/home/dev/workspace/myproject:Z \
390-
-v ~/.localpibox/state:/home/dev/.pi:Z \
391-
-v ~/.localpibox/agent-browser:/home/dev/.agent-browser:Z \
389+
-v /path/to/project:/home/lpb/workspace/myproject:Z \
390+
-v ~/.localpibox/state:/home/lpb/.pi:Z \
391+
-v ~/.localpibox/agent-browser:/home/lpb/.agent-browser:Z \
392392
ghcr.io/localpibox/devstack:latest
393393
```
394394

doc/ARCHITECTURE.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ The system uses a **single image** pulled with `lpb` or direct podman/docker:
66

77
```bash
88
podman run -it --network host --userns keep-id \
9-
-v /path/to/project:/home/dev/workspace/<project>:Z \
9+
-v /path/to/project:/home/lpb/workspace/<project>:Z \
1010
ghcr.io/localpibox/devstack:latest
1111
```
1212

13-
- **Bind mount** for workspace (`$PROJECT:/home/dev/workspace/<project>:Z`)
13+
- **Bind mount** for workspace (`$PROJECT:/home/lpb/workspace/<project>:Z`)
1414
- **`network_mode: host`** for direct host service access (Lemonade)
1515
- **User namespace** (`--userns keep-id` for podman, no-op for docker)
1616
- **First-run init** in `start.sh` (directories, config sync, npm config)
@@ -23,7 +23,7 @@ podman run -it --network host --userns keep-id \
2323
| Item | Status | Notes |
2424
|------|--------|-------|
2525
| UID/GID Mismatch | **Resolved** | `--userns keep-id` (podman) / `--user=1000` (docker) |
26-
| Persistent State | **Resolved** | Bind mount `~/.localpibox/state:/home/dev/.pi:Z` |
26+
| Persistent State | **Resolved** | Bind mount `~/.localpibox/state:/home/lpb/.pi:Z` |
2727
| Docker Compose removed | **Resolved** | Single image + `lpb` is canonical |
2828
| Extension updates | **Resolved** | `pi update --extensions` at every boot — installs missing, upgrades stale |
2929
| Image size | **Resolved** | Extensions installed at runtime (not baked in) — saves ~250MB |
@@ -52,8 +52,8 @@ flowchart TB
5252
B5["Support tools"]
5353
end
5454
subgraph Runtime["Runtime (every boot)"]
55-
R1["/home/dev/workspace"]
56-
R2["/home/dev/.pi"]
55+
R1["/home/lpb/workspace"]
56+
R2["/home/lpb/.pi"]
5757
R3["pi-powerline-footer"]
5858
R4["pi-mcp-adapter"]
5959
R5["pi-subagents"]
@@ -69,9 +69,9 @@ flowchart TB
6969
```
7070

7171
Mount structure:
72-
- Host: `$PROJECT → /home/dev/workspace/<project>/`
73-
- Host: `~/.localpibox/state → /home/dev/.pi` (persistent agent state, skills, agents)
74-
- Host: `~/.localpibox/agent-browser → /home/dev/.agent-browser` (browser sessions)
72+
- Host: `$PROJECT → /home/lpb/workspace/<project>/`
73+
- Host: `~/.localpibox/state → /home/lpb/.pi` (persistent agent state, skills, agents)
74+
- Host: `~/.localpibox/agent-browser → /home/lpb/.agent-browser` (browser sessions)
7575

7676
## Extension Update Flow
7777

@@ -115,9 +115,9 @@ lpb /path/to/project --port 8080 # Custom editor port
115115
### Manual podman command
116116

117117
Mount structure:
118-
- Host: `$PROJECT → /home/dev/workspace/<project>/`
119-
- Host: `~/.localpibox/state → /home/dev/.pi` (persistent agent state)
120-
- Host: `~/.localpibox/agent-browser → /home/dev/.agent-browser` (browser sessions)
118+
- Host: `$PROJECT → /home/lpb/workspace/<project>/`
119+
- Host: `~/.localpibox/state → /home/lpb/.pi` (persistent agent state)
120+
- Host: `~/.localpibox/agent-browser → /home/lpb/.agent-browser` (browser sessions)
121121

122122
## Remaining Risks
123123

0 commit comments

Comments
 (0)