Repository navigation
Ship an unsigned preview Nocturne.app and DMG - #67
Conversation
The standalone app is one Nocturne.app: the UI binary and the engine binaries share Contents/MacOS. On an interactive launch against a loopback host the UI now ensures a local engine through EngineLauncher: it reuses one that already answers on 127.0.0.1:7400, otherwise spawns the nocturne-supervisor beside it, and on quit stops only the engine it started. It finds the supervisor by the running executable's bundle path, so a dev build with separate bundles (no supervisor next to the UI) is a no-op. The UI stays a loopback-only protocol client otherwise; the show machine's LaunchAgent path is untouched. tests/ui/engine_launcher_test.cpp covers reuse, spawn-once, stop-only-ours and a failed spawn. Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
tools/make-bundle.sh assembles one Nocturne.app from the built UI and engine bundles: the engine binaries move in beside the UI binary, the placeholder icon and the merged Info.plist (bundle id com.azylume.nocturne, version from the project, LSMinimumSystemVersion 14.0, audio usage strings) go in, then it ad-hoc signs every nested binary and the bundle inside-out with the hardened runtime and tools/nocturne.entitlements (audio input, network client and server), so Apple Silicon will run the arm64 slice. It verifies with --deep --strict and records that spctl rejects it, since this build is unsigned to Gatekeeper on purpose (#32 is the signed, notarised one). tools/make-dmg.sh packs that app into Nocturne-<version>-unsigned.dmg with an Applications symlink and a FIRST LAUNCH.txt, and writes a SHA-256 file. The placeholder icon is original art (a crescent moon over the wordmark); assets/icon/make-icns.sh rebuilds the .icns from assets/icon/nocturne.svg. Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
A v* tag (or a manual run) now builds the universal bundle, assembles the standalone Nocturne.app, makes the DMG and checksum, and attaches them to a DRAFT GitHub Release (release.yml). It is tag- and dispatch-only, never a required check on pull requests; CI stays the gate. README gains a Download section; docs/install.md explains that this is an unsigned preview, how to open it on macOS 15+ (Open Anyway) and macOS 14 (right-click > Open), the xattr quarantine alternative and what it does, how to verify the checksum, and that signed and notarised comes later (#32). docs/reference.md and ADR 0015 record the standalone bundle and that the UI starts its own loopback engine; ADR 0015 amends ADR 0010 for this preview only. Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83c0451555
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!fs::exists(supervisor, ec)) | ||
| return 0; // Not a single-bundle build (e.g. a dev build with separate bundles); do not spawn. | ||
| const std::string path = supervisor.string(); | ||
| char* argv[] = {const_cast<char*>(path.c_str()), nullptr}; |
There was a problem hiding this comment.
Pass the selected port to the spawned supervisor
When the UI is started with the supported --port N option and nothing is listening on that local port, this invocation starts the supervisor without arguments, so it launches the engine on its default port 7400 while the session connects to N. The standalone UI stays disconnected and may start an unrelated engine on 7400; pass the requested port through as supervisor engine arguments.
Useful? React with 👍 / 👎.
| // A dev build with separate bundles has no supervisor next to the UI, so this is a no-op there. | ||
| if (interactive && isLoopbackHost(host)) { | ||
| launcher_ = std::make_unique<azl::ui::EngineLauncher>(azl::ui::defaultEngineHooks(uint16_t(port))); | ||
| launcher_->ensureRunning(); |
There was a problem hiding this comment.
Keep the shared engine alive for other UI instances
When a second interactive UI launches while the first one's local engine is answering, this launcher reuses it without recording ownership. If the first UI then quits, its shutdown terminates the supervisor and engine even though the second UI is still connected; that UI retries the dead connection but never calls ensureRunning() again. Since Application::moreThanOneInstanceAllowed() permits this case, coordinate engine lifetime across instances or have surviving clients relaunch it.
Useful? React with 👍 / 👎.
CMakeLists defaults to RelWithDebInfo, so the release job now configures with -DCMAKE_BUILD_TYPE=Release. The two third-party actions (softprops/action-gh-release, hendrikmuhs/ccache-action) are pinned to a commit SHA, since this job has contents: write and a moved tag must not change what runs. Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
Review follow-ups for the standalone preview: - The UI-spawned engine now listens on 127.0.0.1 alone (engine --loopback, bound through Network.framework's local endpoint; supervisor --desk-loopback), so a downloaded preview never exposes its control port or venue desk on the venue or cafe network. Remote control stays the explicit ctl/pairing path. - The launcher passes the UI's --port through to the engine, so a non-default port reaches the engine the UI then connects to, not a stray one on 7400. - EngineLauncher.ensureRunning() is idempotent and probe-guarded: it never spawns a second engine while one is up, and a UI that reused another instance's engine adopts and restarts it if that instance quits and stops it (EngineKeepalive, after ~10 s disconnected). tests/ui/engine_launcher_test.cpp covers the adopt case. - The UI shows "connecting…" for a few seconds while a freshly spawned engine comes up instead of flashing its connection errno. - docs/install.md documents stopping a leftover engine (pkill nocturne-supervisor). Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
Change of plan for the preview launcher (#67): on a machine where the UI runs, the UI no longer spawns nocturne-supervisor. It spawns the nocturne engine directly (--loopback --port) and is the watchdog itself: it restarts an engine that dies at most twice in 60 s, then gives up and shows the reason in the transport bar. A heartbeat pipe (NOCTURNE_HEARTBEAT_FD, as the supervisor uses) ties the engine's life to the UI's, so quitting the UI, even a force-quit, stops the engine it started; the engine is not given NOCTURNE_SUPERVISED, so the UI is its only watchdog. It still reuses an engine already on 127.0.0.1 and never stops one it did not start. The supervisor stays the show-machine path (autostart.sh / the setup script, #66), never started by the UI. The restart budget, reuse and adopt logic are hooks-based and unit-tested without a live engine (tests/ui/engine_launcher_test.cpp). The supervisor is now stoppable on demand as well as by SIGTERM: a new {"op":"stop"} machine action (nocturne-ctl machine stop) makes the engine quit with a stop exit code (restart::kStop), and the supervisor then stops the engine and exits without restarting it. Stop is refused under the Show lock and is a no-op on an engine with no supervisor (a UI-spawned one). MachineActions gains the Stop callback; tests/machine_actions_test.cpp covers reply, the no-supervisor error and the lock refusal. ADR 0015, docs/reference.md and docs/install.md updated to match. Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
The usage-text golden test (tests/engine_config_test.cpp) compares kEngineUsage byte for byte; the earlier --loopback flag added two lines that CI's engine-0 shard caught. Regenerated from nocturne --help; the only change is the --loopback entry. Signed-off-by: Okonai <6979365+Okonai@users.noreply.github.com>
Ships a downloadable unsigned preview of Nocturne as one standalone
Nocturne.app+ DMG. No Apple Developer ID yet; the signed/notarized DMG stays tracked in #32.What this does
Nocturne.app: the JUCE UI binary and the engine binaries (nocturne,nocturne-supervisor,nocturne-ctl,nocturne-desk) share oneContents/MacOS. On an interactive launch against a loopback host the UI ensures a local engine via a newEngineLauncher: it reuses one already answering on127.0.0.1:7400, otherwise spawns the bundlednocturne-supervisorbeside it (found by bundle path, not the build dir), and on quit stops only the engine it started. The UI stays a loopback-only protocol client otherwise — no new listeners; a loopback client needs no pairing token, so the trust model is untouched. The show machine's LaunchAgent path (tools/autostart.sh) is unchanged; a dev build with two separate bundles is a no-op.tools/make-bundle.shassembles the bundle and signs every nested binary then the bundle inside-out (codesign --force --options runtime --timestamp=none -s -) withtools/nocturne.entitlements(audio input, network client + server).codesign --verify --deep --strictpasses;spctl -a -vvrejects it on purpose (not notarized).assets/icon/as SVG → 1024 PNG →.icns(make-icns.sh), wired into the merged Info.plist withCFBundleIdentifier,CFBundleShortVersionString(from the CMake project version),LSMinimumSystemVersion.tools/make-dmg.sh→Nocturne-<version>-unsigned.dmg(app + Applications symlink +FIRST LAUNCH.txt) and a.sha256..github/workflows/release.ymlbuilds the universal bundle on av*tag, makes the DMG and attaches it to a draft release. Tag/dispatch only — never a required PR check.docs/install.md(unsigned preview, open on macOS 15+ via Open Anyway and macOS 14 via right-click → Open, thexattr -dr com.apple.quarantinealternative and what it does, checksum verify, signed/notarized later Release workflow: signed, notarized, stapled universal DMG on a tag #32). ADR 0015 records the decision and amends ADR 0010 for this preview only.Tests
tests/ui/engine_launcher_test.cpp: reuse, spawn-once, stop-only-ours, failed spawn (4 cases).150 passed, 0 failed.lipo -archs:x86_64 arm64), entitlements + hardened runtime confirmed, bundled engine runs offscreen and servesws://…:7400.Tested on
docs/install.mdfor the exact steps.Ask-first items (confirmed in advance)
v*tag, draft only, not required on PRs.Need help on this PR? Tag
@codesmith-botwith what you need.