USB sticks, SD cards, phones, external HDDs/SSDs, internal system storage, and multi-cloud accounts (Google Drive, Mega, OneDrive, Dropbox) in the Omarchy bar: mount, open, inspect, format, auto-repair NTFS dirty bits, two-way selective sync, and safely eject without leaving the desktop.
- Multi-Cloud Drive Integration: Add multiple Google Drive, Mega, OneDrive, Dropbox, and Nextcloud/WebDAV accounts via rclone directly in the Network & Cloud tab. Features selective folder syncing at any depth (keep only chosen folders on disk; open a folder to keep just some of the folders inside it, or keep it whole and leave parts out), loose files at the top (off unless you turn them on), cleanup of what no longer syncs (each item checked against the cloud first), auto-sync timers via systemd, and read-only on-demand browsing (
rclone mount) without consuming local disk space. - Cloud account tiles: One full-width row per account, each with a status dot (synced, syncing, needs a look, failed), the signed-in email (Google Drive, OneDrive and now Dropbox), a quota bar that turns urgent when nearly full or over quota, and the last sync time. Sync, open and browse are one click from the tile; the detail view's chips wrap instead of squeezing.
- Accounts cannot trip over each other: The add form checks, as you type, that the new account's sync and browse folders are not used by — or nested inside — another account's. Accounts saved before that check are flagged on their tile, and the browse folder can be moved from the account's Config drawer. A browse mount only counts as an account's if rclone is serving that account there, so a second account can no longer report "mounted" while showing the first one's files, or unmount it on removal.
- Sync failures say why: The status line quotes rclone's own error from the sync log rather than "Resync failed", and Config has View sync log and Resync. A brand-new account's local folder is created on its first sync; once an account has synced, a missing folder stops the sync instead of being treated as "delete everything".
- Disconnect and delete ask twice: Both need a second click within four seconds.
- Drive Info: Open a drive's settings drawer (cog, or
i) for its full spec sheet — kind (thumb drive, external, SD card, internal, system), model, serial, firmware revision, how it is attached and how fast (USB 2.0 (480 Mb/s),NVMe · PCIe 3.0 ×4,SATA · 6.0 Gb/s), solid state or spinning, partition table, TRIM support, hardware write-protect and the exact size in bytes. A volume's drawer has the same for the filesystem: type and version (FAT16 is no longer called FAT32), label, UUID, partition type, PARTUUID, the LUKS mapper behind it, access and mount point. Click any value to copy it. A USB 3 drive that ended up on a USB 2 link is called out, since that is a tenfold slowdown nothing else on screen explains. - Whole-Drive & Partition Formatter, built in: Wipe an entire removable drive or a single volume from the panel, over udisks — no root, no extra packages. A whole-drive wipe writes a fresh GPT table and one partition filling the drive, typed as Microsoft basic data for exFAT/NTFS/FAT32 (so Windows sees it) or Linux data for ext4/Btrfs. Both are confirmed by typing the drive's kernel name, and both refuse system drives, mounted volumes and unlocked containers outright.
- NTFS Dirty-Bit Handling: NTFS volumes that will not mount because Windows hibernated or left them dirty are flagged in the panel. Auto-mount tries a normal mount and, if that fails, sends a notification rather than failing silently; the built-in check → repair flow fixes them over udisks, and if
omarchy-ntfs-fixis installed a one-click repair in a terminal is offered too. - Disk Usage Inspector (
dua): Click the pie chart icon (or pressd) on any mounted volume to inspect space utilization interactively viadua iin a floating terminal. - Terminal at Mountpoint: Click the terminal icon (or press
t) on any mounted volume to immediately launch a terminal in that volume's directory. - System Storage Visibility Toggle: Click the disk icon in the header (or press
s) to toggle display of internal NVMe storage, Btrfs subvolumes, and root partitions alongside external drives. - Sleep / Suspend Auto-Unmount Hook: Safely unmounts removable drives before system suspend to eliminate NTFS dirty bit corruption and ext4 journal desynchronization.
- Mount, open, and eject any removable volume without a password, because udisks2 already lets the logged-in session do it. Ejecting unmounts every volume on the drive, re-locks anything encrypted, then powers it down.
- Shows what is being written, and how fast. While any drive is being written — a USB stick, an internal disk or the system drive — or a cloud account is syncing, the bar icon becomes that drive's own icon and pulses red, and the panel header shows the live speed. Small background writes to the system disk (the journal, caches) are left out, so it only pulses for a real write.
- Knows when a removable drive is still being written to. Only a drive that can be unplugged says "do not remove", because a copy dialog reaching 100% is not the moment a stick is safe to pull. An eject asked for mid-copy is held, then fires once the drive goes quiet.
- Names who is holding a busy mount, instead of stopping at
target is busy, and offers a lazy unmount as an explicit second choice. - Phones and cameras get their own section: Android over MTP, iPhone over
AFC and PTP, with their access labelled
FilesorPhotos. - Unlocks an encrypted volume in place, mounts it as the container opens, and closes it again from the same row without ejecting the drive.
- Renames the volume label. It travels with the stick to every machine that reads it, unlike a nickname, which never leaves this shell. The field counts down against that filesystem's own limit as you type.
- Checks a filesystem, and repairs it only if you ask twice. Repair is the one thing here that rewrites a filesystem, so it appears only after a check has found something, never one stray click from the mount button. A suspect drive mounts read-only first, so files come off without a byte going back.
- Formats a volume, once you have said so twice. Erasing is the only thing here that destroys data on purpose, so the button is on unmounted volumes only and the volume's own kernel name has to be typed out before anything runs. Pick exFAT, FAT32, NTFS, ext4 or Btrfs, name it on the way, and zero the drive first when you want the old contents actually gone.
- A single drive can set its own terms. One drive can mount read-only while the rest mount normally, and one drive can open — or refuse to open — after mounting whatever the global setting says. Both stick to the hardware, the same way a nickname does.
- Reads a drive's own health, for the drives that report any. udisks does that read over D-Bus and hands the answer over, so no extra package is needed and nothing runs as root. Most USB sticks report nothing at all, and a drive that reports nothing shows nothing — see below before expecting a number.
- Unmounts before the machine sleeps, optionally, so a drive pulled from a sleeping laptop is not left half-written, and says which one refused when one does.
- Nicknames stick to the hardware, keyed to the drive's serial rather than
whichever
/dev/sdbit landed on today. A drive can also run a command of your choosing when it appears, and that command can report its progress back, so a stick running a backup looks like one instead of looking idle. - Empties the trash you cannot see: the
.Trash-1000that quietly fills a stick with files you thought were deleted. - Never offers to eject the disk you booted from. A USB-booted system disk
reports itself as removable just like a thumb drive; anything holding
/,/bootor/homeis left out entirely. - Plus: connect and remove notifications, a warning when a drive is pulled while still mounted, free-space bars, eject-all, and optional text beside the bar icon.
omarchy plugin add https://github.com/gameticharles/drives.git --enableNeeds Omarchy 4 (Quattro) and udisks2, both standard. It calls lsblk,
udevadm, udisksctl, busctl, gio, fuser, du, wl-copy and Omarchy's
own omarchy-* helpers. Nothing runs as root.
Cloud drives also need rclone (and fuse3 to browse without syncing). The
plugin never installs them itself: when rclone is missing, the Network & Cloud
tab copies a search for the Omarchy menu (Super + Space) › Install › Package
and opens that menu, then notices by itself once rclone is there.
To remove it:
omarchy plugin remove storage-drives
rm ~/.local/state/omarchy/removable-drives.json # optional: forget nicknamesIt never writes to shell.json or your Hyprland config; the bar entry belongs
to Omarchy's plugin commands and nicknames live in the file above.
A few buttons hand work to a terminal helper instead of doing it in the panel,
because the job needs elevated rights or an interactive tool. The plugin does
not ship these. It checks for each one at startup and on rescan, and hides the
button when the helper is not on your PATH:
| Helper | Button |
|---|---|
dua |
Disk Usage |
omarchy-disk-speedtest |
Speed Test (ships with Omarchy) |
omarchy-drive-trim |
Trim SSD (also needs a drive that accepts discards) |
omarchy-drive-scrub |
Btrfs Scrub |
omarchy-ntfs-fix |
NTFS Fix, and the one-click repair on an unmounted NTFS volume |
omarchy-drive-flash |
Flash Bootable ISO |
omarchy-drive-recover |
Recover & Inspect |
Formatting, checking, repairing, renaming, unlocking and the drive info never depend on these: they go through udisks.
Omarchy ships gvfs-mtp, so Android works out of the box. Apple devices
speak AFC and need three packages Omarchy does not ship: usbmuxd, gvfs-afc
and gvfs-gphoto2.
A plugin may not install packages itself, so when something is plugged in that gvfs cannot reach, the panel names what is missing and offers to open Omarchy's own installer.
A trusted iPhone appears as two entries: app documents over AFC, and the camera roll over PTP. With iCloud Photos set to "Optimize iPhone Storage" the camera roll can read as empty, because the originals are not on the device.
| Where | Action |
|---|---|
| Bar icon | left = open · right = rescan · middle = open first mounted volume |
| Volume row | click = mount and open, or open if mounted · middle-click = copy its path |
| Phone row | click = browse (mounting on demand) |
| Mount / open / unmount icons | mount · open · unmount that volume |
| Rename / check icons | rename the volume · check it for errors |
| Eraser icon | format an unmounted volume: erase it and create a filesystem |
| Format Entire Drive | wipe the whole drive: new partition table and one volume |
| Info values | click any value in a Drive Info or Details grid to copy it |
| Close-apps icon | on a busy unmount: ask the programs holding it to close, then retry |
| Read-only / repair icons | after a failed check: mount read-only · repair |
| Lock icon | locked: type the passphrase to unlock · open: lock it again |
| Health icon | on drives that report health: hover for the reading, click to re-read |
| Drive row icons | open after mounting · mount read-only · nickname · eject |
| Eject icon in the header | eject every attached drive |
Keyboard, while the panel is open:
| Key | Key | ||
|---|---|---|---|
j k ↑ ↓ |
move | e x |
eject the drive |
Enter Space |
mount and open, or eject | E |
eject every drive |
m |
mount or unmount | t |
terminal at this volume |
o |
open | y |
copy its path |
n |
nickname the drive | r Esc |
rescan · close |
l |
rename the volume | c |
check it for errors |
f |
format the volume, or the whole drive on a drive row | i |
open the info drawer |
Set on the widget's entry in ~/.config/omarchy/shell.json, or through
Setup > Plugins.
| Key | Default | What it does |
|---|---|---|
alwaysShow |
false |
Keep the icon in the bar with nothing attached |
openOnMount |
true |
Open the file manager once a volume mounts |
autoMountOnConnect |
true |
Automatically mount removable volumes on plug-in |
autoCleanTrashOnEject |
false |
Empty drive trash folder before ejecting |
showSystemDrives |
true |
Display internal storage drives alongside removable media |
notifications |
true |
Announce drives, warn when one is pulled while mounted |
unmountOnSuspend |
true |
Unmount every removable volume when the machine suspends |
fileManager |
"" |
Command used to open a mount point; empty means xdg-open |
barLabel |
"none" |
Text beside the icon: none, free, name, count |
refreshIntervalSec |
8 |
How often free space is re-read while the panel is open |
fullWarnPct |
90 |
Fill level at which a volume's bar and free-space line turn urgent |
thumbMaxGb |
256 |
USB disks this size or smaller show as thumb drives unless the model says otherwise |
healthAlerts |
true |
Read drive health in the background and notify when it gets worse |
lowSpaceAlerts |
true |
Notify once when a mounted volume crosses fullWarnPct |
Per-drive settings live in ~/.local/state/omarchy/removable-drives.json, keyed
by serial and watched for changes, which is also how you attach a command to a
drive:
{
"version": 1,
"drives": {
"serial:0901f8ef1ed9c144": {
"nickname": "Work backup",
"onConnect": "rsync -a ~/Documents/ \"$2\"/documents/",
"autoOpen": false,
"readOnly": true
}
}
}onConnect runs through bash -c when that drive appears, with $1 as its
device path, $2 as its first mount point, and $3 as a file it may write
progress to. Nothing writes it for you.
Write key=value lines to $3, which means one echo is enough:
percent=42
status=Copying documents
Both keys are optional; done=1 says the hook has finished, and a bare number
on its own line is read as a percent. The panel draws a bar on the drive row
while the hook runs and shows the status beside it. A hook that reports
nothing still counts, it just has no bar.
The drive counts as busy for as long as the hook runs, so an eject asked for mid-copy is held and fires once the hook is done — the same as for a copy the kernel can see. That matters because the kernel cannot see this one: an rsync goes quiet between file batches, and the busy icon goes out with it.
The file lives under $XDG_RUNTIME_DIR, so it never reaches your disk and
never outlives the session. The panel watches the hook's process rather than
the file, so a hook that dies mid-copy ends the bar instead of leaving it
stuck at 42% forever.
A hook that says what it is doing, in one line:
"onConnect": "echo 'status=Backing up' > \"$3\"; rsync -a ~/Documents/ \"$2\"/docs/; echo done=1 > \"$3\""A hook written before $3 existed is unaffected: it is a new argument, not a
changed one.
A drive that reports SMART gets a health icon on its row, with its temperature
and hours powered on in the tooltip and any concern written out in the row
itself. It comes from udisks over D-Bus, which does the privileged read for
us — no smartctl, no smartmontools, nothing running as root.
The reading is taken when the set of drives changes, on a rescan, and every
six hours in the background — never continuously — so the temperature is a
snapshot rather than a live thermometer; open a drive's telemetry row for
minute-by-minute readings. With healthAlerts on, a drive whose verdict gets
worse is announced in a notification even with the panel closed, and a drive
that reports itself failing is announced at the first reading of a session.
Most USB sticks report nothing at all, and that is the ordinary case rather than a fault: a thumb drive carries neither SMART interface, so the icon simply does not appear. External SSDs and hard drives behind a SAT-capable bridge are the ones that answer. Do not read a missing icon as a problem.
No temperature threshold is invented here. The number is shown as udisks gives it, because a figure you can read and look up beats a line drawn on a guess.
autoOpen overrides openOnMount for this drive alone: true always opens,
false never does, and leaving it out follows the global setting. readOnly
mounts every volume on the drive read-only, which is what an archive disk you
never want written to wants. The panel writes both from the drive row, and
reads them strictly on the way back in: a readOnly that is neither true nor
false is taken as true, because a typo in this file must not be the reason
an archive drive came up writable.
omarchy-shell storage-drives toggle
omarchy-shell storage-drives refresh # re-read what is attached
omarchy-shell storage-drives list # drives, as JSON
omarchy-shell storage-drives phones # phones, as JSON
omarchy-shell storage-drives network # network & cloud mounts, as JSON
omarchy-shell storage-drives cloud # configured cloud accounts, as JSON
omarchy-shell storage-drives status # {"busy":false,…}
omarchy-shell storage-drives eject /dev/sdb # or ejectAll
omarchy-shell storage-drives rename /dev/sdb "Work backup" # "" clears it
omarchy-shell storage-drives label /dev/sdb1 "Photos" # the label on the drive
omarchy-shell storage-drives check /dev/sdb1 # verdict lands in status
omarchy-shell storage-drives smart /dev/sdb # health, as JSON
omarchy-shell storage-drives mountReadOnly /dev/sdb1 # rescue without writing
omarchy-shell storage-drives lock /dev/mapper/luks-… # close an open container
omarchy-shell storage-drives format /dev/sdb1 exfat Photos # erases the volume
omarchy-shell storage-drives expandDevice /dev/sdb # expand drive settings
omarchy-shell storage-drives expandVolume /dev/sdb1 # expand volume drawer
omarchy-shell storage-drives toggleTelemetry /dev/sdb # toggle telemetry row
omarchy-shell storage-drives setTab network # switch to "local" or "network"
omarchy-shell storage-drives info /dev/sdb # drive or volume details, as JSON
omarchy-shell storage-drives openCloud gdrive # open a cloud account's detail view
omarchy-shell storage-drives showDrive /dev/sda # open a drive with its Drive Info shown
omarchy-shell storage-drives unmountAll /dev/sdb # unmount every volume, no power-off
omarchy-shell storage-drives formatDrive /dev/sdb exfat Photos # wipes the whole driveformat destroys what is on the volume, and formatDrive destroys everything
on the drive. Both take the same refusals the panel does — a mounted volume, a
drive still being written to, a system drive, or a filesystem udisks will not
create are all turned away with the reason — but naming the node, the type and
the label in one line is the whole confirmation, so there is no second question
the way there is in the panel.
status reports busy: true while the kernel still has I/O in flight or a
connect hook is still running, so a backup script can wait for the drive to
settle; both eject calls wait by themselves. hooks says which drive is at
what percent, and health carries each drive's verdict — unsupported,
healthy, warning or failing. check returns straight away and leaves its
own verdict in status as healthy: true, false, or null when the
answer could not be read.
smart returns the whole record, and answers supported: false with every
other field null for the many drives that report no health at all.
Anything other than ok back from these is the reason they did not run, so a
script never has to guess whether a refusal happened.
Panel.qml draws the bar icon and popup, Service.qml owns everything that
touches the system, and Model.js is pure parsing and formatting with no QML
or processes, which is what makes it testable without a compositor.
Device-supplied strings (labels, vendor names, phone names) are hostile
input: whoever formatted a stick chooses its label. Every Text is pinned to
Text.PlainText so Qt cannot promote one to rich text and fetch a remote
<img>, and strings handed to components whose Text this plugin does not own
are stripped of angle brackets first. Paths stay byte-exact, since commands are
built from them.
Renaming a filesystem, running its fsck, and creating a new one are things
udisks exposes on D-Bus that udisksctl has no verb for, so they go over the
bus through busctl. It is still allow_active: yes, the same no-password
path mounting takes. The object path is asked for rather than built, since
udisks escapes the kernel name into it and an unlocked LUKS volume lands at
dm_2d3. The rename and the fsck unmount the filesystem first and mount it
back afterwards, whether or not the middle step worked; the format does
neither, because a mounted volume is refused outright rather than taken offline
on the way to being erased.
A format is checked as one plan — which volume, which type, which label,
whether to zero the drive first — rather than as four arguments each looked at
somewhere along the way, and the plan names the /dev node it was made for, so
swapping the stick between planning it and confirming it retracts the format
instead of pointing it at the replacement.
Health comes the same way, off org.freedesktop.UDisks2.Drive.Ata or
org.freedesktop.UDisks2.NVMe.Controller on the drive object — one further
lookup, since health belongs to the drive rather than to a block device.
Reading those properties does not refresh them: udisks hands back whatever its
own last poll cached, which measured ten minutes stale here — the bus said
308 K while every sensor on the same drive said 36.85 °C. So SmartUpdate is
called first, which is allow_active: yes in the udisks policy like everything
else here. It is still read rarely — once when the attached set changes and
again on a rescan, never on the free-space timer — because it is now a round
trip to the drive itself, and because every answer but the temperature changes
over hours rather than seconds. The update is best-effort: a drive that refuses
one is still read, since a stale number beats no number. nowakeup goes to
ATA, so a parked external disk is not spun up merely to draw a temperature.
A LUKS passphrase reaches udisks on stdin, never as an argument, because
/proc/<pid>/cmdline is readable by every other process you run. udisksctl
takes a key only from a file, so it is staged under umask 077 in the
RAM-backed runtime directory and a trap removes it however the unlock ends.
Emptying a drive's trash is the only recursive delete here, so the path is
re-derived from the live mount list and must exactly match a .Trash-<uid>
candidate of a mounted removable volume. The tests assert it refuses /,
$HOME, the mount root, and drives it is not tracking.
node test/model.test.js # 288 tests, no compositor required
omarchy plugin validate . # the same check the shell appliesMIT
