# 🖥️ iKeyMon — macOS App for Monitoring KeyHelp Servers

iKeyMon is a native macOS app written in SwiftUI that provides live monitoring for your KeyHelp servers using the KeyHelp API.

## ✅ Current Features

- Displays key system information from your KeyHelp server via the API
- Shows CPU load, memory usage, swap usage, and disk usage
- Periodic ping via `/api/ping` endpoint to check if a server is reachable
- Colored status indicator for each server in the list
- Automatic refreshes:
  - Ping every 10 seconds
  - Server info every 60 seconds
- Built-in Sparkle updater (automatic checks, downloads, and relaunch once a signed release is available)
- Organized layout using tabs: General / Resources / Services
- Stores API keys securely in the macOS Keychain
- Native macOS look & feel using SwiftUI

## 📸 Screenshots

| General | Resources | Services | Edit Server |
|--------|-----------|----------|--------------|
| ![General](Screenshots/general_view.png) | ![Resources](Screenshots/resources_view.png) | ![Services](Screenshots/services_view.png) | ![Edit](Screenshots/edit_server.png) |

## 🛠️ Planned Features

- Preferences dialog
- macOS notifications if servers or services become unavailable
- Optional iOS support if there is demand

## 🚀 How to Run

Clone the repo and open it in [Xcode](https://developer.apple.com/xcode/). You can build and run the app on macOS 14+.
                                     
```
git clone https://git.24unix.net/tracer/iKeyMon
cd iKeyMon
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

MIT — see [LICENSE](LICENSE) for details.

> You're free to build and use this app however you want, but please don't upload an unchanged version to the App Store under the same name.
> Once it's stable, I may release it on the App Store for a small fee (1–2€) to cover my Apple Developer account.

## 💬 Feedback

Join the discussion (in German) here:
🔗 https://community.keyhelp.de/viewtopic.php?t=13851
