a useful set of shell scripts to ease the pain of working with both real floppies, and their disk images
Go to file
Greg Gauthier 6742e41e3d refactor(install): move program files to ~/.local/share/floppy-utils
Installs the core script + lib/ under XDG_DATA_HOME/floppy-utils and generates thin wrappers in ~/.local/bin. Updates floppy resolver, install.sh, README, and adds early returns in check-deps logging helpers.
2026-06-01 21:03:04 +01:00
src refactor(install): move program files to ~/.local/share/floppy-utils 2026-06-01 21:03:04 +01:00
.gitignore add floppy commands 2024-08-22 15:30:37 +01:00
check-deps.sh refactor(install): move program files to ~/.local/share/floppy-utils 2026-06-01 21:03:04 +01:00
config.example feat(floppy-utils): add bash toolkit for retro floppy disk operations 2026-06-01 20:54:30 +01:00
install.sh refactor(install): move program files to ~/.local/share/floppy-utils 2026-06-01 21:03:04 +01:00
README.md refactor(install): move program files to ~/.local/share/floppy-utils 2026-06-01 21:03:04 +01:00

floppy-utils

Bash toolkit for retro floppy work on Linux: create and edit disk images, archive physical floppies to files, and write images back to a USB floppy drive—without memorizing dd, losetup, and mount options.

Designed for day-to-day use with a USB floppy drive (removable /dev/sdX that changes between machines). Loop mounts are kept separate from the physical drive so you can work on .img files offline and only touch hardware when reading or burning.

Tested hardware: Mitsumi SmartDisk USB UFDD (03ee:6901), appearing as /dev/sda, FAT12 volumes, automount via udisks at /media/$USER/<LABEL>.


Table of contents


What it does

Area Capability
Images Create blank 360K / 720K / 1.44M .img files
Edit Loop-attach images, optional FAT format, mount under $FLOPPY_MEDIADIR
Archive Sector-accurate read from physical floppy → image file
Restore Burn image to physical floppy with confirmation
USB FDD Detect drive, refresh after disk swap, safe USB disconnect (power-off)
Visibility List devices, attached state, full status

All operations go through a single CLI: floppy (with subcommands). Status messages go to stderr so command substitution (e.g. device paths) is not corrupted.


Project layout

floppy-utils/
├── README.md              This file
├── check-deps.sh          Debian dependency check / apt install
├── install.sh             Install scripts to ~/.local/bin
├── config.example         Sample ~/.config/floppy-utils/config
└── src/
    ├── floppy             Main CLI
    ├── floppy-make        Legacy → floppy make
    ├── floppy-attach      Legacy → floppy attach
    ├── floppy-burn        Legacy → floppy burn
    └── lib/
        └── common.sh      Shared logic (sourced by floppy)

There is no daemon. Scripts use sudo when not root for dd, blockdev, losetup, and mount operations.

After install.sh, the main script lives at ~/.local/share/floppy-utils/floppy; ~/.local/bin/floppy is a wrapper that finds it.


Requirements

Supported platforms

  • Primary: Debian-based distributions (Debian, Ubuntu, Raspberry Pi OS, Linux Mint, Pop!_OS, Kali, Devuan, etc.)
  • Others: Commands are checked on any Linux; check-deps.sh --install refuses non-Debian systems

Required commands

Command Debian package Used for
lsblk util-linux Device size, label, mount points
blockdev util-linux Sector count for read/burn
losetup util-linux Attach/detach loop devices
findmnt util-linux Mount inspection
blkid util-linux Filesystem / label detection
mount, umount mount Loop and manual mounts
dd coreutils Read, burn, create images
readlink coreutils Resolve paths
mkfs.vfat dosfstools Format new images on attach
Command Debian package Used for
sudo sudo Non-root operation
udisksctl udisks2 Unmount/mount physical floppy (desktop integration)
udevadm udev Settle after media change (refresh)
eject eject Optional media-change hint (refresh --eject)
fdisk util-linux Partition info saved on new formats

Check and install (Debian)

cd /path/to/floppy-utils

./check-deps.sh              # list OK / missing
sudo ./check-deps.sh --install   # apt install missing packages
./check-deps.sh -q           # quiet; used by install.sh

install.sh runs the quiet check first and exits if required tools are missing.


Installation

1. Dependencies

sudo ./check-deps.sh --install

2. Scripts on PATH

Option A — install to ~/.local/bin (recommended)

./install.sh

This installs the library to ~/.local/share/floppy-utils/ (including lib/common.sh) and places small wrapper scripts in ~/.local/bin/. You can still run ./src/floppy directly from a git checkout.

Ensure ~/.local/bin is in your shell PATH (add to ~/.bashrc or ~/.zshrc if needed):

export PATH="$HOME/.local/bin:$PATH"

Option B — run from source

export PATH="/path/to/floppy-utils/src:$PATH"
floppy help

3. Optional configuration

mkdir -p ~/.config/floppy-utils
cp config.example ~/.config/floppy-utils/config
# Edit: FLOPPY_DISKDIR, FLOPPY_DEVICE, etc.

Verify

floppy help
floppy devices -v
floppy list

Configuration

Settings load in this order:

  1. Built-in defaults
  2. ~/.config/floppy-utils/config (shell snippet, sourced if present)
  3. Environment variables (override config file)

Variables

Variable Default Description
FLOPPY_DISKDIR $HOME/Retro/BLANKS Directory for .img files (make, read, attach by name)
FLOPPY_MEDIADIR /media/$USER Base directory for loop mounts (attach)
FLOPPY_DEVICE (unset) Default block device for physical ops (/dev/sda on a dedicated retro box)
FLOPPY_DEVICE_MATCH mitsumi|ufdd|fdd|floppy Extended regex; boosts USB floppy scoring in devices / auto-pick
FLOPPY_DEFAULT_SIZE_KB 1440 Default image size for make / attach

Example config file

# ~/.config/floppy-utils/config
FLOPPY_DISKDIR="$HOME/Retro/BLANKS"
FLOPPY_MEDIADIR="/media/$USER"
FLOPPY_DEVICE=/dev/sda
FLOPPY_DEFAULT_SIZE_KB=1440

Example one-shot env

FLOPPY_DEVICE=/dev/sda floppy read dosboot -y

Quick start

# 1. USB floppy connected, disk inserted
floppy refresh --mount
floppy devices -v

# 2. Archive floppy to an image file
floppy read -y                    # name from volume label or mount dir

# 3. Edit the image without hardware
floppy attach disk
# ... copy files under /media/$USER/disk ...
floppy detach disk

# 4. Write image back to a floppy
floppy refresh                    # after inserting blank or target disk
floppy burn disk.img -d /dev/sda

Command reference

Invoke as floppy <command> [options] [args]. Run floppy help for a short summary.

make — create blank image

floppy make [name] [-s 360|720|1440]
  • Creates $FLOPPY_DISKDIR/<name>.img filled with zeros
  • Omits name → random name floppy<digits>
  • Does not mount or format

Examples

floppy make blank1440
floppy make dos720 -s 720

attach — loop-mount an image for editing

Aliases: mount

floppy attach [name|path] [-s SIZE] [--format] [--no-format]
  • No args: new random 1.44M image, formatted vfat, mounted
  • name: use/create $FLOPPY_DISKDIR/<name>.img
  • path: absolute or relative path to an existing .img
  • New or raw images: formatted vfat unless --no-format
  • Mount point: $FLOPPY_MEDIADIR/<name> (not the FAT volume label path used by udisks for physical disks)
  • Uses losetup --find --show and mounts the loop device (not the file twice)

Examples

floppy attach
floppy attach mydisk -s 720
floppy attach /archive/old.img

detach — unmount and release

Aliases: umount

floppy detach <name|mountpoint|loop|label>

Accepts:

  • Image name (bootdisk$FLOPPY_MEDIADIR/bootdisk or loop for that image)
  • Full mount path (/media/user/BOOTDISK)
  • Loop device (/dev/loop0)
  • Physical volume label directory (BOOTDISK under $FLOPPY_MEDIADIR)

For physical /dev/sdX mounts, only unmounts; does not disconnect USB.


refresh — re-probe after swapping floppies

Aliases: rescan, reload

floppy refresh [-d DEV] [--mount] [--eject] [-w SEC]

Run whenever you change disks without unplugging USB. For end of session, use floppy disconnect instead (see below).

  1. Unmount stale filesystem (udisks or umount)
  2. blockdev --flushbufs
  3. Optional eject (--eject) for drives that need it
  4. udevadm settle and short wait (-w, default 2 seconds)
  5. Re-probe capacity; show before/after lsblk summary
  6. With --mount, mount new media via udisks if detected

When to use: devices still shows old label/size, automount behaves oddly, or read fails after a swap.

Examples

floppy refresh
floppy refresh --mount
floppy refresh --eject -w 3
floppy refresh -d /dev/sda

disconnect — safe USB removal (end of session)

Aliases: power-off, eject-usb

floppy disconnect [-d DEV]

Use when you are done with the USB drive and want to unplug the cable. Not for swapping floppies (use refresh).

Steps performed:

  1. Unmount any volume on the device (udisks or umount)
  2. blockdev --flushbufs and sync
  3. udisksctl power-off -b DEV (falls back to eject if power-off fails)
floppy disconnect
floppy disconnect -d /dev/sda

On success you should see: Safe to unplug the USB floppy drive now.


read — archive physical floppy to disk dir

Aliases: archive

floppy read [name] [-d DEV] [-y] [--force] [--strict]
  • Reads entire device sector-by-sector into $FLOPPY_DISKDIR/<name>.img
  • Sector count from blockdev --getsz (2880 × 512 bytes for 1.44M)
  • Default dd conv: noerror,sync,fsync (tolerates bad sectors on old media)
  • Unmounts drive before read; 1 second settle
  • --strict: stop on first I/O error instead

Default name (if name omitted):

  1. FAT volume label (sanitized, e.g. DISK 2disk_2)
  2. Else basename of mount point (e.g. disk from /media/user/disk)
  3. Else random floppy<digits>

Examples

floppy read -y
floppy read bootdisk -d /dev/sda -y
floppy read --strict -y    # fail fast on errors

dump — raw dump to any path

Aliases: copy

floppy dump -o FILE [-d DEV] [-y] [--force] [--strict]

Same read path as read, but -o is required (any file path, or - for stdout). Does not use FLOPPY_DISKDIR naming rules.

floppy dump -o /tmp/floppy.raw -d /dev/sda -y
floppy dump -o - -d /dev/sda | sha256sum

burn — write image to physical drive

Aliases: write

floppy burn <image> [-d DEV] [-y]
  • Resolves image: path, or name in FLOPPY_DISKDIR
  • Never hardcodes a device; uses -d, FLOPPY_DEVICE, or interactive pick
  • Confirms unless -y; warns if mounted, then unmounts
  • Writes with dd bs=512 conv=fsync
floppy burn disk.img -d /dev/sda
FLOPPY_DEVICE=/dev/sda floppy burn dosboot.img -y

devices — list removable block devices

Aliases: list-devices

floppy devices [-v]
  • Lists /dev/sd* and /dev/fd* disks with RM=1
  • -v: label, fstype, mount, heuristic score (USB FDD models score higher)

Use before burn/read when unsure of device node.


list — what is attached now

Aliases: attached, ls

floppy list [-v]
Section Shows
Loop-attached .img → loop device → mount point
Physical media Drive with disk inserted (mounted or not)
-v only All .img files in FLOPPY_DISKDIR

Lighter than status; focused on “what can I edit or read right now?”


status — full diagnostic snapshot

floppy status

Prints: config paths, loop attachments, mounts under $FLOPPY_MEDIADIR, verbose removable drive list.


Workflows

A. Swap disks in the USB drive (stay on same /dev/sdX)

flowchart LR
    swap[Insert new floppy] --> refresh[floppy refresh --mount]
    refresh --> devices[floppy devices -v]
    devices --> next[read / burn / browse]
floppy refresh --mount
floppy devices -v

You do not need to replug USB between floppies.


B. Archive a floppy (physical → file)

floppy refresh --mount          # optional: browse files first
floppy devices -v               # expect size 1.4M / 720K, not 0B
floppy read -y                  # or: floppy read mylabel -y
floppy list -v                  # confirm image in FLOPPY_DISKDIR

Partial reads with warnings usually mean damaged sectors; image may still be useful. floppy1982469877.img-style random names meant the volume had no usable label—pass an explicit name: floppy read disk -y.


C. Edit an image without hardware

floppy attach myproject
ls /media/$USER/myproject
floppy detach myproject

D. Restore image to floppy

floppy refresh                    # new disk in drive
floppy burn myproject.img -d /dev/sda

G. Done for the day — unplug USB drive

floppy disconnect                 # unmount + power off
# unplug USB cable

E. Create a new blank floppy image for a retro project

floppy make blank -s 1440
floppy attach blank --format
# copy files into mount point
floppy detach blank
floppy burn blank.img -d /dev/sda

F. Inspect current state

floppy list -v       # attached loops + physical + archive dir
floppy status        # everything including config

Device selection

When -d and FLOPPY_DEVICE are unset, floppy picks a removable disk:

  1. List removable sd/fd disks via lsblk
  2. Score each (higher = more likely USB floppy):
    • +100 if MODEL matches FLOPPY_DEVICE_MATCH
    • +40 if TRAN=usb
    • +30 if size is 360K / 720K / 1.4M
    • +10 if volume label present
  3. Auto-select if exactly one device, or exactly one with score ≥ 100
  4. Otherwise interactive menu

Always use floppy devices -v when multiple USB storage devices are attached (thumb drives, SD readers, etc.).


Physical drive vs disk images

Physical USB floppy Loop image (attach)
Device /dev/sda /dev/loopN
Typical mount /media/$USER/<FAT_LABEL> (udisks) /media/$USER/<image_name>
Create refresh, insert disk make, attach
Archive read, dump copy .img file
Write burn burn to physical only
Release detach <label>, refresh (swap disk) detach <name>
Unplug USB disconnect

Safety

Burn

  • Prompts: Write image to /dev/sdX? This will ERASE the floppy. [y/N]
  • Skipped with -y only when you are certain
  • Targets whole disk (/dev/sda), not partitions (/dev/sda1)
  • Unmounts before write

Read / dump

  • Prompt unless -y
  • Refuses 0B size (no disk inserted)
  • Does not overwrite existing output without --force

Permissions

Non-root users need sudo (or polkit via udisksctl for mount/unmount). Configure passwordless sudo only if you accept the security tradeoff on a dedicated retro workstation.


Troubleshooting

floppy: no media in /dev/sda but disk is inserted

  • Run floppy refresh, then floppy devices -v (size should be 1.4M, not 0B)
  • Re-seat the disk; try floppy refresh --eject -w 3

Wrong label in devices -v (path shown as label)

Fixed in current code (per-field lsblk). Update if you see label=/media/....

floppy read uses random name floppy123456789

Volume has no FAT label; udisks mounted as /media/user/disk. Pass a name: floppy read disk -y, or set label on the FAT volume.

dd: Input/output error partway through read

Common on aging floppies. Default noerror,sync completes with holes; warnings are printed. Use --strict only if you want fail-fast. Try refresh before retry; clean drive heads if errors persist.

floppy: command not found

Run ./install.sh or add src to PATH. Use ./floppy from src when testing without install.

>>> using removable device mixed into error text

Status must go to stderr (fixed). Update lib/common.sh if you see this on old copies.

Automount fights read / burn

read, burn, and dump unmount first. Run floppy refresh after manual file-manager mounts.

Leftover empty mount directory after detach

e.g. /media/user/bootdisk (present, not mounted). Safe to remove: rmdir if empty.

Device is /dev/sda on this machine but /dev/sdb elsewhere

Do not rely on fixed names; use floppy devices or set FLOPPY_DEVICE per host in config.

Mitsumi drive shows 2880 during refresh

Internal blockdev --getsz probe (suppressed in output on current versions).

udisks power-off failed on disconnect

Close file managers using the mount. Run floppy detach <label>, then floppy disconnect again. You can still unplug after a successful unmount if power-off fails—wait a few seconds for cache flush.


Legacy wrappers

Thin scripts for muscle memory; all call floppy:

Wrapper Maps to
floppy-make floppy make "$@"
floppy-attach floppy attach (supports old two-arg size: floppy-attach name 1440)
floppy-burn floppy burn "$1"

Limitations

  • Linux only (uses losetup, lsblk, udisks)
  • Debian apt helper only in check-deps.sh; other distros: install packages manually
  • FAT-focused for attach format (mkfs.vfat); raw/non-FAT images can be read/burned but not auto-mounted as vfat
  • No write-protect detection beyond mount errors
  • No internal 5.25" / floppy controller support beyond what the kernel exposes as /dev/fd*
  • Sector size assumed 512 bytes for read/burn
  • Not a GUI; pairs with desktop automount (udisks) when present

See also

  • floppy help — built-in usage text
  • config.example — starter configuration
  • check-deps.sh --help — dependency installer options