flasher: find SD cards in the built-in Mac reader, ease Gatekeeper

macOS disk discovery only asked diskutil for external disks, but the
built-in SDXC slot on Macs enumerates as internal, so the picker came
up empty for anyone using the native reader. Enumerate every physical
whole disk and decide per disk from Removable Media / Ejectable
instead, still excluding the boot disk and fixed internal drives, and
raise the size ceiling that silently hid 256GB+ cards to 2TB.

Sign the macOS bundles with the ad-hoc identity (Apple Silicon kills
completely unsigned binaries with the misleading damaged-app dialog)
and document the one-time xattr quarantine clear in the README, since
proper notarization needs a paid Apple Developer account.

Bump to 1.3.6.
This commit is contained in:
Douglas Teles
2026-07-08 11:25:54 -03:00
parent e743c63ed8
commit 213fc5723c
5 changed files with 45 additions and 12 deletions
+8
View File
@@ -32,6 +32,14 @@ Cross-platform desktop app for flashing [Arch R](https://github.com/archr-linux/
| Linux | glibc 2.31+, `webkit2gtk-4.1`, `gtk-3`, `libayatana-appindicator3` |
| macOS | macOS 10.15+, Apple Silicon |
> **macOS says the app "is damaged and can't be opened".** The app is not damaged: it is not notarized with Apple (that requires a paid Apple Developer account), so Gatekeeper blocks anything downloaded from a browser. After installing, clear the quarantine flag once:
>
> ```bash
> xattr -cr "/Applications/Arch R Flasher.app"
> ```
>
> Then open it normally.
> **Windows 7 is not supported.** Both the GUI runtime (Tauri 2 / Microsoft Edge WebView2) and the privileged flash script (PowerShell `Storage` module — `Clear-Disk`, `Get-Partition`, `Update-Disk`) require Windows 8+. Even with the bundled `bcryptprimitives.dll` shim that fixes Rust's `ProcessPrng` import, the WebView and the flash script still fail on Windows 7. Use Linux, macOS, or upgrade to Windows 10/11.
## Download
+1 -1
View File
@@ -58,7 +58,7 @@ dependencies = [
[[package]]
name = "archr-flasher"
version = "1.3.5"
version = "1.3.6"
dependencies = [
"cc",
"flate2",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "archr-flasher"
version = "1.3.5"
version = "1.3.6"
description = "Arch R SD Card Flasher"
authors = ["Arch R Linux"]
edition = "2024"
+33 -8
View File
@@ -147,10 +147,13 @@ pub fn list_removable_disks() -> Vec<DiskInfo> {
let mut disks = Vec::new();
// Use plain text output (not -plist XML) to list external disks.
// Output format: "/dev/disk2 (external, physical):" as header lines.
// Enumerate every physical whole disk. `diskutil list external` alone
// misses the built-in SDXC slot on Macs (it enumerates as
// "internal, physical"), which made the picker come up empty for
// anyone using the native card reader. Removability is decided per
// disk from `diskutil info` below instead.
let output = Command::new("diskutil")
.args(["list", "external"])
.args(["list", "physical"])
.output();
let output = match output {
@@ -163,15 +166,15 @@ pub fn list_removable_disks() -> Vec<DiskInfo> {
for line in stdout.lines() {
let line = line.trim();
// Match lines like: "/dev/disk2 (external, physical):"
if line.starts_with("/dev/disk") && line.contains("external") {
// Header lines look like: "/dev/disk4 (external, physical):"
if line.starts_with("/dev/disk") && line.contains("physical") {
if let Some(dev) = line.split_whitespace().next() {
device_names.push(dev.to_string());
}
}
}
// Exclude boot disk (safety: never flash the system disk)
// Exclude the boot disk (safety: never flash the system disk)
if let Ok(boot_info) = Command::new("diskutil").args(["info", "/"]).output() {
let info_str = String::from_utf8_lossy(&boot_info.stdout);
for line in info_str.lines() {
@@ -186,7 +189,6 @@ pub fn list_removable_disks() -> Vec<DiskInfo> {
}
for device in device_names {
// Get detailed info for each disk
let info_output = Command::new("diskutil")
.args(["info", &device])
.output();
@@ -195,6 +197,8 @@ pub fn list_removable_disks() -> Vec<DiskInfo> {
let info_str = String::from_utf8_lossy(&info.stdout);
let mut size_bytes: u64 = 0;
let mut name = "SD Card".to_string();
let mut removable = false;
let mut internal = false;
for info_line in info_str.lines() {
if info_line.contains("Disk Size:") {
@@ -210,9 +214,30 @@ pub fn list_removable_disks() -> Vec<DiskInfo> {
if info_line.contains("Device / Media Name:") {
name = info_line.split(':').nth(1).unwrap_or("SD Card").trim().to_string();
}
// SD cards report "Removable Media: Removable" (built-in
// reader) and/or "Ejectable: Yes" (USB readers). Fixed
// internal drives report neither.
if info_line.contains("Removable Media:") && info_line.contains("Removable") {
removable = true;
}
if info_line.contains("Ejectable:") && info_line.contains("Yes") {
removable = true;
}
if info_line.contains("Device Location:") && info_line.contains("Internal") {
internal = true;
}
}
if size_bytes > 0 && size_bytes <= 128 * 1_000_000_000 {
// Internal fixed storage is only excluded when it is not
// removable media (the built-in SD slot is Internal + Removable).
if internal && !removable {
continue;
}
// 2TB ceiling: big enough for every SDXC card (the old 128GB
// cap silently hid 256GB+ cards), small enough to keep fixed
// multi-terabyte drives out of a destructive picker.
if removable && size_bytes > 0 && size_bytes <= 2_000_000_000_000 {
disks.push(DiskInfo {
device,
name: format!("{} ({})", name, format_size(size_bytes)),
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
"productName": "Arch R Flasher",
"version": "1.3.5",
"version": "1.3.6",
"identifier": "com.archr.flasher",
"build": {
"frontendDist": "../src",
@@ -39,7 +39,7 @@
],
"resources": [],
"macOS": {
"signingIdentity": null,
"signingIdentity": "-",
"entitlements": null
}
},