Note
Blackout Secure is proud to partner with Botspot and the Windows on R community as part of this endeavour. Together, we are carrying WoR-Flasher forward while keeping Botspot's original authorship, project direction, and community connections visible.
Create a bootable Windows 10 or Windows 11 ARM64 drive for a Raspberry Pi from Linux or macOS.
WoR-Flasher downloads or imports Windows, adds the required UEFI firmware and available drivers, writes the target drive, and verifies the finished result. It automates the manual process described in worproject's How to install from other OSes guide.
Warning
Flashing erases the selected drive. Check the device carefully, keep the computer powered, and do not remove the drive until verification and ejection finish.
- Table of contents
| Raspberry Pi | Newest usable Windows 11 | Limitations |
|---|---|---|
| Pi 2 v1.2, Pi 3, CM3 | 23H2 (22631.x) |
No Wi-Fi or graphics acceleration; Windows 10 is usually faster |
| Pi 4, Pi 400 | 23H2 (22631.x) |
No Wi-Fi or graphics acceleration; RAM is limited to 3 GB by default |
| CM4 | 23H2 (22631.x) |
Known to freeze at the UEFI boot screen on some units (pftf/RPi4#146); USB requires the RAM limit set to 1 GB, and PCIe does not work |
| Pi 5 | 25H2 and newer | Community/unofficial support only (worproject FAQ); no native Pi hardware drivers; USB Ethernet is required for networking |
Pi 3 and Pi 4 cannot run builds newer than 25163, because those builds require ARMv8.1 atomics. WoR-Flasher rejects incompatible builds. Windows versions that run on these models are past end of support and should be treated as experimental or offline systems.
| Requirement | Detail |
|---|---|
| Host OS | Raspberry Pi OS, Debian, Ubuntu, Linux Mint or another Debian-based Linux; or macOS 13+ with Homebrew |
| Privileges | Administrator or sudo access |
| Network | Internet access, unless every required file is already cached |
| Free space | About 10 GB in the download directory |
| Target drive | At least 8 GB |
| Display | A desktop session, for the graphical interface only |
Dependencies are installed automatically on supported hosts.
Windows, WSL and non-Debian Linux distributions are not supported. WoR-Flasher deliberately refuses to run under WSL: WSL cannot reach USB drives directly, and the drives it does list are WSL's own virtual disks, so erasing one would damage the WSL installation. On Windows, use the official Windows on Raspberry Imager instead.
Linux releases provide two small archives, using the existing script names:
| Download | Run after extraction | Included files |
|---|---|---|
wor-flasher-<version>-linux-cli.tar.gz |
bash install-wor.sh |
Standalone CLI, README.txt, LICENSE, NOTICE |
wor-flasher-<version>-linux-gui.tar.gz |
bash install-wor-gui.sh |
GUI entry script, README.txt, LICENSE, NOTICE |
Verify the archive against the release SHA256SUMS and extract into a new directory. The files extract directly into that directory, with no wor-flasher/ subfolder. Extract both archives into the same directory to put the two scripts next to each other. The CLI embeds the complete runtime. The GUI uses a complete local runtime or the standalone CLI beside it; if its engine, libraries, templates, or artwork are missing, it downloads the complete verified client from the maintained GitHub release instead of mixing individual file versions. The packaged GUI pins that download to its release version and SHA-256 digest.
The first GUI-only launch needs HTTPS access and curl or wget, plus sha256sum or shasum. The verified client is retained under ${XDG_CACHE_HOME:-$HOME/.cache}/wor-flasher/gui-client and can be reused without another download. Existing local files, including config.json, are not overwritten. A directly downloaded source GUI script uses the latest release checksums when it needs a runtime. This bootstrap is for Linux; the macOS app retains its existing verified-runtime startup.
To work from the complete source checkout instead:
git clone https://github.com/blackoutsecure/wor-flasher
cd wor-flasher
./install-wor-gui.shOn macOS, download WoR-Flasher-<version>-macos.dmg, verify it against the release SHA256SUMS, and open the disk image. It contains the complete WoR-Flasher.app and brief instructions. Copy the app to a writable folder, such as ~/Applications, then eject the DMG and open your copied app. Keep the entire .app bundle together. The app opens the same native GUI without leaving a Terminal window open and remains unsigned/unnotarized.
For a local build, npm run build:macos retains release/macos/WoR-Flasher.app and, on macOS, also creates the compressed release/macos/WoR-Flasher-<version>-macos.dmg. Linux can stage and validate the app bundle, but DMG creation requires macOS; the release publisher runs on macOS.
WoR-Flasher runs one GUI session per signed-in user, even if more than one checkout or version is present. Opening the app again brings the current macOS window forward instead of starting another installer workflow.
For command-line use without a checkout, download the install-wor.sh release asset and SHA256SUMS from the same release. This single file works on macOS and supported Debian-family Linux hosts, including Raspberry Pi OS. It embeds that release's engine, libraries, templates, artwork, README, LICENSE, and NOTICE; it does not clone a repository or fetch another version of the code.
#Verify only the standalone download using the checksum file from the same release.
grep ' install-wor.sh$' SHA256SUMS > install-wor.sh.sha256
test -s install-wor.sh.sha256 &&
shasum -a 256 -c install-wor.sh.sha256 &&
bash install-wor.sh --help
#On Linux, sha256sum -c install-wor.sh.sha256 is an alternative.After verification succeeds, bash install-wor.sh --version prints the release version and bash install-wor.sh starts the interactive CLI, with the existing confirmations before erasing a drive. Do not run it if checksum verification fails. Download and save the script first; do not pipe it into Bash. The Raw source copy of install-wor.sh still needs the full repository. The release asset is a generated wrapper around the same engine, not a separate flashing implementation.
Unpacking needs Bash, tar, gzip, base64, standard Unix file utilities, and either shasum or sha256sum, but no Git checkout or Node.js. Normal macOS/Linux flashing dependencies still apply. Arguments such as --config and --gui, environment settings, and the caller's working directory are preserved. Use --config /path/to/config.json for your settings; do not edit the embedded runtime.
The standalone client verifies and caches its runtime privately in ${XDG_CACHE_HOME:-$HOME/.cache}/wor-flasher/standalone, keyed by the embedded archive digest. Each launch checks the cached files and refuses modified or linked content. Staging is cleaned up on errors and normal termination; a verified runtime is retained so an active flash never loses its scripts. If a forcibly killed extraction leaves a preparation lock, close all runs before removing the lock named in the error. To reclaim the runtime cache, likewise close all WoR-Flasher runs first. Downloads and logs remain in the normal download directory, separate from the runtime cache. The script never rewrites itself. Sourcing engine functions still requires the full checkout.
Pi-Apps provides a graphical installation and removal path for the Linux version of WoR-Flasher. Install the Windows Flasher app from Pi-Apps, then open Accessories -> WoR-Flasher or run:
~/wor-flasher/install-wor-gui.shPi-Apps installs the application to ~/wor-flasher from the maintained Blackout Secure patch-1 branch and manages its launcher and removal. See Maintainer partnership for the project history and support links.
There are two built-in front-ends over one engine:
install-wor.shholds all of the logic — drive detection, download and cache handling, ISO validation, partitioning, flashing and verification.install-wor-gui.shsources it and adds only the windows. It collects your choices in native dialogs, then runsinstall-wor.shand shows a native progress window (AppKit on macOS,yadon Linux) instead of a visible terminal.
Both therefore write identical media from identical settings. The built-in GUI intentionally uses the engine directly because it needs shared functions and state while constructing its forms. The separate integration adapter below is a process-level contract for external tools, not an extra layer inside the GUI.
./install-wor-gui.sh
# or, equivalently:
./install-wor.sh --guiThe overview image shows the shared installation workflow. On macOS, the same choices are presented in native AppKit windows rather than Linux yad dialogs.
Both front-ends size windows from the active screen. The Linux GUI detects its desktop geometry with xrandr, xdpyinfo or xwininfo, clamps every requested width and height inside fixed screen margins, and falls back to the narrow product logo when a full-size illustration cannot fit. A conservative 1024x768 fallback is used when the display server exposes no geometry command.
Linux follows the same staged route as macOS: partnership announcement, Windows version, Raspberry Pi model, target drive, installation mode, overview, Advanced Options, progress, and completion. Its overview and Advanced Options scroll inside their bounded windows, and config.txt opens in a separate Save/Back editor. Progress-window Abort and close both stop the installer tree before reporting failure.
Window chrome remains native to each desktop. On Ubuntu, GNOME/Mutter draws the title bar and its controls; WoR-Flasher marks yad dialogs fixed-size so they support native minimize, restore, and close without allowing resize or maximize. Replacing those controls with imitation macOS traffic lights would remove native accessibility and window-manager behavior.
The front-end is never chosen automatically. DISPLAY is also set over SSH and in CI, and a tool that erases a drive should do exactly what it was asked to do.
An Advanced Options window is reachable from the confirmation screen on both platforms. It exposes every configuration-only option as a checkbox, plus an editable config.txt: offline Windows setup, the Pi 4 RAM unlock, whether to use the latest UEFI firmware or drivers instead of the tested pinned versions (the pinned version is shown in each label), whether to skip the final written-image verification, and dry run. APPLY_CUSTOM_CONFIG_TXT controls whether the editable config.txt is applied at all; unchecking it dims the editor and leaves the UEFI firmware package's own default in place. A Downloaded files menu selects the cache mode, since it has three settings rather than two.
Administrator access is requested through a native password dialog on both platforms — there is no terminal to type into.
- Double-click WoR-Flasher.app, or launch
./install-wor-gui.shor./install-wor.sh --gui, from macOS 13 or newer. Before the setup wizard, the GUI checks Accessibility using the same script host as Automatic Ignore. If permission is missing, choose Open Settings, enable the entry macOS shows for WoR-Flasher or its launcher, then return and choose Recheck. Continue Manually proceeds without requiring this optional permission; closing the dialog quits without starting setup or flashing. - Review the partnership announcement. The Botspot and Blackout Secure names open their respective websites, and the Proceed button continues automatically after the countdown.
- Choose the Windows version, language, Raspberry Pi model and target drive in native AppKit windows. The target drive is clearly identified before any erase operation.
- Review the shared Installation Overview, then use Advanced Options for cache mode, firmware and driver choices, Pi 4 RAM handling, offline OOBE and
config.txtcustomization. - Confirm the flash. A native progress window reports each shared installer phase, supports aborting, and retains a failure log when something stops unexpectedly.
- After successful verification, the completion dialog provides the log controls and the next-steps guidance for moving the drive to the Raspberry Pi.
The repository does not currently include desktop captures of the macOS windows because the GUI requires an interactive macOS display session. The workflow and shared overview artwork are kept here so the documented behavior stays accurate across both front-ends.
./install-wor.shUsage: install-wor.sh [OPTIONS]
(no arguments) run the interactive text-mode installer
--gui run the graphical front-end instead
--config <file> load configuration settings from a JSON file
--version print the version and exit
--help show this message
The selected drive is erased. Drives from 8 GB to under 25 GB can create recovery media for another drive. Drives of 25 GB or more can also install Windows onto themselves. The host's current boot drive is always excluded.
install-wor.sh is designed to be driven non-interactively via environment variables or a config.json configuration file.
Settings follow a 3-tier precedence cascade: Environment Variables / CLI Options > JSON Configuration (config.json or --config) > Script Defaults.
# Using environment variables
RPI_MODEL=4 WIN_LANG=en-us BID=22631.2861 DEVICE=/dev/sda CAN_INSTALL_ON_SAME_DRIVE=1 ./install-wor.sh
# Using a JSON configuration file
./install-wor.sh --config config-templates/config.jsonRefer to config-templates/config.json for the default run configuration and config-templates/config.schema.json for the full JSON configuration schema. Project pins such as the WoR-PE package URL, SHA-256 digest, firmware versions, driver version, Windows build guardrails and update target live in src/config/metadata.json, are loaded by src/lib/metadata.sh, and remain overrideable through environment variables or a custom config file.
Sourcing with the source argument makes the engine's functions available without running a flash. Useful ones include list_devs, list_dev_paths, drive_capability, describe_device, get_bid, get_os_name, list_langs, validate_iso_file, list_cached_winfiles, settings_summary and install_packages.
install-wor-hook.sh is the stable command-line adapter for external front-ends and automation. When it is kept next to install-wor.sh, it uses that engine directly. When distributed by itself, it automatically obtains a shallow copy of the complete WoR-Flasher repository in ${XDG_CACHE_HOME:-$HOME/.cache}/wor-flasher-hook; fetching the complete checkout ensures required assets such as config-templates/ are present. It sources the selected engine for discovery and summaries, then executes it directly for a flash.
| Command | Output or behavior |
|---|---|
list-devices |
Safe whole-disk candidate paths, one per line, excluding the current boot drive |
describe-device DEVICE |
The supplied path with its size and model when available |
summary |
The current settings as tab-separated label<TAB>value lines |
run [ARGS...] |
Runs install-wor.sh with the caller's environment and any supplied engine options |
./install-wor-hook.sh list-devices
./install-wor-hook.sh describe-device /dev/sda
./install-wor-hook.sh --set DEVICE=/dev/sda --set RPI_MODEL=4 \
--set BID=22631.2861 --set WIN_LANG=en-us \
--set CAN_INSTALL_ON_SAME_DRIVE=1 summary
./install-wor-hook.sh --progress-file /tmp/wor.progress \
--set DEVICE=/dev/sda --set RPI_MODEL=4 --set BID=22631.2861 \
--set WIN_LANG=en-us --set CAN_INSTALL_ON_SAME_DRIVE=1 runStandalone bootstrap requires git. These variables control where the hook obtains the engine; only point them at a repository and ref you trust:
| Variable | Default | Purpose |
|---|---|---|
WOR_HOOK_REPOSITORY |
https://github.com/blackoutsecure/wor-flasher.git |
Git repository containing the complete tool |
WOR_HOOK_REF |
main |
Branch or tag cloned by the hook |
WOR_HOOK_INSTALL_DIR |
${XDG_CACHE_HOME:-$HOME/.cache}/wor-flasher-hook |
Persistent checkout used by standalone hooks |
Discovery is a snapshot, not authorization to erase a path later. list-devices rejects unsupported hosts and excludes the current boot drive, but device state can change. describe-device formats any supplied path and summary previews settings; neither validates that a device is currently safe. Call list-devices again before run, and let run perform the engine's final host, device, capacity and installation-mode checks. For unattended operation, provide all required values from Parameters through environment variables or repeated --set NAME=VALUE options; otherwise the engine can prompt for missing choices.
The adapter is transport-neutral. A GUI, desktop launcher, test harness or another local imaging application can present its own choices and invoke the same engine without copying its flashing logic. Pass --progress-file FILE or set WOR_GUI_PROGRESS_FILE to a writable path to receive line-oriented, tab-separated events while run is active:
STATUS<TAB>message
STEP<TAB>current<TAB>total<TAB>message
SUBSTEP<TAB>percent
TASK<TAB>percent<TAB>label
STEP reports the major workflow stage. SUBSTEP reports the current stage's numeric progress. TASK carries the friendly current operation, such as install.wim, paired with the same percentage so an external progress bar can display install.wim: 84% without parsing terminal output.
The adapter returns the underlying command's exit status. Usage errors, including an unknown command or a missing DEVICE for describe-device, return 2.
Raspberry Pi Imager supports a custom image repository through --repo, which is useful for publishing image metadata and downloads. It does not by itself turn an arbitrary shell flasher into an Imager write target. A future Imager integration should therefore be a deliberate adapter on the Imager side that calls this contract, rather than embedding or forking the flashing logic. See the Raspberry Pi Imager repository for its current repository and application integration model.
The flashing engine remains install-wor.sh. Node.js is used only for release packaging and validation, where it is a better fit for deterministic file copying, checksum generation and future platform manifests. The tooling has no runtime dependencies.
npm run check # shell syntax, macOS runtime freshness and release-plan validation
npm run build # stage fresh macOS, Linux and Windows-placeholder release folders
npm run build:macos # stage the .app and, on macOS, its versioned .dmg with SHA256SUMS
npm run build:linux # stage separate minimal Linux GUI/CLI tar.gz clients and SHA256SUMS
npm run package:all # refresh the embedded .app runtime, then stage every release folder
npm run pe:check # download the pinned WoR-PE package and verify the recorded SHA-256
npm run pe:update # resolve the latest WoR-PE package, hash it and update project metadata
npm run metadata:check # verify package.json matches src/config/metadata.json
npm run metadata:write # rewrite package.json to match src/config/metadata.json
npm run clean # remove generated release outputGenerated release output is written under release/ and is intentionally ignored by Git. Publish release/linux/wor-flasher-<version>-linux-cli.tar.gz and release/linux/wor-flasher-<version>-linux-gui.tar.gz for Linux users. Each archive has exactly four files: its entry script, README.txt, LICENSE, and NOTICE. The CLI embeds the full runtime; the GUI obtains it only when needed. npm run package:all reuses the same standalone client bytes for the CLI archive and the published install-wor.sh, so the GUI bootstrap digest matches the shared asset. Keep these outputs together when publishing. The old combined Linux tarball and Linux ZIP are no longer generated.
pe:check and pe:update are maintainer commands because they download release assets. Keep pe:check out of routine CI unless network access is expected; use pe:update only when deliberately refreshing the pinned WoR-PE package URL and digest in src/config/metadata.json.
src/config/metadata.json is the source of truth for package.json's version, description, license, homepage, repository, bugs, funding, and keywords; edit product.* there, then run npm run metadata:write (or npm run version:set, which calls it automatically). npm run build and npm run package:all refuse to stage a release while package.json is out of sync, and npm run check runs metadata:check too.
Windows packaging is intentionally only a placeholder today. A Windows UI should drive the same install-wor-hook.sh / engine contract only after a separate device-safety design exists for Windows disks, elevation and removable media. Until then, Windows users should use the official Windows on Raspberry Imager.
Every prompt has a matching environment variable.
| Variable | Default | Function |
|---|---|---|
DL_DIR |
~/wor-flasher-files |
Where components are downloaded and Windows images are extracted |
RPI_MODEL |
ask | Target Raspberry Pi: 3, 4 or 5 |
BID |
ask | Exact Windows build ID, e.g. 22631.2861 |
WIN_LANG |
ask | Windows language code, e.g. en-us |
DEVICE |
ask | Target drive, e.g. /dev/sda or /dev/disk4 |
CAN_INSTALL_ON_SAME_DRIVE |
ask | 1 to install Windows onto the target itself, 0 to make recovery media for another drive |
SOURCE_FILE |
unset | Path to an existing Windows ARM64 ISO, instead of downloading |
CONFIG_TXT |
shipped template | Body of config.txt written to the boot partition |
APPLY_CUSTOM_CONFIG_TXT |
1 |
0 leaves the UEFI firmware package's own config.txt in place |
OOBE_NETWORK_BYPASS |
1 |
0 requires the standard network-connected Windows setup flow |
WINDOWS_ACCOUNT_SETUP |
0 |
1 creates the optional local Windows administrator configured in Advanced Options |
WINDOWS_ACCOUNT_USERNAME |
unset | Username for the optional local Windows account |
WINDOWS_ACCOUNT_PASSWORD |
unset | Password for the optional account; written to unattended setup only when enabled |
WINDOWS_LOCALE_SETUP |
1 |
1 applies WINDOWS_LOCALE to Windows keyboard and regional settings |
WINDOWS_LOCALE |
en-US |
Locale such as en-US or en-GB used when locale setup is enabled |
PI4_AUTO_DISABLE_3GB |
1 |
Pi 4 only. 0 keeps the 3 GB RAM limit |
PI4_UEFI_SHELL_UNLOCK |
0 |
Pi 4 only. 1 stages a one-time verified UEFI Shell handoff and restores the EFI loader after setting the RAM variable |
UEFI_USE_LATEST |
0 |
1 queries GitHub for the newest UEFI firmware instead of the pinned version |
DRIVERS_USE_LATEST |
1 |
0 uses the pinned driver package version |
SKIP_IMAGE_VERIFICATION |
0 |
1 skips post-flash verification. Not recommended |
CHECK_FOR_UPDATES |
1 |
0 disables the read-only release check |
NO_UPDATE |
0 |
Legacy inverse of CHECK_FOR_UPDATES; 1 disables update checks |
HIDE_EMPTY_DRIVES |
1 |
0 shows empty card-reader slots as selectable drives in WoR-PE |
USE_CACHE |
1 |
See Download cache |
DRY_RUN |
0 |
1 runs every step except writing to the drive |
WOR_LOG_FILE |
$DL_DIR/logs/wor-flasher-<timestamp>.log |
Where a failed run's primary log is kept; last-run.log is refreshed for support |
VERIFY_TLS |
1 |
0 skips TLS certificate verification, for hosts with an outdated CA bundle |
RUN_MODE |
cli |
gui makes the engine show graphical error dialogs |
SKIP_PACKAGE_INSTALL |
unset | 1 assumes dependencies are already present |
Example:
DL_DIR=/media/pi/big-drive DEVICE=/dev/sdg RPI_MODEL=4 WIN_LANG=en-us DRY_RUN=1 ./install-wor.shUse an official Windows ARM64 ISO containing sources/install.wim or sources/install.esd:
SOURCE_FILE=/path/to/windows-arm64.iso ./install-wor.shThe build number and language are read from the filename where possible, and you are asked for them if not. Customized Windows images are not supported.
On Raspberry Pi 4 only, the 3 GB RAM limit is disabled automatically after WoR-PE installs Windows and reboots. This setting is ignored for every other model. To keep the limit enabled:
PI4_AUTO_DISABLE_3GB=0 ./install-wor.shWindows Setup attempts to change the pftf RamLimitTo3GB firmware variable during the specialize pass, after the injected drivers are installed, and reboots once before OOBE. It also clears any BCD-level truncatememory cap, a separate Windows Boot Manager memory limit noted in worproject's imager customization guide. The Windows log can report that the runtime variable was changed successfully, but Raspberry Pi UEFI emulates NVRAM in RPI_EFI.fd and changes made from an operating system may not persist after reboot. If the limit returns, disable RamLimitTo3GB from UEFI Device Manager -> Raspberry Pi Configuration -> Advanced Configuration, or pre-edit the UEFI variable in RPI_EFI.fd before flashing.
The specialize answer-file command invokes the staged Pi4Disable3GB.ps1 file instead of embedding the PowerShell program. Windows limits RunSynchronousCommand/Path to 259 characters and rejects the entire answer file when that limit is exceeded. The script logs its result to %WINDIR%\Temp\Pi4Disable3GB.log and always returns success so a firmware-setting failure cannot abort Windows Setup.
Important
Set PI4_AUTO_DISABLE_3GB=0 on Compute Module 4. Per the worproject FAQ, CM4 requires the RAM limit set to 1 GB — not simply left at 3 GB — for USB to work at all, and PCIe does not work regardless. WoR-Flasher cannot distinguish a CM4 from a Pi 4/400, so do not rely on the automatic default for CM4 hardware.
Enabled by default. WoR-Flasher writes a minimal Microsoft unattended-setup answer file that hides the OOBE network and online-account screens, so setup can continue with a local account when Pi networking is not ready yet. It does not automate accounts, licenses, partitions or privacy choices by default.
Advanced Options can optionally configure a Windows local administrator account and a locale profile before the first boot. The account username and password are written to Autounattend.xml only when explicitly enabled; the password is never shown in summaries or logs, but Windows setup necessarily stores it in plaintext on the prepared media temporarily. Remove Autounattend.xml after setup completes. Regional settings are enabled by default and initially use the current host locale when it matches a Windows locale, otherwise en-US; a selection made during the current GUI run is retained when returning to Advanced Options. The locale profile applies one value such as en-US or en-GB to the Windows keyboard/input, system, user and UI locale settings.
OOBE_NETWORK_BYPASS=0 ./install-wor.sh # require networkconfig-templates/ holds the files injected onto the media or used for configuration validation:
| File | Purpose |
|---|---|
config.json |
Shipped default configuration parameter file |
config.schema.json |
JSON Schema definition for config.json parameters |
pi3.config.txt |
config.txt body for Pi 2 v1.2 / Pi 3 |
pi4.config.txt |
config.txt body for Pi 4 / Pi 400 |
pi5.config.txt |
config.txt body for Pi 5 |
pi4-ram-unlock.ps1 |
PowerShell action that clears the Pi 4 3 GB limit |
pi4-ram-unlock-specialize.xml |
Answer-file fragment that runs the above during specialize |
oobe-network-bypass.xml |
Answer-file fragment for offline OOBE |
Edit these directly to customize what gets written. Both the CLI and the GUI start from the same template, so they produce identical media. Updating your checkout picks up any changes to them.
HIDE_EMPTY_DRIVES (default 1) writes HideEmptyDrives=1 into the cached WoR-PE settings.ini before each run, matching worproject's WoR-PE package option of the same name, so empty card-reader slots do not show up as selectable drives during setup.
Downloads are stored in ~/wor-flasher-files by default.
USE_CACHE |
Behaviour |
|---|---|
0 |
Remove cached components and download them again |
1 |
Reuse cache only when its source and SHA-256 payload manifest match (default) |
2 |
Trust the existing cache without update or integrity checks |
USE_CACHE=0 ./install-wor-gui.shMode 1 refreshes changed, missing, extra or outdated cached content. Delete ~/wor-flasher-files when you no longer need the downloads or extracted Windows images.
- Downloads and verifies the PE installer, UEFI firmware, drivers and Windows image.
- Extracts or imports the Windows image.
- Creates FAT32
WOR_BOOTand ExFATWOR_INSTALLpartitions. - Copies the startup and installation files and updates
boot.wim. - Verifies the partition layout, filesystems, boot files, WIM images and the copied
install.wimchecksum. - Unmounts and ejects the drive.
Downloads and final verification take a long time, especially on slow SD cards. Progress is shown for long operations. Do not remove the drive until WoR-Flasher reports success.
Move the completed drive to the Pi and connect a display, a wired keyboard and a wired mouse. Windows Setup may restart several times; do not remove power or the drive until setup completes.
WoR-Flasher never rewrites its own installation. Nothing in the tool runs git pull, git merge,
or replaces its own files on a source checkout, because a partly-updated disk flasher is far more
dangerous than an out-of-date one. Updating is always something you choose to do.
If you installed from a git checkout, update it yourself:
cd wor-flasher
git pullIf you installed from a release archive, download the newer archive from the
releases page and verify it against the published
SHA256SUMS before use.
To help you notice a new version, the engine performs a read-only release check before setup, downloads, or flashing begin, and prints a one-line notice when a newer release exists. It makes a single HTTPS request to the GitHub releases API, writes nothing, and changes nothing. The check needs Node.js; on a host without Node.js it is silently skipped and the flash proceeds normally.
CHECK_FOR_UPDATES=0 ./install-wor.sh # skip the release checkLegacy callers can still set NO_UPDATE=1 to disable update checks. You can run the same check on
its own with npm run update-check.
The macOS app is the one component that can install an update, and only when it has been copied away
from a checkout. It never modifies its own bundle. On first launch it validates the embedded runtime
and copies it to ~/Library/Application Support/WoR-Flasher/runtimes/<version>/runtime. Detached
updates are staged there from release metadata over HTTPS, and are accepted only after the archive
SHA-256, every extracted file digest, and every recorded file mode match the signed package manifest.
Unsafe archive entries, incomplete payloads, equal versions, and downgrades are rejected. Runtime
selection falls back in this order: active, previous, then the immutable embedded copy. Launched from
a source checkout, the combined startup update-and-repair check updates nothing remotely. It restores
only missing tracked runtime files, with your confirmation, from the revision already in your local
checkout.
Check what you are running with ./install-wor.sh --version.
The app performs a bounded preflight rather than a destructive general-purpose "self-heal":
- If a tracked runtime script, configuration file, template or image is absent, the app offers to restore only that missing file from the local Git
HEAD. - If a required Homebrew formula is absent, the app lists the exact formulae and asks before installing only those dependencies. It never runs
brew upgrade. - If Homebrew itself is absent, the app offers to open the official Homebrew website. It does not run a remote installer automatically.
- Existing modified files, untracked files, downloaded Windows content and user settings are never reset or replaced. Download recovery remains controlled by the selected cache mode.
Source-file repair requires a complete Git checkout. A detached app instead validates its installed runtime and falls back to the previous or embedded runtime when the active copy is damaged; it does not attempt Git repair. If no runtime validates, the app stops with a native error instead of guessing or overwriting local work.
If a flash fails from the GUI, the full log is kept under $DL_DIR/logs/ with a UTC timestamp in the filename, or wherever WOR_LOG_FILE points. $DL_DIR/last-run.log is also refreshed as a stable support shortcut. The primary path is shown in the error dialog and listed on the confirmation screen before you start. Attach that log to any bug report.
Canceling the macOS administrator password dialog before writing shows Administrator password entry was canceled, not an unexpected-crash message. The retry screen confirms that this attempt has not changed the target and that prepared downloads are kept. Try Again resumes at the password step; Close exits without flashing. Empty or incorrect password entries have their own concise explanations. Raw sudo/AppleScript diagnostics remain in the saved log, not in this dialog. If writing has already started or its state cannot be confirmed, the normal error details are shown instead of claiming no changes were made.
Each macOS GUI attempt accepts one password submission. If it is rejected, WoR-Flasher returns to Try Again instead of letting sudo reopen another password dialog behind the progress window. Retrying creates fresh prompt state and keeps prepared downloads. Once authorization succeeds, the log records Administrator access granted and progress changes to Preparing the target disk..., rather than continuing to display a password wait during partitioning.
On macOS, Written image verified successfully confirms the copied data, but partition finalization must still finish. If the finalizer fails, the media is not confirmed ready to boot. The installer requires worker readiness before disk preparation and pre-creates user-owned result files.
GUI launches normally have no terminal, so the default sudo policy scopes cached authorization to the parent process. The finalizer launches the external sudo command directly from the authenticated shell, rather than running the GUI wrapper inside another background shell. This avoids the immediate Administrator authentication is no longer reusable startup failure without allowing a second password prompt. The worker script is passed as a fixed command argument instead of relying on detached stdin. Parent-scoped authorization and startup failures are covered by mock-only regression tests; an end-to-end flash with the correction still needs confirmation. Keep the diagnostic log rather than repeatedly reflashing after a startup error.
The same worker applies the Pi 3 GPT patch at its original point before written-image verification, then waits for the final retag request. These late writes do not depend on a cached sudo timestamp remaining valid during long copies. macOS therefore does not start the ineffective background timestamp refresher. ISO images attached by the current user are also detached without sudo, so ISO cleanup does not consume the disk-write password prompt.
The disk can temporarily appear unformatted immediately after administrator authentication. During an active macOS GUI flash, WoR-Flasher automatically chooses Ignore for the exact The disk you attached was not readable by this computer system alert. It never chooses Initialize or Eject, never dismisses other alert types, and does not disable Disk Arbitration or change global disk settings. The alert may appear briefly before dismissal. Automation starts only after authentication and finalizer readiness, and stops when the flash completes, fails, or is aborted.
Routine Automatic Ignore waiting and watching messages are not displayed in the progress window, including when it first opens. Permission warnings and helper errors remain visible.
This requires macOS Accessibility permission for WoR-Flasher and permission to control System Events under Privacy & Security > Automation. Both the packaged app and a directly launched macOS GUI check Accessibility at startup, before the flashing wizard, Windows/firmware preparation, or administrator authentication for disk writing. Already-granted permission does not show a dialog. Otherwise, Open Settings opens the Accessibility pane without closing the startup dialog; add the app with + if it is not listed. Recheck queries the actual script host again, and Continue Manually explicitly accepts manual Ignore handling for that launch. If macOS does not recognize a changed permission immediately, quit and reopen WoR-Flasher.
WoR-Flasher never grants permission itself or caches a successful check as authorization. System Events Automation permission is separate and may still be requested when Automatic Ignore is first used. The live helper continues checking Accessibility during writing, so revoking permission or denying Automation still produces a progress warning without interrupting the flash; choose Ignore manually in that case. Because the system alert does not identify its disk, automatic dismissal is limited to the active-write window and a single matching system alert; multiple matching alerts are left for manual handling.
The developer-only src/macos-disk-claim.c prototype explores a per-disk claim held by a control pipe. It is not packaged or invoked by the app: compilation succeeded, but claim acquisition timed out on a disposable disk image on the development host. Prompt suppression and formatter compatibility remain unverified. Do not use this prototype on physical media until its lifecycle and formatting interactions have been validated on disposable images.
macOS: "Operation not permitted" formatting the drive
An older WoR-Flasher runtime may report newfs_msdos, newfs_exfat, or sgdisk failing with Operation not permitted even though the script already has sudo. Since macOS Catalina, writing directly to a raw disk device (/dev/rdiskN) needs Full Disk Access, which sudo does not grant on its own — and this is a deliberate macOS security boundary, so no app (including WoR-Flasher) can turn the toggle on for you; only a person clicking it in System Settings satisfies it. Current macOS formatting uses diskutil eraseVolume for the created partitions and retains sgdisk only for partition layout and EFI attributes.
If it worked before and fails now with no other change, the most likely cause is that WoR-Flasher.app was rebuilt or reinstalled since it was last granted access — see below.
WoR-Flasher detects this specific failure and opens System Settings > Privacy & Security > Full Disk Access for you automatically, naming the exact app that needs the toggle: WoR-Flasher.app and its bundle path for the packaged app, or Terminal.app/iTerm.app for a CLI run. In GUI mode, the failure dialog also shows an Open Settings button. For a mounted removable-volume denial, it opens Files and Folders first: enable the narrower Removable Volumes permission for bash when macOS lists it. If that permission is unavailable or still denied, open Full Disk Access, click +, press Shift+Command+G, enter /bin/bash, click Open, and enable its toggle. Quit the app completely (not just the window) and try again.
If the exact WoR-Flasher.app is already enabled and macOS still blocks the write, also grant Full Disk Access to the app you launched it from, such as Visual Studio Code.app, Terminal.app, or iTerm.app. WoR-Flasher is a shell-script app bundle, and macOS can attribute protected disk access to the launcher or interpreter chain instead of the displayed app bundle.
If you rebuild or move WoR-Flasher.app (for example after re-running the packaging script), macOS treats it as a new app and its previously granted Full Disk Access is revoked, so you will need to re-add and re-enable it once.
Rainbow screen
The Raspberry Pi firmware did not start UEFI. Reflash the drive and wait for verification to finish. Also update the Pi EEPROM bootloader, and avoid UEFI_USE_LATEST=1 unless you are intentionally testing firmware.
On Pi 4, three ACT LED blinks indicate that start4.elf is missing, four indicate that it failed to launch, and seven indicate that RPI_EFI.fd is missing.
UEFI splash, then freeze
On Pi 3 or Pi 4, use Windows 11 build 22631.2861 or another compatible 22631.x release. In UEFI, verify that System Table Selection is ACPI and that Secure Boot is disabled.
PXE boot, or no local boot option
Reflash with the default pinned UEFI firmware and wait for Written image verified successfully. WoR-Flasher pins Pi 4 UEFI to v1.50, because v1.52 and v1.53 do not boot from microSD (pftf/RPi4#285). Avoid UEFI_USE_LATEST=1 on a Pi 4 for the same reason.
During first boot, Windows Setup creates Windows Boot Manager. If an installed system has lost that entry, open Boot Maintenance Manager > Boot Options > Add Boot Option, select EFI\Microsoft\Boot\bootmgfw.efi, and place it above the network boot entries.
Ethernet does not work, and the MAC address is all zeros
In Windows, ipconfig /all shows the Broadcom GENET adapter with a physical address of 00-00-00-00-00-00 and only an APIPA address (169.254.x.x). The driver is fine; the UEFI firmware never gave it a MAC.
This affects Pi 4 UEFI v1.51 and v1.52 (pftf/RPi4#283). WoR-Flasher now pins v1.50, which is unaffected, so reflashing with the default settings fixes it. Do not work around it with UEFI_USE_LATEST=1: v1.53 fixes the MAC but does not boot from microSD.
To fix an existing installation without reflashing, either update the firmware on the boot partition using the boot partition mount utility, or set a MAC by hand in Device Manager > the adapter > Advanced > Network Address.
Several "Unknown device" entries in Device Manager
Expected. No Windows drivers exist for some Pi hardware - the CYW43455 Wi-Fi, the camera interface, and VCHIQ among others. See the driver status table for what is and is not supported. Wi-Fi in particular will not work; use Ethernet or a supported USB adapter.
WoR-PE says the initialization disk must be recreated
The installer cannot find unallocated space for the Windows target partition. Reflash with a current checkout and wait for final verification. Do not manually expand WOR_INSTALL; the unused space after that staging partition is required during installation.
Keyboard does not work in UEFI
Press Esc repeatedly immediately after power-on. Connect a wired keyboard directly to a USB 2.0 port, and disconnect hubs and unnecessary USB devices.
Only 3 GB of RAM on Pi 4
This is disabled automatically by default; see Pi 4 RAM unlock. To do it manually instead, set Device Manager > Raspberry Pi Configuration > Advanced Configuration > Limit RAM to 3 GB to Disabled (see the worproject FAQ). On Compute Module 4, set the limit to 1 GB instead; leaving it fully disabled or at 3 GB breaks USB.
./tests/run-tests.sh # static checks, plus Linux integration where available
./tests/run-tests.sh --macos-auth # mocked finalizer authorization; no sudo or disks
./tests/run-tests.sh --gui # walk the GUI in DRY_RUN mode
./tests/run-tests.sh --walkthrough # fake drives, then the CLI interactively
./tests/run-linux-integration.sh # force the Dockerised Linux suite
LINUX_TEST_IMAGE=node:22-bookworm-slim ./tests/run-linux-integration.sh # include Node-based Linux checks
npm run check # shell syntax, package-plan checks, and release-tool syntax
npm run build:macos # generate the .app and, on macOS, the release .dmg
npm run build:standalone # generate release/standalone/install-wor.sh
node src/package-macos-app.mjs --check # verify generated macOS runtime matches canonical sources
shellcheck --severity=error src/standalone-launcher.sh src/lib/*.sh install-wor.sh install-wor-gui.sh install-wor-hook.sh src/macos-app/Contents/MacOS/WoR-Flasher tests/*.shThe suite creates loopback devices as stand-in drives, so nothing can be written to physical storage. Tests call the real functions out of install-wor.sh rather than restating their logic, which means a test cannot pass against behaviour the shipped script no longer has.
The Linux wrapper installs its test dependencies, including Python for answer-file XML validation, only inside its disposable container. Its default Ubuntu image does not include Node.js; use the Node image above to exercise the Node-based checks on Linux as well. Platform-specific macOS tests still run on the macOS host.
On a non-Linux host the run prints three summaries — the Docker container's nested run, the integration wrapper, then the host's own run. All three must report failed 0.
CI runs ShellCheck plus the suite on Ubuntu and macOS, and a one-model dry-run integration pass. See CONTRIBUTING.md for house style and for the traps that have already caught us.
Pushing a new semantic version tag matching product.version in
src/config/metadata.json publishes a GitHub Release after those checks pass.
The shell metadata module reads that same canonical value.
The same workflow can be manually dispatched with tag_name: auto to select the next patch, or an
explicit vX.Y.Z tag once the workflow is available on the repository's default branch.
By default, manual releases update the shared version metadata, macOS app
bundle metadata, documentation histories, and embedded runtime before validation; select Do not
update project version files before validating a new manual tag only when those changes are
already committed. A new manual tag is created and published only after validation. Each release
includes the two minimal
wor-flasher-<version>-linux-cli.tar.gz and wor-flasher-<version>-linux-gui.tar.gz clients, a
WoR-Flasher-<version>-macos.dmg containing the app, the app launcher's verified runtime-update payload,
the cross-platform install-wor.sh standalone client, and SHA256SUMS. Every future release built
by this workflow includes the standalone script and its checksum alongside the macOS and Linux packages.
Runtime distributions include this README, LICENSE, and NOTICE. The standalone CLI contains the
runtime source; GitHub additionally offers the full repository source archive for
each tag. The macOS app bundle is unsigned and unnotarized; verify
downloaded artifacts against SHA256SUMS before use.
The root entry points remain stable for existing users and integrations: install-wor.sh is the engine and CLI, install-wor-gui.sh is the Linux/macOS front end, and install-wor-hook.sh is the automation adapter. The macOS app template lives under src/macos-app, while npm run build:macos generates release/macos/WoR-Flasher.app with an embedded runtime and manifest from the canonical files. On macOS, src/package-macos-dmg.mjs also creates and verifies its compressed DMG. Do not edit generated release output directly.
src/package-standalone.mjs combines the canonical runtime with src/standalone-launcher.sh to produce release/standalone/install-wor.sh. Only that generated release asset is self-contained; neither the source engine nor the launcher template is a standalone download.
Shared UI artwork lives in assets/, and boot and setup inputs live in config-templates/.
Shared data and low-level helpers live under src/lib/. Entry points load these modules explicitly; the library files do not source one another:
| Module | Responsibility |
|---|---|
metadata.sh |
Product identity, asset metadata and the named macOS AppleScript host |
dependencies.sh |
Homebrew and Linux package declarations shared by launcher preflight and engine installation |
paths.sh |
Platform-neutral path resolution used by engine bootstrap |
cleanup.sh |
Shared mount, device and temporary-file cleanup registration |
| Where | For |
|---|---|
| Botspot/wor-flasher | Report issues, share feedback, request features or contribute |
| Botspot Software Discord | Real-time help with WoR-Flasher |
| WoR project Discord | Windows on Raspberry, the operating system |
| worproject.com contact | The WoR developers directly |
| Security policy | Anything that should not be public |
WoR-Flasher is a community project created by Botspot and directly maintained by Blackout Secure. This partnership improves its documentation, testing and cross-platform experience while keeping Botspot's original authorship and project direction visible.
Report issues, share feedback, request features or contribute through the Botspot/wor-flasher repository.
Support continued development by sponsoring Botspot or buying Blackout Secure a coffee through GitHub Sponsors.
Blackout Secure is a cybersecurity, secure application development, cloud and AI security consultancy. Its open-source work focuses on practical automation, privacy-conscious tooling and dependable developer workflows. Learn more at blackoutsecure.app, browse the organization's projects at github.com/blackoutsecure, or find Dr Bill McIlhargey through Linktree.
This directly maintained source is intended to strengthen the wider community around Botspot's projects, Windows on Raspberry and the people who use them.
Building on Botspot's original work, this maintained source adds:
| Area | Added capability |
|---|---|
| Hosts | macOS support alongside Debian-based Linux, including diskutil, hdiutil, sgdisk, native password handling and safe external-drive detection |
| Interfaces | Native AppKit/JXA windows on macOS, yad progress on Linux, Advanced Options on both, and an explicit install-wor.sh --gui entry point |
| Installer flow | One shared install-wor.sh engine with the GUI as a presentation layer, so validation, settings, downloads and flashing do not drift between front-ends |
| Progress and failures | File-backed progress reporting, real installer exit codes, abort handling, durable error markers and retained failure logs with a configurable WOR_LOG_FILE path |
| Safety | Boot-drive protection, free-space preflight, cached-payload SHA-256 manifests, written-image verification and clearer cache modes |
| Windows setup | Offline-OOBE support and the Pi 4 RAM-unlock action delivered to the installed OS through WoR-PE's prefinalize.cmd hook, rather than only copying files to media roots |
| Firmware and drivers | Tested Pi 4 UEFI pinning, including the v1.50 choice that avoids both the v1.51 zero-MAC bug and the v1.52/v1.53 microSD boot regression |
| Quality | Cross-platform static checks, ShellCheck, loopback-drive integration tests, XML validation, mutation-tested anti-drift checks and macOS/Linux CI |
These additions are maintained directly by Blackout Secure in cooperation with Botspot and the wider Windows on Raspberry community.
- worproject.com — the WoR-PE installer, UEFI firmware and drivers that WoR-Flasher assembles
- Advanced customization guide — the
scripts/prefinalize.cmdhook andsettings.inioptions. Written for the official WoR imager and not verified against WoR-Flasher's headless media - How can I update the drivers? — updating drivers on an already-installed system
- Boot partition mount utility — mount the boot partition later to edit
config.txtor firmware - PiMon — hardware monitor (CPU temperature and so on) for Windows on Raspberry Pi
- How to perform OS updates — using Windows Update on a WoR installation
- BVM — Botspot's newer project: Windows 11 in a KVM virtual machine on ARM Linux, rather than on bare metal
-
2.0.1
- Package the macOS release as a verified compressed DMG containing the complete app, while retaining the unpacked local app.
- Split Linux releases into minimal GUI and CLI tar.gz clients; obtain a verified matching runtime when GUI dependencies are missing.
- Add a self-contained
install-wor.shclient to release assets for macOS and supported Linux hosts. - Check Automatic Ignore Accessibility permission at macOS GUI startup, with Open Settings, Recheck, and an explicit manual fallback before flashing.
- Hide routine Automatic Ignore waiting/watching captions while retaining permission and failure warnings.
- Handle canceled, empty, and rejected administrator passwords with concise Try Again/Close guidance before writing.
- Use fresh password-free prompt state for each explicit retry, and advance progress as soon as authorization succeeds.
-
2.0.0
- Modernized the cross-platform flashing workflow, release tooling and configuration.
- Added a native standalone macOS runtime with validated, rollback-capable updates.
- Improved macOS disk preparation, remount handling and post-write verification resilience.
- Reused one administrator authorization for late disk writes and automatically chose Ignore only for the matching unreadable-disk alert during active GUI writes.
- Added password-retry resume, configurable completion sounds and desktop notifications.
- Corrected CI progress-test prerequisites and privileged loop-device inspection, and aligned release version lookup with the canonical JSON metadata.
- Included README, LICENSE, and NOTICE in both packaged runtime distributions.
- Aligned bootstrap and update discovery with the publishing repository and enforced the documented macOS 13 minimum.
- Refreshed staged files on package writes, rejected linked or stale runtime manifests, and propagated version-build failures.
- Kept the optional Pi 4 UEFI Shell handoff independent of answer-file customization.
- Reworked Advanced Options with a compact, dedicated
config.txteditor. This maintained source uses its own version line. The product name, window title, current version, runtime file list and pinned system defaults are defined insrc/config/metadata.json, loaded bysrc/lib/metadata.sh, and checked againstpackage.jsonand the macOS app property list. The macOS launcher synchronizes those values intoCFBundleDisplayName,CFBundleExecutable,CFBundleName,CFBundleShortVersionStringandCFBundleVersion. The same release history is repeated at the top ofinstall-wor.sh.
-
1.0.2
WoR-Flasher.appcan run independently of a Git checkout using an immutable embedded runtime, validated writable runtime copies under Application Support, and active/previous/embedded fallback.- Detached runtime updates reject downgrades and verify the archive digest, extracted file digests, file modes, and archive entry safety before atomic promotion.
- A standalone
install-wor-hook.shnow obtains a complete trusted checkout automatically when no adjacent engine is available. - The native macOS partnership announcement now has compatible attributed-text construction, dark-mode contrast and non-overlapping layout on current JXA runtimes.
- The partnership banner is now 800x533, so the Linux announcement window fits on screen.
yaddraws--imageat its native size and cannot scale it down. - Linux dialogs that size themselves to their content no longer log a
gtk_window_resizeassertion warning. - A double-clickable macOS app now checks for clean fast-forward updates, installs missing Homebrew formulae with consent and offers non-destructive repair of missing tracked runtime files.
- Repeated GUI launches now activate the existing macOS window instead of opening concurrent workflows, including launches from another checkout or version.
- Partnership messaging and default update checks now use the directly maintained Blackout Secure source while preserving Botspot's original authorship.
- The engine, GUI, named macOS JXA host and app property list now share the canonical
WoR-Flashername and1.0.2version metadata.
-
1.0.1
- Pi 4 UEFI pinned to v1.50, the only release where both the Ethernet MAC and microSD boot work. v1.51 (the previous pin) and v1.52 report a MAC of
00:00:00:00:00:00, leaving Windows with no DHCP (pftf/RPi4#283); v1.53 fixes that but still does not boot from microSD (pftf/RPi4#285). - The Pi 4 RAM unlock and the offline-OOBE answer file now reach the installed OS through WoR-PE's prefinalize hook. The media-root copies alone were never read, because WoR-PE applies
install.wimwith DISM rather than running Windows Setup's media flow.
- Pi 4 UEFI pinned to v1.50, the only release where both the Ethernet MAC and microSD boot work. v1.51 (the previous pin) and v1.52 report a MAC of
-
1.0.0 — First versioned Blackout Secure release.
- macOS host support:
diskutil/hdiutildrive discovery, andsgdiskGPT partitioning that keepsWOR_BOOTas partition 1. An extra ESP made the Pi 4 fall back to PXE boot. - A native macOS interface: AppKit/JXA wizard, progress window, Advanced Options window and error dialogs.
- No visible terminal in GUI mode: the engine reports progress over a file, and each front-end renders it — AppKit on macOS,
yadon Linux. Administrator access is requested through a native password dialog on both. - Post-flash verification of partitions, filesystems, boot files, WIM images and the copied
install.wimchecksum. - Offline Windows OOBE via a shipped
Autounattend.xml, on by default. - Automatic Pi 4 3 GB RAM unlock after the WoR-PE reboot, including the BCD
truncatememorycap. - Pinned, overridable UEFI firmware and driver versions. Pi 4 stays on UEFI v1.50, the only release where both the Ethernet MAC and microSD boot work.
- Cache modes with SHA-256 payload manifests, a free-space preflight, and
HideEmptyDriveswritten into the cached WoR-PEsettings.ini. - Editable
config.txtfromconfig-templates/, applied by the CLI and the GUI alike. - One engine, two front-ends:
install-wor-gui.shsourcesinstall-wor.shand adds only windows. A function defined in both files now fails a test. - Explicit
--guientry point. The front-end is never chosen by sniffingDISPLAY. - A test suite, plus ShellCheck, macOS and Linux dry-run CI.
- macOS host support:
-
0.x — Original Botspot development history. Highlights, oldest first: the initial WoR automation, the self-updater, the "next steps" window, a complete rewrite to use ESD releases, download-to-RAM support, Pi 5 support, a GitHub API fallback for UEFI firmware, empty block devices filtered out of the drive list, and SHA-256 hashed ESD image handling.
Pull requests are welcome at Botspot/wor-flasher — please read CONTRIBUTING.md first, and CODE_OF_CONDUCT.md.
Original author. WoR-Flasher was created by Botspot, who also created Pi-Apps and BVM. This maintained source rests on five years of his work, given away for free. If you find WoR-Flasher useful, consider sponsoring him.
Project contributors (historical list):
Botspot |
![]() Blackout Secure |
NoozAbooz |
Itai-Nelken |
larskanis |
Marcinoo97 |
ryanfortner |
Maintainer partnership. Blackout Secure — represented here by Dr Bill McIlhargey (links) — is partnering with Botspot to provide ongoing maintenance and support while helping improve this project. Blackout Secure's contributions include macOS host support, native progress and Advanced Options windows, post-flash verification, the shared-engine refactor, documentation, community health files, the expanded test suite, and continued community support. If WoR-Flasher helps you, consider sending Blackout Secure a cup of coffee to support that work.
Projects this tool assembles, each with its own authors and license:
- Windows on Raspberry — the PE-based installer
- RPi-Windows-Drivers — Windows ARM64 drivers for the Pi
- pftf/RPi4 and pftf/RPi3 — Raspberry Pi UEFI firmware
- worproject/rpi5-uefi — Pi 5 UEFI firmware
- UUP dump — retrieves Windows directly from Microsoft's update servers
Released under the GNU General Public License v3.0, matching Botspot's BVM.
Important
The pre-existing Botspot code shipped without a license file and therefore carries no explicit grant. The Blackout Secure additions are offered under GPL-3.0 without reservation. Read NOTICE before commercial redistribution or relicensing.
WoR-Flasher does not redistribute Windows. Proprietary components are downloaded straight from Microsoft's own update servers via UUP dump. This is legal — Raspberry Pi employees confirmed as much on the Raspberry Pi Forums. The resulting installation is unlicensed, exactly like a retail Windows ISO, and needs a product key or a pre-licensed Microsoft account to activate.
No warranty. Neither Botspot nor Blackout Secure can be held responsible for data loss.




