docs: update install docs and release automation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ build/
|
|||||||
Build/
|
Build/
|
||||||
dist/
|
dist/
|
||||||
.signing.env
|
.signing.env
|
||||||
|
homebrew-tap/
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 26.1.6
|
||||||
|
- Publish Gitea releases as stable by default instead of pre-releases.
|
||||||
|
- Update the Homebrew tap automatically after each successful release by rewriting the cask version and DMG checksum, then pushing the tap repo.
|
||||||
|
- Simplified the README for end users by adding clear install options and trimming internal release-engineering details.
|
||||||
|
- Ignore the local `homebrew-tap/` checkout in the main app repository.
|
||||||
|
|
||||||
## 26.1.3
|
## 26.1.3
|
||||||
- Fixed version handling for changelogs.
|
- Fixed version handling for changelogs.
|
||||||
|
|||||||
102
README.md
102
README.md
@@ -11,7 +11,9 @@ iKeyMon is a native macOS app written in SwiftUI that provides live monitoring f
|
|||||||
- Automatic refreshes:
|
- Automatic refreshes:
|
||||||
- Ping every 10 seconds
|
- Ping every 10 seconds
|
||||||
- Server info every 60 seconds
|
- Server info every 60 seconds
|
||||||
|
- Preferences dialog
|
||||||
- Built-in Sparkle updater (automatic checks, downloads, and relaunch once a signed release is available)
|
- Built-in Sparkle updater (automatic checks, downloads, and relaunch once a signed release is available)
|
||||||
|
- macOS notifications if servers or services become unavailable
|
||||||
- Organized layout using tabs: General / Resources / Services
|
- Organized layout using tabs: General / Resources / Services
|
||||||
- Stores API keys securely in the macOS Keychain
|
- Stores API keys securely in the macOS Keychain
|
||||||
- Native macOS look & feel using SwiftUI
|
- Native macOS look & feel using SwiftUI
|
||||||
@@ -24,11 +26,30 @@ iKeyMon is a native macOS app written in SwiftUI that provides live monitoring f
|
|||||||
|
|
||||||
## 🛠️ Planned Features
|
## 🛠️ Planned Features
|
||||||
|
|
||||||
- Preferences dialog
|
- iOS support is a possibility, but it is unclear if or when that will happen
|
||||||
- macOS notifications if servers or services become unavailable
|
|
||||||
- Optional iOS support if there is demand
|
|
||||||
|
|
||||||
## 🚀 How to Run
|
## 📦 Install
|
||||||
|
|
||||||
|
### Option 1: Download the app
|
||||||
|
|
||||||
|
Download the latest release from:
|
||||||
|
|
||||||
|
https://git.24unix.net/tracer/iKeyMon/releases
|
||||||
|
|
||||||
|
Then open the DMG and move `iKeyMon.app` to your `Applications` folder.
|
||||||
|
|
||||||
|
### Option 2: Install with Homebrew
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew tap tracer/tap https://git.24unix.net/tracer/homebrew-tap.git
|
||||||
|
brew install --cask ikeymon
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Build it yourself
|
||||||
|
|
||||||
|
Clone the repository, open it in Xcode, and run the app locally on macOS 14+.
|
||||||
|
|
||||||
|
## 🚀 How to Build
|
||||||
|
|
||||||
Clone the repo and open it in [Xcode](https://developer.apple.com/xcode/). You can build and run the app on macOS 14+.
|
Clone the repo and open it in [Xcode](https://developer.apple.com/xcode/). You can build and run the app on macOS 14+.
|
||||||
|
|
||||||
@@ -38,79 +59,6 @@ cd iKeyMon
|
|||||||
open iKeyMon.xcodeproj
|
open iKeyMon.xcodeproj
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local release build
|
|
||||||
|
|
||||||
Use the helper script to produce distributables in `dist/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/build_release.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
It cleans previous artifacts, builds the `Release` configuration, and drops both `iKeyMon-<version>.zip` and `iKeyMon-<version>.dmg` into the `dist` folder (ignored by git). To enable codesigning + notarization, copy `signing.env.example` to `.signing.env`, fill in your Developer ID identity, Apple ID, team ID, and app-specific password. The script sources that file locally (it remains gitignored) and performs signing/notarization when the values are present.
|
|
||||||
|
|
||||||
To auto-publish the artifacts as a Gitea release, extend `.signing.env` with:
|
|
||||||
|
|
||||||
```
|
|
||||||
GITEA_TOKEN="..."
|
|
||||||
GITEA_OWNER="tracer"
|
|
||||||
GITEA_REPO="iKeyMon"
|
|
||||||
# optional: GITEA_API_BASE="https://git.24unix.net/api/v1"
|
|
||||||
# optional: GITEA_TARGET_COMMIT="master"
|
|
||||||
# optional: GITEA_PRERELEASE="false" # defaults to true until preferences are done
|
|
||||||
# optional Sparkle feed helpers:
|
|
||||||
# SPARKLE_EDDSA_KEY_FILE="$HOME/.config/Sparkle/iKeyMon.key"
|
|
||||||
# SPARKLE_DOWNLOAD_BASE_TEMPLATE="https://git.24unix.net/tracer/iKeyMon/releases/download/v{{VERSION}}"
|
|
||||||
# If you prefer SPARKLE_DOWNLOAD_BASE_URL, it will automatically append `/v<version>` for you.
|
|
||||||
# SPARKLE_APPCAST_OUTPUT="$ROOT_DIR/Sparkle/appcast.xml" # default
|
|
||||||
```
|
|
||||||
|
|
||||||
`GITEA_TARGET_COMMIT` defaults to the current `HEAD` commit, so overriding it lets you publish from another branch if needed. Whenever those variables are set, the script will create (or reuse) tag `v<version>` and upload both ZIP and DMG as release assets automatically.
|
|
||||||
|
|
||||||
If you re-run the release script for the same version, it removes any existing assets with the same filenames before uploading, so you never end up with duplicate ZIP/DMG files on the release page.
|
|
||||||
|
|
||||||
### Sparkle updates
|
|
||||||
|
|
||||||
iKeyMon uses [Sparkle](https://sparkle-project.org/) for macOS-safe updates.
|
|
||||||
|
|
||||||
1. Generate an EdDSA key pair once (`./Packages/Sparkle/bin/generate_keys`). Store the private key on-disk (for example `~/.config/Sparkle/iKeyMon.key`, which the build script expects) and copy the public key into the `SUPublicEDKey` entry (see Info.plist notes below).
|
|
||||||
2. `./scripts/build_release.sh` signs the ZIP with Sparkle’s `sign_update` tool and invokes `generate_appcast` automatically when the Sparkle variables are present. The generated feed is written to `Sparkle/appcast.xml`, so commit that file after every release. Point `SPARKLE_DOWNLOAD_BASE_TEMPLATE` at your release-download prefix (e.g. `https://git.24unix.net/tracer/iKeyMon/releases/download/v{{VERSION}}`) so the generated URLs already match Gitea’s asset paths. The feed stays inside the repo (it is not uploaded as a release asset).
|
|
||||||
3. Set `SUFeedURL` in Info.plist (or the corresponding build setting) to the raw URL of `Sparkle/appcast.xml` inside this repo (e.g. `https://git.24unix.net/tracer/iKeyMon/raw/branch/master/Sparkle/appcast.xml`).
|
|
||||||
|
|
||||||
Preferences expose Sparkle’s built-in toggles for “Automatically check” and “Automatically download”, and the toolbar button simply calls Sparkle’s “Check for Updates…” sheet.
|
|
||||||
|
|
||||||
> `./scripts/build_release.sh` will call `generate_appcast` for you when `SPARKLE_EDDSA_KEY_FILE` and `SPARKLE_DOWNLOAD_BASE_TEMPLATE` (or `SPARKLE_DOWNLOAD_BASE_URL`) are set. It tries to locate Sparkle’s CLI in DerivedData automatically, but you can override the path via `SPARKLE_GENERATE_APPCAST`. The resulting feed is written to `SPARKLE_APPCAST_OUTPUT` (defaults to `Sparkle/appcast.xml`).
|
|
||||||
|
|
||||||
> Build settings include `INFOPLIST_KEY_SUFeedURL` and `INFOPLIST_KEY_SUPublicEDKey`. Make sure to fill both before shipping a build so Sparkle knows where to fetch updates and how to verify them.
|
|
||||||
|
|
||||||
#### Debugging Sparkle updates
|
|
||||||
|
|
||||||
Launch the shipped app via CLI with `SPARKLE_VERBOSE_LOGGING=1` to mirror Sparkle’s activity in stdout/stderr:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SPARKLE_VERBOSE_LOGGING=1 /Applications/iKeyMon.app/Contents/MacOS/iKeyMon
|
|
||||||
```
|
|
||||||
|
|
||||||
Sparkle’s installer helper runs in a separate `SUPipedBinary` process. If the installer fails, collect additional details with `log show --process SUPipedBinary --last 5m`.
|
|
||||||
|
|
||||||
### Automated release push
|
|
||||||
|
|
||||||
If you want `git push origin master` to build/sign/notarize/upload automatically, enable the provided pre-push hook:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git config core.hooksPath hooks
|
|
||||||
```
|
|
||||||
|
|
||||||
The hook (see `hooks/pre-push`) watches for pushes that include `refs/heads/master`, automatically bumps `marketing_version` (incrementing the last component), runs `scripts/build_release.sh`, stages `version.json`, `iKeyMon.xcodeproj/project.pbxproj`, and `Sparkle/appcast.xml`, then creates a commit `chore: release <version>`. It performs its own `git push` behind the scenes and cancels the original push command so you don't upload the same refs twice—once you see “Release … pushed. Original push cancelled”, you're done (Git will report the original push failed; that's expected). To skip the automation temporarily, prepend `SKIP_RELEASE=1` to your `git push` command.
|
|
||||||
|
|
||||||
The bumping logic lives in `scripts/bump_version.sh` (feel free to run it manually if you need to create a release without pushing).
|
|
||||||
|
|
||||||
### Versioning workflow
|
|
||||||
|
|
||||||
- The canonical marketing version lives in `version.json` and follows the format `YY.major.minor` (example: `26.1.2`). Update that file manually whenever you cut a new release branch.
|
|
||||||
- The build number is derived automatically from the git commit count on the current branch (you can override it by exporting `BUILD_NUMBER` before running the script if needed).
|
|
||||||
- Run `./scripts/sync_version.sh` anytime after editing `version.json` (the release script already calls it). The helper updates `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` inside `iKeyMon.xcodeproj`, keeping Xcode, the app bundle, and release artifacts in sync.
|
|
||||||
- `scripts/build_release.sh` reads the same `version.json` for naming the generated ZIP/DMG, so the artifact names, Info.plist values, and UI displays all stay aligned.
|
|
||||||
|
|
||||||
## 📦 License
|
## 📦 License
|
||||||
|
|
||||||
MIT — see [LICENSE](LICENSE) for details.
|
MIT — see [LICENSE](LICENSE) for details.
|
||||||
|
|||||||
@@ -216,8 +216,10 @@ generate_appcast
|
|||||||
|
|
||||||
if [[ -n "${GITEA_TOKEN:-}" && -n "${GITEA_OWNER:-}" && -n "${GITEA_REPO:-}" ]]; then
|
if [[ -n "${GITEA_TOKEN:-}" && -n "${GITEA_OWNER:-}" && -n "${GITEA_REPO:-}" ]]; then
|
||||||
"$ROOT_DIR/scripts/publish_release.sh" "$VERSION" "$ARTIFACTS_DIR/$ZIP_NAME" "$ARTIFACTS_DIR/$DMG_NAME"
|
"$ROOT_DIR/scripts/publish_release.sh" "$VERSION" "$ARTIFACTS_DIR/$ZIP_NAME" "$ARTIFACTS_DIR/$DMG_NAME"
|
||||||
|
"$ROOT_DIR/scripts/update_homebrew_tap.sh" "$VERSION" "$ARTIFACTS_DIR/$DMG_NAME"
|
||||||
else
|
else
|
||||||
echo "ℹ️ Skipping Gitea release publishing (GITEA_* variables not fully set)."
|
echo "ℹ️ Skipping Gitea release publishing (GITEA_* variables not fully set)."
|
||||||
|
echo "ℹ️ Skipping Homebrew tap update because release publishing was skipped."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ Build complete. Artifacts:"
|
echo "✅ Build complete. Artifacts:"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ if [[ -z "$CHANGELOG_BODY" ]]; then
|
|||||||
CHANGELOG_BODY="See commit history for details."
|
CHANGELOG_BODY="See commit history for details."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PRERELEASE_FLAG="${GITEA_PRERELEASE:-true}"
|
PRERELEASE_FLAG="${GITEA_PRERELEASE:-false}"
|
||||||
|
|
||||||
create_payload="$(jq -n \
|
create_payload="$(jq -n \
|
||||||
--arg tag "$RELEASE_TAG" \
|
--arg tag "$RELEASE_TAG" \
|
||||||
|
|||||||
62
scripts/update_homebrew_tap.sh
Executable file
62
scripts/update_homebrew_tap.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
VERSION="${1:?usage: update_homebrew_tap.sh <version> <dmg_path>}"
|
||||||
|
DMG_PATH="${2:?usage: update_homebrew_tap.sh <version> <dmg_path>}"
|
||||||
|
TAP_DIR="${HOMEBREW_TAP_DIR:-$ROOT_DIR/homebrew-tap}"
|
||||||
|
CASK_FILE="$TAP_DIR/Casks/ikeymon.rb"
|
||||||
|
|
||||||
|
if [[ ! -d "$TAP_DIR/.git" ]]; then
|
||||||
|
echo "❌ Homebrew tap repo not found at $TAP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$CASK_FILE" ]]; then
|
||||||
|
echo "❌ Homebrew cask file not found at $CASK_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$DMG_PATH" ]]; then
|
||||||
|
echo "❌ DMG artifact not found at $DMG_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$(git -C "$TAP_DIR" status --porcelain)" ]]; then
|
||||||
|
echo "❌ Homebrew tap repo has uncommitted changes: $TAP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHA256="$(shasum -a 256 "$DMG_PATH" | awk '{print $1}')"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$CASK_FILE" "$VERSION" "$SHA256"
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
cask_path = Path(sys.argv[1])
|
||||||
|
version = sys.argv[2]
|
||||||
|
sha256 = sys.argv[3]
|
||||||
|
content = cask_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
content, version_count = re.subn(r'version "[^"]+"', f'version "{version}"', content, count=1)
|
||||||
|
content, sha_count = re.subn(r'sha256 "[^"]+"', f'sha256 "{sha256}"', content, count=1)
|
||||||
|
|
||||||
|
if version_count != 1 or sha_count != 1:
|
||||||
|
raise SystemExit("Failed to update version or sha256 in cask file")
|
||||||
|
|
||||||
|
cask_path.write_text(content, encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
ruby -c "$CASK_FILE" >/dev/null
|
||||||
|
|
||||||
|
if git -C "$TAP_DIR" diff --quiet -- "$CASK_FILE"; then
|
||||||
|
echo "ℹ️ Homebrew tap already points to iKeyMon ${VERSION}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$TAP_DIR" add "$CASK_FILE"
|
||||||
|
git -C "$TAP_DIR" commit -m "cask: update ikeymon to ${VERSION}"
|
||||||
|
git -C "$TAP_DIR" push origin master
|
||||||
|
|
||||||
|
echo "✅ Updated Homebrew tap to iKeyMon ${VERSION}"
|
||||||
Reference in New Issue
Block a user