Releasing¶
How a version of freedyn2 gets to its users, and what a maintainer sets up for it. Everything
runs in the workflow Wheels (.github/workflows/wheels.yml) when a version tag vX.Y.Z is
pushed:
| Job | What it does |
|---|---|
version |
the tag must equal version in pyproject.toml (tag v0.1.0 for 0.1.0), else everything stops |
wheels, sdist |
the wheels for Windows x64 and Linux x86_64 (cibuildwheel, tested), the source distribution |
installer |
the Windows installer from the Windows wheel (installer/windows), signed if signing is set up, then installed silently, used (freedyn2 --version, doctor, run on an .fds and on a model with a plugin compiled by Zig, setup-mcp --dry-run) and uninstalled |
publish |
the wheels and the sdist to PyPI (trusted publishing, environment pypi) |
release |
the GitHub Release vX.Y.Z: wheels, sdist, freedyn2-X.Y.Z-setup.exe, the same as freedyn2-setup.exe, SHA256SUMS; text from CHANGELOG.md plus generated notes |
winget |
a pull request to microsoft/winget-pkgs with the new version (if WINGET_TOKEN is set) |
Pull requests that change the packaging (pyproject.toml, CMakeLists.txt, tools/,
installer/, the workflow) and a manual run (Actions, Wheels, Run workflow) do the same up to and
including the installer's test, without signing and without publishing. The installer of such a
run is the artifact installer-windows.
A version with letters (0.2.0rc1, 0.2.0b1) is a pre-release: on PyPI as such, on GitHub
marked as pre-release and not "latest" (so the stable download link and the app's update notice
stay on the last stable version), and not sent to winget.
Once: what a maintainer sets up¶
The repository¶
- Public. The download links of the releases (and the app's update check through the GitHub API) work without login only for a public repository. Also required by the SignPath Foundation.
- Releases are GitHub Releases with tags
vX.Y.Z: the app readshttps://api.github.com/repos/freedyn-org/freedyn2/releases/latestfor its update notice, the stable linkhttps://github.com/freedyn-org/freedyn2/releases/latest/download/freedyn2-setup.exepoints to the installer of the latest stable release. - Settings, Actions, General, Workflow permissions: the default (read) is enough; the release job
asks for
contents: writeitself. - Optional: a tag protection rule (Settings, Rules, Rulesets, target tags
v*) so that only maintainers can push version tags.
PyPI (trusted publishing, no token)¶
- An account on pypi.org with two-factor authentication (required for publishing). Ideally two maintainers, both owners of the project later.
- Your account, Publishing, Add a new pending publisher (the project
freedyn2does not exist yet; the first upload creates it): PyPI project namefreedyn2, ownerfreedyn-org, repository namefreedyn2, workflow namewheels.yml, environment namepypi. - On GitHub: Settings, Environments, New environment
pypi. Protection rules: "Required reviewers" (one or two maintainers) so that every upload to PyPI is approved by hand, and "Deployment branches and tags": selected tags,v*. - Optional, for testing the whole path first: the same on test.pypi.org
(a pending publisher there; the workflow would need
repository-url: https://test.pypi.org/legacy/in the publish step for such a test run).
Code signing of the Windows installer (optional, recommended)¶
Without signing everything works, but Windows SmartScreen warns about an "unknown publisher" when the installer is downloaded and started ("More info", "Run anyway"), and some company computers block unsigned programs. A signature names the publisher; SmartScreen's reputation grows with downloads of signed files.
The workflow signs with SignPath when the secret SIGNPATH_API_TOKEN exists, and only for
version tags: first freedyn2's own programs that go into the installer (freedyn2.exe,
freedyn2-gui.exe, freedyn2_run.exe, freedyn2_road.exe), then the installer. Without the
token these steps are skipped and the installer is unsigned. The Inno Setup uninstaller
(unins000.exe) remains unsigned in this setup.
Status (November 2026)
Releases are unsigned. The SignPath Foundation declined the first application: the program
needs public signals of adoption (stars, forks, contributors, independent references,
institutional backing) that freedyn2 does not have yet; a new application is welcome once it
has them. Until then: unsigned releases with SHA256SUMS (docs/download.md), or a paid signer
below (a SignPath subscription uses the same workflow steps).
SignPath Foundation (signpath.org) signs open-source projects free of charge with its own certificate; the publisher shown is then "SignPath Foundation", and the signature attests that the file was built from the project's repository. Requirements (check the current conditions on signpath.org before applying), which freedyn2 meets as set up here:
- an OSI-approved license (Apache-2.0), no proprietary components;
- a public repository, the software actively maintained and already released or about to be;
- the signed files built in CI from that repository (GitHub-hosted runners; SignPath checks the origin of each artifact through the GitHub API);
- a documented release process (this page) and roles: who may submit, and who approves each release signing request (manual approval of every release);
- a code signing policy on the project's website (the roles above, what is signed: the artifact configurations below, a privacy statement) and the note "Free code signing provided by SignPath.io, certificate by SignPath Foundation", e.g. in the README's install section.
Steps:
- Apply on signpath.org (the application form for open-source projects) with the repository URL. How long the review takes is not stated: unknown, to be checked (allow some weeks).
- After approval SignPath creates the organization and the project; in it:
- a trusted build system "GitHub.com" linked to the repository (the SignPath GitHub app);
- two artifact configurations, contents from the repository:
installer/windows/signpath/programs.xml(slug e.g.programs: the zip artifactprograms-unsignedwith the paths of the programs relative to the installation directory, each Authenticode-signed) andinstaller/windows/signpath/installer.xml(slug e.g.installer: the artifactinstaller-unsignedwithfreedyn2-*-setup.exe). Two requests are needed because SignPath cannot sign inside an Inno Setup installer: the programs are signed before Inno Setup packs them; - a signing policy (e.g.
release-signing) with the approvers, origin verification ("allowed branches/tags":refs/tags/v*); - a CI user with an API token for submitting requests.
- On GitHub (Settings, Secrets and variables, Actions):
- secret
SIGNPATH_API_TOKEN: the API token of the CI user; - variables
SIGNPATH_ORGANIZATION_ID(a GUID),SIGNPATH_PROJECT_SLUG(e.g.freedyn2),SIGNPATH_SIGNING_POLICY_SLUG(e.g.release-signing),SIGNPATH_CONFIG_PROGRAMS(e.g.programs),SIGNPATH_CONFIG_INSTALLER(e.g.installer).
- secret
- At every release an approver approves the two signing requests in SignPath (an e-mail comes; the job waits up to two hours for each).
Alternatives (not wired up; they replace the steps "Sign the programs" and "Sign the installer",
which read build/installer/to-sign and dist-installer and write the signed files back):
- Azure Artifact Signing (formerly Trusted Signing, action
azure/artifact-signing-action): about 10 USD per month (Basic), the publisher is the organization itself; needs an Azure subscription and an identity validation of the organization (a legal entity with some years of history; individuals only in some countries). - A classic OV or EV certificate from a certificate authority (some hundred USD per year; the key on a hardware token or in a cloud HSM, so it needs a signing service reachable from CI).
winget (Windows Package Manager)¶
Package identifier proposed: freedyn-org.freedyn2 (to be confirmed before the first
submission: it cannot be changed easily afterwards). Templates in
installer/winget.
- First version by hand, after its GitHub Release exists: on Windows,
download the released
freedyn2-X.Y.Z-setup.exe, thenpython installer/winget/render.py X.Y.Z freedyn2-X.Y.Z-setup.exewrites the manifests tobuild/winget/manifests/f/FreeDynOrg/freedyn2/X.Y.Z;winget validate --manifest <that directory>,winget install --manifest <that directory>(needswinget settings --enable LocalManifestFilesas administrator) to test; then a pull request to microsoft/winget-pkgs with that directory (fork, commit, PR; orwingetcreate submit <directory>). Microsoft's validation runs on the PR; a moderator merges it. - Later versions automatically: a GitHub account with a fork of microsoft/winget-pkgs and a
classic personal access token with scope
public_repo, as the secretWINGET_TOKEN. The jobwingetthen runswingetcreate update freedyn-org.freedyn2with the new installer for both scopes (user, machine) and opens the pull request.
Documentation site¶
The documentation link of the installer's Start menu points to
https://freedyn-org.github.io/freedyn2/ (GitHub Pages of this repository).
Per release¶
- Version: set
versioninpyproject.toml(e.g.0.2.0; pre-releases0.2.0rc1). - Changelog: in
CHANGELOG.mdmove the entries under## [Unreleased]to a new section## [0.2.0] - 2026-12-01. Its text becomes the text of the GitHub Release. - Commit, pull request, merge to
main(the CI and, as the version changedpyproject.toml, the Wheels workflow with the installer test run on the pull request). -
Tag the merge commit on
mainand push the tag:git checkout main && git pull git tag -a v0.2.0 -m "freedyn2 0.2.0" git push origin v0.2.0 -
What happens: the Wheels workflow runs (about an hour): checks the version, builds and tests the wheels, builds, signs (approve the two SignPath requests) and tests the installer, waits for the approval of the environment
pypi(approve it under the workflow run), uploads to PyPI, creates the GitHub Release, opens the winget pull request. - Verify:
- pypi.org/project/freedyn2 shows the version;
uvx freedyn2@0.2.0 --versionprints it; - the release page lists the wheels, the sdist, both installer names and
SHA256SUMS;https://github.com/freedyn-org/freedyn2/releases/latest/download/freedyn2-setup.exedownloads the new installer (stable releases only); - on a Windows computer: download, check the hash (
Get-FileHash freedyn2-setup.exeagainstSHA256SUMS), right click, Properties, Digital Signatures (if signed), install over the previous version, start "freedyn2" from the Start menu,freedyn2 doctorin the "freedyn2 Command Prompt"; - later: the winget pull request is merged (
winget show freedyn-org.freedyn2).
- pypi.org/project/freedyn2 shows the version;
If a job fails after PyPI accepted the upload: a version on PyPI cannot be replaced. Fix the cause and re-run the failed jobs of the same run (the release and winget jobs can be re-run on their own); if the code itself must change, release the next patch version.
The Windows installer in detail¶
installer/windows/build.py builds it from the Windows wheel (locally on Windows with Inno
Setup 6 installed: python installer/windows/build.py all --wheel-dir wheelhouse; on any system
--dry-run checks the steps without Windows):
- a pinned CPython 3.12 of python-build-standalone
(version and SHA256 in
build.py; to update, take both from the release'sSHA256SUMS); freedyn2[gui,compiler]and all dependencies as wheels (pip wheel, which also builds the few that only have a source distribution), then installed offline from them;- the launchers
freedyn2.exe,freedyn2-gui.exemade relocatable (their interpreter path relative to the launcher), inbin\(on the PATH) and inpython\Scripts\; - Zig (
ziglang) separated as the component "C compiler"; tests and caches removed; bytecode precompiled;licenses\python-packages.txtgenerated from the packages' metadata.
installer/windows/freedyn2.iss (Inno Setup): per user by default
(%LOCALAPPDATA%\Programs\freedyn2) or for all users (C:\Program Files\freedyn2), components
core, C compiler, examples; tasks PATH, .fds association, "Open with freedyn2" for .yaml,
Claude Desktop registration (freedyn2 setup-mcp --client claude-desktop, undone at uninstall),
desktop icon. A new version installs over the old one. Uninstalling keeps the user data in
%APPDATA%\freedyn2 and %LOCALAPPDATA%\freedyn2. Silent:
freedyn2-setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /CURRENTUSER /TASKS="path"
freedyn2-setup.exe /VERYSILENT /ALLUSERS /COMPONENTS="core" /TASKS="path,assocfds"
"%LOCALAPPDATA%\Programs\freedyn2\unins000.exe" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART
Task names: path, assocfds, openyaml, claudedesktop, desktopicon; components: core,
compiler, examples. /TASKS replaces the default selection, /MERGETASKS adds to it.