feat(installer): tag-triggered Windows installer, unsigned and without ffmpeg - #2
Merged
Conversation
…t ffmpeg Pushing a v* tag now builds a PyInstaller one-file keycut.exe on a Windows runner, wraps it in an Inno Setup installer, and attaches keycut-setup-<version>.exe to that tag's GitHub release. The installer puts keycut in Program Files, appends that directory to the machine PATH, and registers an uninstaller in Add/Remove Programs. Its version comes from the tag, and the workflow refuses a tag that disagrees with pyproject.toml, so the number Add/Remove Programs carries cannot be a guess. Two things it does not do, said in the installer's finish page and next to the README download rather than left for the user to find out: it does not bundle ffmpeg (an ffmpeg build dwarfs keycut, and which licence one falls under depends on how it was configured), and it is not code-signed, so SmartScreen will warn. No signing step is wired up, because there is no certificate for it to use. PyInstaller is aimed at installer/keycut_launcher.py because the console entry point is a module function, not a script on disk. Inno Setup is installed with choco rather than assumed present on the runner image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pushing a
v*tag now builds a real Windows installer onwindows-latestand attaches it to thattag's GitHub release. PyInstaller freezes a one-file
keycut.exe; Inno Setup wraps it, installs toProgram Files, puts the directory on PATH and registers an uninstaller in Add/Remove Programs.
This is the reference implementation for the same pattern in redactcam, agent-audit and
claude-batch-runner, so it is deliberately explicit.
What it does not do, said out loud
The installer's finish page and the README both state the requirement, and the README gives the
winget install -e --id Gyan.FFmpegline. An installer that produces a tool which cannot run isthe failure mode this avoids.
warn and describes what the user will see. There is no signing step that would no-op or fail.
Design calls
gh releaseoversoftprops/action-gh-release—ghships on the runner, so this is onefewer third-party action to pin and audit. The create-if-missing branch is three explicit lines.
failure mode: a standard user elevating with a different admin's credentials writes to that
admin's hive, and the invoking user never gets the entry. HKLM has no such case.
pyproject.tomlor the build fails. Otherwise Add/Remove Programscarries a version the packaged code never claimed, for as long as it stays installed. Practical
consequence: the first tag must be
v0.1.0, or bumppyproject.tomlfirst.AppVersionhas no default — compiling without/DAppVersion=fails at ISCC rather thanquietly emitting
keycut-setup-0.0.0-dev.exe.where-style check cannot betested from here, and a check that false-negatives puts a wrong claim in front of the user, which
is worse than a static true one.
[Registry]PATH line is one long line on purpose. It was written with ISPP\line-continuation, which nothing in this repo can compile-test, and a wrapped line that
mis-parses would corrupt the machine PATH. One line cannot.
Test plan
Verified here:
python -m PyInstaller --onefile --console --name keycut installer/keycut_launcher.py→ buildsucceeds, 7.5 MB binary. (
python -m pyinstallerlowercase is not a module and fails — theworkflow uses the capitalised form.)
dist/keycut --helpon the frozen binary → usage, exit 0.dist/keycut foo.mp4 out.mp4→at least one --range is required, exit 1, so CLI logic is reached inside the freeze.warn-keycut.txtshows no keycut-related missing modules → no--hidden-import.yaml.safe_loadonrelease.yml→ parses; trigger{'push': {'tags': ['v*']}},windows-latest.0.1.0verbatim..issis pure ASCII (grep -P '[^\x00-\x7F]'empty) — Inno reads a BOM-less script as ANSI.ruff check .clean;pytest -q→ 135 passed, re-run unpiped by the dispatcher after theline-continuation change.
Every Inno directive was read against jrsoftware.org rather than written from memory, including
ArchitecturesInstallIn64BitMode=x64compatible(plainx64is deprecated since Inno 6.3) and theexact signatures of
RegQueryStringValue/RegWriteExpandStringValue/Pos/Delete.Unverified until the first real tag build
Nothing here runs Windows, so the Windows half is unrun by construction:
.isshas never been through ISCC — directives are documentation-checked only.ArchitecturesInstallIn64BitMode=x64compatibleneeds Inno ≥6.3.choco install innosetupgetscurrent Inno so this should hold; if it ever fails,
{autopf}silently becomesProgram Files (x86).single-entry cases; it has never executed. Uninstall paths are where this kind of code bites.
false-positives on one-file PyInstaller binaries would surface here first.
%nbreaks,[name]expansion).Diff 246 sum (cap 300).
src/,tests/andci.ymluntouched.