forked from viewit/OrcaSlicer-KX
Compare commits
3 Commits
stable
...
macos-note
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f2199dd82 | |||
| 406681b94b | |||
| f1948f429c |
@ -75,8 +75,10 @@ version `2.4.1-kx1`).
|
||||
with exit 0 and `[202/202] Completed`.
|
||||
- The signature is **ad-hoc / linker-signed**. Strict `codesign -v` and `spctl`
|
||||
complain ("code has no resources but signature indicates they must be present").
|
||||
This is **harmless**: a local build is **not quarantined**, so it opens with a
|
||||
double-click, no Gatekeeper block.
|
||||
This is **harmless** for launching: a local build is **not quarantined**, so it
|
||||
opens with a double-click, no Gatekeeper block. It does, however, make macOS
|
||||
re-ask for folder and keychain permissions on every rebuild — see
|
||||
*Stop the repeated folder / keychain permission prompts* below to fix that.
|
||||
|
||||
---
|
||||
|
||||
@ -87,10 +89,41 @@ it, copy under a different name:
|
||||
|
||||
```bash
|
||||
ditto build/arm64/OrcaSlicer/OrcaSlicer.app /Applications/OrcaSlicer-KX.app
|
||||
./scripts/macos_codesign_local.sh # stable local signature — stops repeated permission prompts (see below)
|
||||
open /Applications/OrcaSlicer-KX.app # verify it boots
|
||||
```
|
||||
|
||||
(`ditto` preserves the bundle + ad-hoc signature better than `cp -R`.)
|
||||
(`ditto` preserves the bundle + signature better than `cp -R`.)
|
||||
|
||||
---
|
||||
|
||||
## Stop the repeated folder / keychain permission prompts (recommended)
|
||||
|
||||
Because a local build is only **ad-hoc / linker-signed**, macOS identifies the app
|
||||
by its content hash (`cdhash`). That hash changes on every rebuild, so macOS treats
|
||||
each build as a brand-new app and re-asks for **folder access (TCC)** and the
|
||||
**keychain password** every time — the grants never stick.
|
||||
|
||||
Fix it by signing the app with a **stable, machine-local, self-signed** identity, so
|
||||
its Designated Requirement becomes certificate-based (identical across rebuilds):
|
||||
|
||||
```bash
|
||||
./scripts/macos_codesign_local.sh # run AFTER copying the app to /Applications
|
||||
```
|
||||
|
||||
On first run the script creates the identity `OrcaSlicer Local Codesign` in your
|
||||
login keychain (no admin password needed) and re-signs the installed app + local
|
||||
build output. Re-run it after every build/redeploy — it reuses the same identity.
|
||||
The Designated Requirement changes from `cdhash H"…"` to
|
||||
`identifier "com.orcaslicer.OrcaSlicer" and certificate leaf = H"…"`, which is stable.
|
||||
|
||||
On the **next launch only**, allow the folder prompts and click **"Always Allow"**
|
||||
on the keychain prompt once; macOS then remembers them across future rebuilds.
|
||||
|
||||
**Per-machine — share the script, never the cert.** The certificate and its private
|
||||
key are generated locally and live only in your login keychain; they are never
|
||||
written to the repo. Each developer runs the script once on their own Mac to create
|
||||
their own local identity. Commit and share the *script*, not the certificate.
|
||||
|
||||
---
|
||||
|
||||
|
||||
128
scripts/macos_codesign_local.sh
Executable file
128
scripts/macos_codesign_local.sh
Executable file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# macos_codesign_local.sh — stop macOS from re-prompting for folder (TCC) and
|
||||
# Keychain access on every launch/rebuild of a locally-built OrcaSlicer.
|
||||
#
|
||||
# WHY THIS IS NEEDED
|
||||
# On Apple Silicon the linker automatically applies an *ad-hoc* code signature
|
||||
# to every binary. macOS then identifies the app by the exact hash (cdhash) of
|
||||
# its contents, so its "Designated Requirement" looks like:
|
||||
# designated => cdhash H"...."
|
||||
# macOS binds TCC (folder access) grants and Keychain ACLs to that requirement.
|
||||
# Because the cdhash changes on every rebuild / redeploy, macOS treats each new
|
||||
# build as a brand-new unknown app and asks again for folder access and the
|
||||
# keychain password. Permissions never persist.
|
||||
#
|
||||
# WHAT THIS DOES
|
||||
# Signs the app with a STABLE, self-signed, machine-local code-signing
|
||||
# certificate. The Designated Requirement then becomes certificate-based:
|
||||
# designated => identifier "com.orcaslicer.OrcaSlicer" and certificate leaf = H"...."
|
||||
# which is identical across every rebuild. Grant folder access + click
|
||||
# "Always Allow" in the keychain ONCE and they persist forever after.
|
||||
#
|
||||
# SHARING / PRIVACY
|
||||
# This script is safe to commit and share (e.g. via Gitea). It is per-machine:
|
||||
# the certificate and its PRIVATE KEY are generated locally and live only in
|
||||
# YOUR login keychain — they are never written to the repo. Each developer runs
|
||||
# this once on their own Mac to create their own local identity. Do NOT export
|
||||
# or share the certificate/private key; share the script, not the cert.
|
||||
#
|
||||
# USAGE
|
||||
# scripts/macos_codesign_local.sh [APP_BUNDLE ...]
|
||||
# With no arguments it signs the installed app and the local build output if
|
||||
# they exist. Run it as the LAST step, AFTER copying the app to /Applications
|
||||
# (any change to the bundle after signing invalidates the signature).
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
CERT_CN="OrcaSlicer Local Codesign"
|
||||
KEYCHAIN="$HOME/Library/Keychains/login.keychain-db"
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENTITLEMENTS="$REPO_ROOT/src/dev-utils/platform/osx/entitlements.plist"
|
||||
BUNDLE_ID="com.orcaslicer.OrcaSlicer"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1) Ensure a stable local code-signing identity exists (create it if missing).
|
||||
# ---------------------------------------------------------------------------
|
||||
if security find-identity -p codesigning 2>/dev/null | grep -qF "$CERT_CN"; then
|
||||
echo "✓ Local signing identity '$CERT_CN' already present — reusing it."
|
||||
else
|
||||
echo "→ Creating local self-signed code-signing identity '$CERT_CN' ..."
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
cat > "$TMP/req.cnf" <<EOF
|
||||
[req]
|
||||
distinguished_name = dn
|
||||
x509_extensions = v3
|
||||
prompt = no
|
||||
[dn]
|
||||
CN = $CERT_CN
|
||||
[v3]
|
||||
basicConstraints = critical,CA:FALSE
|
||||
keyUsage = critical,digitalSignature
|
||||
extendedKeyUsage = critical,codeSigning
|
||||
EOF
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \
|
||||
-keyout "$TMP/key.pem" -out "$TMP/cert.pem" -config "$TMP/req.cnf" >/dev/null 2>&1
|
||||
|
||||
# Import key + cert separately (avoids PKCS#12 algorithm mismatches between
|
||||
# OpenSSL 3.x and the macOS `security` importer). -A lets codesign use the key
|
||||
# without a per-signature keychain prompt.
|
||||
security import "$TMP/key.pem" -k "$KEYCHAIN" -T /usr/bin/codesign -A >/dev/null
|
||||
security import "$TMP/cert.pem" -k "$KEYCHAIN" -T /usr/bin/codesign -A >/dev/null
|
||||
echo "✓ Identity created (self-signed, local only, not exported)."
|
||||
# Note: the certificate is intentionally left untrusted (CSSMERR_TP_NOT_TRUSTED).
|
||||
# codesign can still sign with it, and that is all we need for a stable
|
||||
# Designated Requirement — no admin password / trust settings required.
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2) Collect target bundles.
|
||||
# ---------------------------------------------------------------------------
|
||||
TARGETS=("$@")
|
||||
if [ ${#TARGETS[@]} -eq 0 ]; then
|
||||
for candidate in \
|
||||
"/Applications/OrcaSlicer-KX.app" \
|
||||
"/Applications/OrcaSlicer.app" \
|
||||
"$REPO_ROOT/build/arm64/OrcaSlicer/OrcaSlicer.app"; do
|
||||
[ -d "$candidate" ] && TARGETS+=("$candidate")
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ${#TARGETS[@]} -eq 0 ]; then
|
||||
echo "!! No app bundle found to sign. Pass one explicitly:"
|
||||
echo " $0 /path/to/OrcaSlicer.app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3) Sign each target with the stable identity.
|
||||
# Top-level only (no --deep): nested/third-party dylibs keep their own
|
||||
# signatures, which is fine because the app is not hardened-runtime.
|
||||
# ---------------------------------------------------------------------------
|
||||
ENT_ARG=()
|
||||
[ -f "$ENTITLEMENTS" ] && ENT_ARG=(--entitlements "$ENTITLEMENTS")
|
||||
|
||||
for app in "${TARGETS[@]}"; do
|
||||
echo "→ Signing: $app"
|
||||
codesign --force --sign "$CERT_CN" --identifier "$BUNDLE_ID" "${ENT_ARG[@]}" "$app"
|
||||
dr="$(codesign -d -r- "$app" 2>&1 | grep -i designated || true)"
|
||||
echo " $dr"
|
||||
if echo "$dr" | grep -q "certificate leaf"; then
|
||||
echo " ✓ Certificate-based requirement — permissions will now persist across rebuilds."
|
||||
else
|
||||
echo " !! Unexpected: requirement is not certificate-based."
|
||||
fi
|
||||
done
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
Done. One last time, on the NEXT launch:
|
||||
• allow the folder-access prompts, and
|
||||
• click "Always Allow" on any keychain password prompt.
|
||||
After that macOS will remember them across future rebuilds. Re-run this script
|
||||
after each build/redeploy (it reuses the same identity).
|
||||
EOF
|
||||
@ -2215,6 +2215,16 @@ GUI_App::~GUI_App()
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Belt-and-suspenders for the graceful quit path (Cmd+Q / File>Quit / red button),
|
||||
// which reaches ~GUI_App() instead of OSXOnWillTerminate(). All app cleanup has run
|
||||
// by this point (config saved, network agent deleted, Bambu plugin shut down), so we
|
||||
// hard-exit to ensure the C runtime's exit() never runs libbambu_networking's static
|
||||
// destructors — they abort during teardown on macOS. This mirrors the app-menu/Dock/
|
||||
// logout path handled in GUI_App::OSXOnWillTerminate().
|
||||
std::_Exit(0);
|
||||
#endif /* __APPLE__ */
|
||||
}
|
||||
|
||||
bool GUI_App::is_blocking_printing(MachineObject *obj_)
|
||||
@ -2593,6 +2603,30 @@ int GUI_App::OnExit()
|
||||
return wxApp::OnExit();
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
// On macOS, quitting via the app menu / Dock / logout (or a system Quit AppleEvent)
|
||||
// routes through Cocoa's -[NSApplication terminate:], which calls exit() directly
|
||||
// from inside the native run loop. That bypasses wx's normal shutdown
|
||||
// (OnExit()/~GUI_App never run) and, during __cxa_finalize_ranges, runs the C++
|
||||
// static/atexit destructors of the closed-source Bambu networking plugin
|
||||
// (libbambu_networking) while its background threads are still alive. One of those
|
||||
// destructors calls std::terminate() -> abort(), producing a SIGABRT crash report
|
||||
// *after* the user has already quit (purely a teardown crash, no data at risk).
|
||||
//
|
||||
// OSXOnWillTerminate() is the last wx hook invoked before that exit(), and only
|
||||
// runs once termination has already been approved (OSXOnShouldTerminate() returned
|
||||
// no veto). We let the base class fire wxEVT_END_SESSION for any listeners, then
|
||||
// hard-exit with _Exit() so __cxa_finalize_ranges — and thus the plugin's crashing
|
||||
// destructors — never run. Nothing is persisted on this path today either, so this
|
||||
// is behavior-preserving apart from removing the cosmetic crash.
|
||||
void GUI_App::OSXOnWillTerminate()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "GUI_App::OSXOnWillTerminate: hard-exit to avoid Bambu network plugin teardown crash";
|
||||
wxApp::OSXOnWillTerminate();
|
||||
std::_Exit(0);
|
||||
}
|
||||
#endif /* __APPLE__ */
|
||||
|
||||
class wxBoostLog : public wxLog
|
||||
{
|
||||
void DoLogText(const wxString &msg) override {
|
||||
|
||||
@ -626,6 +626,10 @@ public:
|
||||
// wxWidgets override to get an event on open files.
|
||||
void MacOpenFiles(const wxArrayString &fileNames) override;
|
||||
void MacOpenURL(const wxString& url) override;
|
||||
// Last wx hook before Cocoa's exit() on the app-menu/Dock/logout quit path.
|
||||
// Hard-exits to skip the crashing static-destructor teardown of the Bambu
|
||||
// networking plugin. See implementation in GUI_App.cpp.
|
||||
void OSXOnWillTerminate() override;
|
||||
#endif /* __APPLE */
|
||||
|
||||
Sidebar& sidebar();
|
||||
|
||||
Reference in New Issue
Block a user