🏑 index : quine-global/quintodrome.git

# Quintodrome Desktop

A [Tauri](https://tauri.app) desktop wrapper that hosts [Navidrome](https://www.navidrome.org)
as its own web app.

On launch the app:

1. Checks whether a Navidrome server is already listening on `127.0.0.1:4533`
   (via its `/ping` health endpoint). If so, it reuses it as-is.
2. Otherwise it spawns the bundled `navidrome` binary as a **separate sidecar
   process**, pointed at the OS app-data/music directories, and waits for it to
   become ready.
3. Loads the Navidrome UI in the content webview, below a thin toolbar with
   back/forward/reload buttons and an address bar.
4. On exit, sends `SIGTERM` to the spawned server so it shuts down gracefully
   (falls back to a hard kill after 3s).

On a **fresh install** (no existing Navidrome data) the app also auto-creates
the initial admin user (`admin` / `admin`) behind the scenes, and auto-logs-in
as that user on startup β€” you skip both Navidrome's "create an admin user"
first-run screen and the login page. Change the password in Navidrome settings
afterwards; override it via `QUINTODROME_ADMIN_PASSWORD`.

## How to launch

From the repo root:

```sh
# 0. one-time: make sure Node >= 24 is active (already pinned in .nvmrc)
nodenv local 24.15.0        # or `nvm use` / ensure `node --version` is v24+

# 1. build the Navidrome server + stage it as the sidecar
./desktop/scripts/build.sh --skip-tauri

# 2. launch the app
cd desktop/src-tauri && cargo run
```

A window opens on the splash screen, the app starts Navidrome (or reuses a
running one), then loads the UI. Navidrome is reachable at
<http://127.0.0.1:4533> for the first-run admin setup.

To build a standalone `.app` instead:

```sh
./desktop/scripts/build.sh
# β†’ desktop/src-tauri/target/release/bundle/macos/Quintodrome.app
```

## Layout

```
desktop/
β”œβ”€β”€ src/                 # toolbar.html (nav bar) + index.html (loading splash)
β”œβ”€β”€ src-tauri/
β”‚   β”œβ”€β”€ src/             # Rust: sidecar spawn, health-check, lifecycle, toolbar
β”‚   β”œβ”€β”€ binaries/        # navidrome-<target-triple> sidecar (built, not committed)
β”‚   β”œβ”€β”€ icons/           # App icons
β”‚   β”œβ”€β”€ tauri.conf.json
β”‚   └── Cargo.toml
└── scripts/
    β”œβ”€β”€ build.sh         # Build navidrome + stage sidecar + tauri build
    └── gen_icon.go      # Regenerates the source icon (optional)
```

## Prerequisites

- Rust (stable) with the `aarch64-apple-darwin` / `x86_64-apple-darwin` (or
  matching host) target.
- Node.js >= 24 (the repo already pins this in `.nvmrc`; via `nodenv` you can
  run `nodenv local 24.15.0`).
- Go >= 1.26 (for building the Navidrome binary).

## Build

```sh
./desktop/scripts/build.sh
```

This builds the Navidrome server (`make build`), stages it under
`src-tauri/binaries/navidrome-<target-triple>` (with the executable bit set),
then runs `npx tauri build`. The packaged app lands in
`desktop/src-tauri/target/release/bundle/`.

To build the sidecar without running the full Tauri bundle:

```sh
./desktop/scripts/build.sh --skip-tauri
```

## Run (development)

```sh
# 1. build + stage the sidecar binary
./desktop/scripts/build.sh --skip-tauri

# 2. run the app
cd desktop/src-tauri && cargo run
```

The Rust `build.rs` copies the staged sidecar next to the dev binary
(`target/debug/navidrome`) automatically, so plain `cargo run` works β€” no
`tauri dev` CLI needed.

## Configuration

Environment variables override the defaults:

| Variable                    | Default                  | Purpose                         |
| --------------------------- | ------------------------ | ------------------------------- |
| `QUINTODROME_HOST`          | `127.0.0.1`              | Address the webview connects to |
| `QUINTODROME_PORT`          | `4533`                   | Port for the Navidrome server   |
| `QUINTODROME_MUSIC_FOLDER`  | OS music dir (e.g. `~/Music`) | Where your music lives      |
| `QUINTODROME_ADMIN_PASSWORD` | `admin`                 | Password for the auto-created admin user (fresh installs only) |
| `QUINTODROME_PUBLIC_URL`    | `http://quintodrome`     | Friendly origin shown in the address bar |

The data folder (DB, cache) is always the OS app-data directory
(e.g. `~/Library/Application Support/com.quineglobal.drome` on macOS).

> Note: if a Navidrome server is already running on the configured host/port,
> the app simply loads it and does **not** start a second instance.

## Updates (macOS)

The Mac app updates itself with [Sparkle](https://sparkle-project.org/), the
standard macOS updater, through
[tauri-plugin-sparkle-updater](https://crates.io/crates/tauri-plugin-sparkle-updater):
**Quintodrome β€Ί Check for Updates…**, plus a daily check in the background.
Sparkle shows its native "A new version is available" window and installs the
update itself (`src-tauri/src/updater.rs`).

- **Feeds:** one appcast per architecture, which the release workflow writes
  into the Homebrew tap (`appcasts/quintodrome-<arch>.xml`); the app reads them
  through git.quineglobal.com. `src-tauri/Info.plist` names the arm64 one,
  and Intel builds switch to theirs at startup.
- **Signing:** each update zip is signed with an Ed25519 key. The public half
  is `SUPublicEDKey` in `src-tauri/Info.plist`; the private half is the
  `SPARKLE_PRIVATE_KEY` Actions secret (base64 of the 32-byte seed, as Sparkle's
  `generate_keys -x` exports it). Sparkle refuses updates not signed with it.
  Lose the key and installed apps can't be updated by Sparkle any more.
- **Version:** release builds take the tag's version (`v1.2.3` β†’ `1.2.3`,
  `CFBundleShortVersionString`, what people see); `tauri.conf.json`'s `0.1.0`
  is only for local builds. Sparkle compares `CFBundleVersion` instead, and
  ignores anything after a `-` (`1.0.0-alpha.10` and `1.0.0-alpha.11` are
  the same to it), so that one is the release workflow's run number, which
  only goes up, and so is the appcast's `sparkle:version`.
- **Homebrew:** the cask says `auto_updates true`, so `brew upgrade` leaves
  the app to Sparkle (`brew upgrade --greedy` still upgrades it).
- **Building:** `scripts/download-sparkle.sh src-tauri` fetches
  Sparkle.framework and its tools (`build.sh` does it for you); they're
  git-ignored. Sparkle only runs in the packaged `.app`, not `cargo run`.