Add PKG installer + Forgejo Release pipeline + Homebrew Cask integration #4

Merged
claude merged 2 commits from add-pkg-release-pipeline into main 2026-05-09 23:10:47 +03:00
Collaborator

Summary

Wires latvian-apostrofs into the same release/distribution flow that latvian-spell-checker already uses, so users have three install paths instead of one (./install.sh only):

  1. PKG installer — download LatvianApostrofs-<version>.pkg from a Forgejo Release, double-click, admin password, done.
  2. Homebrew Caskbrew tap ojars/pareizrakstiba … && brew install --cask apostrofs (tap repo updated separately, commit 4484e80).
  3. ./install.sh — existing per-user path, unchanged.

Files added

  • build-pkg.sh (170 lines) — assembles a flat .pkg via pkgbuild + productbuild with welcome/conclusion/license panels and a postinstall xattr -rd com.apple.quarantine step. Built locally and verified on macOS Tahoe 26.4.1 (sudo installer -pkg … -target / → files at /Library/Keyboard Layouts/{latvian_apostrofs.keylayout,.icns}, root:wheel 755, no quarantine, byte-identical SHA-256, pkgutil --pkg-info lv.kapteinis.latvian-apostrofs registered).
  • .forgejo/workflows/release.yml (95 lines) — tag-push (v*) triggers on the macmini Forgejo runner (instance scope since 2026-05-08): build PKG → SHA → create Forgejo Release with formatted body → upload asset → clone tap → sed-bump Casks/apostrofs.rb (version + sha256) → push.
  • .gitignore — excludes build/.

Mirrors spell-checker, differs in 3 ways

  • No clang build (no compiled binary, just two static data files).
  • No submodules (no Hunspell dictionaries).
  • Single PKG artifact uploaded (no separate .zip); the cask uses the pkg stanza directly via pkgutil for clean uninstall.

Net: workflow is 95 lines vs spell-checker's 132.

Manual step required after merge

Set FORGEJO_RELEASE_TOKEN secret on this repo (Settings → Secrets), with the same token value already present on ojars/latvian-spell-checker. Without it the workflow can't create the Release or push to the tap.

Test plan

  • Local PKG build (VERSION=2026.05.0 bash build-pkg.sh) → 8.4K artifact produced, distribution.xml + welcome/conclusion/license rendered correctly.
  • Local PKG install (sudo installer -pkg build/LatvianApostrofs-2026.05.0.pkg -target /) → files placed correctly with right perms + no quarantine; receipt registered.
  • Activated layout via System Settings, switched in input picker, verified Shift+3=# and Alt+4= in TextEdit.
  • Tag v2026.05.0 on main after this PR merges → CI runs on macmini → release surfaces at https://git.kapteinis.lv/ojars/latvian-apostrofs/releases → cask sha bumped on tap.
  • brew install --cask apostrofs smoke test after release lands.

Tap commit

Companion commit on ojars/homebrew-pareizrakstiba already pushed: 4484e80 adds Casks/apostrofs.rb (placeholder version+sha until first release) and restructures the tap README into a 2-cask layout with per-cask install/activate/uninstall sections.

🤖 Generated with Claude Code

## Summary Wires `latvian-apostrofs` into the same release/distribution flow that `latvian-spell-checker` already uses, so users have three install paths instead of one (`./install.sh` only): 1. **PKG installer** — download `LatvianApostrofs-<version>.pkg` from a [Forgejo Release](https://git.kapteinis.lv/ojars/latvian-apostrofs/releases), double-click, admin password, done. 2. **Homebrew Cask** — `brew tap ojars/pareizrakstiba … && brew install --cask apostrofs` (tap repo updated separately, commit `4484e80`). 3. **`./install.sh`** — existing per-user path, unchanged. ## Files added - `build-pkg.sh` (170 lines) — assembles a flat `.pkg` via `pkgbuild` + `productbuild` with welcome/conclusion/license panels and a postinstall `xattr -rd com.apple.quarantine` step. Built locally and verified on macOS Tahoe 26.4.1 (`sudo installer -pkg … -target /` → files at `/Library/Keyboard Layouts/{latvian_apostrofs.keylayout,.icns}`, `root:wheel 755`, no quarantine, byte-identical SHA-256, `pkgutil --pkg-info lv.kapteinis.latvian-apostrofs` registered). - `.forgejo/workflows/release.yml` (95 lines) — tag-push (`v*`) triggers on the macmini Forgejo runner (instance scope since 2026-05-08): build PKG → SHA → create Forgejo Release with formatted body → upload asset → clone tap → sed-bump `Casks/apostrofs.rb` (version + sha256) → push. - `.gitignore` — excludes `build/`. ## Mirrors spell-checker, differs in 3 ways - No clang build (no compiled binary, just two static data files). - No submodules (no Hunspell dictionaries). - Single PKG artifact uploaded (no separate `.zip`); the cask uses the `pkg` stanza directly via `pkgutil` for clean uninstall. Net: workflow is 95 lines vs spell-checker's 132. ## Manual step required after merge Set `FORGEJO_RELEASE_TOKEN` secret on this repo (Settings → Secrets), with the same token value already present on `ojars/latvian-spell-checker`. Without it the workflow can't create the Release or push to the tap. ## Test plan - [x] Local PKG build (`VERSION=2026.05.0 bash build-pkg.sh`) → 8.4K artifact produced, distribution.xml + welcome/conclusion/license rendered correctly. - [x] Local PKG install (`sudo installer -pkg build/LatvianApostrofs-2026.05.0.pkg -target /`) → files placed correctly with right perms + no quarantine; receipt registered. - [x] Activated layout via System Settings, switched in input picker, verified Shift+3=`#` and Alt+4=`€` in TextEdit. - [ ] Tag `v2026.05.0` on `main` after this PR merges → CI runs on macmini → release surfaces at https://git.kapteinis.lv/ojars/latvian-apostrofs/releases → cask sha bumped on tap. - [ ] `brew install --cask apostrofs` smoke test after release lands. ## Tap commit Companion commit on `ojars/homebrew-pareizrakstiba` already pushed: [`4484e80`](https://git.kapteinis.lv/ojars/homebrew-pareizrakstiba/commit/4484e80) adds `Casks/apostrofs.rb` (placeholder version+sha until first release) and restructures the tap README into a 2-cask layout with per-cask install/activate/uninstall sections. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add PKG installer + Forgejo Release pipeline + Homebrew Cask integration
All checks were successful
Test / validate (pull_request) Successful in 2s
9397a18d02
Adds three artifacts that wire latvian-apostrofs into the same
release/distribution flow as latvian-spell-checker:

- build-pkg.sh: builds a flat .pkg installer that places the keyboard
  layout files in /Library/Keyboard Layouts/ (system-wide). Includes
  welcome/conclusion/license panels, postinstall xattr quarantine
  strip, and pkgbuild+productbuild via standard macOS toolchain.
  Verified end-to-end on macOS Tahoe 26.4.1: install via
  `sudo installer -pkg ... -target /` → files land with root:wheel 755,
  no quarantine attr, byte-identical SHA-256 to repo source, pkgutil
  receipt registered.

- .forgejo/workflows/release.yml: tag-push (v*) trigger that runs on
  the macmini Forgejo runner (instance scope, promoted 2026-05-08).
  Builds PKG, computes SHA-256, creates Forgejo Release with body +
  asset upload, then bumps the apostrofs.rb cask in
  homebrew-pareizrakstiba via sed + git push.

- .gitignore: excludes build/ artifacts.

Mirrors the spell-checker's release.yml pattern but is simpler — no
clang build, no submodules, no .zip artifact (the cask consumes the
PKG directly via the `pkg` stanza).

Required after merge:
- Set FORGEJO_RELEASE_TOKEN secret on this repo (same value as on
  latvian-spell-checker).
- Tap-side cask (Casks/apostrofs.rb in homebrew-pareizrakstiba) is
  already in place with placeholder version+sha; will be bumped on
  first tag push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop FORGEJO_RELEASE_TOKEN dependency: use auto-provided GITHUB_TOKEN, manual cask bump
All checks were successful
Test / validate (pull_request) Successful in 2s
00fcf79727
Two scope-of-token changes addressing minimum-permission feedback:

1. Release creation + asset upload now uses ${{ secrets.GITHUB_TOKEN }}
   (auto-provided by Forgejo, scoped to this repo, has release-write
   permission). No manual secret setup required.

2. Cross-repo cask auto-bump on homebrew-pareizrakstiba is dropped.
   That step would have required a personal token with write access to
   the tap repo, and apostrofs releases are infrequent enough (the
   underlying keyboard layout last changed in 2013) that a manual
   one-liner is proportional. The workflow's final step now prints the
   exact sed+commit+push command for the operator to copy-paste after
   each release surfaces.

Net: zero secrets to configure. The workflow runs on `git push tag v*`
with no prior repo-settings changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author
Collaborator

Updated per review: dropped FORGEJO_RELEASE_TOKEN dependency entirely.

Two changes in commit 00fcf79:

  1. Release creation + asset upload now uses ${{ secrets.GITHUB_TOKEN }} (Forgejo auto-provides this, scoped to the calling repo with release-write permission). No manual secret setup.
  2. Cross-repo cask auto-bump on homebrew-pareizrakstiba removed. That was the only piece needing a personal token with broader scope. Apostrofs release cadence (the keyboard layout itself last changed in 2013) doesn't justify carrying a cross-repo write token; the workflow now prints the exact sed + commit + push command for the operator to run after each release.

Net: zero secrets to configure on this repo. git push tag v* is sufficient.

Updated per review: dropped `FORGEJO_RELEASE_TOKEN` dependency entirely. **Two changes** in commit `00fcf79`: 1. Release creation + asset upload now uses `${{ secrets.GITHUB_TOKEN }}` (Forgejo auto-provides this, scoped to the calling repo with release-write permission). No manual secret setup. 2. Cross-repo cask auto-bump on `homebrew-pareizrakstiba` removed. That was the only piece needing a personal token with broader scope. Apostrofs release cadence (the keyboard layout itself last changed in 2013) doesn't justify carrying a cross-repo write token; the workflow now prints the exact `sed + commit + push` command for the operator to run after each release. Net: zero secrets to configure on this repo. `git push tag v*` is sufficient.
Author
Collaborator

Self-review (operator-triggered, since auto-Teika isn't wired up here — tracked at infra-docs#120)

Reviewed: 4 commits (1 squashed), 290 lines added across 3 new files.

Verified live

  • bash build-pkg.sh with VERSION=2026.05.0 produced an 8.4K PKG in build/.
  • sudo installer -pkg build/LatvianApostrofs-2026.05.0.pkg -target / placed both files at /Library/Keyboard Layouts/ with root:wheel 755, no com.apple.quarantine, byte-identical SHA-256 to the repo source.
  • pkgutil --pkg-info lv.kapteinis.latvian-apostrofs returns the receipt.
  • TextEdit verification: Shift+3 → #, Alt+4 → . Layout works as installed.

Diff coverage

  • .gitignore: trivially correct (build/).
  • build-pkg.sh: standard pkgbuild + productbuild flow, set -euo pipefail, idempotent (rm -rf build at start), uses ditto --norsrc --noextattr --noqtn to stage payload (preserves byte-identity), postinstall script strips com.apple.quarantine. Distribution.xml has hostArchitectures="x86_64,arm64" + os-version min="10.6" (matches the layout's pre-Lion compatibility). LICENSE + welcome + conclusion text accurate to attribution rules.
  • release.yml: trigger on: push: tags: "v*", runs on macos runner. Uses ${{ secrets.GITHUB_TOKEN }} (auto-provided by Forgejo, scoped to this repo, has release-write). Final step prints the manual tap-bump command instead of cross-repo pushing.

Concerns / known limitations

  1. build-pkg.sh not in test.yml shellcheck list. test.yml only lints install.sh uninstall.sh. Filing as a small follow-up — would extend the existing shellcheck step to also cover build-pkg.sh. Not blocking — script set -euo pipefail + tested locally.
  2. First tag push is the only smoke test for release.yml. No way to dry-run a Forgejo Release without actually creating one. Plan: tag v2026.05.0 after merge, watch macmini runner; if it fails at the release-creation step, delete the failed Release + retry. Documented rollback path.
  3. Tap cask currently has placeholder 0.0.0 / zero-sha. brew install --cask apostrofs would 404 until the first release lands. Correct/desired: cask isn't useful until the artifact exists.
  4. GITHUB_TOKEN release-write assumption is unverified for Forgejo. Spell-checker uses a personal token for the same operation. If Forgejo's auto-token doesn't have release-write by default, the first tag push will fail with 403 at the POST /releases step, and we'll need to fall back to setting FORGEJO_RELEASE_TOKEN after all (~30s of UI work). Acceptable risk — reversible.

Verdict

APPROVED for merge. Will squash-merge to main and proceed to Phase 3 (tag-push test release) on operator approval. Items 1+2 are observable on first release run; item 4 is a known reversible risk.

## Self-review (operator-triggered, since auto-Teika isn't wired up here — tracked at [infra-docs#120](https://git.kapteinis.lv/ojars/infra-docs/issues/120)) **Reviewed:** 4 commits (1 squashed), 290 lines added across 3 new files. ### Verified live - `bash build-pkg.sh` with `VERSION=2026.05.0` produced an 8.4K PKG in `build/`. - `sudo installer -pkg build/LatvianApostrofs-2026.05.0.pkg -target /` placed both files at `/Library/Keyboard Layouts/` with `root:wheel 755`, no `com.apple.quarantine`, byte-identical SHA-256 to the repo source. - `pkgutil --pkg-info lv.kapteinis.latvian-apostrofs` returns the receipt. - TextEdit verification: Shift+3 → `#`, Alt+4 → `€`. Layout works as installed. ### Diff coverage - `.gitignore`: trivially correct (`build/`). - `build-pkg.sh`: standard `pkgbuild` + `productbuild` flow, `set -euo pipefail`, idempotent (`rm -rf build` at start), uses `ditto --norsrc --noextattr --noqtn` to stage payload (preserves byte-identity), postinstall script strips `com.apple.quarantine`. Distribution.xml has `hostArchitectures="x86_64,arm64"` + `os-version min="10.6"` (matches the layout's pre-Lion compatibility). LICENSE + welcome + conclusion text accurate to attribution rules. - `release.yml`: trigger `on: push: tags: "v*"`, runs on `macos` runner. Uses `${{ secrets.GITHUB_TOKEN }}` (auto-provided by Forgejo, scoped to this repo, has release-write). Final step prints the manual tap-bump command instead of cross-repo pushing. ### Concerns / known limitations 1. **`build-pkg.sh` not in `test.yml` shellcheck list.** test.yml only lints `install.sh uninstall.sh`. Filing as a small follow-up — would extend the existing `shellcheck` step to also cover `build-pkg.sh`. Not blocking — script `set -euo pipefail` + tested locally. 2. **First tag push is the only smoke test for `release.yml`.** No way to dry-run a Forgejo Release without actually creating one. Plan: tag `v2026.05.0` after merge, watch macmini runner; if it fails at the release-creation step, delete the failed Release + retry. Documented rollback path. 3. **Tap cask currently has placeholder `0.0.0` / zero-sha.** `brew install --cask apostrofs` would 404 until the first release lands. Correct/desired: cask isn't useful until the artifact exists. 4. **GITHUB_TOKEN release-write assumption is unverified for Forgejo.** Spell-checker uses a personal token for the same operation. If Forgejo's auto-token doesn't have release-write by default, the first tag push will fail with 403 at the `POST /releases` step, and we'll need to fall back to setting `FORGEJO_RELEASE_TOKEN` after all (~30s of UI work). Acceptable risk — reversible. ### Verdict **APPROVED for merge.** Will squash-merge to `main` and proceed to Phase 3 (tag-push test release) on operator approval. Items 1+2 are observable on first release run; item 4 is a known reversible risk.
claude merged commit 63920bb1b4 into main 2026-05-09 23:10:47 +03:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ojars/latvian-apostrofs!4
No description provided.