anthodev/lapin-futepublic Fork
⑂ 594f8e2
Commits
⬇ Clone ▾
git clone https://git.rickub.com/anthodev/lapin-fute.git
git clone ssh://git@rickub.com/anthodev/lapin-fute.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

fix(release): repair CI backup and publish to Rickub in 1.2.4

Cedric ANTHONY committed 2026-10-07T09:02:19+02:00 Browse files
594f8e2 parent: 51e43db
modified .github/workflows/deploy-bunny.yml +98 -44
@@ -141,34 +141,38 @@ jobs:
141141 mkdir -p var/deploy-backup/files
142142 : > var/deploy-backup/missing
143143
144- while IFS= read -r -d '' file; do
145- relative_path="${file#var/config-site/}"
146- case "$relative_path" in
147- *[!A-Za-z0-9._/-]* | /* | *../*)
148- echo "Refusing unsafe backup path: $relative_path" >&2
149- exit 1
150- ;;
151- esac
152-
153- target="var/deploy-backup/files/$relative_path"
154- mkdir -p "$(dirname "$target")"
155- status="$(curl --silent --show-error \
156- --output "$target" \
157- --write-out '%{http_code}' \
158- --header "AccessKey: $BUNNY_STORAGE_PASSWORD" \
159- "$endpoint/$relative_path")"
160- case "$status" in
161- 200) ;;
162- 404)
163- rm -f "$target"
164- printf '%s\0' "$relative_path" >> var/deploy-backup/missing
165- ;;
166- *)
167- echo "Could not back up $relative_path: HTTP $status" >&2
168- exit 1
169- ;;
170- esac
171- done < <(find var/config-site -type f -print0)
144+ # Feed the loop through a plain pipe. Process substitution exposes
145+ # the file list as a /dev/fd path, which is unavailable on some
146+ # hardened runners and aborts the backup before any upload.
147+ find var/config-site -type f -print0 \
148+ | while IFS= read -r -d '' file; do
149+ relative_path="${file#var/config-site/}"
150+ case "$relative_path" in
151+ *[!A-Za-z0-9._/-]* | /* | *../*)
152+ echo "Refusing unsafe backup path: $relative_path" >&2
153+ exit 1
154+ ;;
155+ esac
156+
157+ target="var/deploy-backup/files/$relative_path"
158+ mkdir -p "$(dirname "$target")"
159+ status="$(curl --silent --show-error \
160+ --output "$target" \
161+ --write-out '%{http_code}' \
162+ --header "AccessKey: $BUNNY_STORAGE_PASSWORD" \
163+ "$endpoint/$relative_path")"
164+ case "$status" in
165+ 200) ;;
166+ 404)
167+ rm -f "$target"
168+ printf '%s\0' "$relative_path" >> var/deploy-backup/missing
169+ ;;
170+ *)
171+ echo "Could not back up $relative_path: HTTP $status" >&2
172+ exit 1
173+ ;;
174+ esac
175+ done
172176
173177 - name: Retain rollback bundle
174178 uses: actions/upload-artifact@v4
@@ -306,7 +310,7 @@ jobs:
306310 "https://api.bunny.net/pullzone/$BUNNY_PULL_ZONE_ID/purgeCache"
307311
308312 publish:
309- name: Publish GitHub release notes
313+ name: Publish Rickub release notes
310314 needs: [test, package, deploy]
311315 if: ${{ startsWith(github.ref_name, 'v') }}
312316 runs-on: ubuntu-latest
@@ -329,18 +333,54 @@ jobs:
329333 - name: Generate release notes
330334 run: node scripts/generate-release-notes.mjs
331335
332- - name: Create release with changelog notes
333- env:
334- GH_TOKEN: ${{ github.token }}
336+ - name: Select Rickub release API
337+ id: forge_api
335338 shell: bash
336339 run: |
337340 set -euo pipefail
338- gh release create "$GITHUB_REF_NAME" \
339- --repo "$GITHUB_REPOSITORY" \
340- --title "$GITHUB_REF_NAME" \
341- --notes-file release-notes.md \
342- --verify-tag \
343- release-assets/*
341+ if [[ -z "${GITHUB_API_URL:-}" ]]; then
342+ echo "GITHUB_API_URL is required for Rickub releases" >&2
343+ exit 1
344+ fi
345+ node -e 'const url = new URL(process.env.GITHUB_API_URL); if (!["http:", "https:"].includes(url.protocol) || url.hostname === "github.com" || url.hostname.endsWith(".github.com")) throw new Error("GITHUB_API_URL must point to the Rickub release API")'
346+ printf 'api_url=%s\n' "$GITHUB_API_URL" >> "$GITHUB_OUTPUT"
347+ - name: Publish Rickub release with changelog notes and artifacts
348+ uses: actions/github-script@v7
349+ env:
350+ RELEASE_TAG: ${{ github.ref_name }}
351+ RICKUB_API_URL: ${{ steps.forge_api.outputs.api_url }}
352+ RICKUB_SERVER_URL: ${{ github.server_url }}
353+ with:
354+ base-url: ${{ steps.forge_api.outputs.api_url }}
355+ script: |
356+ const fs = require("node:fs");
357+ const tag = process.env.RELEASE_TAG;
358+ const { owner, repo } = context.repo;
359+ const body = fs.readFileSync("release-notes.md", "utf8");
360+ const { data: release } = await github.rest.repos.createRelease({
361+ owner, repo, tag_name: tag, name: tag, body,
362+ draft: false, prerelease: false,
363+ });
364+ const uploadUrl = new URL(release.upload_url.replace(/\{.*$/u, ""));
365+ const apiOrigin = new URL(process.env.RICKUB_API_URL).origin;
366+ const forgeOrigin = new URL(process.env.RICKUB_SERVER_URL).origin;
367+ if (!["http:", "https:"].includes(uploadUrl.protocol) ||
368+ uploadUrl.username || uploadUrl.password ||
369+ uploadUrl.hostname === "github.com" || uploadUrl.hostname.endsWith(".github.com") ||
370+ ![apiOrigin, forgeOrigin].includes(uploadUrl.origin)) {
371+ throw new Error("Release asset upload URL must use the configured Rickub host");
372+ }
373+ for (const name of [`lapin-fute-${tag}.pbw`, "SHA256SUMS"]) {
374+ const bytes = fs.readFileSync(`release-assets/${name}`);
375+ uploadUrl.searchParams.set("name", name);
376+ await github.request(`POST ${uploadUrl}`, {
377+ data: bytes,
378+ headers: {
379+ "content-type": "application/octet-stream",
380+ "content-length": bytes.length,
381+ },
382+ });
383+ }
344384
345385 store:
346386 name: Publish release to the RePebble store
@@ -371,15 +411,29 @@ jobs:
371411 name: lapin-fute-${{ github.ref_name }}
372412 path: release-assets
373413
374- - name: Fetch published release notes
375- env:
376- GH_TOKEN: ${{ github.token }}
377- RELEASE_TAG: ${{ github.ref_name }}
414+ - name: Select Rickub release API
415+ id: forge_api
378416 shell: bash
379417 run: |
380418 set -euo pipefail
381- gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json body --jq .body \
382- > "$RUNNER_TEMP/store-release-notes.md"
419+ if [[ -z "${GITHUB_API_URL:-}" ]]; then
420+ echo "GITHUB_API_URL is required for Rickub releases" >&2
421+ exit 1
422+ fi
423+ node -e 'const url = new URL(process.env.GITHUB_API_URL); if (!["http:", "https:"].includes(url.protocol) || url.hostname === "github.com" || url.hostname.endsWith(".github.com")) throw new Error("GITHUB_API_URL must point to the Rickub release API")'
424+ printf 'api_url=%s\n' "$GITHUB_API_URL" >> "$GITHUB_OUTPUT"
425+ - name: Fetch published Rickub release notes
426+ uses: actions/github-script@v7
427+ env:
428+ RELEASE_TAG: ${{ github.ref_name }}
429+ with:
430+ base-url: ${{ steps.forge_api.outputs.api_url }}
431+ script: |
432+ const fs = require("node:fs");
433+ const { data: release } = await github.rest.repos.getReleaseByTag({
434+ ...context.repo, tag: process.env.RELEASE_TAG,
435+ });
436+ fs.writeFileSync(`${process.env.RUNNER_TEMP}/store-release-notes.md`, release.body);
383437
384438 - name: Publish to the RePebble store
385439 env:
@@ -141,34 +141,38 @@ jobs:
141 mkdir -p var/deploy-backup/files141 mkdir -p var/deploy-backup/files
142 : > var/deploy-backup/missing142 : > var/deploy-backup/missing
143 143
144- while IFS= read -r -d '' file; do144+ # Feed the loop through a plain pipe. Process substitution exposes
145- relative_path="${file#var/config-site/}"145+ # the file list as a /dev/fd path, which is unavailable on some
146- case "$relative_path" in146+ # hardened runners and aborts the backup before any upload.
147- *[!A-Za-z0-9._/-]* | /* | *../*)147+ find var/config-site -type f -print0 \
148- echo "Refusing unsafe backup path: $relative_path" >&2148+ | while IFS= read -r -d '' file; do
149- exit 1149+ relative_path="${file#var/config-site/}"
150- ;;150+ case "$relative_path" in
151- esac151+ *[!A-Za-z0-9._/-]* | /* | *../*)
152-152+ echo "Refusing unsafe backup path: $relative_path" >&2
153- target="var/deploy-backup/files/$relative_path"153+ exit 1
154- mkdir -p "$(dirname "$target")"154+ ;;
155- status="$(curl --silent --show-error \155+ esac
156- --output "$target" \156+
157- --write-out '%{http_code}' \157+ target="var/deploy-backup/files/$relative_path"
158- --header "AccessKey: $BUNNY_STORAGE_PASSWORD" \158+ mkdir -p "$(dirname "$target")"
159- "$endpoint/$relative_path")"159+ status="$(curl --silent --show-error \
160- case "$status" in160+ --output "$target" \
161- 200) ;;161+ --write-out '%{http_code}' \
162- 404)162+ --header "AccessKey: $BUNNY_STORAGE_PASSWORD" \
163- rm -f "$target"163+ "$endpoint/$relative_path")"
164- printf '%s\0' "$relative_path" >> var/deploy-backup/missing164+ case "$status" in
165- ;;165+ 200) ;;
166- *)166+ 404)
167- echo "Could not back up $relative_path: HTTP $status" >&2167+ rm -f "$target"
168- exit 1168+ printf '%s\0' "$relative_path" >> var/deploy-backup/missing
169- ;;169+ ;;
170- esac170+ *)
171- done < <(find var/config-site -type f -print0)171+ echo "Could not back up $relative_path: HTTP $status" >&2
172+ exit 1
173+ ;;
174+ esac
175+ done
172 176
173 - name: Retain rollback bundle177 - name: Retain rollback bundle
174 uses: actions/upload-artifact@v4178 uses: actions/upload-artifact@v4
@@ -306,7 +310,7 @@ jobs:
306 "https://api.bunny.net/pullzone/$BUNNY_PULL_ZONE_ID/purgeCache"310 "https://api.bunny.net/pullzone/$BUNNY_PULL_ZONE_ID/purgeCache"
307 311
308 publish:312 publish:
309- name: Publish GitHub release notes313+ name: Publish Rickub release notes
310 needs: [test, package, deploy]314 needs: [test, package, deploy]
311 if: ${{ startsWith(github.ref_name, 'v') }}315 if: ${{ startsWith(github.ref_name, 'v') }}
312 runs-on: ubuntu-latest316 runs-on: ubuntu-latest
@@ -329,18 +333,54 @@ jobs:
329 - name: Generate release notes333 - name: Generate release notes
330 run: node scripts/generate-release-notes.mjs334 run: node scripts/generate-release-notes.mjs
331 335
332- - name: Create release with changelog notes336+ - name: Select Rickub release API
333- env:337+ id: forge_api
334- GH_TOKEN: ${{ github.token }}
335 shell: bash338 shell: bash
336 run: |339 run: |
337 set -euo pipefail340 set -euo pipefail
338- gh release create "$GITHUB_REF_NAME" \341+ if [[ -z "${GITHUB_API_URL:-}" ]]; then
339- --repo "$GITHUB_REPOSITORY" \342+ echo "GITHUB_API_URL is required for Rickub releases" >&2
340- --title "$GITHUB_REF_NAME" \343+ exit 1
341- --notes-file release-notes.md \344+ fi
342- --verify-tag \345+ node -e 'const url = new URL(process.env.GITHUB_API_URL); if (!["http:", "https:"].includes(url.protocol) || url.hostname === "github.com" || url.hostname.endsWith(".github.com")) throw new Error("GITHUB_API_URL must point to the Rickub release API")'
343- release-assets/*346+ printf 'api_url=%s\n' "$GITHUB_API_URL" >> "$GITHUB_OUTPUT"
347+ - name: Publish Rickub release with changelog notes and artifacts
348+ uses: actions/github-script@v7
349+ env:
350+ RELEASE_TAG: ${{ github.ref_name }}
351+ RICKUB_API_URL: ${{ steps.forge_api.outputs.api_url }}
352+ RICKUB_SERVER_URL: ${{ github.server_url }}
353+ with:
354+ base-url: ${{ steps.forge_api.outputs.api_url }}
355+ script: |
356+ const fs = require("node:fs");
357+ const tag = process.env.RELEASE_TAG;
358+ const { owner, repo } = context.repo;
359+ const body = fs.readFileSync("release-notes.md", "utf8");
360+ const { data: release } = await github.rest.repos.createRelease({
361+ owner, repo, tag_name: tag, name: tag, body,
362+ draft: false, prerelease: false,
363+ });
364+ const uploadUrl = new URL(release.upload_url.replace(/\{.*$/u, ""));
365+ const apiOrigin = new URL(process.env.RICKUB_API_URL).origin;
366+ const forgeOrigin = new URL(process.env.RICKUB_SERVER_URL).origin;
367+ if (!["http:", "https:"].includes(uploadUrl.protocol) ||
368+ uploadUrl.username || uploadUrl.password ||
369+ uploadUrl.hostname === "github.com" || uploadUrl.hostname.endsWith(".github.com") ||
370+ ![apiOrigin, forgeOrigin].includes(uploadUrl.origin)) {
371+ throw new Error("Release asset upload URL must use the configured Rickub host");
372+ }
373+ for (const name of [`lapin-fute-${tag}.pbw`, "SHA256SUMS"]) {
374+ const bytes = fs.readFileSync(`release-assets/${name}`);
375+ uploadUrl.searchParams.set("name", name);
376+ await github.request(`POST ${uploadUrl}`, {
377+ data: bytes,
378+ headers: {
379+ "content-type": "application/octet-stream",
380+ "content-length": bytes.length,
381+ },
382+ });
383+ }
344 384
345 store:385 store:
346 name: Publish release to the RePebble store386 name: Publish release to the RePebble store
@@ -371,15 +411,29 @@ jobs:
371 name: lapin-fute-${{ github.ref_name }}411 name: lapin-fute-${{ github.ref_name }}
372 path: release-assets412 path: release-assets
373 413
374- - name: Fetch published release notes414+ - name: Select Rickub release API
375- env:415+ id: forge_api
376- GH_TOKEN: ${{ github.token }}
377- RELEASE_TAG: ${{ github.ref_name }}
378 shell: bash416 shell: bash
379 run: |417 run: |
380 set -euo pipefail418 set -euo pipefail
381- gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json body --jq .body \419+ if [[ -z "${GITHUB_API_URL:-}" ]]; then
382- > "$RUNNER_TEMP/store-release-notes.md"420+ echo "GITHUB_API_URL is required for Rickub releases" >&2
421+ exit 1
422+ fi
423+ node -e 'const url = new URL(process.env.GITHUB_API_URL); if (!["http:", "https:"].includes(url.protocol) || url.hostname === "github.com" || url.hostname.endsWith(".github.com")) throw new Error("GITHUB_API_URL must point to the Rickub release API")'
424+ printf 'api_url=%s\n' "$GITHUB_API_URL" >> "$GITHUB_OUTPUT"
425+ - name: Fetch published Rickub release notes
426+ uses: actions/github-script@v7
427+ env:
428+ RELEASE_TAG: ${{ github.ref_name }}
429+ with:
430+ base-url: ${{ steps.forge_api.outputs.api_url }}
431+ script: |
432+ const fs = require("node:fs");
433+ const { data: release } = await github.rest.repos.getReleaseByTag({
434+ ...context.repo, tag: process.env.RELEASE_TAG,
435+ });
436+ fs.writeFileSync(`${process.env.RUNNER_TEMP}/store-release-notes.md`, release.body);
383 437
384 - name: Publish to the RePebble store438 - name: Publish to the RePebble store
385 env:439 env:
modified CHANGELOG.md +57 -42
@@ -1,12 +1,27 @@
11 # Changelog
22
3+## [1.2.4] - 2026-10-07
4+
5+Release deployment backups now feed their file loop through a plain pipe instead of process substitution, so the backup can no longer fail on runners where `/dev/fd` is unavailable.
6+
7+Release publishing now uses the runner's Rickub API rather than relying on a GitHub CLI. The Store job retrieves the notes from that Rickub release.
8+
9+### Fixes
10+
11+- fix(deploy): avoid process substitution in production backup
12+- fix(release): publish through the configured forge API
13+
14+### Changes
15+
16+- chore(release): bump to version 1.2.4
17+
318 ## [1.2.3] - 2026-10-07
419
520 Release deployments install the pinned `pebble-tool` with a uv-managed Python 3.11 so SDK installation does not depend on the runner's system Python providing `ensurepip`. The production backup gate also names missing Bunny storage settings instead of failing silently.
621
722 ### Fixes
823
9-- fix(deploy): use managed Python and report missing Bunny credentials (`07fd9ff`)
24+- fix(deploy): use managed Python and report missing Bunny credentials
1025
1126 ### Changes
1227
@@ -22,12 +37,12 @@ Release deployments now install the pinned `pebble-tool` with a uv-managed Pytho
2237
2338 ### Fixes
2439
25-- fix(traffic): accept up to 256 application periods per disruption (`1380da0`)
26-- fix(deploy): use managed Python and report missing Bunny credentials (`07fd9ff`)
40+- fix(traffic): accept up to 256 application periods per disruption
41+- fix(deploy): use managed Python and report missing Bunny credentials
2742
2843 ### Changes
2944
30-- chore(release): bump to version 1.2.2 (`5736f05`)
45+- chore(release): bump to version 1.2.2
3146
3247 ## [1.2.1] - 2026-09-20
3348
@@ -37,12 +52,12 @@ Active works now participate in traffic reporting according to their severity, r
3752
3853 ### Fixes
3954
40-- fix(traffic): include active works in disruption selection (`a259698`)
55+- fix(traffic): include active works in disruption selection
4156
4257 ### Changes
4358
44-- chore(release): bump to version 1.2.1 (`66c22cf`)
45-- ci(catalog): separate catalog publishing from release deployment (`8f2b8fe`)
59+- chore(release): bump to version 1.2.1
60+- ci(catalog): separate catalog publishing from release deployment
4661
4762 ## [1.2.0] - 2026-09-20
4863
@@ -52,7 +67,7 @@ If saving a replacement credential fails, the previous language and favorites re
5267
5368 ### Features
5469
55-- feat(configuration): add persistent watch language selection (#30) (#31) (`1650944`)
70+- feat(configuration): add persistent watch language selection (#30) (#31)
5671
5772 ### Changes
5873
@@ -68,11 +83,11 @@ Departure-to-arrival favorites require rebuilding and publishing the catalog wit
6883
6984 ### Features
7085
71-- feat(arrivals): support departure-to-arrival favorites (#28) (#29) (`102d719`)
86+- feat(arrivals): support departure-to-arrival favorites (#28) (#29)
7287
7388 ### Fixes
7489
75-- fix(traffic): show the latest active incident and exclude works (`6ae56e9`)
90+- fix(traffic): show the latest active incident and exclude works
7691
7792 ### Changes
7893
@@ -84,12 +99,12 @@ This release fixes partial-service arrival pooling in the companion and relocate
8499
85100 ### Changes
86101
87-- chore(release): bump version to 1.0.4 (`bd3b82f`)
88-- docs: clarify stop and arrival terminology (`59caea0`)
102+- chore(release): bump version to 1.0.4
103+- docs: clarify stop and arrival terminology
89104
90105 ### Fixes
91106
92-- fix(companion): pool partial-service arrivals and move store assets (`2635f28`)
107+- fix(companion): pool partial-service arrivals and move store assets
93108
94109 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.3...v1.0.4
95110
@@ -101,7 +116,7 @@ This release clarifies stop and arrival terminology in the interface (#27).
101116
102117 ### Fixes
103118
104-- fix(ui): clarify stop and arrival terminology (#27) (`8835364`)
119+- fix(ui): clarify stop and arrival terminology (#27)
105120
106121 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.2...v1.0.3
107122
@@ -113,15 +128,15 @@ Line badges on the watch now carry a metro or tram prefix, so metro and tram lin
113128
114129 ### Features
115130
116-- feat(display): prefix metro and tram line labels on watch badges (#24) (#25) (`0cc3c0b`)
131+- feat(display): prefix metro and tram line labels on watch badges (#24) (#25)
117132
118133 ### Changes
119134
120-- chore(docs): updated README.md (`1889044`)
135+- chore(docs): updated README.md
121136
122137 ### Fixes
123138
124-- fix(release): handle squash merges when generating release notes (`9e36c16`)
139+- fix(release): handle squash merges when generating release notes
125140
126141 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.1...v1.0.2
127142
@@ -157,40 +172,40 @@ First public release. Lapin Futé puts Île-de-France arrivals on a Pebble watch
157172
158173 ### Features
159174
160-- feat: verify published configuration site (`9dc8049`)
161-- feat: harden stale and failure handling (#20) (`835b18b`)
162-- feat(companion): modernize settings and introduce app branding (#19) (`2799b0f`)
163-- feat: migrate to phone-only data and phone-prepared watch displays (#17) (`a46bf24`)
164-- feat: deliver Pebble watch consultation (#15) (`aabca5b`)
165-- feat: persist and synchronize favorites (#14) (`5ff79d1`)
166-- feat: build companion configuration page (#13) (`f7d01bd`)
167-- feat: normalize live PRIM departures (#12) (`5a0b437`)
168-- feat: establish Alloy app foundation (#10) (`ffd859a`)
175+- feat: verify published configuration site
176+- feat: harden stale and failure handling (#20)
177+- feat(companion): modernize settings and introduce app branding (#19)
178+- feat: migrate to phone-only data and phone-prepared watch displays (#17)
179+- feat: deliver Pebble watch consultation (#15)
180+- feat: persist and synchronize favorites (#14)
181+- feat: build companion configuration page (#13)
182+- feat: normalize live PRIM departures (#12)
183+- feat: establish Alloy app foundation (#10)
169184
170185 ### Changes
171186
172-- docs: link published RePebble release (`de5dd7f`)
173-- chore: add RePebble store screenshots and banner (`e008516`)
174-- chore: add RePebble store icons (`646389f`)
175-- docs: streamline project overview and controls (`3e430d6`)
176-- docs: add v1 release checklist (`d6184a2`)
177-- docs: research official v1 release requirements (`30a8f4c`)
178-- chore: ignore Pebble build lock (`6b61693`)
179-- chore: set release version to 1.0.0 (`e1d539d`)
180-- Resolve IDFM service catalog with bounded importer (#11) (`acf5bba`)
181-- chore: initialize develop branch (`4c1dead`)
187+- docs: link published RePebble release
188+- chore: add RePebble store screenshots and banner
189+- chore: add RePebble store icons
190+- docs: streamline project overview and controls
191+- docs: add v1 release checklist
192+- docs: research official v1 release requirements
193+- chore: ignore Pebble build lock
194+- chore: set release version to 1.0.0
195+- Resolve IDFM service catalog with bounded importer (#11)
196+- chore: initialize develop branch
182197
183198 ### Fixes
184199
185-- fix: normalize Emery store screenshot brightness (`befc545`)
186-- fix: close release verification gaps (`457bcb9`)
200+- fix: normalize Emery store screenshot brightness
201+- fix: close release verification gaps
187202
188203 ### CI
189204
190-- ci: skip unchanged catalog uploads (`91a4982`)
191-- ci: retain and rehearse deployment rollback (`5f1ff8c`)
192-- ci: package PBW release assets (`30f04fc`)
193-- ci: verify deploys and restore failures (`f224ada`)
205+- ci: skip unchanged catalog uploads
206+- ci: retain and rehearse deployment rollback
207+- ci: package PBW release assets
208+- ci: verify deploys and restore failures
194209
195210 **Full changelog**: https://github.com/Anthodev/lapin-fute/commits/v1.0.0
196211
@@ -1,12 +1,27 @@
1 # Changelog1 # Changelog
2 2
3+## [1.2.4] - 2026-10-07
4+
5+Release deployment backups now feed their file loop through a plain pipe instead of process substitution, so the backup can no longer fail on runners where `/dev/fd` is unavailable.
6+
7+Release publishing now uses the runner's Rickub API rather than relying on a GitHub CLI. The Store job retrieves the notes from that Rickub release.
8+
9+### Fixes
10+
11+- fix(deploy): avoid process substitution in production backup
12+- fix(release): publish through the configured forge API
13+
14+### Changes
15+
16+- chore(release): bump to version 1.2.4
17+
3 ## [1.2.3] - 2026-10-0718 ## [1.2.3] - 2026-10-07
4 19
5 Release deployments install the pinned `pebble-tool` with a uv-managed Python 3.11 so SDK installation does not depend on the runner's system Python providing `ensurepip`. The production backup gate also names missing Bunny storage settings instead of failing silently.20 Release deployments install the pinned `pebble-tool` with a uv-managed Python 3.11 so SDK installation does not depend on the runner's system Python providing `ensurepip`. The production backup gate also names missing Bunny storage settings instead of failing silently.
6 21
7 ### Fixes22 ### Fixes
8 23
9-- fix(deploy): use managed Python and report missing Bunny credentials (`07fd9ff`)24+- fix(deploy): use managed Python and report missing Bunny credentials
10 25
11 ### Changes26 ### Changes
12 27
@@ -22,12 +37,12 @@ Release deployments now install the pinned `pebble-tool` with a uv-managed Pytho
22 37
23 ### Fixes38 ### Fixes
24 39
25-- fix(traffic): accept up to 256 application periods per disruption (`1380da0`)40+- fix(traffic): accept up to 256 application periods per disruption
26-- fix(deploy): use managed Python and report missing Bunny credentials (`07fd9ff`)41+- fix(deploy): use managed Python and report missing Bunny credentials
27 42
28 ### Changes43 ### Changes
29 44
30-- chore(release): bump to version 1.2.2 (`5736f05`)45+- chore(release): bump to version 1.2.2
31 46
32 ## [1.2.1] - 2026-09-2047 ## [1.2.1] - 2026-09-20
33 48
@@ -37,12 +52,12 @@ Active works now participate in traffic reporting according to their severity, r
37 52
38 ### Fixes53 ### Fixes
39 54
40-- fix(traffic): include active works in disruption selection (`a259698`)55+- fix(traffic): include active works in disruption selection
41 56
42 ### Changes57 ### Changes
43 58
44-- chore(release): bump to version 1.2.1 (`66c22cf`)59+- chore(release): bump to version 1.2.1
45-- ci(catalog): separate catalog publishing from release deployment (`8f2b8fe`)60+- ci(catalog): separate catalog publishing from release deployment
46 61
47 ## [1.2.0] - 2026-09-2062 ## [1.2.0] - 2026-09-20
48 63
@@ -52,7 +67,7 @@ If saving a replacement credential fails, the previous language and favorites re
52 67
53 ### Features68 ### Features
54 69
55-- feat(configuration): add persistent watch language selection (#30) (#31) (`1650944`)70+- feat(configuration): add persistent watch language selection (#30) (#31)
56 71
57 ### Changes72 ### Changes
58 73
@@ -68,11 +83,11 @@ Departure-to-arrival favorites require rebuilding and publishing the catalog wit
68 83
69 ### Features84 ### Features
70 85
71-- feat(arrivals): support departure-to-arrival favorites (#28) (#29) (`102d719`)86+- feat(arrivals): support departure-to-arrival favorites (#28) (#29)
72 87
73 ### Fixes88 ### Fixes
74 89
75-- fix(traffic): show the latest active incident and exclude works (`6ae56e9`)90+- fix(traffic): show the latest active incident and exclude works
76 91
77 ### Changes92 ### Changes
78 93
@@ -84,12 +99,12 @@ This release fixes partial-service arrival pooling in the companion and relocate
84 99
85 ### Changes100 ### Changes
86 101
87-- chore(release): bump version to 1.0.4 (`bd3b82f`)102+- chore(release): bump version to 1.0.4
88-- docs: clarify stop and arrival terminology (`59caea0`)103+- docs: clarify stop and arrival terminology
89 104
90 ### Fixes105 ### Fixes
91 106
92-- fix(companion): pool partial-service arrivals and move store assets (`2635f28`)107+- fix(companion): pool partial-service arrivals and move store assets
93 108
94 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.3...v1.0.4109 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.3...v1.0.4
95 110
@@ -101,7 +116,7 @@ This release clarifies stop and arrival terminology in the interface (#27).
101 116
102 ### Fixes117 ### Fixes
103 118
104-- fix(ui): clarify stop and arrival terminology (#27) (`8835364`)119+- fix(ui): clarify stop and arrival terminology (#27)
105 120
106 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.2...v1.0.3121 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.2...v1.0.3
107 122
@@ -113,15 +128,15 @@ Line badges on the watch now carry a metro or tram prefix, so metro and tram lin
113 128
114 ### Features129 ### Features
115 130
116-- feat(display): prefix metro and tram line labels on watch badges (#24) (#25) (`0cc3c0b`)131+- feat(display): prefix metro and tram line labels on watch badges (#24) (#25)
117 132
118 ### Changes133 ### Changes
119 134
120-- chore(docs): updated README.md (`1889044`)135+- chore(docs): updated README.md
121 136
122 ### Fixes137 ### Fixes
123 138
124-- fix(release): handle squash merges when generating release notes (`9e36c16`)139+- fix(release): handle squash merges when generating release notes
125 140
126 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.1...v1.0.2141 **Full changelog**: https://github.com/Anthodev/lapin-fute/compare/v1.0.1...v1.0.2
127 142
@@ -157,40 +172,40 @@ First public release. Lapin Futé puts Île-de-France arrivals on a Pebble watch
157 172
158 ### Features173 ### Features
159 174
160-- feat: verify published configuration site (`9dc8049`)175+- feat: verify published configuration site
161-- feat: harden stale and failure handling (#20) (`835b18b`)176+- feat: harden stale and failure handling (#20)
162-- feat(companion): modernize settings and introduce app branding (#19) (`2799b0f`)177+- feat(companion): modernize settings and introduce app branding (#19)
163-- feat: migrate to phone-only data and phone-prepared watch displays (#17) (`a46bf24`)178+- feat: migrate to phone-only data and phone-prepared watch displays (#17)
164-- feat: deliver Pebble watch consultation (#15) (`aabca5b`)179+- feat: deliver Pebble watch consultation (#15)
165-- feat: persist and synchronize favorites (#14) (`5ff79d1`)180+- feat: persist and synchronize favorites (#14)
166-- feat: build companion configuration page (#13) (`f7d01bd`)181+- feat: build companion configuration page (#13)
167-- feat: normalize live PRIM departures (#12) (`5a0b437`)182+- feat: normalize live PRIM departures (#12)
168-- feat: establish Alloy app foundation (#10) (`ffd859a`)183+- feat: establish Alloy app foundation (#10)
169 184
170 ### Changes185 ### Changes
171 186
172-- docs: link published RePebble release (`de5dd7f`)187+- docs: link published RePebble release
173-- chore: add RePebble store screenshots and banner (`e008516`)188+- chore: add RePebble store screenshots and banner
174-- chore: add RePebble store icons (`646389f`)189+- chore: add RePebble store icons
175-- docs: streamline project overview and controls (`3e430d6`)190+- docs: streamline project overview and controls
176-- docs: add v1 release checklist (`d6184a2`)191+- docs: add v1 release checklist
177-- docs: research official v1 release requirements (`30a8f4c`)192+- docs: research official v1 release requirements
178-- chore: ignore Pebble build lock (`6b61693`)193+- chore: ignore Pebble build lock
179-- chore: set release version to 1.0.0 (`e1d539d`)194+- chore: set release version to 1.0.0
180-- Resolve IDFM service catalog with bounded importer (#11) (`acf5bba`)195+- Resolve IDFM service catalog with bounded importer (#11)
181-- chore: initialize develop branch (`4c1dead`)196+- chore: initialize develop branch
182 197
183 ### Fixes198 ### Fixes
184 199
185-- fix: normalize Emery store screenshot brightness (`befc545`)200+- fix: normalize Emery store screenshot brightness
186-- fix: close release verification gaps (`457bcb9`)201+- fix: close release verification gaps
187 202
188 ### CI203 ### CI
189 204
190-- ci: skip unchanged catalog uploads (`91a4982`)205+- ci: skip unchanged catalog uploads
191-- ci: retain and rehearse deployment rollback (`5f1ff8c`)206+- ci: retain and rehearse deployment rollback
192-- ci: package PBW release assets (`30f04fc`)207+- ci: package PBW release assets
193-- ci: verify deploys and restore failures (`f224ada`)208+- ci: verify deploys and restore failures
194 209
195 **Full changelog**: https://github.com/Anthodev/lapin-fute/commits/v1.0.0210 **Full changelog**: https://github.com/Anthodev/lapin-fute/commits/v1.0.0
196 211
modified README.md +2 -2
@@ -110,9 +110,9 @@ npm run build:config-site # assemble var/config-site; f
110110
111111 `node --env-file=.env scripts/probe-catalog.mjs` runs a small live check against PRIM, one request per transport mode, and needs a `PRIM_API_KEY`. Operator tokens live only in `.env` and are never printed or embedded in build output.
112112
113-To rebuild the hosted catalog independently, run **Rebuild and publish IDFM catalog** from GitHub Actions, using `.github/workflows/rebuild-catalog.yml`. It uses the existing `production` environment's IDFM and Bunny credentials and publishes only catalog files, with up to 75 concurrent curl uploads. The manifest is uploaded last. It does not build the PBW, deploy the settings page, or create a GitHub release.
113+To rebuild the hosted catalog independently, run **Rebuild and publish IDFM catalog** from Rickub Actions, using `.github/workflows/rebuild-catalog.yml`. It uses the existing `production` environment's IDFM and Bunny credentials and publishes only catalog files, with up to 75 concurrent curl uploads. The manifest is uploaded last. It does not build the PBW, deploy the settings page, or create a Rickub release.
114114
115-Uploads are skipped when the hosted IDFM source revision is unchanged. Enable `force_catalog_upload` when republishing a catalog format change from the same source data. The workflow retains the previous manifest for rollback and shares the production publication lock with page deployments and rollbacks. Release tags continue through `deploy-bunny.yml`, without rebuilding the catalog. Release deployments install the pinned `pebble-tool` on a uv-managed Python 3.11, because the runner's system Python has no `ensurepip`, and require the `production` environment's `BUNNY_STORAGE_ENDPOINT` variable and `BUNNY_STORAGE_PASSWORD` secret; when either is unset, the deployment fails at the backup gate with an error naming the missing one.
115+Uploads are skipped when the hosted IDFM source revision is unchanged. Enable `force_catalog_upload` when republishing a catalog format change from the same source data. The workflow retains the previous manifest for rollback and shares the production publication lock with page deployments and rollbacks. Release tags continue through `deploy-bunny.yml`, without rebuilding the catalog. Release deployments install the pinned `pebble-tool` on a uv-managed Python 3.11, because the runner's system Python has no `ensurepip`, and require the `production` environment's `BUNNY_STORAGE_ENDPOINT` variable and `BUNNY_STORAGE_PASSWORD` secret; when either is unset, the deployment fails at the backup gate with an error naming the missing one. The backup step feeds its file loop through a plain pipe rather than process substitution, which hardened runners may not provide. After tests, packaging, and deployment pass, the publish job creates a Rickub release through the runner's `GITHUB_API_URL` with the tagged changelog notes and attaches the PBW and `SHA256SUMS`. The Store job reads those published notes through the same API.
116116
117117 ## Contributing
118118
@@ -110,9 +110,9 @@ npm run build:config-site # assemble var/config-site; f
110 110
111 `node --env-file=.env scripts/probe-catalog.mjs` runs a small live check against PRIM, one request per transport mode, and needs a `PRIM_API_KEY`. Operator tokens live only in `.env` and are never printed or embedded in build output.111 `node --env-file=.env scripts/probe-catalog.mjs` runs a small live check against PRIM, one request per transport mode, and needs a `PRIM_API_KEY`. Operator tokens live only in `.env` and are never printed or embedded in build output.
112 112
113-To rebuild the hosted catalog independently, run **Rebuild and publish IDFM catalog** from GitHub Actions, using `.github/workflows/rebuild-catalog.yml`. It uses the existing `production` environment's IDFM and Bunny credentials and publishes only catalog files, with up to 75 concurrent curl uploads. The manifest is uploaded last. It does not build the PBW, deploy the settings page, or create a GitHub release.113+To rebuild the hosted catalog independently, run **Rebuild and publish IDFM catalog** from Rickub Actions, using `.github/workflows/rebuild-catalog.yml`. It uses the existing `production` environment's IDFM and Bunny credentials and publishes only catalog files, with up to 75 concurrent curl uploads. The manifest is uploaded last. It does not build the PBW, deploy the settings page, or create a Rickub release.
114 114
115-Uploads are skipped when the hosted IDFM source revision is unchanged. Enable `force_catalog_upload` when republishing a catalog format change from the same source data. The workflow retains the previous manifest for rollback and shares the production publication lock with page deployments and rollbacks. Release tags continue through `deploy-bunny.yml`, without rebuilding the catalog. Release deployments install the pinned `pebble-tool` on a uv-managed Python 3.11, because the runner's system Python has no `ensurepip`, and require the `production` environment's `BUNNY_STORAGE_ENDPOINT` variable and `BUNNY_STORAGE_PASSWORD` secret; when either is unset, the deployment fails at the backup gate with an error naming the missing one.115+Uploads are skipped when the hosted IDFM source revision is unchanged. Enable `force_catalog_upload` when republishing a catalog format change from the same source data. The workflow retains the previous manifest for rollback and shares the production publication lock with page deployments and rollbacks. Release tags continue through `deploy-bunny.yml`, without rebuilding the catalog. Release deployments install the pinned `pebble-tool` on a uv-managed Python 3.11, because the runner's system Python has no `ensurepip`, and require the `production` environment's `BUNNY_STORAGE_ENDPOINT` variable and `BUNNY_STORAGE_PASSWORD` secret; when either is unset, the deployment fails at the backup gate with an error naming the missing one. The backup step feeds its file loop through a plain pipe rather than process substitution, which hardened runners may not provide. After tests, packaging, and deployment pass, the publish job creates a Rickub release through the runner's `GITHUB_API_URL` with the tagged changelog notes and attaches the PBW and `SHA256SUMS`. The Store job reads those published notes through the same API.
116 116
117 ## Contributing117 ## Contributing
118 118
modified package-lock.json +6 -6
@@ -1,12 +1,12 @@
11 {
22 "name": "lapin-fute",
3- "version": "1.2.3",
3+ "version": "1.2.4",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "lapin-fute",
9- "version": "1.2.3",
9+ "version": "1.2.4",
1010 "license": "MIT",
1111 "workspaces": [
1212 "packages/*"
@@ -57,7 +57,7 @@
5757 },
5858 "packages/catalog": {
5959 "name": "@lapin-fute/catalog",
60- "version": "1.2.3",
60+ "version": "1.2.4",
6161 "license": "MIT",
6262 "dependencies": {
6363 "csv-parse": "7.0.2",
@@ -66,17 +66,17 @@
6666 },
6767 "packages/companion": {
6868 "name": "@lapin-fute/companion",
69- "version": "1.2.3",
69+ "version": "1.2.4",
7070 "license": "MIT"
7171 },
7272 "packages/config-page": {
7373 "name": "@lapin-fute/config-page",
74- "version": "1.2.3",
74+ "version": "1.2.4",
7575 "license": "MIT"
7676 },
7777 "packages/watch": {
7878 "name": "lapin-fute-watch",
79- "version": "1.2.3",
79+ "version": "1.2.4",
8080 "license": "MIT"
8181 }
8282 }
@@ -1,12 +1,12 @@
1 {1 {
2 "name": "lapin-fute",2 "name": "lapin-fute",
3- "version": "1.2.3",3+ "version": "1.2.4",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "lapin-fute",8 "name": "lapin-fute",
9- "version": "1.2.3",9+ "version": "1.2.4",
10 "license": "MIT",10 "license": "MIT",
11 "workspaces": [11 "workspaces": [
12 "packages/*"12 "packages/*"
@@ -57,7 +57,7 @@
57 },57 },
58 "packages/catalog": {58 "packages/catalog": {
59 "name": "@lapin-fute/catalog",59 "name": "@lapin-fute/catalog",
60- "version": "1.2.3",60+ "version": "1.2.4",
61 "license": "MIT",61 "license": "MIT",
62 "dependencies": {62 "dependencies": {
63 "csv-parse": "7.0.2",63 "csv-parse": "7.0.2",
@@ -66,17 +66,17 @@
66 },66 },
67 "packages/companion": {67 "packages/companion": {
68 "name": "@lapin-fute/companion",68 "name": "@lapin-fute/companion",
69- "version": "1.2.3",69+ "version": "1.2.4",
70 "license": "MIT"70 "license": "MIT"
71 },71 },
72 "packages/config-page": {72 "packages/config-page": {
73 "name": "@lapin-fute/config-page",73 "name": "@lapin-fute/config-page",
74- "version": "1.2.3",74+ "version": "1.2.4",
75 "license": "MIT"75 "license": "MIT"
76 },76 },
77 "packages/watch": {77 "packages/watch": {
78 "name": "lapin-fute-watch",78 "name": "lapin-fute-watch",
79- "version": "1.2.3",79+ "version": "1.2.4",
80 "license": "MIT"80 "license": "MIT"
81 }81 }
82 }82 }
modified package.json +1 -1
@@ -1,6 +1,6 @@
11 {
22 "name": "lapin-fute",
3- "version": "1.2.3",
3+ "version": "1.2.4",
44 "license": "MIT",
55 "private": true,
66 "type": "module",
@@ -1,6 +1,6 @@
1 {1 {
2 "name": "lapin-fute",2 "name": "lapin-fute",
3- "version": "1.2.3",3+ "version": "1.2.4",
4 "license": "MIT",4 "license": "MIT",
5 "private": true,5 "private": true,
6 "type": "module",6 "type": "module",
modified packages/catalog/package.json +1 -1
@@ -1,6 +1,6 @@
11 {
22 "name": "@lapin-fute/catalog",
3- "version": "1.2.3",
3+ "version": "1.2.4",
44 "license": "MIT",
55 "private": true,
66 "type": "module",
@@ -1,6 +1,6 @@
1 {1 {
2 "name": "@lapin-fute/catalog",2 "name": "@lapin-fute/catalog",
3- "version": "1.2.3",3+ "version": "1.2.4",
4 "license": "MIT",4 "license": "MIT",
5 "private": true,5 "private": true,
6 "type": "module",6 "type": "module",
modified packages/companion/package.json +1 -1
@@ -1,6 +1,6 @@
11 {
22 "name": "@lapin-fute/companion",
3- "version": "1.2.3",
3+ "version": "1.2.4",
44 "license": "MIT",
55 "private": true,
66 "type": "commonjs",
@@ -1,6 +1,6 @@
1 {1 {
2 "name": "@lapin-fute/companion",2 "name": "@lapin-fute/companion",
3- "version": "1.2.3",3+ "version": "1.2.4",
4 "license": "MIT",4 "license": "MIT",
5 "private": true,5 "private": true,
6 "type": "commonjs",6 "type": "commonjs",
modified packages/config-page/index.html +2 -2
@@ -175,7 +175,7 @@
175175 </div>
176176
177177 <div id="about-en" lang="en">
178- <p class="about-version">Version 1.2.3</p>
178+ <p class="about-version">Version 1.2.4</p>
179179 <h3>Publisher and contact</h3>
180180 <p><strong>Author and publisher:</strong> Anthodev, a private individual acting on a non-professional basis.</p>
181181 <p><strong>Publication director:</strong> Anthodev.</p>
@@ -197,7 +197,7 @@
197197 </div>
198198
199199 <div id="about-fr" lang="fr" hidden>
200- <p class="about-version">Version 1.2.3</p>
200+ <p class="about-version">Version 1.2.4</p>
201201 <h3>Édition et contact</h3>
202202 <p><strong>Auteur et éditeur :</strong> Anthodev, personne physique agissant à titre privé et non professionnel.</p>
203203 <p><strong>Directeur de la publication :</strong> Anthodev.</p>
@@ -175,7 +175,7 @@
175 </div>175 </div>
176 176
177 <div id="about-en" lang="en">177 <div id="about-en" lang="en">
178- <p class="about-version">Version 1.2.3</p>178+ <p class="about-version">Version 1.2.4</p>
179 <h3>Publisher and contact</h3>179 <h3>Publisher and contact</h3>
180 <p><strong>Author and publisher:</strong> Anthodev, a private individual acting on a non-professional basis.</p>180 <p><strong>Author and publisher:</strong> Anthodev, a private individual acting on a non-professional basis.</p>
181 <p><strong>Publication director:</strong> Anthodev.</p>181 <p><strong>Publication director:</strong> Anthodev.</p>
@@ -197,7 +197,7 @@
197 </div>197 </div>
198 198
199 <div id="about-fr" lang="fr" hidden>199 <div id="about-fr" lang="fr" hidden>
200- <p class="about-version">Version 1.2.3</p>200+ <p class="about-version">Version 1.2.4</p>
201 <h3>Édition et contact</h3>201 <h3>Édition et contact</h3>
202 <p><strong>Auteur et éditeur :</strong> Anthodev, personne physique agissant à titre privé et non professionnel.</p>202 <p><strong>Auteur et éditeur :</strong> Anthodev, personne physique agissant à titre privé et non professionnel.</p>
203 <p><strong>Directeur de la publication :</strong> Anthodev.</p>203 <p><strong>Directeur de la publication :</strong> Anthodev.</p>
modified packages/config-page/package.json +1 -1
@@ -1,6 +1,6 @@
11 {
22 "name": "@lapin-fute/config-page",
3- "version": "1.2.3",
3+ "version": "1.2.4",
44 "license": "MIT",
55 "private": true,
66 "type": "module",
@@ -1,6 +1,6 @@
1 {1 {
2 "name": "@lapin-fute/config-page",2 "name": "@lapin-fute/config-page",
3- "version": "1.2.3",3+ "version": "1.2.4",
4 "license": "MIT",4 "license": "MIT",
5 "private": true,5 "private": true,
6 "type": "module",6 "type": "module",
modified packages/watch/package.json +1 -1
@@ -1,7 +1,7 @@
11 {
22 "name": "lapin-fute-watch",
33 "author": "Anthodev",
4- "version": "1.2.3",
4+ "version": "1.2.4",
55 "license": "MIT",
66 "keywords": [
77 "pebble-app"
@@ -1,7 +1,7 @@
1 {1 {
2 "name": "lapin-fute-watch",2 "name": "lapin-fute-watch",
3 "author": "Anthodev",3 "author": "Anthodev",
4- "version": "1.2.3",4+ "version": "1.2.4",
5 "license": "MIT",5 "license": "MIT",
6 "keywords": [6 "keywords": [
7 "pebble-app"7 "pebble-app"