Eight roadmap features from the 2026-06-19 review, each a full vertical (backend + tests + frontend + i18n en/ru/zh); ~67 new unit tests: - automations: SolarRule sunrise/sunset trigger (new utils/solar.py, shared with the daylight cycle; window logic mirrors TimeOfDayRule) - ci: best-effort arm64 multi-arch Docker manifest via QEMU + docker manifest (release.yml; amd64 path untouched, continue-on-error) - game-integration: wire the orphaned LoLPoller via a LoLPollManager + a shared runtime_state module (poll lifecycle on enable/CRUD/startup/shutdown) - ui: color-harmony gradient generator (complementary/analogous/triadic/...) - effects: audio-reactive palette modulation (new audio_energy_tap; brightness/ saturation modulation across all 12 procedural effects) - capture: linear-light blending + spatio-temporal dithering, opt-in per calibration (new utils/linear_light.py, utils/dither.py) - devices: Nanoleaf extControl v2 per-panel UDP streaming (per_panel mode) Also bundles the pending 2026-06-18 production-review fixes and other in-progress work already in the working tree (manual-trigger rule, etc.), since they share files and could not be cleanly separated. Gate: ruff + tsc clean; pytest 2654 passed / 2 skipped. The single failing test (automation manual_trigger handler coverage) is a separate in-progress item owned elsewhere, intentionally left as-is.
7.3 KiB
CI/CD & Release Workflow
Reference guide: Gitea Python CI/CD Guide — reusable patterns for Gitea Actions, cross-build, NSIS, Docker. When modifying workflows or build scripts, consult this guide to stay in sync with established patterns.
Workflows
| File | Trigger | Purpose |
|---|---|---|
.gitea/workflows/test.yml |
Push/PR to master | Lint (ruff) + pytest |
.gitea/workflows/release.yml |
Tag v* |
Build Windows/Linux/Docker artifacts + create Gitea release |
.gitea/workflows/build-android.yml |
Push master (android/server paths), tag v*, manual |
Build Android APK; attach to release on tag |
Release Pipeline (release.yml)
Four parallel jobs triggered by pushing a v* tag:
1. create-release
Creates the Gitea release with a description table listing all artifacts. The description must stay in sync with actual build outputs — if you add/remove/rename an artifact, update the body template here.
2. build-windows (cross-built from Linux)
- Runs
build/build-dist-windows.shon Ubuntu with NSIS + msitools - Downloads Windows embedded Python 3.11 + pip wheels cross-platform
- Bundles tkinter from Python MSI via msiextract
- Builds frontend (
npm run build) - Pre-compiles Python bytecode (
compileall) - Produces:
LedGrab-{tag}-win-x64.zip(portable) andLedGrab-{tag}-setup.exe(NSIS installer)
3. build-linux
- Runs
build/build-dist.shon Ubuntu - Creates a venv, installs deps, builds frontend
- Produces:
LedGrab-{tag}-linux-x64.tar.gz
4. build-docker
- Plain
docker build+docker pushfor amd64 (no Buildx — TrueNAS runners lack nested networking) - Registry:
{gitea_host}/{repo}:{tag} - Tags:
v0.x.x,0.x.x, andlatest(stable only, not alpha/beta/rc) - arm64 is best-effort (Raspberry Pi / arm64 HAOS): a
continue-on-errorstep cross-builds arm64 via QEMU binfmt (tonistiigi/binfmt) +docker manifest(NOT buildx — sidesteps the docker-container-driver networking limit) and folds amd64 + arm64 into multi-arch manifest lists under the same tags, plus:{tag}-amd64/:{tag}-arm64arch-suffixed tags. If the runner can't run privileged binfmt the step is skipped and the amd64 tags above remain valid.
Build Scripts
| Script | Platform | Output |
|---|---|---|
build/build-dist-windows.sh |
Linux → Windows cross-build | ZIP + NSIS installer |
build/build-dist.sh |
Linux native | tarball |
server/Dockerfile |
Docker | Container image |
Release Versioning
- Tags:
v{major}.{minor}.{patch}for stable,v{major}.{minor}.{patch}-alpha.{n}for pre-release - Pre-release tags set
prerelease: trueon the Gitea release - Docker
latesttag only applied to stable releases - Version in
server/pyproject.tomlshould match the tag (withoutvprefix)
CI Runners
- Two TrueNAS Gitea runners with
ubuntutags - No Windows runner available — Windows builds are cross-compiled from Linux
- Docker Buildx not available (networking limitations) — use plain
docker build
Test Pipeline (test.yml)
- Installs
opencv-python-headlessandlibportaudio2for CI compatibility - Display-dependent tests are skipped via
@requires_displaymarker - Uses
pythonnotpython3(Git Bash on Windows resolvespython3to MS Store stub)
Version Detection Pattern
Build scripts use a fallback chain: CLI argument → exact git tag → CI env var (GITEA_REF_NAME / GITHUB_REF_NAME) → hardcoded in source. Always strip leading v for clean version strings.
NSIS Installer Best Practices
- User-scoped install (
$LOCALAPPDATA,RequestExecutionLevel user) — no admin required - Launch after install: Use
MUI_FINISHPAGE_RUN_FUNCTION(notMUI_FINISHPAGE_RUN_PARAMETERS— NSISExecchokes on quoting). Still requiresMUI_FINISHPAGE_RUN ""defined for checkbox visibility - Detect running instance:
.onInitchecks file lock onpython.exe, offers to kill process before install - Uninstall preserves user data: Remove
python/,app/,logs/but NOTdata/ - CI build:
sudo apt-get install -y nsis msitools zipthenmakensis -DVERSION="${VERSION}" build/installer.nsi
Hidden Launcher (VBS)
All shortcuts and the installer finish page use scripts/start-hidden.vbs instead of .bat to avoid console window flash. The VBS launcher must include an embedded Python fallback — installed distributions don't have Python on PATH, dev environment uses system Python.
Gitea vs GitHub Actions Differences
| Feature | GitHub Actions | Gitea Actions |
|---|---|---|
| Context prefix | github.* |
gitea.* |
| Ref name | ${{ github.ref_name }} |
${{ gitea.ref_name }} |
| Server URL | ${{ github.server_url }} |
${{ gitea.server_url }} |
| Output vars | $GITHUB_OUTPUT |
$GITHUB_OUTPUT (same) |
| Secrets | ${{ secrets.NAME }} |
${{ secrets.NAME }} (same) |
| Docker Buildx | Available | May not work (runner networking) |
Common Tasks
Creating a release
git tag v0.2.0
git push origin v0.2.0
Creating a pre-release
git tag v0.2.0-alpha.1
git push origin v0.2.0-alpha.1
Adding a new build artifact
- Update the build script to produce the new file
- Add upload step in the relevant
build-*job - Update the release description in
create-releasejob body template - Test with a pre-release tag first
Re-triggering a failed release workflow
# Option A: Delete and re-push the same tag
git push origin :refs/tags/v0.1.0-alpha.2
# Delete the release in Gitea UI or via API
git tag -f v0.1.0-alpha.2
git push origin v0.1.0-alpha.2
# Option B: Just bump the version (simpler)
git tag v0.1.0-alpha.3
git push origin v0.1.0-alpha.3
The create-release job has fallback logic — if the release already exists for a tag, it fetches and reuses the existing release ID.
Local Build Testing (Windows)
Prerequisites
- NSIS:
& "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" install NSIS.NSIS - Installs to
C:\Program Files (x86)\NSIS\makensis.exe
Build steps
npm ci && npm run build # frontend
bash build/build-dist-windows.sh v1.0.0 # Windows dist
"/c/Program Files (x86)/NSIS/makensis.exe" -DVERSION="1.0.0" build/installer.nsi # installer
Iterating on installer only
If only installer.nsi changed (not app code), skip the full rebuild — just re-run makensis. If app code changed, re-run build/build-dist-windows.sh first since dist/ is a snapshot.
Common issues
| Issue | Fix |
|---|---|
zip: command not found |
Git Bash doesn't include zip — harmless for installer builds |
Exec expects 1 parameters, got 2 |
Use MUI_FINISHPAGE_RUN_FUNCTION instead of MUI_FINISHPAGE_RUN_PARAMETERS |
Error opening file for writing: python\_asyncio.pyd |
Server is running — stop it before installing |
| App doesn't start after install | VBS must use embedded Python fallback, not bare python |
winget not recognized |
Use full path: $env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe |
dist/ has stale files |
Re-run full build script — dist/ doesn't auto-update |