Skip to content

Commit 2750e7b

Browse files
committed
Fix macOS "damaged" DMG and add delete-model confirmation (v1.2.1)
The macOS build has shipped as "damaged and can't be opened" since v1.0.0 because electron-builder was producing a hardened-runtime binary without a matching Apple Developer signature — Gatekeeper treats that combination as corrupt with no right-click bypass. Drops hardenedRuntime + entitlements and sets identity: null so the build falls back to the normal "unidentified developer" prompt users can bypass, and documents the xattr -cr fallback in the README for quarantine-blocked downloads. Also adds a confirm modal before deleting a Whisper model from Settings so a click on the trash icon no longer wipes the file immediately; the modal calls out when the target is the currently active model.
1 parent 37adcc7 commit 2750e7b

4 files changed

Lines changed: 58 additions & 7 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ Download the latest installer from the [Releases page](https://github.com/Kenshi
4343
| Windows 10/11 (x64) | `Echo-Setup-X.Y.Z.exe` |
4444
| macOS (Apple Silicon) | `Echo-X.Y.Z-arm64.dmg` |
4545

46-
> **macOS note:** the build is unsigned for now. Right-click the app → **Open** on first launch to bypass Gatekeeper.
46+
> **macOS note:** the build is unsigned (no paid Apple Developer ID). After dragging **Echo** to Applications, right-click the app → **Open** on first launch to bypass Gatekeeper.
47+
>
48+
> If macOS still refuses with *"Echo is damaged and can't be opened"*, it's the quarantine flag from the download. Clear it with:
49+
>
50+
> ```bash
51+
> xattr -cr /Applications/Echo.app && open /Applications/Echo.app
52+
> ```
4753
4854
On first launch, Echo will auto-download the selected whisper model (`base` by default, ~142 MB) with a progress indicator. If you have an NVIDIA GPU and select the **CUDA** backend, it will also download the CUDA-enabled binary.
4955

electron/electron-builder.config.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,14 @@ module.exports = {
6969
{ target: "dmg", arch: ["arm64"] },
7070
{ target: "dmg", arch: ["x64"] },
7171
],
72-
entitlements: "build-resources/entitlements.mac.plist",
73-
entitlementsInherit: "build-resources/entitlements.mac.plist",
74-
hardenedRuntime: true,
72+
// We don't have an Apple Developer ID, so we ship unsigned.
73+
// `hardenedRuntime: true` with a missing/invalid signature makes Gatekeeper
74+
// report the app as "damaged" with no right-click bypass — worse UX than
75+
// shipping plain unsigned. `identity: null` tells electron-builder to skip
76+
// signing entirely so Gatekeeper falls back to the normal "unidentified
77+
// developer" prompt that users can bypass with right-click → Open (and, in
78+
// the worst case, by clearing the quarantine xattr — see README).
79+
identity: null,
7580
},
7681
dmg: {
7782
contents: [

electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "echo",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"description": "Local push-to-talk voice-to-text",
55
"main": "dist/main/main/index.js",
66
"author": "Kenshiin13",

electron/src/renderer/settings/App.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
22
import {
33
Stack, Group, Text, Button, Select, Switch,
44
Badge, Loader, Alert, Box, ScrollArea, ActionIcon,
5-
PasswordInput, HoverCard, Anchor,
5+
PasswordInput, HoverCard, Anchor, Modal,
66
} from "@mantine/core";
77
import {
88
IconAlertCircle, IconCheck, IconRefresh,
@@ -155,6 +155,7 @@ export function App() {
155155
const [needsRestart, setNeedsRestart] = useState(false);
156156
const [downloadedModels, setDownloadedModels] = useState<string[]>([]);
157157
const [deletingModel, setDeletingModel] = useState<string | null>(null);
158+
const [confirmDelete, setConfirmDelete] = useState<string | null>(null);
158159
const didFireReady = useRef(false);
159160

160161
useEffect(() => {
@@ -171,6 +172,7 @@ export function App() {
171172

172173
async function handleDeleteModel(modelSize: string) {
173174
setDeletingModel(modelSize);
175+
setConfirmDelete(null);
174176
await window.echo.deleteModel(modelSize);
175177
const models = await window.echo.listModels();
176178
setDownloadedModels(models);
@@ -332,7 +334,7 @@ export function App() {
332334
radius="sm"
333335
loading={deletingModel === m}
334336
disabled={deletingModel !== null}
335-
onClick={() => handleDeleteModel(m)}
337+
onClick={() => setConfirmDelete(m)}
336338
aria-label={`Delete ${m} model`}
337339
>
338340
<IconTrash size={13} />
@@ -575,6 +577,44 @@ export function App() {
575577
</Group>
576578
</Stack>
577579
</Box>
580+
581+
<Modal
582+
opened={confirmDelete !== null}
583+
onClose={() => setConfirmDelete(null)}
584+
title="Delete model?"
585+
centered
586+
radius="md"
587+
overlayProps={{ backgroundOpacity: 0.5, blur: 3 }}
588+
>
589+
<Stack gap="md">
590+
<Text size="sm">
591+
Delete{" "}
592+
<Text span fw={600} c="echo.4">
593+
{confirmDelete ? (MODEL_LABELS[confirmDelete] ?? confirmDelete) : ""}
594+
</Text>
595+
? You can re-download it any time, but it'll need to be fetched again.
596+
</Text>
597+
{confirmDelete === config?.modelSize && (
598+
<Alert icon={<IconAlertCircle size={14} />} color="yellow" variant="light" radius="md" p="xs">
599+
<Text size="xs">
600+
This is your <Text span fw={600}>currently active</Text> model. Transcription will be unavailable until you pick another model or re-download.
601+
</Text>
602+
</Alert>
603+
)}
604+
<Group justify="flex-end" gap="sm">
605+
<Button variant="default" onClick={() => setConfirmDelete(null)}>
606+
Cancel
607+
</Button>
608+
<Button
609+
color="red"
610+
leftSection={<IconTrash size={14} />}
611+
onClick={() => confirmDelete && handleDeleteModel(confirmDelete)}
612+
>
613+
Delete
614+
</Button>
615+
</Group>
616+
</Stack>
617+
</Modal>
578618
</Box>
579619
);
580620
}

0 commit comments

Comments
 (0)