Skip to main content

diskmgr

Python License Platform Status Dependencies

Tests Lint Typed Build TUI SemVer Changelog PyPI

Manage hard disks and USB drives from one tool: list what is attached, copy or mirror files between drives, and clone a whole disk sector-for-sector.

Windows-first (it uses the Storage PowerShell module and raw \\.\PhysicalDrive access); disk listing and all file-level operations also work on Linux.

Disclaimer

⚠️ READ BEFORE USE. This software permanently destroys data by design. Cloning, imaging and mirroring overwrite entire disks and delete files. A mistyped disk number or path can wipe the wrong drive in seconds, and there is no undo.

USE ENTIRELY AT YOUR OWN RISK. This software is provided "AS IS", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and non-infringement. See the licence for the full terms, which govern.

To the maximum extent permitted by applicable law, the author and contributors accept no liability whatsoever for any claim, damages or other liability — including but not limited to data loss, data corruption, hardware damage, rendering a system unbootable, business interruption, lost profits, or any direct, indirect, incidental, special, exemplary or consequential damages — arising from or in connection with this software or its use, however caused and on any theory of liability, even if advised of the possibility of such damage.

You are solely responsible for:

  • verifying that the target disk is the one you intend to write to, every time;
  • maintaining tested, independent backups before running any destructive operation;
  • confirming you are authorised to access and modify the devices concerned;
  • complying with all applicable laws, regulations, licences and organisational policies where you use it.

This is not certified, validated or warranted for any regulated, forensic, evidentiary, medical, safety-critical, or otherwise high-assurance purpose, and must not be relied upon as the sole safeguard for irreplaceable data. Its safety checks reduce risk; they do not eliminate it, and they can be overridden deliberately.

If you do not accept these terms, do not install or use this software.

Why

Copying a folder to a backup drive and cloning a bootable USB stick are two different jobs that usually need two different tools. This does both, with the same safety model:

  • Nothing destructive happens without a typed confirmation. Erasing disk 2 means typing ERASE DISK 2, not pressing y.
  • The system disk is refused outright, and no combination of flags erases the running OS without that phrase.
  • --dry-run prints the exact plan a real run would execute — the same plan object, not an approximation of it.
  • Everything written is verified: files by hash, disks by reading the target back and comparing digests.

Install

pip install diskmgr                # CLI only, zero dependencies
pip install "diskmgr[tui]"         # adds the interactive terminal UI

From a clone:

pip install -e ".[dev]"

Usage

The CLI is diskmgr; python -m disk_manager works too.

See what is attached

$ diskmgr list
 #  MODEL                              SIZE  BUS     STYLE   FLAGS        VOLUMES
---------------------------------------------------------------------------------
 0  UMIS RPJTJ512MGE1QDQ           476.9 GB  NVMe    GPT     SYSTEM       C: [OS] NTFS, D: [Data] NTFS
 1  SanDisk Ultra USB 3.0           28.9 GB  USB     MBR     REMOVABLE    E: [STICK] FAT32
diskmgr list --json              # machine-readable
diskmgr list --removable-only    # just the USB sticks and SD cards
diskmgr info 1                   # one disk in full, including write safety

diskmgr info also tells you whether the disk may be written to and what the confirmation phrase would be:

  Write safety  : REFUSED
  BLOCKED: disk 0 carries the running operating system (C:, D:)

A disk can be named by index (1), device path (\\.\PhysicalDrive1), or a drive letter it hosts (E:).

Check drive health

$ diskmgr health
 #  MODEL                         HEALTH      TEMP      HOURS    USED  SOURCE
------------------------------------------------------------------------------------------------
 0  UMIS RPJTJ512MGE1QDQ          GOOD        38 C     21,812     21%  nvme-ioctl
$ diskmgr health 0
Disk 0: UMIS RPJTJ512MGE1QDQ (476.9 GB, NVMe, C:, D:)
  Health        : GOOD
  Reported by   : nvme-ioctl
  Temperature   : 38 C
  Power-on time : 21,812 hours (2.5 years)
  Power cycles  : 1,304
  Life used     : 21% of rated endurance
  Spare         : 100% (threshold 10%)
  Host written  : 48.0 TB
  Media errors  : 0

NVMe health needs no elevation. The drive is opened query-only, which is what makes an unprivileged read work: asking for GENERIC_READ on the same device fails where asking for no access at all succeeds.

Every report names the route that answered it, because "GOOD" from a full NVMe health log and "GOOD" from Windows' own lax HealthStatus are very different claims. --json always carries source and available, so a script can tell "this drive is healthy" from "nothing could be read".

Honest limits, rather than a number that looks authoritative:

  • NVMe drives report fully, unelevated, which covers most modern machines.
  • USB-attached drives usually report nothing. The bridge hides SMART, and real support means per-vendor quirks this tool does not carry.
  • ATA/SATA drives report their SMART attribute table (reallocated and pending sectors, CRC errors, temperature, power-on hours), but this needs an elevated terminal, and the transport has not been verified against real SATA hardware - the decoder is covered by tests, the plumbing that feeds it is not. A drive that does not answer falls back to Windows' own health status, which is a floor rather than a diagnosis: a drive with hundreds of reallocated sectors can still report Healthy.
  • A UDMA CRC error (C7) is reported as a cable or connector fault, not as drive failure. Treating it as failure would condemn every drive that was ever hot-plugged.
  • There is no composite "health percentage". A single score cannot carry its own justification, so the verdict is one of GOOD / CAUTION / BAD and every rule that fires says why.
  • Endurance past 100% is reported as CAUTION, not BAD. It is wear, not failure: the drive is specified to keep working.

--fail-on-caution exits non-zero when any drive is not healthy, for monitoring. Without it, a successful query of a failing drive still exits 0, so "the tool broke" stays distinguishable from "the drive is dying".

Scan the surface for bad blocks

diskmgr scan 1                        # read every sector, time every block
diskmgr scan 1 --limit-bytes 1073741824   # just the first GB
diskmgr scan 1 --start-offset 5000000000  # resume roughly where you stopped

A read-only pass that records where a disk is slow and where it cannot be read at all, reporting a latency histogram and the byte offset of every unreadable sector. It needs elevation, because raw device reads do, and it takes hours on a large spinning disk, which is what --limit-bytes and --start-offset are for.

There are no write, butterfly or erase patterns. Those destroy data, and this tool's guardrails are designed to fail closed.

Copy files between drives

# Add new and changed files; never deletes anything
diskmgr copy D:\projects E:\backup\projects

# Make the destination identical to the source, deleting extras
diskmgr copy D:\projects E:\backup\projects --mode mirror

# Only take files that are strictly newer than the destination copy
diskmgr copy D:\projects E:\backup\projects --mode sync

Useful flags:

Flag Effect
--dry-run Print the plan, write nothing
--show-files List every planned copy/overwrite/delete
--verify hash Hash every written file against its source (default is size)
--exclude GLOB Skip matching files or folders; repeatable
--include GLOB Only copy matching files; repeatable
--retries N Retries per file on a transient I/O error (default 2)
--stop-on-error Abort on the first failure instead of continuing
diskmgr copy D:\code E:\backup --mode mirror \
    --exclude "*.tmp" --exclude node_modules --exclude .git \
    --verify hash --dry-run --show-files

Each file is written to a temporary name and atomically renamed into place, so an interrupted run leaves either the old file or the complete new one — never a half-written file that the next run would mistake for up to date.

Resuming an interrupted copy

diskmgr copy D:\projects E:\backup --resume
diskmgr copy D:\projects E:\backup --no-journal   # record nothing

A journalled copy records each completed file under .diskmgr/ in the destination, so a four-hour transfer that dies at 90% can pick up rather than start over. .diskmgr/ is excluded from every scan, so a mirror run cannot delete its own journal and a nested copy cannot copy it.

Records are appended after the file is renamed into place, never before. A crash between the two costs one redundant re-copy; the reverse ordering would skip a file that was never written.

Resuming is refused, and the full plan runs instead, when any of these hold:

  • the source, destination, mode or filters differ from the journalled run (they are hashed into a plan id, and a different id is ignored);
  • a source file changed since it was copied;
  • a destination file has gone missing since;
  • the earlier run verified by size and this one verifies by hash.

Much of the time the planner's own size/mtime diff already spots the finished files and reports them as up to date, and the tool says so rather than claiming credit. The journal earns its keep where that diff cannot tell: carrying hash verification forward, and catching a file that looks identical but was never actually written.

Interrupted runs can also leave a stale .dmpart behind if the process was killed outright. --resume sweeps those first.

Image a disk

# Read a USB stick into an image file (.gz compresses it)
diskmgr image save 1 D:\images\stick.img.gz

# Write it back, to the same stick or a different one
diskmgr image restore D:\images\stick.img.gz 1

image save is read-only but still needs elevation, because raw device access does. --skip-bad-sectors re-reads a failing block one sector at a time and zero-fills only the sectors that genuinely cannot be read, reporting the count rather than silently producing a short image.

Every write is read back and compared by default. Hashing the bytes on their way past only proves what was read from the disk, not what landed in the file, so the image is re-read after it is written: a truncated write or a full destination volume is caught rather than reported as verified. --no-verify skips that pass and says so in the output, and --no-hash computes no digest at all; neither is ever reported as a passed check.

Verify a stored image

diskmgr verify D:\images\stick.img.gz          # against its recorded digest
diskmgr verify D:\images\stick.img.gz --against 1   # against a disk

Every image save writes a .dmimg.json beside the image recording which disk it came from, when, the digest, the uncompressed size, and whether any sectors were unreadable at the time. That turns an image from an anonymous blob into something you can come back to in a month and check.

Checking an image against its recorded digest needs no elevation, because nothing touches a raw device. It detects bit rot on the backup drive, a truncated copy, or a transfer that failed quietly. A backup you have never verified is not a backup, and until now the only way to test one was to restore it over a disk you might still need.

The sidecar also closes a real hole in the safety model. A .gz image's uncompressed size cannot be known before expanding it, so the "target too small" check could not run for one: the disk was erased and the write then failed partway. The recorded size makes that check possible before anything is overwritten. --no-sidecar opts out; images written without one still restore exactly as before.

Clone one disk onto another

diskmgr clone 1 2          # copy disk 1 onto disk 2, sector for sector

The target must be at least as large as the source. Before any byte is written the tool locks and dismounts every volume on the target — if a program still has a file open there, the clone is refused rather than corrupting a live filesystem. Afterwards the target is read back and its digest compared with the source:

  source digest      : 9f2c...
  destination digest : 9f2c...
  verification       : PASSED

Interactive terminal UI

diskmgr tui
Key Does
↑ ↓ Select a disk
r Re-scan (runs off the UI thread; the interface stays responsive)
u Show only removable devices, or everything again
f Copy files — options form, then a plan preview
i Save an image of the selected disk
R Restore an image onto the selected disk (shifted on purpose)
c Clone the selected disk onto another
x Cancel the running operation
q Quit — refused while an operation is writing

Long operations run on a worker thread, so the interface stays responsive and cancellable throughout, and a crash in one cannot leave the app wedged.

Every flow starts with an options form carrying the same settings as the corresponding CLI command — transfer mode, verify depth, glob filters, retries, bad-sector recovery, size limits, block size, digests. The last values you used are pre-filled, so a repeated job is a couple of keystrokes. Paths can be typed or browsed with a directory tree.

The copy plan preview is the dry run. Before anything is written you get the full list of actions — deletions sorted to the top — plus the free-space check and any folders that could not be scanned. Cancelling there is exactly a dry run, and it shows more than copy --dry-run does, because the engine's own dry run skips the free-space check. For that reason --dry-run, --quiet and --show-files have no TUI equivalent: the preview replaces all three. --yes is deliberately absent too — it exists so scripts can skip the confirmation, which is not something an interactive UI should offer.

Destructive operations show the whole safety picture before the gate: the target's blockers and warnings, whether you are elevated, and the exact phrase required. If the target is refused — the system disk, a read-only or undersized disk — or you are not elevated, the dialog has no input and no proceed button at all. There is nothing to press.

Afterwards the verdict stays on screen until the next run, with both full digests, so a clone that finished hours ago can still be checked against a digest produced by the CLI:

CLONED - VERIFICATION PASSED
  32.0 GB written in 12:41
  source digest      : 9f2c…
  destination digest : 9f2c…

If you are not running elevated, a banner says so on startup and the imaging, restore and clone actions are disabled rather than failing several screens later.

Safety model

Guard Behaviour
System disk Refused. --allow-system downgrades it to a warning but still requires the typed phrase, even with --yes.
Confirmation Exact phrase ERASE DISK <n>, case-insensitive. --yes skips it for non-system disks only.
Same disk Cloning a disk onto itself is refused.
Size A target smaller than the source or image is refused before any write. A .gz image is the exception: its uncompressed size is not knowable up front, so the check cannot run and both UIs say so.
Read-only A write-protected disk is refused.
Mounted volumes Locked and dismounted first; a busy volume aborts the operation.
Destination inside source Refused — it would copy the transfer's own output.
Mirror onto a parent of the source Refused — it would delete the source.
Not elevated Surfaced in the TUI on startup and before the phrase gate, rather than as a failure after it.

Raw device access needs an elevated terminal on Windows (or root on Linux). Listing disks and copying files do not.

Development

pip install -e ".[dev]"
pytest          # no hardware or elevation required
flake8 disk_manager tests

The raw-clone tests substitute a plain file for the block device, so the read, write, hash and verify paths all execute for real — including bad-sector recovery and cancellation — without touching any hardware.

The TUI is tested through Textual's own run_test/Pilot, needing no extra dependency. Those tests pin the guarantees that matter: a blocked target's dialog composes no confirmation input at all, declining the gate never starts the clone engine, and a worker crashing with an exception the engine never raises still leaves the app usable.

Licence

MIT — see the LICENSE file in the source distribution.

The MIT licence's warranty disclaimer and limitation of liability are the operative legal terms and apply in full; the disclaimer at the top of this document restates and expands on them in plain language. Nothing in this README creates any warranty, guarantee or obligation of support.

Metadata

Release files for diskmgr 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for diskmgr 0.2.0
File Size Uploaded Provenance
diskmgr-0.2.0.tar.gz 130.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for diskmgr 0.2.0
File Interpreter ABI Platform
diskmgr-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 246.5 kB

Release files / diskmgr-0.2.0.tar.gz

Download URL diskmgr-0.2.0.tar.gz
Size 130.9 kB
Tags Source
SHA-256 checksum
How to use checksums
5f55d9a2784796d51eb37ca529ea4df253c3fa135f730c6a9f53e76fa24bbe76
BLAKE2b-256 checksum
How to use checksums
ca57cde4c3c9c436723408d12f5d0ea3324ebc579a105b35da22d9c3e66ff137
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / diskmgr-0.2.0-py3-none-any.whl

Download URL diskmgr-0.2.0-py3-none-any.whl
Size 115.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
748376aa4af536af4e5bc224ea5be017784fec95022c3578a9e399da0438c326
BLAKE2b-256 checksum
How to use checksums
5bffba061dcbd3f032ab5f49a3caaa7281e00e5701895a93a3d60cf393826781
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for diskmgr 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page