Skip to content

brainchillz/StorageDashboard

Repository files navigation

Storage Management Dashboard

A web-based management dashboard for Ubuntu Linux that provides a unified interface for managing:

  • ZFS Storage Pools — Pools, datasets, snapshots, ZVOLs; scrub, capacity/health visualization, full device lifecycle (replace/offline/online/detach, add spare/cache/log vdevs), pool import/export, snapshot diff, snapshot browse + single-file restore, and send/receive replication to a remote host
  • iSCSI Targets — LIO targets, backstores (fileio/block/ZVOL), LUNs, ACLs, CHAP, connected-initiator view, auto-saved config, and a shared multi-initiator default for Proxmox/VMware clusters
  • NFS Exports — Manage NFS exports and client access
  • SMB/CIFS Shares — Create and manage Samba shares and users
  • Disks — SMART health, role/usage labeling, drive locate (LED + activity), and safe wipe of free/stale disks
  • LVM — full PV/VG/LV management (create, resize, extend, pvmove) with the system/boot LVM protected
  • MD RAID — Linux software RAID (mdadm): create arrays from free disks, add/fail/remove members, hot spares, persisted to mdadm.conf; in-use arrays protected
  • Alerting — email (SMTP) + webhook (Google Chat / Slack-compatible) notifications on degraded/full pools, stopped services, and SMART failures, de-duplicated (notify once per condition, with a resolved notice when it clears)
  • Scheduled maintenance — periodic ZFS scrubs and SMART self-tests
  • Network — set hostname/domain and per-interface IP (DHCP or static: address/gateway/DNS) plus bridges, via netplan; every change auto-reverts after 90s unless confirmed, so a bad setting can't lock you out
  • Monitoring — Prometheus /metrics, live resource overview, audit log, RBAC users + API tokens, first-run forced password change

Built with Python/Flask backend and a dark-theme single-page web UI, served over HTTPS with session authentication.

Architecture

┌─────────────────────────────────────────────────────┐
│                    Web Browser                       │
│         https://<host>:8443                          │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│        Flask Web Server (HTTPS, port 8443)           │
│          /opt/storage-dashboard/app.py               │
│          Runs as: dashboard user                      │
└───────┬──────────┬──────────┬──────────┬────────────┘
        │          │          │          │
   ┌────▼────┐ ┌──▼───┐ ┌───▼────┐ ┌──▼────┐
   │  ZFS    │ │iSCSI │ │  NFS   │ │  SMB  │
   │zfsutils │ │LIO   │ │nfs-ker-│ │samba  │
   │         │ │target│ │nel-svr │ │       │
   └────────┘ └──────┘ └────────┘ └───────┘
   All managed via sudo with passwordless sudoers rules

Prerequisites

  • Ubuntu 22.04, 24.04, or 26.04 LTS
  • Root or sudo access
  • At least one unused disk for ZFS pools (optional)

Quick Install

Copy the project to /opt/storage-dashboard, then from that directory run (as root):

sudo ./install-prerequisites.sh   # install required apt packages
sudo ./install.sh                 # create user, venv, sudoers, systemd service

install.sh invokes install-prerequisites.sh automatically, so on a host that already has the packages you can run install.sh alone.

On first start an admin account is created with a random password printed to the log — see Authentication below.

Step-by-step manual install:

# 1. Install system dependencies (or just run ./install-prerequisites.sh)
sudo apt-get update
sudo apt-get install -y python3 python3-venv python3-pip \
  zfsutils-linux targetcli-fb nfs-kernel-server nfs-common samba \
  smbclient cifs-utils lsscsi smartmontools gdisk mdadm parted ledmon \
  lvm2 xfsprogs sg3-utils iproute2 openssl openssh-client acl curl

# 2. Create the directory and files
sudo mkdir -p /opt/storage-dashboard/static /opt/storage-dashboard/templates
# Copy all project files into /opt/storage-dashboard/

# 3. Create the dashboard user
sudo useradd -r -s /usr/sbin/nologin -M -d /opt/storage-dashboard dashboard

# 4. Set up Python virtual environment
sudo python3 -m venv /opt/storage-dashboard/venv
sudo /opt/storage-dashboard/venv/bin/pip install flask

# 5. Set up sudoers + the disk-locate helper.
# install.sh is the single source of truth for both (the exact, security-scoped
# command list is large and version-specific). Rather than hand-copy it, install
# it from install.sh's heredocs:
#
#   - /etc/sudoers.d/storage-dashboard  (the `SUDOERS` heredoc; chmod 440)
#   - /usr/local/sbin/storage-dashboard-locate-read  (root-owned 0755 helper)
#   - /usr/local/sbin/storage-dashboard-snap-fs       (root-owned 0755 helper)
#
# Easiest: just run ./install.sh, which writes both. Always validate with
# `visudo -cf /etc/sudoers.d/storage-dashboard` before relying on it.

# 6. Set ownership
sudo chown -R dashboard:dashboard /opt/storage-dashboard
sudo mkdir -p /var/log/storage-dashboard
sudo chown dashboard:dashboard /var/log/storage-dashboard

# 7. Create systemd service
sudo tee /etc/systemd/system/storage-dashboard.service << 'SERVICE'
[Unit]
Description=Ubuntu Storage Management Dashboard
After=network.target zfs.target nfs-server.service smbd.service
Wants=zfs.target nfs-server.service smbd.service

[Service]
Type=simple
User=dashboard
Group=dashboard
WorkingDirectory=/opt/storage-dashboard
ExecStart=/opt/storage-dashboard/venv/bin/python /opt/storage-dashboard/app.py
Restart=on-failure
RestartSec=10
StandardOutput=append:/var/log/storage-dashboard/app.log
StandardError=append:/var/log/storage-dashboard/app.log

[Install]
WantedBy=multi-user.target
SERVICE

# 8. Enable and start services
sudo systemctl daemon-reload
sudo systemctl enable zfs.target
sudo systemctl enable target
sudo systemctl enable nfs-server
sudo systemctl enable smbd
sudo systemctl enable storage-dashboard
sudo systemctl start target nfs-server smbd storage-dashboard

# 9. (Optional) Open firewall ports
sudo ufw allow 8443/tcp comment 'Storage Dashboard'
sudo ufw allow 3260/tcp comment 'iSCSI Target'
sudo ufw allow 2049/tcp comment 'NFS'
sudo ufw allow 445/tcp comment 'SMB'
sudo ufw allow 139/tcp comment 'SMB NetBIOS'

Accessing the Dashboard

Once installed, open a web browser to:

https://<server-ip>:8443

(Self-signed certificate by default — accept the browser warning once, or install your own cert; see TLS.)

The dashboard will show the service status on the main page. Use the sidebar to navigate between:

Section Description
Dashboard At-a-glance metrics (pool usage, iSCSI/NFS/SMB counts, disks) + health alerts
Disks Disks with role/usage, SMART health, locate (LED + activity), and wipe
ZFS Pools Pools/datasets/snapshots/ZVOLs, scrub, capacity bars, device lifecycle, import/export, snapshot diff/browse/restore, send/receive replication
LVM Physical volumes, volume groups, logical volumes (create/resize/extend/move)
MD RAID Linux software RAID arrays (create/manage/replace), members from free disks
iSCSI Targets Manage iSCSI targets, backstores, LUNs, ACLs
NFS Exports Create and manage NFS shared directories
SMB/CIFS Shares (access control, recycle, Previous Versions, Time Machine), users, groups, home dirs, global settings
Auto-Snapshots Opt-in scheduled ZFS snapshots (per dataset/pool, with retention)
System Services, Network (hostname/IP/bridges), My Account, Users & Tokens, Notifications, TLS Certificate, and the Audit Log

Authentication

The dashboard requires a login. All /api/* endpoints (except the login route) return 401 without a valid session; the session cookie is HttpOnly and SameSite=Lax.

On first start, if no account exists, an admin user is created:

  • If DASHBOARD_ADMIN_PASSWORD is set in the environment, that password is used.

  • Otherwise a random password is generated and printed to the log. Retrieve it:

    sudo grep -A2 'initial admin account' /var/log/storage-dashboard/app.log

Additional users with roles can be created in System → Users & Tokens: administrator (full access) or read-only (can view but not change anything; enforced server-side — all mutating requests return 403). An optional "SMB user" checkbox also creates a matching Samba account.

Change your own password from System → My Account, or from the CLI:

sudo -u dashboard /opt/storage-dashboard/venv/bin/python \
  /opt/storage-dashboard/app.py set-password admin

Credentials and the session secret are stored in auth.json (mode 0600, owned by the dashboard user) next to app.py; override its location with DASHBOARD_AUTH_FILE.

API tokens (automation)

For scripts that can't carry a session cookie, create tokens in System → Users & Tokens (admin only). Each token has a name and a role (administrator or read-only, enforced by the same server-side RBAC as users), and the secret is shown once at creation — only its SHA-256 is stored. Present it as a header on any API request:

# read-only example
curl -sk -H "Authorization: Bearer sd_xxxxxxxx" https://host:8443/api/summary
# (the X-API-Token: <token> header works too)

Revoke a token any time from the same panel. Token actions are recorded in the audit log as token:<name>. (Tokens are stored in auth.json; the metrics endpoint has its own separate DASHBOARD_METRICS_TOKEN.)

TLS

The dashboard serves HTTPS on port 8443 by default. On first start it generates a self-signed certificate (certs/dashboard.crt / .key next to app.py) — browsers show a one-time warning you can accept.

To install your own certificate, any of:

  • System → Certificate in the UI → paste your PEM cert + private key (validated and saved; restart the service to apply).
  • Drop your PEM files at certs/dashboard.crt / certs/dashboard.key (replacing the self-signed pair) and restart.
  • Point DASHBOARD_TLS_CERT / DASHBOARD_TLS_KEY at your files.

Relevant environment variables:

Variable Default Purpose
DASHBOARD_TLS 1 Set 0 to serve plain HTTP (e.g. behind a TLS-terminating reverse proxy)
DASHBOARD_PORT 8443 (TLS) / 8080 (no TLS) Listen port
DASHBOARD_TLS_CERT / DASHBOARD_TLS_KEY certs/dashboard.* Certificate / key paths
DASHBOARD_COOKIE_SECURE follows DASHBOARD_TLS Force the session cookie to HTTPS-only
DASHBOARD_METRICS_TOKEN (unset) If set, /metrics requires this token (?token= or Bearer); otherwise open

API Endpoints

Authentication

  • POST /api/login — Log in {username, password} (sets session cookie)
  • POST /api/logout — Log out
  • GET /api/me — Current session status
  • POST /api/account/password — Change own password {old_password, new_password}
  • GET|POST /api/users — List / create users {username, password, role, smb} (admin)
  • POST /api/users/<u>/role {role} · /password {password} · DELETE /api/users/<u> (admin)
  • GET|POST /api/tokens — List / create API tokens {name, role} (admin; the secret is returned once)
  • DELETE /api/tokens/<id> — Revoke a token (admin)

System

  • GET /api/summary — Aggregated dashboard overview (pools/usage, iSCSI targets/LUNs/sessions, NFS exports/mounts, SMB shares/users, disks, alerts)
  • GET /api/system/resources — Live CPU %, load average, memory/swap, uptime
  • GET /api/status — Service status overview
  • GET /api/network — Network interfaces
  • GET /api/logs/<service> — Service logs
  • GET /metrics — Prometheus metrics (host resources, ZFS pools, services, SMART). Public so a scraper can reach it; set DASHBOARD_METRICS_TOKEN to require a token (?token= or Authorization: Bearer).
  • GET|POST /api/notifications — Email/webhook config (SMTP password masked on read)
  • POST /api/notifications/test — Send a test notification via the saved channels
  • GET|POST /api/maintenance — Scheduled scrubs + SMART self-tests
  • POST /api/maintenance/smart-test — Start a SMART self-test now {device, type}
  • GET /api/network — Hostname/domain, interfaces, gateway, DNS, managed netplan, pending-revert status
  • POST /api/network/hostname — Set {hostname, domain} (hostnamectl + /etc/hosts)
  • POST /api/network/interface — Configure {iface, mode: dhcp|static, address, gateway, nameservers[]} (auto-reverts)
  • POST /api/network/bridge — Create {name, interfaces[], mode, address, gateway, nameservers[]} (auto-reverts)
  • POST /api/network/confirm {token} · POST /api/network/revert — Keep or roll back a pending change

Disks

  • GET /api/disks — List disks (annotated with usage, wipeable, wipe_reason)
  • GET /api/disks/<dev>/smart — SMART health (ATA + NVMe)
  • POST /api/disks/<dev>/locate — Flash the drive {seconds} or {stop:true} (read-only)
  • POST /api/disks/<dev>/wipe — Blank a free/stale disk (refused on protected disks)

TLS

  • GET /api/tls/info — Current certificate (subject, issuer, expiry, self-signed?)
  • POST /api/tls/cert — Install a custom certificate {cert, key} (PEM); restart to apply
  • POST /api/tls/regenerate — Regenerate the self-signed certificate; restart to apply

ZFS

  • GET /api/zfs/pools — List pools
  • GET /api/zfs/pools/detail — Per-pool status (state, scan, devices, errors)
  • POST /api/zfs/pools — Create pool {name, vdev_type, disks[]}
  • DELETE /api/zfs/pools/<name> — Destroy pool
  • POST /api/zfs/pools/<name>/scrub — Scrub {action: start|stop}
  • POST /api/zfs/pools/<name>/device — Device op {action: replace|offline|online|detach, device, new_device?}
  • POST /api/zfs/pools/<name>/vdev — Add vdev {role: ''|mirror|raidz*|spare|cache|log, disks[]}
  • GET /api/zfs/pools/importable — Scan for importable (not-yet-imported) pools
  • POST /api/zfs/pools/import — Import {name|id, new_name?, altroot?, force?}
  • POST /api/zfs/pools/<name>/export — Export pool (refused 409 if it backs the system)
  • GET /api/zfs/pools/<name>/datasets — List datasets
  • POST /api/zfs/datasets — Create dataset/ZVOL {name, properties{}, volsize?}
  • GET /api/zfs/zvols — List ZVOLs (for iSCSI block backstores)
  • POST /api/zfs/datasets/rename — Rename {name, new_name}
  • GET|PUT /api/zfs/datasets/<name>/properties — Get / set {property, value}
  • DELETE /api/zfs/datasets/<name> — Destroy dataset
  • GET /api/zfs/snapshots — List snapshots
  • POST /api/zfs/snapshots — Create snapshot {dataset, snap_name, recursive?}
  • POST /api/zfs/snapshots/clone — Clone {snapshot, target}
  • POST /api/zfs/snapshots/rollback — Rollback {snapshot}
  • DELETE /api/zfs/snapshots/<name> — Destroy snapshot
  • GET /api/zfs/snapshots/diff?from=<snap>&to=<snap|dataset> — File-level diff (vs the live filesystem if to omitted)
  • GET /api/zfs/snapshots/<snap>/browse?path= — List a directory inside a snapshot
  • POST /api/zfs/snapshots/<snap>/restore — Restore {path, mode: copy|inplace}
  • GET /api/zfs/datasets/all — All snapshot targets (pools, datasets, volumes)
  • GET /api/zfs/replication — Replication jobs + the dashboard's SSH public key
  • POST /api/zfs/replication — Create/update job {source, host, user, port, target, recursive?, enabled?}
  • POST /api/zfs/replication/test — Test SSH + remote zfs {host, user, port}
  • POST /api/zfs/replication/<id>/run — Run a job now (full or incremental)
  • POST /api/zfs/replication/key/regenerate — Regenerate the replication keypair
  • DELETE /api/zfs/replication/<id> — Delete a job

Snapshot schedules (opt-in)

Automatic snapshots run only while an enabled schedule exists; the systemd timer is enabled/disabled to match. Pruning only removes autosnap_* snapshots.

  • GET /api/snapshots/schedules — List schedules + timer state
  • POST /api/snapshots/schedules — Create/update {dataset, recursive, enabled, keep{hourly,daily,weekly,monthly}}
  • DELETE /api/snapshots/schedules/<dataset> — Remove schedule (keeps existing snapshots)
  • POST /api/snapshots/schedules/<dataset>/run — Run the schedule now

LVM

Destructive ops are refused on anything backing a mounted filesystem (the boot/root LVM); new PVs only on free disks.

  • GET /api/lvm — PVs, VGs, LVs (with protection flags)
  • POST /api/lvm/pv {device} — create PV · /pv/resize · /pv/move {source, dest?} · /pv/remove {device}
  • POST /api/lvm/vg {name, devices[]} · /vg/<name>/extend {device} · /vg/<name>/reduce {device} · DELETE /api/lvm/vg/<name>
  • POST /api/lvm/lv {vg, name, size, fstype?} · /lv/<vg>/<name>/extend {size, resize_fs} · DELETE /api/lvm/lv/<vg>/<name>

MD RAID

Members must be free disks; arrays backing a mounted FS / pool / LVM are protected.

  • GET /api/mdadm/arrays — List arrays (state, members, sync)
  • POST /api/mdadm/arrays — Create {name, level, devices[], spares[], persist}
  • POST /api/mdadm/arrays/<dev>/device{action: add|remove|fail, device}
  • POST /api/mdadm/arrays/<dev>/stop · POST /api/mdadm/assemble · DELETE /api/mdadm/arrays/<dev>

iSCSI

All mutating iSCSI operations auto-save the LIO config (survives a target.service restart).

  • GET /api/iscsi/targets — List target IQNs
  • POST /api/iscsi/targets — Create target {iqn, access_mode} where access_mode is shared (default; any initiator, for Proxmox/VMware clusters) or restricted (explicit ACLs only)
  • GET /api/iscsi/targets/<iqn> — Target detail (LUNs, ACLs, portals, mode)
  • DELETE /api/iscsi/targets/<iqn> — Delete target
  • POST /api/iscsi/targets/<iqn>/mode — Set access mode {mode: shared|restricted}
  • GET /api/iscsi/backstores — List backstores (with size + in-use status)
  • POST /api/iscsi/backstores — Create backstore {type, name, path, size} (type fileio or block; a ZVOL is a block backstore at /dev/zvol/<pool>/<vol>)
  • DELETE /api/iscsi/backstores/<type>/<name> — Delete backstore
  • POST /api/iscsi/luns — Create LUN {iqn, backstore_type, backstore_name, lun_id?}
  • POST /api/iscsi/luns/delete — Delete LUN {iqn, lun}
  • POST /api/iscsi/acls — Create ACL {iqn, initiator_iqn}
  • POST /api/iscsi/acls/delete — Delete ACL {iqn, initiator_iqn}
  • POST /api/iscsi/acls/chap — Set/clear CHAP {iqn, initiator_iqn, userid, password} or {…, clear:true}
  • POST /api/iscsi/portals — Create portal {iqn, ip, port}
  • POST /api/iscsi/portals/delete — Delete portal {iqn, ip, port}
  • GET /api/iscsi/sessions — Connected initiators per target (from configfs)
  • POST /api/iscsi/saveconfig — Save config to disk (also done automatically)

NFS

  • GET /api/nfs/exports — List exports
  • POST /api/nfs/exports — Create/replace export {path, clients[{host, options}]} (multiple clients supported; options e.g. rw,sync,no_subtree_check,no_root_squash)
  • DELETE /api/nfs/exports/<path> — Remove export (also removes the directory if empty)
  • GET /api/nfs/exportfs — Active exports (exportfs -v)
  • GET /api/nfs/clients — Active client mounts (showmount -a)

SMB

  • GET /api/smb/shares — List shares
  • POST /api/smb/shares — Create share {name, path, ...}
  • DELETE /api/smb/shares/<name> — Remove share
  • POST /api/smb/users — Create SMB user {username, password} (no password rules)
  • GET /api/smb/users — List SMB users (with enabled/disabled state)
  • POST /api/smb/users/<u>/password — Set password {password}
  • POST /api/smb/users/<u>/enable | /disable — Toggle account
  • GET|POST /api/smb/groups, DELETE /api/smb/groups/<name>, POST /api/smb/groups/<name>/members {username, action} — Groups for @group ACLs
  • GET|POST /api/smb/homes {enabled} — One-click home-directory shares ([homes])
  • GET /api/smb/shares — List shares (with access-control + VFS flags)
  • POST /api/smb/shares — Create/update a share (path, access control, VFS features)
  • POST /api/smb/shares/<name>/toggle — Enable/disable a share
  • DELETE /api/smb/shares/<name> — Remove a share
  • GET /api/smb/status — Parsed sessions / share connections / open files
  • GET|POST /api/smb/global — Global settings (workgroup, guest mapping, min protocol incl. SMB1, encryption, signing)

File Structure

/opt/storage-dashboard/
├── app.py                    # Flask backend application
├── install.sh                # Automated installation script
├── install-prerequisites.sh  # Installs required apt packages
├── requirements.txt          # Python dependencies
├── auth.json                 # Credentials + session secret (0600, gitignored)
├── schedules.json            # Snapshot schedules (gitignored; absent until used)
├── certs/                    # TLS cert + key (gitignored; self-signed by default)
├── .gitignore
├── README.md
├── static/
│   └── css/
│       └── style.css         # Dark theme UI styles
├── templates/
│   └── index.html            # Single-page web application
└── venv/                     # Python virtual environment

Plus, installed outside the app directory:

/usr/local/sbin/storage-dashboard-locate-read     # root-owned read-only disk-locate helper
/usr/local/sbin/storage-dashboard-iscsi-sessions  # root-owned read-only iSCSI sessions helper
/usr/local/sbin/storage-dashboard-snap-fs         # root-owned snapshot browse/restore helper (path-confined)
/usr/local/sbin/storage-dashboard-netplan         # root-owned netplan apply helper (validates, restores on failure)
/etc/sudoers.d/storage-dashboard                  # passwordless sudo rules
/etc/systemd/system/storage-dashboard.service     # systemd unit
/etc/systemd/system/storage-dashboard-autosnap.{service,timer}   # auto-snapshots (disabled until used)
/etc/systemd/system/storage-dashboard-replicate.{service,timer}  # ZFS replication (disabled until used)

Service Management

# View status
sudo systemctl status storage-dashboard

# View logs
sudo journalctl -u storage-dashboard -f

# Restart
sudo systemctl restart storage-dashboard

# Stop
sudo systemctl stop storage-dashboard

Troubleshooting

Check if the service is running:

sudo systemctl status storage-dashboard

Check logs for errors:

sudo journalctl -u storage-dashboard --no-pager -n 50

Test API directly (HTTPS, self-signed → -k; most endpoints need auth):

curl -k https://localhost:8443/

Check sudoers permissions (run as dashboard user):

sudo -u dashboard sudo -n /usr/sbin/zpool list

If ZFS module is not loaded:

sudo modprobe zfs

If targetcli fails with lockfile error:

sudo rm -f /var/run/targetcli.lock

License

MIT

About

Storage Management Dashboard — single-host web UI for managing ZFS pools, iSCSI, NFS and SMB/Samba on Ubuntu.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors