isozap 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isozap-0.1.0/LICENSE +21 -0
- isozap-0.1.0/PKG-INFO +223 -0
- isozap-0.1.0/README.md +192 -0
- isozap-0.1.0/pyproject.toml +45 -0
- isozap-0.1.0/setup.cfg +4 -0
- isozap-0.1.0/src/isozap/__init__.py +8 -0
- isozap-0.1.0/src/isozap/__main__.py +3 -0
- isozap-0.1.0/src/isozap/cli.py +151 -0
- isozap-0.1.0/src/isozap/core.py +22 -0
- isozap-0.1.0/src/isozap/devices.py +36 -0
- isozap-0.1.0/src/isozap/flash.py +76 -0
- isozap-0.1.0/src/isozap/models.py +22 -0
- isozap-0.1.0/src/isozap/platforms/__init__.py +1 -0
- isozap-0.1.0/src/isozap/platforms/linux.py +64 -0
- isozap-0.1.0/src/isozap/platforms/macos.py +91 -0
- isozap-0.1.0/src/isozap/platforms/windows.py +76 -0
- isozap-0.1.0/src/isozap/safety.py +31 -0
- isozap-0.1.0/src/isozap/utils.py +83 -0
- isozap-0.1.0/src/isozap/windows_media.py +148 -0
- isozap-0.1.0/src/isozap.egg-info/PKG-INFO +223 -0
- isozap-0.1.0/src/isozap.egg-info/SOURCES.txt +29 -0
- isozap-0.1.0/src/isozap.egg-info/dependency_links.txt +1 -0
- isozap-0.1.0/src/isozap.egg-info/entry_points.txt +2 -0
- isozap-0.1.0/src/isozap.egg-info/requires.txt +5 -0
- isozap-0.1.0/src/isozap.egg-info/top_level.txt +1 -0
- isozap-0.1.0/tests/test_cli.py +93 -0
- isozap-0.1.0/tests/test_core.py +37 -0
- isozap-0.1.0/tests/test_devices.py +13 -0
- isozap-0.1.0/tests/test_operations.py +84 -0
- isozap-0.1.0/tests/test_platforms.py +62 -0
- isozap-0.1.0/tests/test_safety.py +68 -0
isozap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shariar Imtiaz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
isozap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: isozap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A safe cross-platform command-line utility for creating bootable USB drives from ISO images.
|
|
5
|
+
Author: Shariar Imtiaz
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/simtiaz019/IsoZap
|
|
8
|
+
Project-URL: Issues, https://github.com/simtiaz019/IsoZap/issues
|
|
9
|
+
Keywords: iso,usb,bootable-usb,usb-flasher,iso-flasher,windows,macos,linux,cli,bootable,disk,flash
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
22
|
+
Classifier: Topic :: System :: Systems Administration
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
29
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# IsoZap
|
|
33
|
+
|
|
34
|
+
Create bootable USB drives safely from your terminal.
|
|
35
|
+
|
|
36
|
+
IsoZap is a cross-platform command-line utility for creating bootable USB drives from ISO images on Windows, macOS, and Linux.
|
|
37
|
+
|
|
38
|
+
> **WARNING:** IsoZap writes directly to storage devices. Selecting the wrong disk may permanently destroy data. IsoZap includes safeguards designed to prevent accidental system-disk selection, but users remain responsible for confirming the correct destination device.
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
IsoZap discovers conservative removable/external whole-disk targets, validates the source and capacity, displays a destructive-operation summary, and requires explicit confirmation before writing. It supports raw hybrid images and specially prepared FAT32 Windows installer media.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- Native device discovery for Windows, macOS, and Linux
|
|
47
|
+
- System-, boot-, internal-, and partition-target rejection
|
|
48
|
+
- Device identity and capacity revalidation immediately before writing
|
|
49
|
+
- Exact `ERASE` confirmation or an intentionally verbose automation flag
|
|
50
|
+
- Non-destructive `--dry-run`
|
|
51
|
+
- Source SHA-256 checking and optional raw-write read-back verification
|
|
52
|
+
- Windows x64 and ARM64 UEFI loader validation
|
|
53
|
+
- FAT32-compatible splitting of Windows `install.wim` files larger than 4 GiB
|
|
54
|
+
- Standard-library-only runtime
|
|
55
|
+
|
|
56
|
+
## Supported operating systems
|
|
57
|
+
|
|
58
|
+
| Host | Raw/hybrid images | Prepared Windows installer media |
|
|
59
|
+
|---|---:|---:|
|
|
60
|
+
| Windows | Yes | Yes, UEFI-focused |
|
|
61
|
+
| macOS | Yes | Yes, UEFI-focused |
|
|
62
|
+
| Linux | Yes | Yes, UEFI-focused |
|
|
63
|
+
|
|
64
|
+
Python 3.9 or newer is required. ChromeOS, Android, iOS, and restricted Python environments generally do not expose raw USB disks and are not supported.
|
|
65
|
+
|
|
66
|
+
## Supported ISO types and limitations
|
|
67
|
+
|
|
68
|
+
- Linux/Unix hybrid ISOs and raw `.img` files are written byte-for-byte.
|
|
69
|
+
- Arbitrary non-hybrid ISOs are not made bootable merely by writing them.
|
|
70
|
+
- Windows installer mode creates MBR + FAT32 media and validates the selected x64 or ARM64 UEFI loader.
|
|
71
|
+
- Windows installer mode does not guarantee legacy BIOS/CSM booting or support every customized ISO.
|
|
72
|
+
- Windows host support and creating media from a Windows ISO are separate capabilities.
|
|
73
|
+
- Platform logic and package builds are tested with mocks; release 0.1.0 has not yet completed broad physical-device testing.
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
Recommended:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pipx install isozap
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Alternative:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python -m pip install isozap
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Do not install IsoZap with `sudo pip` or an Administrator-level pip. Install as your normal user; elevate only the later flashing command when required.
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
isozap --help
|
|
95
|
+
isozap --version
|
|
96
|
+
python -m isozap --help
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The existing direct command style is retained:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
isozap IMAGE --type {hybrid,raw,windows}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Device listing
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
isozap --list
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Only whole disks considered removable or external and not identified as system/boot disks are displayed.
|
|
112
|
+
|
|
113
|
+
## Flashing an ISO
|
|
114
|
+
|
|
115
|
+
macOS or Linux:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
sudo "$(command -v isozap)" ~/Downloads/ubuntu.iso --type hybrid
|
|
119
|
+
sudo "$(command -v isozap)" ~/Downloads/windows.iso --type windows --arch x64
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Windows Administrator PowerShell:
|
|
123
|
+
|
|
124
|
+
```powershell
|
|
125
|
+
isozap C:\Users\me\Downloads\ubuntu.iso --type hybrid
|
|
126
|
+
isozap C:\Users\me\Downloads\windows.iso --type windows --arch x64
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Verify a publisher-provided source checksum and read back a raw write:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sudo "$(command -v isozap)" image.iso --type hybrid --sha256 EXPECTED_SHA256 --verify-write
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For automation, `--device` still requires a currently safe discovered disk and `--yes-i-really-mean-it` explicitly replaces the prompt:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
sudo "$(command -v isozap)" image.img --type raw --device /dev/sdX --yes-i-really-mean-it
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Never guess a device identifier.
|
|
142
|
+
|
|
143
|
+
## Dry-run
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
isozap image.iso --type hybrid --device /dev/sdX --dry-run
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Dry-run performs source, target, identity, and capacity checks and displays the intended operation. It does not mount, unmount, repartition, format, write, or eject any device. Windows installer structural mounting is deferred until an actual operation.
|
|
150
|
+
|
|
151
|
+
## Safety protections
|
|
152
|
+
|
|
153
|
+
IsoZap rejects partitions, internal/non-removable targets, known system or boot disks, insufficient capacity, targets that disappear, and targets whose size/name/hardware identity changes between selection and execution. Windows explicitly maps the running system drive; macOS protects startup and reported APFS physical stores; Linux follows the block-device tree containing `/`, `/boot`, or `/boot/efi`.
|
|
154
|
+
|
|
155
|
+
No automated safeguard can replace checking the displayed identifier, model, and size.
|
|
156
|
+
|
|
157
|
+
## Administrator/root requirements
|
|
158
|
+
|
|
159
|
+
Listing and dry-run do not require application installation as an administrator. Actual raw-device access does. Windows users should open an Administrator terminal. macOS/Linux users should install normally, then use `sudo` only when launching the destructive flash operation.
|
|
160
|
+
|
|
161
|
+
## Windows notes
|
|
162
|
+
|
|
163
|
+
IsoZap uses structured `Get-Disk`/`Get-Partition` PowerShell data and `\\.\PhysicalDriveN`; drive letters are never treated as physical disks. Raw writes take the selected disk offline during access and restore it afterward. Windows installer mode uses diskpart because repartitioning is necessary for MBR + FAT32 media. Windows' built-in formatter limits FAT32 to 32 GiB, so larger USB drives receive a 32 GiB installer partition and leave the remaining space unallocated.
|
|
164
|
+
|
|
165
|
+
## macOS notes
|
|
166
|
+
|
|
167
|
+
IsoZap uses plist output from `diskutil`, rejects internal disks and the startup disk/known APFS backing stores, unmounts the selected whole disk, and writes through `/dev/rdiskN` for raw-image performance.
|
|
168
|
+
|
|
169
|
+
## Linux notes
|
|
170
|
+
|
|
171
|
+
IsoZap uses JSON `lsblk` data and protects the top-level disk containing `/`, `/boot`, or `/boot/efi`, including nested LVM/device-mapper layouts represented in the block tree. Windows media additionally needs `parted`, `dosfstools`, and mount tools.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
macOS/Linux:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python3 -m venv .venv
|
|
179
|
+
source .venv/bin/activate
|
|
180
|
+
python -m pip install -e ".[dev]"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Windows PowerShell:
|
|
184
|
+
|
|
185
|
+
```powershell
|
|
186
|
+
py -m venv .venv
|
|
187
|
+
.\.venv\Scripts\Activate.ps1
|
|
188
|
+
python -m pip install -e ".[dev]"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Testing
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
python -m pytest
|
|
195
|
+
isozap --help
|
|
196
|
+
isozap --version
|
|
197
|
+
python -m isozap --help
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Tests mock destructive and system-level operations. Never point a test at a real disk.
|
|
201
|
+
|
|
202
|
+
## Building
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
python -m build
|
|
206
|
+
python -m twine check dist/*
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Contributing
|
|
210
|
+
|
|
211
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Platform changes require mocked safety tests.
|
|
212
|
+
|
|
213
|
+
## Security
|
|
214
|
+
|
|
215
|
+
See [SECURITY.md](SECURITY.md) for private reporting guidance and security scope.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
IsoZap is available under the MIT License. See [LICENSE](LICENSE).
|
|
220
|
+
|
|
221
|
+
## Disclaimer
|
|
222
|
+
|
|
223
|
+
IsoZap is provided without warranty. Successful writing does not guarantee that an image supports USB booting or that the target firmware supports the image. Back up important data and test only with a disposable USB drive until you are confident in your platform and image combination.
|
isozap-0.1.0/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# IsoZap
|
|
2
|
+
|
|
3
|
+
Create bootable USB drives safely from your terminal.
|
|
4
|
+
|
|
5
|
+
IsoZap is a cross-platform command-line utility for creating bootable USB drives from ISO images on Windows, macOS, and Linux.
|
|
6
|
+
|
|
7
|
+
> **WARNING:** IsoZap writes directly to storage devices. Selecting the wrong disk may permanently destroy data. IsoZap includes safeguards designed to prevent accidental system-disk selection, but users remain responsible for confirming the correct destination device.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
IsoZap discovers conservative removable/external whole-disk targets, validates the source and capacity, displays a destructive-operation summary, and requires explicit confirmation before writing. It supports raw hybrid images and specially prepared FAT32 Windows installer media.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Native device discovery for Windows, macOS, and Linux
|
|
16
|
+
- System-, boot-, internal-, and partition-target rejection
|
|
17
|
+
- Device identity and capacity revalidation immediately before writing
|
|
18
|
+
- Exact `ERASE` confirmation or an intentionally verbose automation flag
|
|
19
|
+
- Non-destructive `--dry-run`
|
|
20
|
+
- Source SHA-256 checking and optional raw-write read-back verification
|
|
21
|
+
- Windows x64 and ARM64 UEFI loader validation
|
|
22
|
+
- FAT32-compatible splitting of Windows `install.wim` files larger than 4 GiB
|
|
23
|
+
- Standard-library-only runtime
|
|
24
|
+
|
|
25
|
+
## Supported operating systems
|
|
26
|
+
|
|
27
|
+
| Host | Raw/hybrid images | Prepared Windows installer media |
|
|
28
|
+
|---|---:|---:|
|
|
29
|
+
| Windows | Yes | Yes, UEFI-focused |
|
|
30
|
+
| macOS | Yes | Yes, UEFI-focused |
|
|
31
|
+
| Linux | Yes | Yes, UEFI-focused |
|
|
32
|
+
|
|
33
|
+
Python 3.9 or newer is required. ChromeOS, Android, iOS, and restricted Python environments generally do not expose raw USB disks and are not supported.
|
|
34
|
+
|
|
35
|
+
## Supported ISO types and limitations
|
|
36
|
+
|
|
37
|
+
- Linux/Unix hybrid ISOs and raw `.img` files are written byte-for-byte.
|
|
38
|
+
- Arbitrary non-hybrid ISOs are not made bootable merely by writing them.
|
|
39
|
+
- Windows installer mode creates MBR + FAT32 media and validates the selected x64 or ARM64 UEFI loader.
|
|
40
|
+
- Windows installer mode does not guarantee legacy BIOS/CSM booting or support every customized ISO.
|
|
41
|
+
- Windows host support and creating media from a Windows ISO are separate capabilities.
|
|
42
|
+
- Platform logic and package builds are tested with mocks; release 0.1.0 has not yet completed broad physical-device testing.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
Recommended:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pipx install isozap
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Alternative:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python -m pip install isozap
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Do not install IsoZap with `sudo pip` or an Administrator-level pip. Install as your normal user; elevate only the later flashing command when required.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
isozap --help
|
|
64
|
+
isozap --version
|
|
65
|
+
python -m isozap --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The existing direct command style is retained:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
isozap IMAGE --type {hybrid,raw,windows}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Device listing
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
isozap --list
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Only whole disks considered removable or external and not identified as system/boot disks are displayed.
|
|
81
|
+
|
|
82
|
+
## Flashing an ISO
|
|
83
|
+
|
|
84
|
+
macOS or Linux:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
sudo "$(command -v isozap)" ~/Downloads/ubuntu.iso --type hybrid
|
|
88
|
+
sudo "$(command -v isozap)" ~/Downloads/windows.iso --type windows --arch x64
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Windows Administrator PowerShell:
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
isozap C:\Users\me\Downloads\ubuntu.iso --type hybrid
|
|
95
|
+
isozap C:\Users\me\Downloads\windows.iso --type windows --arch x64
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Verify a publisher-provided source checksum and read back a raw write:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
sudo "$(command -v isozap)" image.iso --type hybrid --sha256 EXPECTED_SHA256 --verify-write
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For automation, `--device` still requires a currently safe discovered disk and `--yes-i-really-mean-it` explicitly replaces the prompt:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
sudo "$(command -v isozap)" image.img --type raw --device /dev/sdX --yes-i-really-mean-it
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Never guess a device identifier.
|
|
111
|
+
|
|
112
|
+
## Dry-run
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
isozap image.iso --type hybrid --device /dev/sdX --dry-run
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Dry-run performs source, target, identity, and capacity checks and displays the intended operation. It does not mount, unmount, repartition, format, write, or eject any device. Windows installer structural mounting is deferred until an actual operation.
|
|
119
|
+
|
|
120
|
+
## Safety protections
|
|
121
|
+
|
|
122
|
+
IsoZap rejects partitions, internal/non-removable targets, known system or boot disks, insufficient capacity, targets that disappear, and targets whose size/name/hardware identity changes between selection and execution. Windows explicitly maps the running system drive; macOS protects startup and reported APFS physical stores; Linux follows the block-device tree containing `/`, `/boot`, or `/boot/efi`.
|
|
123
|
+
|
|
124
|
+
No automated safeguard can replace checking the displayed identifier, model, and size.
|
|
125
|
+
|
|
126
|
+
## Administrator/root requirements
|
|
127
|
+
|
|
128
|
+
Listing and dry-run do not require application installation as an administrator. Actual raw-device access does. Windows users should open an Administrator terminal. macOS/Linux users should install normally, then use `sudo` only when launching the destructive flash operation.
|
|
129
|
+
|
|
130
|
+
## Windows notes
|
|
131
|
+
|
|
132
|
+
IsoZap uses structured `Get-Disk`/`Get-Partition` PowerShell data and `\\.\PhysicalDriveN`; drive letters are never treated as physical disks. Raw writes take the selected disk offline during access and restore it afterward. Windows installer mode uses diskpart because repartitioning is necessary for MBR + FAT32 media. Windows' built-in formatter limits FAT32 to 32 GiB, so larger USB drives receive a 32 GiB installer partition and leave the remaining space unallocated.
|
|
133
|
+
|
|
134
|
+
## macOS notes
|
|
135
|
+
|
|
136
|
+
IsoZap uses plist output from `diskutil`, rejects internal disks and the startup disk/known APFS backing stores, unmounts the selected whole disk, and writes through `/dev/rdiskN` for raw-image performance.
|
|
137
|
+
|
|
138
|
+
## Linux notes
|
|
139
|
+
|
|
140
|
+
IsoZap uses JSON `lsblk` data and protects the top-level disk containing `/`, `/boot`, or `/boot/efi`, including nested LVM/device-mapper layouts represented in the block tree. Windows media additionally needs `parted`, `dosfstools`, and mount tools.
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
macOS/Linux:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python3 -m venv .venv
|
|
148
|
+
source .venv/bin/activate
|
|
149
|
+
python -m pip install -e ".[dev]"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Windows PowerShell:
|
|
153
|
+
|
|
154
|
+
```powershell
|
|
155
|
+
py -m venv .venv
|
|
156
|
+
.\.venv\Scripts\Activate.ps1
|
|
157
|
+
python -m pip install -e ".[dev]"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python -m pytest
|
|
164
|
+
isozap --help
|
|
165
|
+
isozap --version
|
|
166
|
+
python -m isozap --help
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Tests mock destructive and system-level operations. Never point a test at a real disk.
|
|
170
|
+
|
|
171
|
+
## Building
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
python -m build
|
|
175
|
+
python -m twine check dist/*
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Contributing
|
|
179
|
+
|
|
180
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Platform changes require mocked safety tests.
|
|
181
|
+
|
|
182
|
+
## Security
|
|
183
|
+
|
|
184
|
+
See [SECURITY.md](SECURITY.md) for private reporting guidance and security scope.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
IsoZap is available under the MIT License. See [LICENSE](LICENSE).
|
|
189
|
+
|
|
190
|
+
## Disclaimer
|
|
191
|
+
|
|
192
|
+
IsoZap is provided without warranty. Successful writing does not guarantee that an image supports USB booting or that the target firmware supports the image. Back up important data and test only with a disposable USB drive until you are confident in your platform and image combination.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "isozap"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A safe cross-platform command-line utility for creating bootable USB drives from ISO images."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Shariar Imtiaz" }]
|
|
13
|
+
keywords = ["iso", "usb", "bootable-usb", "usb-flasher", "iso-flasher", "windows", "macos", "linux", "cli", "bootable", "disk", "flash"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Operating System :: MacOS",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: System :: Installation/Setup",
|
|
27
|
+
"Topic :: System :: Systems Administration",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
isozap = "isozap.cli:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/simtiaz019/IsoZap"
|
|
35
|
+
Issues = "https://github.com/simtiaz019/IsoZap/issues"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["build>=1.2", "pytest>=8", "twine>=5"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
addopts = "-q"
|
|
45
|
+
testpaths = ["tests"]
|
isozap-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .devices import safe_disks
|
|
9
|
+
from .flash import raw_write, sha256
|
|
10
|
+
from .models import Disk
|
|
11
|
+
from .safety import assert_safe
|
|
12
|
+
from .utils import IsoZapError, host_os, human_bytes, require_admin, validate_image
|
|
13
|
+
from .windows_media import create_windows_media, inspect_windows_iso
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parser() -> argparse.ArgumentParser:
|
|
17
|
+
result = argparse.ArgumentParser(
|
|
18
|
+
prog="isozap",
|
|
19
|
+
description="Create bootable USB drives safely from your terminal.",
|
|
20
|
+
)
|
|
21
|
+
result.add_argument("image", nargs="?", type=Path, help="ISO or IMG file")
|
|
22
|
+
result.add_argument("--type", choices=("windows", "hybrid", "raw"), dest="image_type", help="image creation method")
|
|
23
|
+
result.add_argument("--arch", choices=("x64", "arm64"), default="x64", help="Windows target architecture")
|
|
24
|
+
result.add_argument("--device", help="whole target device; must be detected as removable/external")
|
|
25
|
+
result.add_argument("--sha256", dest="expected_hash", help="verify the source's publisher-provided SHA-256")
|
|
26
|
+
result.add_argument("--verify-write", action="store_true", help="read back and checksum a raw write")
|
|
27
|
+
result.add_argument("--dry-run", action="store_true", help="validate and show operations without changing a device")
|
|
28
|
+
result.add_argument("--list", action="store_true", help="list safe removable/external disks and exit")
|
|
29
|
+
result.add_argument("--yes-i-really-mean-it", action="store_true", help="non-interactive destructive confirmation")
|
|
30
|
+
result.add_argument("--version", action="version", version=f"IsoZap {__version__}")
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def choose(prompt: str, values: list, render):
|
|
35
|
+
if not values:
|
|
36
|
+
raise IsoZapError("No safe removable USB disks were detected.")
|
|
37
|
+
print(prompt)
|
|
38
|
+
for index, value in enumerate(values, 1):
|
|
39
|
+
print(f" {index}) {render(value)}")
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
selected = int(input("Selection: "))
|
|
43
|
+
if 1 <= selected <= len(values):
|
|
44
|
+
return values[selected - 1]
|
|
45
|
+
except ValueError:
|
|
46
|
+
pass
|
|
47
|
+
print("Enter a number from the list.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def progress(label: str):
|
|
51
|
+
def report(done: int, total: int, elapsed: float) -> None:
|
|
52
|
+
speed = done / elapsed
|
|
53
|
+
end = "\n" if done == total else ""
|
|
54
|
+
print(
|
|
55
|
+
f"\r{label}: {done * 100 / total:6.2f}% "
|
|
56
|
+
f"{human_bytes(done)} / {human_bytes(total)} {human_bytes(int(speed))}/s",
|
|
57
|
+
end=end, flush=True,
|
|
58
|
+
)
|
|
59
|
+
return report
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _select_disk(requested: str | None, disks: list[Disk]) -> Disk:
|
|
63
|
+
if requested:
|
|
64
|
+
matches = [item for item in disks if item.path.casefold() == requested.casefold()]
|
|
65
|
+
if not matches:
|
|
66
|
+
raise IsoZapError("Requested target is not in the safe whole removable-disk list.")
|
|
67
|
+
return matches[0]
|
|
68
|
+
return choose(
|
|
69
|
+
"Target USB (ALL DATA WILL BE DESTROYED):",
|
|
70
|
+
disks,
|
|
71
|
+
lambda item: f"{item.path} — {human_bytes(item.size)} — {item.name}",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _overview(system: str, image: Path, disk: Disk, image_type: str) -> None:
|
|
76
|
+
print("\nWARNING: ALL DATA ON THIS DEVICE WILL BE DESTROYED.\n")
|
|
77
|
+
print(f"Host OS: {system}")
|
|
78
|
+
print(f"ISO/image: {image}")
|
|
79
|
+
print(f"ISO size: {human_bytes(image.stat().st_size)}")
|
|
80
|
+
print(f"Device: {disk.path}")
|
|
81
|
+
print(f"Model: {disk.name}")
|
|
82
|
+
print(f"Size: {human_bytes(disk.size)}")
|
|
83
|
+
print(f"External: {'Yes' if disk.external else 'No'}")
|
|
84
|
+
print(f"Removable: {'Yes' if disk.removable else 'No'}")
|
|
85
|
+
print(f"Method: {'FAT32 Windows installer preparation' if image_type == 'windows' else 'raw image write'}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main(argv: list[str] | None = None) -> int:
|
|
89
|
+
args = parser().parse_args(argv)
|
|
90
|
+
try:
|
|
91
|
+
system = host_os()
|
|
92
|
+
disks = safe_disks()
|
|
93
|
+
if args.list:
|
|
94
|
+
if not disks:
|
|
95
|
+
print(f"No safe removable USB disks detected on {system}.")
|
|
96
|
+
for disk in disks:
|
|
97
|
+
print(f"{disk.path}\t{human_bytes(disk.size)}\t{disk.name}\t{disk.transport}")
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
image_input = args.image or Path(input("Path to ISO/IMG: ").strip().strip("\"'"))
|
|
101
|
+
image = validate_image(image_input)
|
|
102
|
+
image_type = args.image_type or choose("Image type:", ["windows", "hybrid", "raw"], str)
|
|
103
|
+
disk = _select_disk(args.device, disks)
|
|
104
|
+
disk = assert_safe(disk, image, safe_disks)
|
|
105
|
+
|
|
106
|
+
if args.expected_hash:
|
|
107
|
+
print("Verifying source SHA-256...")
|
|
108
|
+
actual = sha256(image)
|
|
109
|
+
if actual.casefold() != args.expected_hash.casefold():
|
|
110
|
+
raise IsoZapError(f"SHA-256 mismatch. Expected {args.expected_hash}, got {actual}.")
|
|
111
|
+
print("Source SHA-256 verified.")
|
|
112
|
+
|
|
113
|
+
needs_split = inspect_windows_iso(image, args.arch) if image_type == "windows" and not args.dry_run else False
|
|
114
|
+
_overview(system, image, disk, image_type)
|
|
115
|
+
if args.dry_run:
|
|
116
|
+
print("\nDRY RUN: safety checks passed; no device was unmounted, formatted, or written.")
|
|
117
|
+
if image_type == "windows":
|
|
118
|
+
print("Windows ISO structural mounting is deferred until an actual operation.")
|
|
119
|
+
return 0
|
|
120
|
+
if not args.yes_i_really_mean_it and input("\nType exactly ERASE to continue: ") != "ERASE":
|
|
121
|
+
print("Cancelled; no changes were made.")
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
require_admin()
|
|
125
|
+
disk = assert_safe(disk, image, safe_disks)
|
|
126
|
+
if image_type == "windows":
|
|
127
|
+
create_windows_media(image, disk, args.arch, needs_split)
|
|
128
|
+
print("Write completed; required Windows boot files were found on the USB.")
|
|
129
|
+
else:
|
|
130
|
+
verified = raw_write(
|
|
131
|
+
image, disk, progress("Writing"), verify=args.verify_write,
|
|
132
|
+
verify_progress=progress("Verifying"),
|
|
133
|
+
)
|
|
134
|
+
if args.verify_write:
|
|
135
|
+
if not verified:
|
|
136
|
+
raise IsoZapError("Write completed, but read-back verification failed.")
|
|
137
|
+
print("Write completed and read-back SHA-256 verification passed.")
|
|
138
|
+
else:
|
|
139
|
+
print("Write completed. Read-back verification was not requested.")
|
|
140
|
+
print("The USB has been ejected where supported and can be removed safely.")
|
|
141
|
+
return 0
|
|
142
|
+
except KeyboardInterrupt:
|
|
143
|
+
print("\nCancelled by user.", file=sys.stderr)
|
|
144
|
+
return 130
|
|
145
|
+
except (IsoZapError, OSError, ValueError) as exc:
|
|
146
|
+
print(f"ERROR: {exc}", file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Compatibility exports for IsoZap's original public module."""
|
|
2
|
+
|
|
3
|
+
from .devices import safe_disks as list_disks
|
|
4
|
+
from .flash import raw_write, sha256
|
|
5
|
+
from .models import Disk
|
|
6
|
+
from .platforms.windows import physical_drive_number
|
|
7
|
+
from .utils import IsoZapError, host_os, human_bytes, require_admin, require_commands, run
|
|
8
|
+
|
|
9
|
+
FAT32_MAX = 4_294_967_295
|
|
10
|
+
WIM_SPLIT_MB = 3_800
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_target(disk: Disk, image_size: int) -> None:
|
|
14
|
+
current = {item.path: item for item in list_disks()}
|
|
15
|
+
if disk.path not in current:
|
|
16
|
+
raise IsoZapError("The selected removable disk is no longer present or is no longer safe.")
|
|
17
|
+
if image_size > current[disk.path].size:
|
|
18
|
+
raise IsoZapError("The source image is larger than the selected disk.")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def windows_loader(arch: str) -> str:
|
|
22
|
+
return "efi/boot/bootx64.efi" if arch == "x64" else "efi/boot/bootaa64.efi"
|