# Releasing

The project is developed on Linux and the installer only compiles on Windows,
so a release is split across two machines. The Linux half is the release; the
Windows half is only the `.exe`.

**You do not need Windows to ship an update.** Existing installs update from
the zip, which builds here — `updater.py` unpacks it in place and never runs
an installer. Windows is needed only to give *new* downloads a current
installer. Skipping it means someone downloads the previous `.exe` and the app
updates itself to the new version on first run; `stamp_site.py` knows this and
leaves the download button pointing at the installer that actually exists.

## 1. Here (Linux)

    scripts/release.sh 0.1.12

That bumps `__version__` and **commits it first** — the zip is built from
`git archive HEAD`, so an uncommitted bump produces a zip named for the new
version containing the old one. That shipped once: 0.1.1 on the site contained
0.1.0, so every install was offered an update that installed the same version
again, forever. The script now asserts the packaged version matches the name.

It also writes `dist/releases/latest.json`, cuts the changelog's Unreleased
section under the new version, submits the artifacts to VirusTotal if
`VT_API_KEY` is set, and stamps the download page.

    git push

## 2. On Windows

Clone once (private repo — `gh auth login`, or a PAT):

    git clone https://github.com/DevinProv/clipshelf.git
    cd clipshelf

Thereafter just `git pull`. Then:

    .\installer\build_local.ps1

It reads the version from `src\clipshelf\__init__.py`, so no argument is
needed after a release; `-Version 0.1.12` overrides, and `-SkipPayload` reuses
`installer\payload` instead of re-downloading ~11 MB of CPython.

Needs **Git for Windows** (`build_payload.sh` is bash, and git-bash runs it)
and **Inno Setup 6** — if Inno is missing the script offers to fetch it with
winget. Output is `dist\clipshelf-setup-<version>.exe`.

**Then test it, because the script does not.** CI would install the result and
run the app's self-test. By hand: run the installer and watch the six setup
steps, then open "Clipshelf (console)" from the Start menu and read the
self-test. Installing is per-user with no UAC prompt and no "for all users?"
question — if you see either, the `.iss` changed.

## 3. Publishing

Get the `.exe` back to Linux, then:

    scp dist/clipshelf-<ver>.zip dist/clipshelf-setup-<ver>.exe \
        root@108.61.215.163:/var/www/clipshelf/releases/
    scp dist/releases/latest.json root@108.61.215.163:/var/www/clipshelf/releases/

`latest.json` is what existing installs poll (`DEFAULT_UPDATE_URL =
https://clipshelf.gg/releases/latest.json`); its `url` is relative to itself.
**Upload the zip before the manifest** — a manifest naming a file that is not
there yet turns every running copy's update check into an error.

Then re-stamp the page now that the installer's size and hash are knowable,
and deploy the site:

    python3 scripts/stamp_site.py <ver> --exe-sha <sha256> --exe <size-in-MB>
    rsync -az --delete --exclude 'releases' site/ root@108.61.215.163:/var/www/clipshelf/

Add `--vt-verified` only if `scripts/virustotal.py` submitted the files and
came back clean; otherwise the page keeps the generic VirusTotal link rather
than pointing at a report that 404s.

Finally, check the thing a user would:

    curl -sSI https://clipshelf.gg/releases/clipshelf-setup-<ver>.exe | head -1
    curl -sS  https://clipshelf.gg/releases/latest.json

and confirm the published checksums match what actually downloads — a wrong
checksum on a page that tells people to click past a security warning is worse
than publishing none.
