Metadata-Version: 2.4
Name: diskmgr
Version: 0.1.0
Summary: Manage hard disks and USB drives: enumerate, copy/sync files between drives, and raw block-level clone or image.
Project-URL: Repository, https://github.com/Pandiyarajk/disk-manager
Project-URL: Issues, https://github.com/Pandiyarajk/disk-manager/issues
Author-email: Pandiyaraj Karuppasamy <pandiyarajk@live.com>
License-Expression: MIT
License-File: LICENSE
Keywords: backup,clone,dd,disk,imaging,mirror,sync,usb,windows
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Archiving :: Backup
Classifier: Topic :: System :: Filesystems
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: textual<9,>=8; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Provides-Extra: tui
Requires-Dist: textual<9,>=8; extra == 'tui'
Description-Content-Type: text/markdown

# diskmgr

[![Python](https://img.shields.io/badge/python-3.11%2B-blue?logo=python&logoColor=white)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green)](#licence)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey)](#install)
[![Status](https://img.shields.io/badge/status-beta-orange)](#disclaimer)
[![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](#install)

[![Tests](https://img.shields.io/badge/tests-163%20passing-brightgreen?logo=pytest&logoColor=white)](#development)
[![Lint](https://img.shields.io/badge/lint-flake8-blueviolet)](#development)
[![Typed](https://img.shields.io/badge/typed-py.typed-blue)](#development)
[![Build](https://img.shields.io/badge/build-hatchling-4051b5)](#install)
[![TUI](https://img.shields.io/badge/TUI-Textual-5a4fcf)](#interactive-terminal-ui)
[![SemVer](https://img.shields.io/badge/semver-2.0.0-blue)](https://semver.org/)
[![Changelog](https://img.shields.io/badge/changelog-keep%20a%20changelog-orange)](https://keepachangelog.com/)

<!--
Badge notes, so nobody "fixes" what is not broken:

* Every badge above is STATIC and renders unconditionally - each was fetched and
  its rendered text checked. Each states a fact about this repo: zero runtime
  dependencies for the CLI, a PEP 561 py.typed marker, flake8 as the linter,
  hatchling as the build backend, Textual behind the optional [tui] extra.

* The Tests badge is hand-maintained. Update the number when the suite changes.

* There is NO GitHub Actions status badge because this repo is PRIVATE.
  shields.io queries api.github.com anonymously; a private repo returns 404, and
  the badge renders "repo or workflow not found". That is an ACCESS error, not a
  filename error - verified against this repo. GitHub's own badge.svg fails the
  same way, because README images are proxied through camo, which also fetches
  anonymously. The gist-backed workaround needs a CI workflow, and this repo
  deliberately has none.

* The two PyPI badges below are DYNAMIC. They read "package or version not
  found" until the first release is uploaded, so they are commented out to keep
  the badge row clean. Uncomment both immediately after the first successful
  `twine upload`:

  [![PyPI](https://img.shields.io/pypi/v/diskmgr)](https://pypi.org/project/diskmgr/)
  [![Downloads](https://img.shields.io/pypi/dm/diskmgr)](https://pypi.org/project/diskmgr/)

* Badge targets are in-document anchors, not relative file links: this README is
  also the PyPI long description, where a relative link 404s.
-->

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](#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

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

From a clone:

```bash
pip install -e ".[dev]"
```

## Usage

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

### See what is attached

```console
$ 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
```

```bash
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:

```console
  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:`).

### Copy files between drives

```bash
# 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 |

```bash
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.

### Image a disk

```bash
# 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.

### Clone one disk onto another

```bash
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:

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

### Interactive terminal UI

```bash
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. |
| 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

```bash
pip install -e ".[dev]"
pytest          # 160 tests, 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.
