55# Extensions update at runtime via pi update --extensions
66#
77# Triggers:
8+ # - Push to dev (code changes, NOT VERSION bumps)
89# - Push to main (Dockerfile, support/, lpb.stack.env, lpb.conf.env)
910# - Weekly cron (Monday 3am UTC) — keep image fresh
1011# - Manual dispatch
1112#
12- # Images built:
13- # ghcr.io/localpibox/devstack:cli — Base dev environment + Pi CLI
14- # ghcr.io/localpibox/devstack:web — Extends cli + VSCodium server
13+ # Version model (Option C):
14+ # - Single source: devstack/VERSION
15+ # - CI bumps version after tests pass, pushes commit + tags
16+ # - All 6 repos share the same tag (0.0.x-lpb)
17+ # - Docker images tagged: :{version}-cli, :{version}-web
1518#
1619# Actions: all latest major versions (Node.js 24 native)
1720# actions/checkout@v6 · docker/build-push-action@v7
2730 paths :
2831 - ' Dockerfile'
2932 - ' support/**'
30- - ' lpb.stack.env'
31- - ' lpb.conf.env'
32- - ' .github/workflows/*.yml'
3333 - ' scripts/**'
34+ - ' .github/workflows/*.yml'
35+ # NOTE: VERSION + lpb.stack.env changes intentionally excluded
36+ # to avoid re-triggering on auto-bump commits
3437 pull_request :
3538 branches : [main]
3639 paths :
3740 - ' Dockerfile'
3841 - ' support/**'
39- - ' lpb.stack.env'
40- - ' lpb.conf.env'
41- - ' .github/workflows/*.yml'
4242 - ' scripts/**'
43+ - ' .github/workflows/*.yml'
4344 schedule :
4445 - cron : ' 0 3 * * 1'
4546 workflow_dispatch :
6162 IMAGE_NAME : ghcr.io/localpibox/devstack
6263
6364jobs :
65+ # ──────────────────────────────────────────────────────
66+ # Phase 1: Test
67+ # ──────────────────────────────────────────────────────
6468 test-lpb :
6569 name : Run lpb.py unit tests
6670 runs-on : ubuntu-latest
@@ -73,11 +77,77 @@ jobs:
7377 python3 scripts/test_lpb.py
7478 python3 scripts/test_localpibox.py
7579
80+ # ──────────────────────────────────────────────────────
81+ # Phase 2: Bump version + create tags
82+ # ──────────────────────────────────────────────────────
83+ bump-version :
84+ name : Bump version & create tags
85+ runs-on : ubuntu-latest
86+ needs : [test-lpb]
87+ if : ${{ github.event_name != 'pull_request' }}
88+ outputs :
89+ version : ${{ steps.bump.outputs.version }}
90+ steps :
91+ - name : Checkout (with write access)
92+ uses : actions/checkout@v6
93+ with :
94+ fetch-depth : 0
95+ persist-credentials : true
96+
97+ - name : Bump patch version
98+ id : bump
99+ run : |
100+ set -e
101+ VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0-lpb")
102+ PATCH=$(echo "$VERSION" | sed 's/^[0-9]*\.[0-9]*\.\([0-9]*\).*/\1/')
103+ NEW_PATCH=$((PATCH + 1))
104+ NEW_VERSION="0.0.${NEW_PATCH}-lpb"
105+ echo "Bumping $VERSION → $NEW_VERSION"
106+ echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
107+
108+ # Update devstack
109+ echo "$NEW_VERSION" > VERSION
110+ sed -i "s/^LPB_PI_REF=.*/LPB_PI_REF=$NEW_VERSION/" lpb.stack.env
111+
112+ # Commit and push
113+ git config user.name "ci-localpibox"
114+ git config user.email "ci@localpibox.dev"
115+ git add VERSION lpb.stack.env
116+ git commit -m "chore: bump VERSION $NEW_VERSION [skip-version]"
117+ git push origin dev
118+
119+ - name : Create tags on all repos
120+ env :
121+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
122+ VERSION : ${{ steps.bump.outputs.version }}
123+ run : |
124+ set -e
125+ REPOS=(
126+ "localpibox/pi"
127+ "localpibox/pi-subagents"
128+ "localpibox/lemonade-pi-plugin"
129+ "localpibox/config"
130+ "localpibox/lpb-memory"
131+ )
132+ for repo in "${REPOS[@]}"; do
133+ echo "Tagging $repo@$VERSION"
134+ gh api repos/$repo/git/refs/tags \
135+ --method POST \
136+ -f ref="refs/tags/$VERSION" \
137+ -f sha="$(git rev-parse origin/lpb-dev 2>/dev/null || git rev-parse origin/dev 2>/dev/null || git rev-parse HEAD)" \
138+ --jq '.message' 2>/dev/null || true
139+ done
140+ echo "✅ Tags created on all 5 repos"
141+
142+ # ──────────────────────────────────────────────────────
143+ # Phase 3: Build & publish images
144+ # ──────────────────────────────────────────────────────
76145 build-cli :
77146 name : Build & publish cli image
78147 runs-on : ubuntu-latest
79148 timeout-minutes : 45
80- needs : [test-lpb]
149+ needs : [bump-version]
150+ if : ${{ github.event_name != 'pull_request' }}
81151 steps :
82152 - name : Checkout
83153 uses : actions/checkout@v6
@@ -111,11 +181,8 @@ jobs:
111181 MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
112182 echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
113183 sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
114- # Read stack version from config repo
115- cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
116- stack_ver=$(curl -sf "https://raw.githubusercontent.com/$cfg_repo/$LPB_CONFIG_REF/VERSION" 2>/dev/null || echo "unknown")
117184 echo "sha=${sha:-unknown}" >> "$GITHUB_OUTPUT"
118- echo "stack_version=${stack_ver }" >> "$GITHUB_OUTPUT"
185+ echo "stack_version=${LPB_PI_REF }" >> "$GITHUB_OUTPUT"
119186
120187 - name : Build & push cli
121188 uses : docker/build-push-action@v7
@@ -142,7 +209,7 @@ jobs:
142209 ${{ env.IMAGE_NAME }}:dev-cli
143210 ${{ env.IMAGE_NAME }}:main-cli
144211 ${{ env.IMAGE_NAME }}:${{ github.sha }}-cli
145- ${{ env.IMAGE_NAME }}:${{ steps.config .outputs.stack_version }}-cli
212+ ${{ env.IMAGE_NAME }}:${{ needs.bump-version .outputs.version }}-cli
146213 ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && format('{0}:latest', env.IMAGE_NAME) || '' }}
147214 ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' && format('{0}:dev', env.IMAGE_NAME) || '' }}
148215 ${{ github.event.inputs.publish_latest && format('{0}:latest', env.IMAGE_NAME) || '' }}
@@ -154,7 +221,8 @@ jobs:
154221 name : Build & publish web image
155222 runs-on : ubuntu-latest
156223 timeout-minutes : 45
157- needs : [build-cli]
224+ needs : [build-cli, bump-version]
225+ if : ${{ github.event_name != 'pull_request' }}
158226 steps :
159227 - name : Checkout
160228 uses : actions/checkout@v6
@@ -188,11 +256,8 @@ jobs:
188256 MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
189257 echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
190258 sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
191- # Read stack version from config repo
192- cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
193- stack_ver=$(curl -sf "https://raw.githubusercontent.com/$cfg_repo/$LPB_CONFIG_REF/VERSION" 2>/dev/null || echo "unknown")
194259 echo "sha=${sha:-unknown}" >> "$GITHUB_OUTPUT"
195- echo "stack_version=${stack_ver }" >> "$GITHUB_OUTPUT"
260+ echo "stack_version=${LPB_PI_REF }" >> "$GITHUB_OUTPUT"
196261
197262 - name : Build & push web
198263 uses : docker/build-push-action@v7
@@ -219,6 +284,7 @@ jobs:
219284 ${{ env.IMAGE_NAME }}:dev-web
220285 ${{ env.IMAGE_NAME }}:main-web
221286 ${{ env.IMAGE_NAME }}:${{ github.sha }}-web
287+ ${{ env.IMAGE_NAME }}:${{ needs.bump-version.outputs.version }}-web
222288 ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && format('{0}:latest-web', env.IMAGE_NAME) || '' }}
223289 ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' && format('{0}:dev-web', env.IMAGE_NAME) || '' }}
224290 ${{ github.event.inputs.publish_latest && format('{0}:latest-web', env.IMAGE_NAME) || '' }}
0 commit comments