Nextcloud app for external disk auto-ingest: detect, mount, scan, deduplicate, classify.
"Where your data goes to live."
— Named after
/dev/null, the Unix black hole where data goes to die. This app does the opposite.
Warning
DevNull is feature-complete (v0.5.0) but not yet published on the Nextcloud App Store (certificate pending: PR #1152).
DevNull transforms your Nextcloud server into a central ingest point for external drives. Plug in a disk, click mount, and your server detects, mounts, scans, deduplicates, and classifies — all from the web interface. No terminal required.
Built for the person with 10+ external drives in a drawer, full of photos and documents that deserve an organized life.
| Component | Status | Notes |
|---|---|---|
| Disk detection (lsblk) | 🟢 Working | Recursive, filters system partitions |
| Web UI (Vue.js) | 🟢 Working | Disk cards, mount/eject/process buttons, badges |
| Mount via udisks2 | 🟢 Working | www-data with polkit, handles AlreadyMounted |
| Eject (unmount) | 🟢 Working | Removes NC external storage by saved ID |
| Storage registration | 🟢 Working | PHP API (GlobalStoragesService), no subprocess |
| File scan (auto) | 🟢 Working | \OC\Files\Utils\Scanner (NC 34 compatible) |
| Ingest pipeline | 🟢 Working | Scan (sync) + Dedup/Classify (background jobs) |
| Permissions | 🟢 Working | Admin mounts; users see shared storage |
| Error handling | 🟢 Working | Consistent {success, error, code} + HTTP status |
| Notifications | 🟢 Working | NC notification panel (mount, ingest, hotplug) |
| Auto-classify | 🟢 Working | Triggers Recognize after scan (configurable) |
| Dashboard widget | 🟢 Working | IAPIWidgetV2 on NC home screen |
| Admin settings | 🟢 Working | Automation rules in NC admin panel |
| Daemon (hotplug) | 🟢 Working | Python + pyudev, SSE, webhook bridge |
| DB schema | 🟢 Working | 3 tables (disks, operations, mounts) |
| App Store | 📋 Pending | Certificate PR open |
- Detect USB/external drives connected to the server
- List disks in Nextcloud UI (name, size, filesystem, model, serial)
- One-click mount via udisks2 (no root required)
- One-click eject with --force
- Auto-register as Nextcloud external storage on mount
- Auto file scan after mount (content visible immediately)
- Auto-remove external storage on eject (clean lifecycle)
- Ingest pipeline: scan → deduplicate → classify (AI)
- Background job scheduling for heavy operations (dedup, classify)
- Auto-classify via Recognize after scan (configurable)
- Nextcloud notifications (mount complete, ingest complete, disk detected)
- Dashboard widget (mounted disks + recent ops on NC home)
- Admin settings page (automation rules, daemon config)
- Python daemon with hotplug detection (pyudev + polling fallback)
- SSE real-time events endpoint
- Webhook bridge (daemon → NC app)
- Auto-mount on plug (configurable)
- Deploy script (
scripts/deploy.sh) - Automated test suite (
scripts/test.sh) - Filter system partitions (/, /boot, swap)
-
.devnullmarker file on mounted disks - PHPStan level 5 compliance
- Structured error responses with error codes
- Nextcloud App Store publication (awaiting certificate)
DevNull/
├── app/ # Nextcloud PHP app
│ ├── appinfo/ # info.xml, routes.php
│ ├── lib/
│ │ ├── AppInfo/ # Bootstrap + DI (IBootstrap)
│ │ ├── Capability/ # Interfaces (DiskDetector, MountStrategy, StorageRegistrar)
│ │ ├── Command/ # SecureCommandRunner (whitelisted exec)
│ │ ├── Controller/ # OCS API (Disk, Mount, Ingest, Status, Operation, Daemon, Settings)
│ │ ├── Dashboard/ # NC Dashboard widget (IAPIWidgetV2)
│ │ ├── Detection/ # LsblkDetector, DetectorFactory
│ │ ├── Event/ # DiskMounted, DiskUnmounted, IngestCompleted
│ │ ├── Ingest/ # Pipeline + Steps (Scan, Deduplicate, Classify)
│ │ ├── Listener/ # TriggerScanOnMount, LogOnUnmount, NotifyOnIngestComplete
│ │ ├── Migration/ # DB schema (disks, operations, mounts)
│ │ ├── Mount/ # UdisksMountStrategy, SudoMountStrategy, NullMountStrategy
│ │ ├── Notification/ # Notifier + NotificationService
│ │ ├── Bridge/ # HttpDaemonClient, NullDaemonClient
│ │ ├── Settings/ # AdminSection, AdminSettings
│ │ └── Storage/ # NextcloudStorageRegistrar (PHP API + auto-classify)
│ ├── src/ # Vue.js frontend
│ │ ├── components/ # DiskCard, DiskList, OperationLog (dashboard)
│ │ └── App.vue
│ ├── templates/ # Admin settings page
│ └── js/ # Built frontend bundle
├── daemon/ # Python daemon (optional enhancer)
│ ├── devnull_daemon/
│ │ ├── api/ # FastAPI REST + SSE
│ │ ├── detection/ # UdevMonitor + polling fallback
│ │ ├── mount/ # Strategy pattern (udisks, sudo)
│ │ ├── notifications/ # Webhook notifier → NC app
│ │ ├── events.py # EventBus (async pub/sub)
│ │ └── models/ # Pydantic models
│ └── systemd/ # Service unit
├── scripts/
│ ├── deploy.sh # One-command deploy
│ └── test.sh # Automated test suite
└── docs/ # Audit reports, continuation prompts
- Backend: PHP 8.2+ / Nextcloud App Framework 28+
- Frontend: Vue.js 2.7 / @nextcloud/vue / Webpack 5
- Daemon: Python 3.11+ / FastAPI / pyudev / httpx
- Mount: udisks2 (userspace, polkit) or sudo mount (fallback)
- Detection: lsblk --json (PHP) / pyudev real-time (daemon)
- Storage: files_external GlobalStoragesService (PHP API)
- Scan: \OC\Files\Utils\Scanner (NC 34, IUser + SetupManager)
- Jobs: IJobList for dedup/classify/Recognize scheduling
- Notifications: OCP\Notification\IManager
- Dashboard: IAPIWidgetV2 + IReloadableWidget
# On the server
cd /opt/devnull && git pull && sudo bash scripts/deploy.sh
# With daemon (hotplug detection)
cd /opt/devnull && git pull && sudo bash scripts/deploy.sh --with-daemon
# Run tests
export DEVNULL_TEST_PASS='your-nc-password'
bash /opt/devnull/scripts/test.sh| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/disks |
User | List detected disks |
| POST | /api/v1/mount |
Admin | Mount a device |
| POST | /api/v1/unmount |
Admin | Eject a device |
| POST | /api/v1/ingest |
Admin | Start ingest pipeline |
| GET | /api/v1/ingest/steps |
User | List available steps |
| GET | /api/v1/status |
User | Current mount/operation status |
| GET | /api/v1/logs |
User | Operation history |
| GET | /api/v1/settings |
Admin | Get automation settings |
| PUT | /api/v1/settings |
Admin | Update automation settings |
| POST | /api/v1/daemon/event |
Token | Daemon webhook receiver |
| GET | /api/v1/daemon/config |
Token | Daemon self-configuration |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/health |
— | Health check |
| GET | /api/v1/disks |
Token | List available disks |
| POST | /api/v1/mount |
Token | Mount a device |
| POST | /api/v1/unmount |
Token | Unmount a device |
| GET | /api/v1/events |
— | SSE real-time stream |
| GET | /api/v1/events/history |
Token | Recent event history |
Contributions welcome. Areas where help is needed:
- Testing — PHPUnit tests for controllers and services
- New mount strategies — Docker volumes, NFS, CIFS
- Device support — testing with different USB enclosures
- Platform support — Ubuntu, Fedora, Arch variations
- Frontend — Vue.js admin settings component