Initial import: nasmon health monitor, split out of home_nas

Go service (nasmon/) plus nfpm packaging (packaging/), carried over from
the home_nas monorepo. Adds rpm packaging alongside the existing deb
build (Fedora/RHEL/Rocky dependency names via nfpm overrides, per-format
systemd unit path, dpkg+rpm-compatible maintainer scripts) and a Gitea
Actions workflow that builds both formats on every push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 10:24:34 +03:00
co-authored by Claude Sonnet 5
commit 09159a5f31
61 changed files with 7882 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# nasmon
Lightweight NAS health monitor: web dashboard on port 8090 with CPU, memory
(incl. ZFS ARC), temperatures, physical disks with SMART, ZFS pools and datasets;
periodic SMART/ZFS checks with e-mail alerts.
* Web UI: http://<host>:8090
* Settings: http://<host>:8090/settings (also /etc/nasmon/config.json)
* State: /var/lib/nasmon/nasmon.db (SMART history, alert log)
* Service: systemctl status nasmon ; journalctl -u nasmon -f
* JSON API: /api/status, /api/history?hours=24, /api/disks/<serial>/history?days=30
## ZFS datasets from the UI
The ZFS page (http://<host>:8090/zfs) can create datasets in a pool — name (nested
names like media/video are fine), owner uid:gid for the mountpoint, compression,
optional quota — and destroy datasets that are empty (no data, no children, no
snapshots; the check is repeated against the live system before "zfs destroy").
nasmon runs sandboxed (ProtectSystem=strict → own mount namespace), so these
commands are executed on the host through transient units:
`systemd-run --wait --pipe --collect zfs create …`. Every action is logged
(journalctl -u nasmon).
nasmon has no authentication: keep port 8090 on the LAN only.
## SMB / NFS shares from the UI
http://<host>:8090/shares manages Samba shares (guest or per-user, files always
written as the data owner uid 1000), SMB users (smbpasswd; a login-less Unix
account is created when needed) and NFS exports (host/IP/CIDR clients, rw/ro,
squash). Definitions are stored in /etc/nasmon/shares.json; nasmon regenerates
/etc/samba/smb.conf (whole file, header "Managed by nasmon") and
/etc/exports.d/nasmon.exports, validates with testparm, then reloads smbd /
exportfs -ra. A rejected config is rolled back to the .bak copy. Daemons are
enabled while at least one share exists and disabled otherwise.
Alerts: the × on an open alert acknowledges it (hidden and not mailed until the
problem changes or clears); the × on a resolved alert removes it from the log.
Resolved alerts older than 7 days are hidden by default, older than 90 days
deleted.
+32
View File
@@ -0,0 +1,32 @@
{
"smtp": {
"host": "",
"port": 587,
"security": "starttls",
"user": "",
"pass": "",
"from": "",
"to": [],
"subject_prefix": "[nasmon]"
},
"alerts_enabled": true,
"notify_resolved": true,
"remind_hours": 24,
"smart_interval_min": 30,
"smart_skip_standby": true,
"thresholds": {
"disk_temp_c": 50,
"cpu_temp_c": 80,
"pool_capacity_pct": 85,
"scrub_max_age_days": 45,
"rootfs_free_pct": 10,
"nvme_used_pct": 90,
"nvme_spare_pct": 10
},
"self_tests": {
"enabled": false,
"short_days": 7,
"long_days": 30,
"hour": 3
}
}
+27
View File
@@ -0,0 +1,27 @@
[Unit]
Description=nasmon - NAS health monitor (SMART/ZFS dashboard and alerts)
Documentation=file:///usr/share/doc/nasmon/README.md
After=network-online.target zfs.target
Wants=network-online.target
[Service]
Type=simple
# smartctl and zpool need root
User=root
ExecStart=/usr/bin/nasmon -listen :8090 -config /etc/nasmon/config.json -data /var/lib/nasmon
Restart=on-failure
RestartSec=5s
# hardening
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/nasmon /etc/nasmon
ProtectKernelTunables=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yes
[Install]
WantedBy=multi-user.target
+78
View File
@@ -0,0 +1,78 @@
# nfpm packaging config — build with build.ps1/build.sh
# (or directly: nfpm package -f packaging/nfpm.yaml -p deb|rpm)
name: nasmon
arch: amd64
platform: linux
version: ${NASMON_VERSION}
section: admin
priority: optional
maintainer: home_nas <nas@localhost>
description: |
Lightweight NAS health monitor.
Web dashboard (CPU, memory, temperatures, disks with SMART, ZFS pools)
and periodic SMART/ZFS checks with e-mail alerts.
homepage: https://gitea.virtlab.space/a.gritsenko/nasmon
license: MIT
# Debian/Ubuntu package names by default; overrides.rpm below swaps in the
# Fedora/RHEL/Rocky names. ZFS has no native package on RHEL-family distros
# (needs the OpenZFS third-party repo), so it's intentionally left out of the
# rpm depends — see packaging/README.md.
depends:
- smartmontools
- zfsutils-linux
- util-linux
# file sharing is optional: the /shares page works once these are present
recommends:
- samba
- nfs-kernel-server
- wsdd
overrides:
rpm:
depends:
- smartmontools
- util-linux
recommends:
- samba
- nfs-utils
- wsdd
contents:
- src: ../dist/nasmon
dst: /usr/bin/nasmon
file_info:
mode: 0755
- src: ./nasmon.service
dst: /lib/systemd/system/nasmon.service
packager: deb
file_info:
mode: 0644
- src: ./nasmon.service
dst: /usr/lib/systemd/system/nasmon.service
packager: rpm
file_info:
mode: 0644
- src: ./wsdd.service
dst: /etc/systemd/system/wsdd.service
type: config
file_info:
mode: 0644
- src: ./config.json
dst: /etc/nasmon/config.json
type: config|noreplace
file_info:
mode: 0600
- dst: /var/lib/nasmon
type: dir
file_info:
mode: 0750
- src: ./README.md
dst: /usr/share/doc/nasmon/README.md
file_info:
mode: 0644
scripts:
postinstall: ./postinst.sh
preremove: ./prerm.sh
postremove: ./postrm.sh
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
set -e
# config holds the SMTP password: root only
chmod 0600 /etc/nasmon/config.json 2>/dev/null || true
chmod 0750 /var/lib/nasmon
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
systemctl enable nasmon.service >/dev/null 2>&1 || true
# restart on upgrade, start on fresh install
systemctl restart nasmon.service || true
# Windows network discovery, only when the wsdd package is present
if [ -x /usr/bin/wsdd ]; then
systemctl enable --now wsdd.service >/dev/null 2>&1 || true
fi
fi
exit 0
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
set -e
if [ -d /run/systemd/system ]; then
systemctl daemon-reload || true
fi
if [ "$1" = "purge" ]; then
rm -rf /var/lib/nasmon /etc/nasmon
fi
exit 0
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
set -e
# dpkg passes "remove" here; rpm passes a numeric $1 (0 = last uninstall) - cover both.
if { [ "$1" = "remove" ] || [ "$1" = "0" ]; } && [ -d /run/systemd/system ]; then
systemctl stop nasmon.service >/dev/null 2>&1 || true
systemctl disable nasmon.service >/dev/null 2>&1 || true
systemctl disable --now wsdd.service >/dev/null 2>&1 || true
fi
exit 0
+16
View File
@@ -0,0 +1,16 @@
# Installed by nasmon: the Ubuntu wsdd package ships no unit file.
# wsdd answers WS-Discovery so the NAS shows up under "Network" in Windows Explorer.
[Unit]
Description=Web Services Dynamic Discovery host daemon
After=network-online.target
Wants=network-online.target
ConditionPathExists=/usr/bin/wsdd
[Service]
ExecStart=/usr/bin/wsdd --shortlog
DynamicUser=yes
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target