Skip to content

Build a single-file portable desktop app - #18

Open
owenpkent wants to merge 1 commit into
windows-updaterfrom
windows-portable
Open

owenpkent wants to merge 1 commit into
windows-updaterfrom
windows-portable

Conversation

@owenpkent

Copy link
Copy Markdown
Owner

Stacked on #13; merge that first, or retarget this to main after it lands.

What

Adds Offloader-{version}-portable.exe: the desktop app as one file that runs from any folder without installing. The existing ZIP stays; it is still the only portable way to get the CLI.

  • build/windows/portable_entry.py: new entry point that runs the GUI without the installation lock. gui_entry.py gains a lock flag; installed programs still require the lock.
  • offloader.spec: onefile target outside the bundle, with its own version resource.
  • build.py: checks the embedded version, signs and verifies the outer exe, records its digest in the build record (so --skip-build cannot reuse a swapped file), smoke-tests it, and lists it in SHA256SUMS.txt. --verify-only verifies its signature.
  • smoke.py --portable: starts a copy from an empty folder with a minimal PATH and fails if it writes anything beside itself.

Why no lock

No installer or maintenance helper manages the folder a portable exe sits in. Requiring the lock file would stop it starting; creating one would litter the user's folder. The installer never touches the portable exe, so there is nothing for the lock to protect.

Limits

  • No CLI (windowed exe) and no in-place update.
  • Unpacks to %TEMP% on each launch, so startup is slower than the installed app.
  • Only the outer exe is signed here. The native modules it unpacks keep the signatures they were frozen with, which may matter under strict app-control policies.

Testing

  • python -m pytest -q: 797 passed, 12 skipped.
  • python -m ruff check src tests scripts build/windows: clean.
  • build.py --clean --no-sign --no-installer in a Python 3.12 venv with requirements-build.txt: built, all smoke checks passed including the portable launch. Output 58,333,663 bytes.
  • Not run: signed build (needs the hardware token), NSIS installer build, visible desktop launch, clean-machine test.

The ZIP is portable only as a folder: Offloader.exe needs _internal beside it
and refuses to start without the installation lock file. This adds
Offloader-{version}-portable.exe, the desktop app as one self-extracting file
that runs from any folder.

It skips the installation lock. No installer or maintenance helper manages the
folder it sits in, so requiring a lock file would stop it starting and
creating one would litter the user's folder. The installed programs still take
the lock; the portable path goes through its own entry point rather than a
runtime guess about how the executable was frozen.

The builder treats it like the other release outputs: it signs and verifies
the outer executable, checks its embedded version, records its digest in the
build record so --skip-build cannot reuse a swapped file, and lists it in
SHA256SUMS.txt. The smoke runner starts a copy from an empty folder with a
minimal PATH and fails if it writes anything beside itself.

It has no CLI and no in-place update. The ZIP and installer still provide
those. The native modules it unpacks at launch keep the signatures they were
frozen with; only the outer executable is signed by this pipeline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant