6745e25b20
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.
162 lines
7.3 KiB
Markdown
162 lines
7.3 KiB
Markdown
# CI/CD & Release Workflow
|
|
|
|
> **Reference guide:** [Gitea Python CI/CD Guide](https://git.dolgolyov-family.by/alexei.dolgolyov/claude-code-facts/src/branch/main/gitea-python-ci-cd.md) — 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.sh` on 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) and **`LedGrab-{tag}-setup.exe`** (NSIS installer)
|
|
|
|
### 3. `build-linux`
|
|
- Runs `build/build-dist.sh` on Ubuntu
|
|
- Creates a venv, installs deps, builds frontend
|
|
- Produces: **`LedGrab-{tag}-linux-x64.tar.gz`**
|
|
|
|
### 4. `build-docker`
|
|
- Plain `docker build` + `docker push` for **amd64** (no Buildx — TrueNAS runners lack nested networking)
|
|
- Registry: `{gitea_host}/{repo}:{tag}`
|
|
- Tags: `v0.x.x`, `0.x.x`, and `latest` (stable only, not alpha/beta/rc)
|
|
- **arm64 is best-effort** (Raspberry Pi / arm64 HAOS): a `continue-on-error` step
|
|
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}-arm64` arch-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: true` on the Gitea release
|
|
- Docker `latest` tag only applied to stable releases
|
|
- Version in `server/pyproject.toml` should match the tag (without `v` prefix)
|
|
|
|
## CI Runners
|
|
|
|
- Two TrueNAS Gitea runners with `ubuntu` tags
|
|
- 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-headless` and `libportaudio2` for CI compatibility
|
|
- Display-dependent tests are skipped via `@requires_display` marker
|
|
- Uses `python` not `python3` (Git Bash on Windows resolves `python3` to 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` (not `MUI_FINISHPAGE_RUN_PARAMETERS` — NSIS `Exec` chokes on quoting). Still requires `MUI_FINISHPAGE_RUN ""` defined for checkbox visibility
|
|
- **Detect running instance**: `.onInit` checks file lock on `python.exe`, offers to kill process before install
|
|
- **Uninstall preserves user data**: Remove `python/`, `app/`, `logs/` but NOT `data/`
|
|
- **CI build**: `sudo apt-get install -y nsis msitools zip` then `makensis -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
|
|
```bash
|
|
git tag v0.2.0
|
|
git push origin v0.2.0
|
|
```
|
|
|
|
### Creating a pre-release
|
|
```bash
|
|
git tag v0.2.0-alpha.1
|
|
git push origin v0.2.0-alpha.1
|
|
```
|
|
|
|
### Adding a new build artifact
|
|
1. Update the build script to produce the new file
|
|
2. Add upload step in the relevant `build-*` job
|
|
3. **Update the release description** in `create-release` job body template
|
|
4. Test with a pre-release tag first
|
|
|
|
### Re-triggering a failed release workflow
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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 |
|