Feature Request: Unassigned Devices Plugin Support
Summary
The Unraid Management Agent currently only exposes disks that are part of the Unraid array (parity, cache, data disks). It does not expose information about Unassigned Devices - disks and remote shares managed by the popular "Unassigned Devices" plugin. This feature request proposes adding comprehensive support for discovering, monitoring, and controlling unassigned devices through the REST API and WebSocket interface.
Background
Unassigned Devices is one of the most popular Unraid plugins with over 100,000 active installations. It allows users to:
- Mount and use disks that are not part of the Unraid array
- Connect to remote SMB/NFS shares
- Mount ISO files as virtual devices
- Automatically mount devices on boot
- Share unassigned devices via SMB/NFS
- Run custom scripts on device mount/unmount events
This is a critical plugin for many Unraid users who need additional storage flexibility beyond the array.
Current State
What's Currently Exposed ✅
The agent exposes array disks via /api/v1/disks:
- Parity disks
- Cache pools
- Data disks (disk1, disk2, etc.)
Source: /var/local/emhttp/disks.ini
What's Missing ❌
- Unassigned disk devices (USB drives, eSATA, internal disks not in array)
- Remote SMB shares (mounted network shares)
- Remote NFS shares (mounted NFS exports)
- ISO file shares (mounted ISO images)
- Historical devices (previously connected devices that are currently absent)
Proposed Solution
1. New DTOs
Create new data transfer objects in daemon/dto/unassigned.go:
// UnassignedDevice represents an unassigned disk device
type UnassignedDevice struct {
// Device identification
Device string `json:"device"` // e.g., "sdc", "nvme0n1"
SerialNumber string `json:"serial_number"`
Model string `json:"model"`
Identification string `json:"identification"` // Friendly name/label
// Partition information
Partitions []UnassignedPartition `json:"partitions"`
// Status
Status string `json:"status"` // "mounted", "unmounted", "mounting", "error"
SpinState string `json:"spin_state"` // "active", "standby", "unknown"
Temperature float64 `json:"temperature_celsius"`
// Configuration
AutoMount bool `json:"auto_mount"`
PassThrough bool `json:"pass_through"`
DisableMount bool `json:"disable_mount"`
ScriptEnabled bool `json:"script_enabled"`
ScriptPath string `json:"script_path,omitempty"`
// I/O Statistics
Reads uint64 `json:"reads"`
Writes uint64 `json:"writes"`
Timestamp time.Time `json:"timestamp"`
}
// UnassignedPartition represents a partition on an unassigned device
type UnassignedPartition struct {
PartitionNumber int `json:"partition_number"`
Label string `json:"label,omitempty"`
FileSystem string `json:"filesystem"` // "ntfs", "ext4", "xfs", "btrfs", "exfat", "hfsplus", "apfs"
MountPoint string `json:"mount_point"`
Size uint64 `json:"size_bytes"`
Used uint64 `json:"used_bytes"`
Free uint64 `json:"free_bytes"`
UsagePercent float64 `json:"usage_percent"`
ReadOnly bool `json:"read_only"`
SMBShare bool `json:"smb_share"` // Is shared via SMB?
NFSShare bool `json:"nfs_share"` // Is shared via NFS?
Status string `json:"status"` // "mounted", "unmounted"
}
// UnassignedRemoteShare represents a mounted remote SMB/NFS share
type UnassignedRemoteShare struct {
// Share identification
Type string `json:"type"` // "smb", "nfs", "iso"
Source string `json:"source"` // "//server/share", "server:/export", "/path/file.iso"
MountPoint string `json:"mount_point"`
// Status
Status string `json:"status"` // "mounted", "unmounted", "mounting", "error"
// Capacity (if mounted)
Size uint64 `json:"size_bytes"`
Used uint64 `json:"used_bytes"`
Free uint64 `json:"free_bytes"`
UsagePercent float64 `json:"usage_percent"`
// Configuration
AutoMount bool `json:"auto_mount"`
ReadOnly bool `json:"read_only"`
// SMB-specific
SMBServer string `json:"smb_server,omitempty"`
SMBShare string `json:"smb_share,omitempty"`
SMBDomain string `json:"smb_domain,omitempty"`
SMBUser string `json:"smb_user,omitempty"`
// NFS-specific
NFSServer string `json:"nfs_server,omitempty"`
NFSExport string `json:"nfs_export,omitempty"`
NFSOptions string `json:"nfs_options,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
// UnassignedHistoricalDevice represents a previously connected device that's currently absent
type UnassignedHistoricalDevice struct {
SerialNumber string `json:"serial_number"`
LastMountPoint string `json:"last_mount_point"`
LastSeen time.Time `json:"last_seen,omitempty"`
AutoMount bool `json:"auto_mount"`
Configuration bool `json:"has_configuration"` // Has saved config
}
2. New Collector
Create daemon/services/collectors/unassigned.go:
type UnassignedCollector struct {
ctx *domain.Context
}
func (c *UnassignedCollector) Collect() {
devices := c.collectUnassignedDevices()
remoteShares := c.collectRemoteShares()
historical := c.collectHistoricalDevices()
// Publish events
c.ctx.Hub.Pub(devices, "unassigned_devices_update")
c.ctx.Hub.Pub(remoteShares, "unassigned_remote_shares_update")
c.ctx.Hub.Pub(historical, "unassigned_historical_update")
}
3. Data Sources
The collector will read from multiple sources:
Configuration Files
- Main config:
/boot/config/plugins/unassigned.devices/unassigned.devices.cfg
- Stores per-device settings (automount, pass_through, read_only, scripts)
- SMB mounts:
/boot/config/plugins/unassigned.devices/samba_mount.cfg
- Remote SMB/CIFS share configurations
- ISO mounts:
/boot/config/plugins/unassigned.devices/iso_mount.cfg
- ISO file mount configurations
Runtime State Files
- Device hosts:
/tmp/unassigned.devices/config/device_hosts.json
- Maps serial numbers to SCSI host identifiers
- Run status:
/tmp/unassigned.devices/config/run_status.json
- Spin state, timestamps, operational status
System Information
- Block devices:
/sys/block/ and lsblk command
- Discover attached devices not in array
- Mount points:
/proc/mounts
- Determine which partitions are currently mounted
- Disk info:
smartctl and /proc/diskstats
- Temperature, SMART status, I/O statistics
Detection Logic
# 1. Get all block devices
lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,LABEL,SERIAL,MODEL
# 2. Filter out array disks (compare against disks.ini)
# 3. Read unassigned.devices.cfg for per-device config
# 4. Parse /proc/mounts for mount status
# 5. Get temperature from smartctl or /sys/class/hwmon
# 6. Read run_status.json for spin state
4. API Endpoints
Monitoring Endpoints (GET)
GET /api/v1/unassigned/devices
GET /api/v1/unassigned/devices/{serial}
GET /api/v1/unassigned/remote-shares
GET /api/v1/unassigned/remote-shares/{id}
GET /api/v1/unassigned/historical
Example Response - GET /api/v1/unassigned/devices:
{
"devices": [
{
"device": "sdc",
"serial_number": "WD-WCC4N5XZ8P5R",
"model": "WDC WD40EFRX-68N32N0",
"identification": "WD Red 4TB",
"partitions": [
{
"partition_number": 1,
"label": "BackupDrive",
"filesystem": "xfs",
"mount_point": "/mnt/disks/BackupDrive",
"size_bytes": 4000787030016,
"used_bytes": 2500000000000,
"free_bytes": 1500787030016,
"usage_percent": 62.5,
"read_only": false,
"smb_share": true,
"nfs_share": false,
"status": "mounted"
}
],
"status": "mounted",
"spin_state": "active",
"temperature_celsius": 35.0,
"auto_mount": true,
"pass_through": false,
"disable_mount": false,
"script_enabled": true,
"script_path": "/boot/config/plugins/unassigned.devices/scripts/backup_script.sh",
"reads": 145623,
"writes": 89456,
"timestamp": "2025-11-14T10:30:00Z"
}
]
}
Example Response - GET /api/v1/unassigned/remote-shares:
{
"shares": [
{
"type": "smb",
"source": "//nas.local/Media",
"mount_point": "/mnt/remotes/Media",
"status": "mounted",
"size_bytes": 12000000000000,
"used_bytes": 8000000000000,
"free_bytes": 4000000000000,
"usage_percent": 66.67,
"auto_mount": true,
"read_only": false,
"smb_server": "nas.local",
"smb_share": "Media",
"smb_domain": "WORKGROUP",
"smb_user": "unraid",
"timestamp": "2025-11-14T10:30:00Z"
},
{
"type": "iso",
"source": "/mnt/user/isos/ubuntu-22.04.iso",
"mount_point": "/mnt/disks/ubuntu-22.04",
"status": "mounted",
"size_bytes": 3654957056,
"used_bytes": 3654957056,
"free_bytes": 0,
"usage_percent": 100.0,
"auto_mount": false,
"read_only": true,
"timestamp": "2025-11-14T10:30:00Z"
}
]
}
Control Endpoints (POST)
POST /api/v1/unassigned/devices/{serial}/mount
POST /api/v1/unassigned/devices/{serial}/unmount
POST /api/v1/unassigned/devices/{serial}/spin-down
POST /api/v1/unassigned/devices/{serial}/spin-up
POST /api/v1/unassigned/devices/{serial}/script-run
POST /api/v1/unassigned/remote-shares/{id}/mount
POST /api/v1/unassigned/remote-shares/{id}/unmount
POST /api/v1/unassigned/historical/{serial}/remove
Request Body Example - POST /api/v1/unassigned/devices/{serial}/mount:
{
"partition": 1,
"read_only": false
}
Configuration Endpoints
GET /api/v1/unassigned/devices/{serial}/config
POST /api/v1/unassigned/devices/{serial}/config
GET /api/v1/unassigned/remote-shares/{id}/config
POST /api/v1/unassigned/remote-shares/{id}/config
Config Fields:
{
"auto_mount": true,
"pass_through": false,
"read_only": false,
"disable_mount": false,
"script_enabled": true,
"script_path": "/path/to/script.sh",
"smb_share": true,
"nfs_share": false
}
5. WebSocket Events
Real-time updates for device changes:
unassigned_devices_update - Device list changed
unassigned_device_mount - Device mounted
unassigned_device_unmount - Device unmounted
unassigned_remote_shares_update - Remote share list changed
unassigned_remote_mount - Remote share mounted
unassigned_remote_unmount - Remote share unmounted
6. Controller Operations
Create daemon/services/controllers/unassigned.go:
// Mount unassigned device partition
func MountDevice(serial string, partition int, readOnly bool) error {
// Call /usr/local/emhttp/plugins/unassigned.devices/scripts/rc.unassigned mount <serial> <partition>
}
// Unmount unassigned device partition
func UnmountDevice(serial string, partition int) error {
// Call /usr/local/emhttp/plugins/unassigned.devices/scripts/rc.unassigned unmount <serial> <partition>
}
// Spin down disk
func SpinDownDevice(serial string) error {
// Use smartctl or hdparm to spin down
}
// Run device script
func RunDeviceScript(serial string) error {
// Execute configured script for device
}
// Mount remote share
func MountRemoteShare(id string) error {
// Call appropriate mount command (mount.cifs or mount.nfs)
}
Implementation Details
Detection Logic
-
Discover all block devices:
lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,LABEL,SERIAL,MODEL
-
Filter out array disks:
- Read
/var/local/emhttp/disks.ini
- Exclude any device listed in disks.ini
-
Enrich with plugin configuration:
- Parse
/boot/config/plugins/unassigned.devices/unassigned.devices.cfg
- Match devices by serial number
-
Get mount status:
- Parse
/proc/mounts
- Check for mounts under
/mnt/disks/ and /mnt/remotes/
-
Get device statistics:
- Temperature:
smartctl -A /dev/sdX | grep Temperature
- I/O stats:
/proc/diskstats
- Spin state: Parse
run_status.json or use hdparm -C
Remote Share Detection
-
Parse SMB mounts config:
/boot/config/plugins/unassigned.devices/samba_mount.cfg
-
Parse ISO mounts config:
/boot/config/plugins/unassigned.devices/iso_mount.cfg
-
Check mount status:
grep '/mnt/remotes/' /proc/mounts
-
Get capacity info:
df -B1 /mnt/remotes/<mount_point>
Collection Interval
- Unassigned devices: 30 seconds (same as disk collector)
- Remote shares: 60 seconds (similar to share collector)
- Historical devices: 5 minutes (changes infrequently)
Error Handling
- Plugin not installed: Return empty list with warning in logs
- Config file missing: Return devices based on system detection only
- Mount failures: Include error message in device status
- Permission issues: Log error, return partial data
Use Cases
- Monitoring Dashboards: Display all storage including unassigned devices
- Mobile Apps: Control unassigned device mounting remotely
- Backup Scripts: Detect when backup drive is connected and mounted
- Remote Management: Mount/unmount devices without SSH access
- Alerting Systems: Monitor unassigned device temperature and health
- Automation: Auto-mount network shares on specific events
Benefits
- Complete Storage Visibility: See all disks, not just array disks
- Remote Control: Mount/unmount devices via API
- Integration Support: Allows third-party tools to manage unassigned devices
- Monitoring: Track usage, temperature, and I/O of all disks
- Consistency: Same API pattern as array disks
Performance Considerations
Caching Strategy
- Cache device list and configuration
- Update on UDEV events or polling interval
- Track mount state changes via inotify on
/proc/mounts
Plugin Dependency
- Gracefully handle when plugin is not installed
- Return empty lists rather than errors
- Log warning about missing plugin
File System Checks
- Use async operations for disk I/O stats
- Throttle
smartctl calls (expensive operation)
- Cache temperature readings for 30-60 seconds
Implementation Checklist
Alternative Approaches Considered
1. Only expose mounted devices
Rejected: Users need to see unmounted devices to mount them remotely
2. Combine with regular disk endpoint
Rejected: Unassigned devices have different properties and lifecycle than array disks
3. Read-only monitoring, no control
Rejected: Control operations (mount/unmount) are core functionality users expect
Plugin Compatibility
The Unassigned Devices plugin has two editions:
- Unassigned Devices (free): Supports NTFS, ext4, XFS, Btrfs, ReiserFS
- Unassigned Devices Plus (paid): Adds HFS+, exFAT, APFS support
The API should support all filesystem types regardless of edition.
Priority
High - This plugin is used by a very large portion of the Unraid community. Supporting it would significantly expand the agent's usefulness and coverage of the system state.
Related
Generated with Claude Code
Feature Request: Unassigned Devices Plugin Support
Summary
The Unraid Management Agent currently only exposes disks that are part of the Unraid array (parity, cache, data disks). It does not expose information about Unassigned Devices - disks and remote shares managed by the popular "Unassigned Devices" plugin. This feature request proposes adding comprehensive support for discovering, monitoring, and controlling unassigned devices through the REST API and WebSocket interface.
Background
Unassigned Devices is one of the most popular Unraid plugins with over 100,000 active installations. It allows users to:
This is a critical plugin for many Unraid users who need additional storage flexibility beyond the array.
Current State
What's Currently Exposed ✅
The agent exposes array disks via
/api/v1/disks:Source:
/var/local/emhttp/disks.iniWhat's Missing ❌
Proposed Solution
1. New DTOs
Create new data transfer objects in
daemon/dto/unassigned.go:2. New Collector
Create
daemon/services/collectors/unassigned.go:3. Data Sources
The collector will read from multiple sources:
Configuration Files
/boot/config/plugins/unassigned.devices/unassigned.devices.cfg/boot/config/plugins/unassigned.devices/samba_mount.cfg/boot/config/plugins/unassigned.devices/iso_mount.cfgRuntime State Files
/tmp/unassigned.devices/config/device_hosts.json/tmp/unassigned.devices/config/run_status.jsonSystem Information
/sys/block/andlsblkcommand/proc/mountssmartctland/proc/diskstatsDetection Logic
4. API Endpoints
Monitoring Endpoints (GET)
Example Response -
GET /api/v1/unassigned/devices:{ "devices": [ { "device": "sdc", "serial_number": "WD-WCC4N5XZ8P5R", "model": "WDC WD40EFRX-68N32N0", "identification": "WD Red 4TB", "partitions": [ { "partition_number": 1, "label": "BackupDrive", "filesystem": "xfs", "mount_point": "/mnt/disks/BackupDrive", "size_bytes": 4000787030016, "used_bytes": 2500000000000, "free_bytes": 1500787030016, "usage_percent": 62.5, "read_only": false, "smb_share": true, "nfs_share": false, "status": "mounted" } ], "status": "mounted", "spin_state": "active", "temperature_celsius": 35.0, "auto_mount": true, "pass_through": false, "disable_mount": false, "script_enabled": true, "script_path": "/boot/config/plugins/unassigned.devices/scripts/backup_script.sh", "reads": 145623, "writes": 89456, "timestamp": "2025-11-14T10:30:00Z" } ] }Example Response -
GET /api/v1/unassigned/remote-shares:{ "shares": [ { "type": "smb", "source": "//nas.local/Media", "mount_point": "/mnt/remotes/Media", "status": "mounted", "size_bytes": 12000000000000, "used_bytes": 8000000000000, "free_bytes": 4000000000000, "usage_percent": 66.67, "auto_mount": true, "read_only": false, "smb_server": "nas.local", "smb_share": "Media", "smb_domain": "WORKGROUP", "smb_user": "unraid", "timestamp": "2025-11-14T10:30:00Z" }, { "type": "iso", "source": "/mnt/user/isos/ubuntu-22.04.iso", "mount_point": "/mnt/disks/ubuntu-22.04", "status": "mounted", "size_bytes": 3654957056, "used_bytes": 3654957056, "free_bytes": 0, "usage_percent": 100.0, "auto_mount": false, "read_only": true, "timestamp": "2025-11-14T10:30:00Z" } ] }Control Endpoints (POST)
Request Body Example -
POST /api/v1/unassigned/devices/{serial}/mount:{ "partition": 1, "read_only": false }Configuration Endpoints
Config Fields:
{ "auto_mount": true, "pass_through": false, "read_only": false, "disable_mount": false, "script_enabled": true, "script_path": "/path/to/script.sh", "smb_share": true, "nfs_share": false }5. WebSocket Events
Real-time updates for device changes:
6. Controller Operations
Create
daemon/services/controllers/unassigned.go:Implementation Details
Detection Logic
Discover all block devices:
Filter out array disks:
/var/local/emhttp/disks.iniEnrich with plugin configuration:
/boot/config/plugins/unassigned.devices/unassigned.devices.cfgGet mount status:
/proc/mounts/mnt/disks/and/mnt/remotes/Get device statistics:
smartctl -A /dev/sdX | grep Temperature/proc/diskstatsrun_status.jsonor usehdparm -CRemote Share Detection
Parse SMB mounts config:
Parse ISO mounts config:
Check mount status:
grep '/mnt/remotes/' /proc/mountsGet capacity info:
Collection Interval
Error Handling
Use Cases
Benefits
Performance Considerations
Caching Strategy
/proc/mountsPlugin Dependency
File System Checks
smartctlcalls (expensive operation)Implementation Checklist
dto/unassigned.gowith UnassignedDevice, UnassignedPartition, UnassignedRemoteShare DTOscollectors/unassigned.gocollectorAlternative Approaches Considered
1. Only expose mounted devices
Rejected: Users need to see unmounted devices to mount them remotely
2. Combine with regular disk endpoint
Rejected: Unassigned devices have different properties and lifecycle than array disks
3. Read-only monitoring, no control
Rejected: Control operations (mount/unmount) are core functionality users expect
Plugin Compatibility
The Unassigned Devices plugin has two editions:
The API should support all filesystem types regardless of edition.
Priority
High - This plugin is used by a very large portion of the Unraid community. Supporting it would significantly expand the agent's usefulness and coverage of the system state.
Related
daemon/services/collectors/disk.goGenerated with Claude Code