1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# Release process (manual, local signing, per platform)
All TUF signing keys stay on your machine, one keystore per platform, in
`./<platform>/keystore` (gitignored — never commit it). Nothing here touches
CI or GitHub Secrets — Linux/macOS builds and signing should also happen
locally (a Linux build machine, or WSL2 on Windows works fine — PyInstaller
doesn't cross-compile, so you must actually run the build on the target OS).
`<platform>` is `win`, `mac`, or `linux` — whichever `python` is currently
running on (`core.updater._platform_key()` / the matching helper in
`repo_init.py`/`repo_release.py` decide this automatically; you never pass
it explicitly). Each platform gets a fully independent tree:
```
scripts/release/
win/{keystore,repository}/
linux/{keystore,repository}/
mac/{keystore,repository}/
```
Why fully separate rather than shared: a Windows build and a Linux build of
the same app version are different byte-for-byte archives, and TUF's
metadata filenames (`root.json`, `timestamp.json`, ...) aren't
platform-prefixed — sharing one repo/release between platforms means one
platform's publish silently overwrites the other's trust root and metadata.
## One-time setup (repeat once per platform, on that platform)
```
pip install -r requirements-dev.txt
python scripts/release/repo_init.py
```
Generates `scripts/release/<platform>/keystore/` (private + public keys for
the root/targets/snapshot/timestamp TUF roles) and
`scripts/release/<platform>/repository/` (initial signed metadata, including
`root.json`).
**Back up `<platform>/keystore/` immediately, somewhere private and durable
outside this repo — one backup per platform.** Losing a platform's root key
means you can never publish a trusted update to that platform's existing
installs again — they'd all need a fresh, non-updating reinstall. Losing one
platform's key doesn't affect the others (that's the point of keeping them
separate).
Rebuild once more (`pyinstaller labui.spec`) after this step, so the
freshly-generated `root.json` gets bundled into the app (see the `.spec`
file's `_root_json` check — it only bundles the file if it already exists,
and it reads from *this platform's* tree).
## Every release (repeat once per platform, on that platform)
1. Bump `__version__` in `core/version.py` (same version number across all
platforms for a given release, even though each is signed/published
separately).
2. `pyinstaller labui.spec` → produces `dist/LabUI/` for *this* OS.
3. `python scripts/release/repo_release.py` → creates+signs
`scripts/release/<platform>/repository/targets/labui-<version>.tar.gz`
and updates the metadata files in
`scripts/release/<platform>/repository/metadata/`.
4. Create (first time) or reuse the GitHub Release tagged
**`updates-<platform>`** (e.g. `updates-win`, `updates-linux`) on this
repo, and upload every file from
`scripts/release/<platform>/repository/metadata/` and
`scripts/release/<platform>/repository/targets/` as release assets,
**replacing** any same-named files already there.
- Each platform's tag is fixed on purpose — the app's
`metadata_base_url` (computed by `core.updater._platform_key()`)
always points at
`https://github.com/<owner>/<repo>/releases/download/updates-<platform>/`.
A normal per-version-tagged release would give every version a
different URL, which breaks TUF's requirement that top-level metadata
live at a stable location — and a *shared* tag across platforms would
break the platform-isolation described above.
- Cut a normal, separately-tagged human-facing release too if you want a
changelog/download page (e.g. `v1.2.3`, one release with per-platform
zip assets attached) — this doesn't have to be the same release as the
`updates-*` ones, and normally shouldn't be (see `docs/building.md`).
5. Test: launch a previous installed version of *this platform*,
Settings → Check for Updates, confirm it finds and applies the new
version.
## Re-signing without a new release
Metadata expires even if nothing changed (`expiration_days` in
`repo_init.py`: root 365 days, targets/snapshot/timestamp 90 days) —
per platform, independently. If you haven't shipped a release for a given
platform before its targets/snapshot/timestamp expire, that platform's
clients will start rejecting the metadata as stale. Re-sign:
```
python -m tufup sign snapshot scripts/release/<platform>/keystore
python -m tufup sign timestamp scripts/release/<platform>/keystore
```
(run from the repo root; substitute the actual platform directory) and
re-upload the resulting files to that platform's `updates-<platform>`
release. Set a calendar reminder per platform — there is no automation for
this on purpose (manual signing was a deliberate choice, not an oversight).
|