release.yml: add fallback for existing releases on tag re-push. installer.nsi: add .onInit file lock check, use LaunchApp function instead of RUN_PARAMETERS to fix NSIS quoting bug. build-dist.ps1: copy start-hidden.vbs to dist scripts/. start-hidden.vbs: embedded Python fallback for installed/dev envs. Update ci-cd.md with version detection, NSIS best practices, local build testing, Gitea vs GitHub differences, troubleshooting. Update frontend.md with full entity type checklist and common pitfalls.
6.6 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 artifacts + create Gitea release |
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-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-dist.shon Ubuntu - Creates a venv, installs deps, builds frontend
- Produces:
LedGrab-{tag}-linux-x64.tar.gz
4. build-docker
- Plain
docker build+docker push(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)
Build Scripts
| Script | Platform | Output |
|---|---|---|
build-dist-windows.sh |
Linux → Windows cross-build | ZIP + NSIS installer |
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}" 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-dist-windows.sh v1.0.0 # Windows dist
"/c/Program Files (x86)/NSIS/makensis.exe" -DVERSION="1.0.0" 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-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 |