Insert the card in your Mac. Everything on it is erased. The car writes all the time, so the card matters most: take an A2 or V30 card of 32 GB or more, or one sold for dashcams (High Endurance).
+
Insert the card in your computer. Everything on it is erased. The car writes all the time, so the card matters most: take an A2 or V30 card of 32 GB or more, or one sold for dashcams (High Endurance).
@@ -110,7 +110,7 @@
The card is ready
Wait a moment. The first start takes a few minutes. The LED blinks fast while the Pi is busy.
Open it.Go tohttp://squirrel.local/, or join the wifi Squirrel if the Pi does not find your network.
-
Not working? Put the card in your Mac: squirrel/firstboot.log tells where it stopped.
+
Not working? Put the card in your computer: squirrel/firstboot.log tells where it stopped.
diff --git a/internal/makerui/static/maker.js b/internal/makerui/static/maker.js
index 889f792..2c058b7 100644
--- a/internal/makerui/static/maker.js
+++ b/internal/makerui/static/maker.js
@@ -7,6 +7,7 @@ const form = $('#settings');
// In the Mac app the page runs in a web view without confirm() or
// alert(); the app's own dialogs answer instead.
let NATIVE = false;
+let PLATFORM = '';
async function ask(message, ok) {
if (!NATIVE) return confirm(message);
@@ -106,6 +107,11 @@ function fill(s) {
else if (el.type === 'radio') el.checked = el.value === (v || (a === 'board' ? 'rpi' : ''));
else if (v != null) el.value = v;
}
+ // Where the computer could not tell the time zone by its IANA name
+ // (Windows), the browser can.
+ if (!form.elements.timezone.value) {
+ try { form.elements.timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch {}
+ }
form.elements.usb_storage.checked = s.storage === 'usb';
form.elements.sd_overclock.checked = !!s.sd_overclock;
savedImage = s.image || '';
@@ -213,7 +219,7 @@ form.addEventListener('submit', async e => {
if (j && j.done && !j.error) {
const p = document.createElement('p');
p.className = 'result';
- p.innerHTML = `${t('Image ready.')} `;
+ p.innerHTML = `${t('Image ready.')} `;
$('button', p).onclick = () => api('reveal', { method: 'POST' });
box.append(p);
built = true;
@@ -518,6 +524,7 @@ translatePage();
const d = await api('defaults');
// The Mac app quits with its menu (Cmd-Q), not from the page.
NATIVE = !!d.native;
+ PLATFORM = d.platform || '';
$('#quit').hidden = NATIVE;
boards = d.boards || [];
appVersion = d.version;
From 5250487639f004d177a21fcb6fe5297e65e80ae0 Mon Sep 17 00:00:00 2001
From: Marcus Aram <4435760+marcusaram@users.noreply.github.com>
Date: Thu, 29 Oct 2026 22:34:06 +0100
Subject: [PATCH 4/8] Squirrel Maker for Windows: icon, manifest and version in
the exe
go-winres (pinned as a go.mod tool) makes the resources when the Windows
build runs: the squirrel icon, a manifest that asks for administrator
rights for the app and none for the command line, and version details.
The generated .syso files are not committed.
---
.gitignore | 2 ++
Makefile | 14 ++++++++++++--
go.mod | 10 ++++++++++
go.sum | 16 ++++++++++++++++
4 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index 9184f85..38a0f63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
/.claude/
/internal/makerui/payload/armbian/
/internal/makerui/payload/files-armv6/
+# Windows resources, made by make app-windows
+*.syso
diff --git a/Makefile b/Makefile
index cdafbfc..67c2461 100644
--- a/Makefile
+++ b/Makefile
@@ -147,9 +147,15 @@ maker-bin:
lipo -create -output dist/squirrel-maker dist/squirrel-maker-darwin-arm64 dist/squirrel-maker-darwin-amd64
# Squirrel Maker for Windows (64-bit): the app (Wails, without cgo, so it
-# builds anywhere) and the command line version, in one zip. The app asks
-# for administrator rights when it starts; writing a card needs them.
+# builds anywhere) and the command line version, in one zip. Each gets a
+# Windows resource (go-winres, pinned in go.mod) with the squirrel icon,
+# a manifest and the version; the app's manifest asks for administrator
+# rights when it starts, which writing a card needs.
WIN := dist/windows/Squirrel Maker
+# The file version must be numbers: 0.16.0 from v0.16.0-rc1, 0.0.0 from dev.
+WINVER = $(shell echo "$(VERSION)" | sed -E -e 's/^v?([0-9]+(\.[0-9]+){0,3}).*/\1/' -e t -e 's/.*/0.0.0/')
+WINRES = go tool go-winres simply --arch amd64 --icon cmd/squirrel-app/icon.png --product-name "Squirrel Maker" \
+ --product-version "$(VERSION)" --file-version "$(WINVER)" --copyright "Marcus Aram, MIT license"
.PHONY: app-windows app-windows-bundle
app-windows: payload
@@ -157,6 +163,10 @@ app-windows: payload
app-windows-bundle:
rm -rf "$(WIN)" && mkdir -p "$(WIN)"
+ $(WINRES) --manifest gui --admin --file-description "Squirrel Maker" \
+ --original-filename "Squirrel Maker.exe" --out cmd/squirrel-app/rsrc
+ $(WINRES) --manifest cli --file-description "Squirrel Maker (command line)" \
+ --original-filename squirrel-maker.exe --out cmd/squirrel-maker/rsrc
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS) -H windowsgui" -o "$(WIN)/Squirrel Maker.exe" ./cmd/squirrel-app
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o "$(WIN)/squirrel-maker.exe" ./cmd/squirrel-maker
cp LICENSE THIRD_PARTY_NOTICES.md "$(WIN)/"
diff --git a/go.mod b/go.mod
index 74ab128..4a12668 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
github.com/adrg/xdg v0.5.3 // indirect
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc // indirect
github.com/coder/websocket v1.8.14 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
@@ -25,6 +26,15 @@ require (
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/tc-hib/go-winres v0.3.3 // indirect
+ github.com/tc-hib/winres v0.3.1 // indirect
+ github.com/urfave/cli/v2 v2.25.7 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/crypto v0.57.0 // indirect
+ golang.org/x/image v0.45.0 // indirect
golang.org/x/net v0.60.0 // indirect
)
+
+tool github.com/tc-hib/go-winres
diff --git a/go.sum b/go.sum
index 2e73c23..2379bf6 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,8 @@ github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc h1:0xCWmFKBmarCqqqL
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc/go.mod h1:uvR42Hb/t52HQd7x5/ZLzZEK8oihrFpgnodIJ1vte2E=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -44,8 +46,12 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -55,16 +61,26 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tc-hib/go-winres v0.3.3 h1:DQ50qlvDVhqrDOY0svTxZFZWfKWtZtfnXXHPCw6lqh0=
+github.com/tc-hib/go-winres v0.3.3/go.mod h1:5NGzOtuvjSqnpIEi2o1h48MKZzP9olvrf+PeY2t1uoA=
+github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4=
+github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
+github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
+github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/wailsapp/wails/v3 v3.0.0 h1:lBe89kFu23bVz1dc1H9v0srkHQgWsy7BAKpH4sLQuDw=
github.com/wailsapp/wails/v3 v3.0.0/go.mod h1:U3p3/QfSer2XxXM3XWhwis9O7LvvuNKN/AcGoEFD+K8=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
+golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
+golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
From 42bf16513a5a8fa894f99f558e2a3a9f6fdaf5c6 Mon Sep 17 00:00:00 2001
From: Marcus Aram <4435760+marcusaram@users.noreply.github.com>
Date: Thu, 29 Oct 2026 23:04:18 +0100
Subject: [PATCH 5/8] README: the ROCK 4C+ as a board for the car too, and
computer instead of Mac
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 499b345..969a28d 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Squirrel is a replacement for [teslausb](https://github.com/marcone/teslausb), w
- Raspberry Pi **Zero 2 W**: the usual choice, small, powered by the car through the same cable.
- Raspberry Pi **4** or **5**: faster, and can keep the clips on a USB SSD.
- Original Raspberry Pi **Zero W**: works, but slowly (one core, 2.4 GHz wifi only).
- - Radxa **ROCK 4C+**.
+ - Radxa **ROCK 4C+**: fast (six cores), and can keep the clips on a USB SSD. Needs its own power next to the cable from the car.
A Raspberry Pi 3 Model B or B+ cannot act as a USB drive and does not work.
- **A good SD card** of 32 GB or more: A2 or V30, or one sold for dashcams ("High Endurance"). The car writes all the time.
@@ -48,7 +48,7 @@ Squirrel Maker also runs from the command line (`squirrel-maker`, on Windows `sq
### 2. Make the card
-Put the SD card in your Mac, open Squirrel Maker and follow the seven steps:
+Put the SD card in your computer, open Squirrel Maker and follow the seven steps:
1. **Board**: which board the card is for.
2. **Wifi**: your home network (for the NAS) and perhaps your phone's hotspot, and your country. A Pi Zero W only sees 2.4 GHz networks. Also the password for the Pi's own wifi, which it starts when it finds no known network (see [Wifi in the car](#wifi-in-the-car)).
@@ -141,11 +141,11 @@ The car's drive is disconnected meanwhile, nothing is deleted for space, and a r
### Updating
-Put the card in your Mac, open Squirrel Maker and choose **Update it** on the first page. It shows the Squirrel version on each card and puts this version on the one you pick. The board installs it at its next start; settings, wifi networks and kept clips stay.
+Put the card in your computer, open Squirrel Maker and choose **Update it** on the first page. It shows the Squirrel version on each card and puts this version on the one you pick. The board installs it at its next start; settings, wifi networks and kept clips stay.
## Troubleshooting
-**The LED flickers very fast after the first start.** The first start failed and will try again at the next start. Put the card in your Mac and open `squirrel/firstboot.log` on it (on a ROCK 4C+ the log is also put on the USB drive as `squirrel-firstboot.txt`). If the card was made with an older Squirrel Maker, update or remake it with the latest.
+**The LED flickers very fast after the first start.** The first start failed and will try again at the next start. Put the card in your computer and open `squirrel/firstboot.log` on it (on a ROCK 4C+ the log is also put on the USB drive as `squirrel-firstboot.txt`). If the card was made with an older Squirrel Maker, update or remake it with the latest.
**The car does not see the drive.** Check that the cable goes to the board's data port (see [Connect it to the car](#3-connect-it-to-the-car)) and is a data cable, not a charging-only one. The Status page shows the USB state; "configured" means the car has the drive. After a cold start the car can take a minute to notice a new drive.
From c6c10fa35b3d55a945ce01d09cacfe16bc43e6fa Mon Sep 17 00:00:00 2001
From: Marcus Aram <4435760+marcusaram@users.noreply.github.com>
Date: Thu, 29 Oct 2026 23:14:49 +0100
Subject: [PATCH 6/8] README: describe the ROCK 4C+ USB device mode instead of
pointing to the teslausb wiki
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 969a28d..1909597 100644
--- a/README.md
+++ b/README.md
@@ -195,7 +195,7 @@ The original Zero W gets 32-bit Raspberry Pi OS with an ARMv6 build of squirreld
### Radxa ROCK 4C+
-The ROCK 4C+ gets [Armbian](https://www.armbian.com/) (Debian trixie, minimal). There is no stable Armbian release for this board, only community builds; Squirrel Maker uses one that was checked. Armbian keeps everything on ext4, which macOS and Windows cannot write, so Squirrel Maker writes its files itself, without mounting the card: it only touches a file system that is clean and uses ext4 features it knows, and reads everything back afterwards. Updating such a card asks for your password. At the end of the first start the board restarts once; the car then sees the drive. Ethernet stays with Armbian's own network setup; NetworkManager handles wifi and the hotspot. The USB device mode follows the [teslausb wiki](https://github.com/marcone/teslausb/wiki/Rock-Pi-4C-plus-Installation).
+The ROCK 4C+ gets [Armbian](https://www.armbian.com/) (Debian trixie, minimal). There is no stable Armbian release for this board, only community builds; Squirrel Maker uses one that was checked. Armbian keeps everything on ext4, which macOS and Windows cannot write, so Squirrel Maker writes its files itself, without mounting the card: it only touches a file system that is clean and uses ext4 features it knows, and reads everything back afterwards. Updating such a card asks for your password on a Mac. At the end of the first start the board restarts once; the car then sees the drive. Ethernet stays with Armbian's own network setup; NetworkManager handles wifi and the hotspot. Normally both USB 3 ports are host ports. The first start adds a device tree overlay (`deploy/armbian/dwc3-0-device.dts`) that puts the RK3399's first USB 3 controller (`dwc3_0`, the upper blue port) in peripheral mode; it takes effect after the restart. From then on squirreld offers the car's drive on that port through the kernel's USB gadget, as on a Pi (where `dtoverlay=dwc2` in `config.txt` does the same for the port marked USB).
### Clips on a USB drive
From 4812f7b6867db61852bdc7e99c10ddc512f51e55 Mon Sep 17 00:00:00 2001
From: Marcus Aram <4435760+marcusaram@users.noreply.github.com>
Date: Thu, 29 Oct 2026 23:18:06 +0100
Subject: [PATCH 7/8] Review: Flush's promise, Explorer selection with spaces,
one disk listing
- ext4: say that a failed Flush can leave part of the change written.
- Show in Explorer quotes only the path, so a path with spaces works.
- ListDisks names each card from the listing it has, instead of listing
all disks again per card.
- README: updating a ROCK 4C+ card asks for a password on a Mac only.
---
README.md | 2 +-
internal/ext4/ext4.go | 6 ++++--
internal/ext4/write.go | 3 ++-
internal/maker/disks_windows.go | 27 +++++++++++++++------------
internal/makerui/makerui.go | 8 +-------
internal/makerui/reveal_other.go | 10 ++++++++++
internal/makerui/reveal_windows.go | 16 ++++++++++++++++
7 files changed, 49 insertions(+), 23 deletions(-)
create mode 100644 internal/makerui/reveal_other.go
create mode 100644 internal/makerui/reveal_windows.go
diff --git a/README.md b/README.md
index 1909597..7e7da60 100644
--- a/README.md
+++ b/README.md
@@ -195,7 +195,7 @@ The original Zero W gets 32-bit Raspberry Pi OS with an ARMv6 build of squirreld
### Radxa ROCK 4C+
-The ROCK 4C+ gets [Armbian](https://www.armbian.com/) (Debian trixie, minimal). There is no stable Armbian release for this board, only community builds; Squirrel Maker uses one that was checked. Armbian keeps everything on ext4, which macOS and Windows cannot write, so Squirrel Maker writes its files itself, without mounting the card: it only touches a file system that is clean and uses ext4 features it knows, and reads everything back afterwards. Updating such a card asks for your password on a Mac. At the end of the first start the board restarts once; the car then sees the drive. Ethernet stays with Armbian's own network setup; NetworkManager handles wifi and the hotspot. Normally both USB 3 ports are host ports. The first start adds a device tree overlay (`deploy/armbian/dwc3-0-device.dts`) that puts the RK3399's first USB 3 controller (`dwc3_0`, the upper blue port) in peripheral mode; it takes effect after the restart. From then on squirreld offers the car's drive on that port through the kernel's USB gadget, as on a Pi (where `dtoverlay=dwc2` in `config.txt` does the same for the port marked USB).
+The ROCK 4C+ gets [Armbian](https://www.armbian.com/) (Debian trixie, minimal). There is no stable Armbian release for this board, only community builds; Squirrel Maker uses one that was checked. Armbian keeps everything on ext4, which macOS and Windows cannot write, so Squirrel Maker writes its files itself, without mounting the card: it only touches a file system that is clean and uses ext4 features it knows, and reads everything back afterwards. Updating such a card asks for your password on a Mac; on Windows it uses the administrator rights the app already has, without asking. At the end of the first start the board restarts once; the car then sees the drive. Ethernet stays with Armbian's own network setup; NetworkManager handles wifi and the hotspot. Normally both USB 3 ports are host ports. The first start adds a device tree overlay (`deploy/armbian/dwc3-0-device.dts`) that puts the RK3399's first USB 3 controller (`dwc3_0`, the upper blue port) in peripheral mode; it takes effect after the restart. From then on squirreld offers the car's drive on that port through the kernel's USB gadget, as on a Pi (where `dtoverlay=dwc2` in `config.txt` does the same for the port marked USB).
### Clips on a USB drive
diff --git a/internal/ext4/ext4.go b/internal/ext4/ext4.go
index 9664bea..e107974 100644
--- a/internal/ext4/ext4.go
+++ b/internal/ext4/ext4.go
@@ -4,8 +4,10 @@
// directories and symlinks), for the features that root has.
//
// It reads and writes whole blocks only, which raw disks require, and
-// keeps every change in memory until Flush, so an error halfway leaves
-// the file system as it was. It refuses a file system it does not fully
+// keeps every change in memory until Flush, so an error before Flush
+// leaves the file system as it was. Flush writes the changed blocks in no
+// particular order: if a write fails or is interrupted, part of the
+// change can be on disk. It refuses a file system it does not fully
// understand (an unknown feature) or that is not clean (a journal to
// replay, errors, orphans), rather than guess.
package ext4
diff --git a/internal/ext4/write.go b/internal/ext4/write.go
index 5344588..0313398 100644
--- a/internal/ext4/write.go
+++ b/internal/ext4/write.go
@@ -822,7 +822,8 @@ func (f *FS) Remove(p string) error {
}
// Flush brings the checksums of the changed groups and the superblock up
-// to date and writes every changed block.
+// to date and writes every changed block, in no particular order; when a
+// write fails, the blocks written before it stay written.
func (f *FS) Flush() error {
for g := range f.touched {
d, n, err := f.desc(g)
diff --git a/internal/maker/disks_windows.go b/internal/maker/disks_windows.go
index 05e4976..b88904d 100644
--- a/internal/maker/disks_windows.go
+++ b/internal/maker/disks_windows.go
@@ -126,18 +126,23 @@ func checkDisk(ctx context.Context, id string) (Disk, error) {
if err := diskOK(d); err != nil {
return Disk{}, fmt.Errorf("%s %w", id, err)
}
- name := d.Name
- if parts, err := partitions(ctx, n); err == nil {
- for _, p := range parts {
- if p.Label != "" {
- name = p.Label + ", " + name
- break
- }
+ return toDisk(ctx, d), nil
+ }
+ return Disk{}, fmt.Errorf("%s not found", id)
+}
+
+// toDisk names a disk after its first labelled partition, if it has one.
+func toDisk(ctx context.Context, d winDisk) Disk {
+ name := d.Name
+ if parts, err := partitions(ctx, d.Number); err == nil {
+ for _, p := range parts {
+ if p.Label != "" {
+ name = p.Label + ", " + name
+ break
}
}
- return Disk{ID: id, Name: name, Size: d.Size, Protocol: d.Bus}, nil
}
- return Disk{}, fmt.Errorf("%s not found", id)
+ return Disk{ID: fmt.Sprintf("PhysicalDrive%d", d.Number), Name: name, Size: d.Size, Protocol: d.Bus}
}
func diskOK(d winDisk) error {
@@ -165,9 +170,7 @@ func ListDisks(ctx context.Context) ([]Disk, error) {
var out []Disk
for _, d := range disks {
if diskOK(d) == nil {
- if disk, err := checkDisk(ctx, fmt.Sprintf("PhysicalDrive%d", d.Number)); err == nil {
- out = append(out, disk)
- }
+ out = append(out, toDisk(ctx, d))
}
}
return out, nil
diff --git a/internal/makerui/makerui.go b/internal/makerui/makerui.go
index b1befc1..b77f461 100644
--- a/internal/makerui/makerui.go
+++ b/internal/makerui/makerui.go
@@ -13,7 +13,6 @@ import (
"io/fs"
"net/http"
"os"
- "os/exec"
"os/user"
"path/filepath"
"regexp"
@@ -480,12 +479,7 @@ func (a *Server) reveal(w http.ResponseWriter, r *http.Request) {
image := a.image
a.mu.Unlock()
if image != "" {
- // Finder or Explorer shows the image selected.
- if runtime.GOOS == "windows" {
- exec.Command("explorer", "/select,"+image).Run()
- } else {
- exec.Command("open", "-R", image).Run()
- }
+ revealFile(image) // Finder or Explorer shows the image selected
}
writeJSON(w, map[string]bool{"ok": true})
}
diff --git a/internal/makerui/reveal_other.go b/internal/makerui/reveal_other.go
new file mode 100644
index 0000000..5443f9a
--- /dev/null
+++ b/internal/makerui/reveal_other.go
@@ -0,0 +1,10 @@
+//go:build !windows
+
+package makerui
+
+import "os/exec"
+
+// revealFile shows path selected in the Finder.
+func revealFile(path string) {
+ exec.Command("open", "-R", path).Run()
+}
diff --git a/internal/makerui/reveal_windows.go b/internal/makerui/reveal_windows.go
new file mode 100644
index 0000000..665f911
--- /dev/null
+++ b/internal/makerui/reveal_windows.go
@@ -0,0 +1,16 @@
+package makerui
+
+import (
+ "os/exec"
+ "syscall"
+)
+
+// revealFile shows path selected in Explorer. Explorer reads its own
+// command line: Go would quote "/select,C:\a b\c" as a whole when the path
+// has a space, which Explorer does not understand, so only the path is
+// quoted.
+func revealFile(path string) {
+ cmd := exec.Command("explorer.exe")
+ cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: `explorer.exe /select,"` + path + `"`}
+ cmd.Run()
+}
From fd1ae45ebb3f555acb28a5b3fa632f55ec68bf85 Mon Sep 17 00:00:00 2001
From: Marcus Aram <4435760+marcusaram@users.noreply.github.com>
Date: Fri, 30 Oct 2026 08:23:12 +0100
Subject: [PATCH 8/8] README: what to do when the Windows app does not start
(WebView2)
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7e7da60..a3d7e74 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Tesla puts speed, pedals, steering and position into every frame from firmware 2
Squirrel Maker is the app that makes the SD card, for macOS and Windows. Download it from the [releases](https://github.com/marcusaram/squirrel/releases), or build it yourself (see [Development](#development)):
- **macOS:** `Squirrel-Maker-.zip`, with the app inside. It is not signed with an Apple developer certificate: the first time, macOS says it cannot check the developer. Open **System Settings → Privacy & Security** and choose **Open Anyway** below the message, or right-click the app and choose **Open**.
-- **Windows:** `Squirrel-Maker--windows.zip`. Unpack it and start `Squirrel Maker.exe`. It asks for administrator rights, which writing a card needs. It is not signed either: if Windows says it protected your PC, choose **More info** and then **Run anyway**. The app uses Microsoft Edge WebView2, which Windows 10 and 11 normally have.
+- **Windows:** `Squirrel-Maker--windows.zip`. Unpack it and start `Squirrel Maker.exe`. It asks for administrator rights, which writing a card needs. It is not signed either: if Windows says it protected your PC, choose **More info** and then **Run anyway**. The app uses Microsoft Edge WebView2, which Windows 10 and 11 normally have. If the app does not start, install the [WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/) from Microsoft, or use `squirrel-maker.exe` instead, which shows the same steps in your browser (see below).
Squirrel Maker also runs from the command line (`squirrel-maker`, on Windows `squirrel-maker.exe` from a terminal opened as administrator), with the same steps in your browser.