nas16-scripts/omv/SSD-fstrim-Summary.html

522 lines
21 KiB
HTML
Raw Permalink Normal View History

2026-07-26 21:21:03 -04:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KingDezigns — SSD Hardening Summary</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f7f6f2;
--surface: #ffffff;
--surface-alt: #f1f0eb;
--border: #e2e0d8;
--border-strong: #c8c5ba;
--text-primary: #1a1917;
--text-secondary: #6b6960;
--text-muted: #9b9890;
--green-bg: #eaf3de;
--green-border: #97c459;
--green-text: #2d5a0e;
--red-bg: #fcebeb;
--red-border: #f09595;
--red-text: #7a1f1f;
--amber-bg: #faeeda;
--amber-border: #fac775;
--amber-text: #633806;
--coral-bg: #faece7;
--coral-border: #f0997b;
--coral-text: #4a1b0c;
--mono: 'IBM Plex Mono', monospace;
--sans: 'IBM Plex Sans', sans-serif;
}
body {
font-family: var(--sans);
font-size: 14px;
background: var(--bg);
color: var(--text-primary);
line-height: 1.7;
padding: 2.5rem 1.5rem;
}
.page { max-width: 860px; margin: 0 auto; }
header {
border-bottom: 1px solid var(--border-strong);
padding-bottom: 1.25rem;
margin-bottom: 2rem;
}
header h1 {
font-family: var(--mono);
font-size: 18px;
font-weight: 500;
letter-spacing: -0.02em;
margin-bottom: 6px;
}
.meta {
font-size: 12px;
color: var(--text-muted);
font-family: var(--mono);
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.section { margin-bottom: 2.25rem; }
.section-title {
font-family: var(--mono);
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 10px;
}
.section-title::before {
content: attr(data-num);
background: var(--text-primary);
color: var(--bg);
font-size: 10px;
width: 18px;
height: 18px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.section-title::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
p { margin-bottom: 0.75rem; color: var(--text-primary); }
p:last-child { margin-bottom: 0; }
.badge-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 1rem; }
.badge {
font-family: var(--mono);
font-size: 11px;
padding: 3px 10px;
border-radius: 3px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text-secondary);
}
.badge.red { background: var(--red-bg); color: var(--red-text); border-color: var(--red-border); }
.badge.green { background: var(--green-bg); color: var(--green-text); border-color: var(--green-border); }
.badge.amber { background: var(--amber-bg); color: var(--amber-text); border-color: var(--amber-border); }
.step {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 1rem 1.25rem;
margin-bottom: 0.6rem;
}
.step-header {
font-family: var(--mono);
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 0.6rem;
display: flex;
align-items: center;
gap: 8px;
}
.step-num {
background: var(--surface-alt);
border: 1px solid var(--border);
border-radius: 50%;
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 10px;
flex-shrink: 0;
color: var(--text-muted);
}
pre {
background: var(--surface-alt);
border: 1px solid var(--border);
border-radius: 4px;
padding: 10px 12px;
font-family: var(--mono);
font-size: 12px;
overflow-x: auto;
margin: 0.5rem 0;
white-space: pre;
line-height: 1.6;
}
code {
font-family: var(--mono);
font-size: 12px;
background: var(--surface-alt);
padding: 1px 5px;
border-radius: 3px;
border: 1px solid var(--border);
}
.why {
font-size: 12px;
color: var(--text-muted);
margin-top: 0.4rem;
padding-left: 2px;
}
.why::before { content: '↳ why: '; font-family: var(--mono); }
.result { font-size: 12px; margin-top: 0.35rem; padding-left: 2px; }
.result.pass { color: var(--green-text); }
.result.pass::before { content: '✓ '; font-family: var(--mono); }
.result.fail { color: var(--red-text); }
.result.fail::before { content: '✗ '; font-family: var(--mono); }
.result.neutral { color: var(--text-secondary); }
.result.neutral::before { content: '→ '; font-family: var(--mono); }
.callout { border-radius: 6px; padding: 1rem 1.25rem; margin-bottom: 0.75rem; }
.callout.coral { background: var(--coral-bg); border: 1px solid var(--coral-border); }
.callout.coral p { color: var(--coral-text); }
.callout.coral pre { background: #fdf0eb; border-color: var(--coral-border); }
.callout.amber { background: var(--amber-bg); border: 1px solid var(--amber-border); }
.callout.amber p { color: var(--amber-text); }
.callout.amber pre { background: #fdf5e6; border-color: var(--amber-border); }
.callout.amber code { background: #fdf5e6; border-color: var(--amber-border); color: var(--amber-text); }
.callout.green { background: var(--green-bg); border: 1px solid var(--green-border); }
.callout.green p { color: var(--green-text); }
.callout.green pre { background: #d8efc0; border-color: var(--green-border); }
.clue-label {
font-family: var(--mono);
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--amber-text);
margin-bottom: 8px;
}
.script-box { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.script-row { padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--border); }
.script-row:last-child { border-bottom: none; }
.script-label { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-muted); margin-bottom: 6px; }
.script-row pre { margin: 0; }
table { width: 100%; border-collapse: collapse; font-size: 13px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
th { text-align: left; padding: 8px 14px; font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); background: var(--surface-alt); border-bottom: 1px solid var(--border); font-weight: 500; }
td { padding: 9px 14px; border-bottom: 1px solid var(--border); vertical-align: top; line-height: 1.5; }
tr:last-child td { border-bottom: none; }
.footer {
font-family: var(--mono);
font-size: 11px;
color: var(--text-muted);
margin-top: 2.5rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
}
</style>
</head>
<body>
<div class="page">
<header>
<h1>KingDezigns — SSD Hardening (TRIM + noatime) summary</h1>
<div class="meta">
<span>📅 2026-05-23</span>
<span>🖥 NAS08 &amp; NAS16</span>
<span>⚙️ OpenMediaVault</span>
<span>🔧 Raspberry Pi 5 / Raspberry Pi</span>
</div>
</header>
<!-- SECTION 1 — OBJECTIVE -->
<div class="section">
<div class="section-title" data-num="1">Objective</div>
<p>Harden both NAS08 and NAS16 to extend SSD lifespan by ensuring TRIM is running correctly, noatime is active, and SMART health is being monitored — with all scheduled jobs visible in OMV's scheduler for easy management.</p>
<div class="badge-row">
<span class="badge green">NAS08 — 5 drives (1x PNY CS900 + 4x Crucial BX500)</span>
<span class="badge green">NAS16 — 1 drive (unknown model, standard SMART attrs)</span>
</div>
</div>
<!-- SECTION 2 — INVESTIGATION STEPS -->
<div class="section">
<div class="section-title" data-num="2">Investigation steps — both units</div>
<div class="step">
<div class="step-header"><span class="step-num">1</span> Verify TRIM support on all drives</div>
<pre>lsblk --discard /dev/sda</pre>
<p class="why">Confirm DISC-GRAN and DISC-MAX are non-zero before enabling TRIM.</p>
<p class="result pass">Both units: DISC-GRAN 4K / DISC-MAX 4G — TRIM fully supported on all drives.</p>
</div>
<div class="step">
<div class="step-header"><span class="step-num">2</span> Check if systemd fstrim.timer was already active</div>
<pre>systemctl status fstrim.timer</pre>
<p class="why">Determine whether a TRIM schedule already existed before creating a new one.</p>
<p class="result neutral">Both units: timer active and enabled, running weekly. NAS08 next run Monday 12:33 AM — NAS16 next run Monday 1:37 AM.</p>
</div>
<div class="step">
<div class="step-header"><span class="step-num">3</span> Confirm which volumes were being trimmed</div>
<pre>journalctl -u fstrim.service --no-pager</pre>
<p class="why">Verify the timer was actually running and catching all mounted volumes.</p>
<p class="result pass">Both units: / and /boot/firmware trimmed successfully on May 11 and May 18. NAS08 ~400 GiB freed per run. NAS16 ~228 GiB on first run, 3.3 GiB on second (less free space).</p>
</div>
<div class="step">
<div class="step-header"><span class="step-num">4</span> Check noatime mount status</div>
<pre>findmnt -t ext4,xfs,btrfs -o TARGET,SOURCE,OPTIONS</pre>
<p class="why">Confirm noatime was already set on all filesystems to prevent reads generating writes.</p>
<p class="result pass">Both units: every mount point already showing noatime — handled automatically by the openmediavault-flashmemory plugin. No action required.</p>
</div>
<div class="step">
<div class="step-header"><span class="step-num">5</span> Inspect SMART attributes per drive</div>
<pre>sudo smartctl -A /dev/sda
sudo smartctl -A /dev/sdb # NAS08 data drives</pre>
<p class="why">Identify vendor-specific SMART attribute names before writing the monitoring script — attribute names differ significantly between PNY and Crucial.</p>
<p class="result neutral">NAS08 PNY CS900: uses SSD_Life_Left (attr 231), Bad_Blk_Ct_Lat/Erl (attr 170), no pending/uncorrectable attrs. NAS08 Crucial BX500: uses Percent_Lifetime_Remain (attr 202), Reallocate_NAND_Blk_Cnt (attr 5), Reported_Uncorrect. NAS16 drive: uses standard attribute names — no vendor mapping required.</p>
</div>
</div>
<!-- SECTION 3 — WHAT WAS ALREADY DONE -->
<div class="section">
<div class="section-title" data-num="3">What was already handled by OMV</div>
<div class="callout green">
<p><strong>noatime:</strong> Already active on all filesystems on both units via the <code>openmediavault-flashmemory</code> plugin. No manual configuration required.</p>
</div>
<div class="callout green">
<p><strong>Swap disabled, logs in tmpfs:</strong> Also handled automatically by the flashmemory plugin — swap off, /var/log and /tmp in RAM, reducing write amplification significantly.</p>
</div>
<div class="callout amber">
<div class="clue-label">⚠ Note</div>
<p>The systemd <code>fstrim.timer</code> was already running weekly on both units but was invisible in OMV's scheduler. It was replaced with an OMV Scheduled Job so all jobs are visible and manageable in one place.</p>
</div>
</div>
<!-- SECTION 4 — TRIM REPORT SCRIPT -->
<div class="section">
<div class="section-title" data-num="4">TRIM report script — created and deployed</div>
<p>A custom script was written to replace the bare <code>fstrim</code> call with a full monitoring run that emails an HTML report matching the KingDezigns backup monitor style (dark header, badge/banner, section rows, cards, footer).</p>
<p>The email report includes: TRIM results per volume with total bytes freed, SSD SMART health per drive (health, temperature, wear, power-on hours, sector counts), filesystem usage with colour-coded warnings at 75% and 90%, and system info (OMV version, kernel, IP, uptime, load, memory, next run time).</p>
<div class="script-box">
<div class="script-row">
<div class="script-label">Script location</div>
<pre>/usr/scripts/omv/fstrim-report.sh</pre>
</div>
<div class="script-row">
<div class="script-label">Log location</div>
<pre>/var/log/fstrim/fstrim-YYYY-MM-DD.log (90-day retention)</pre>
</div>
<div class="script-row">
<div class="script-label">Deploy (preferred method — sudo nano)</div>
<pre>sudo nano /usr/scripts/omv/fstrim-report.sh
# paste script content, save with Ctrl+O, exit with Ctrl+X
sudo chmod +x /usr/scripts/omv/fstrim-report.sh</pre>
</div>
<div class="script-row">
<div class="script-label">Test run</div>
<pre>sudo systemctl start fstrim.service
sudo journalctl -u fstrim.service -f</pre>
</div>
<div class="script-row">
<div class="script-label">Email delivery</div>
<pre>sendmail -t (via OMV's configured SMTP relay)</pre>
</div>
</div>
<div class="callout amber" style="margin-top:0.75rem;">
<div class="clue-label">⚠ SMART attribute fixes required</div>
<p style="margin-bottom:6px;">Initial script used generic attribute names. Two rounds of fixes were needed after testing:</p>
<pre>Round 1 — Crucial BX500 fix:
Reallocated: Reallocated_Sector → Reallocate_NAND_Blk_Cnt
Pending: Current_Pending_Sector → Current_Pending_ECC_Cnt
Uncorrect: Offline_Uncorrectable → Reported_Uncorrect
Temperature: $10 → $10+0 (strips "32 (Min/Max 25/56)" format)
Round 2 — PNY CS900 fix:
Wear: Wear_Leveling_Count → SSD_Life_Left
Reallocated: Reallocate_NAND_Blk_Cnt → Bad_Blk_Ct_Lat/Erl (first value before /)</pre>
<p style="margin-top:8px;">Each pattern now tries vendor-specific names first, then falls back to generic names — script works correctly on any brand.</p>
</div>
</div>
<!-- SECTION 5 — SYSTEMD → OMV MIGRATION -->
<div class="section">
<div class="section-title" data-num="5">Migrating from systemd timer to OMV scheduler</div>
<p>The systemd <code>fstrim.timer</code> was disabled on both units and replaced with OMV Scheduled Jobs so all scheduled tasks are visible in one place.</p>
<div class="step">
<div class="step-header"><span class="step-num">1</span> Disable systemd timer (both units)</div>
<pre>sudo systemctl disable fstrim.timer
sudo systemctl stop fstrim.timer</pre>
<p class="why">Prevent the systemd timer from running alongside the new OMV job — avoids double execution.</p>
<p class="result pass">Timer stopped and disabled on both NAS08 and NAS16.</p>
</div>
<div class="step">
<div class="step-header"><span class="step-num">2</span> Remove systemd override (both units)</div>
<pre>sudo rm /etc/systemd/system/fstrim.service.d/override.conf
sudo systemctl daemon-reload</pre>
<p class="why">Clean up the override that pointed fstrim.service to the script — no longer needed since OMV cron calls the script directly.</p>
<p class="result pass">Override removed, systemd reloaded cleanly on both units.</p>
</div>
<div class="step">
<div class="step-header"><span class="step-num">3</span> Add OMV Scheduled Job (each unit)</div>
<pre>System → Scheduled Jobs → Add
Command: /usr/scripts/omv/fstrim-report.sh
User: root
NAS08: Wednesday at 1:00 AM
NAS16: Wednesday at 1:30 AM</pre>
<p class="why">Wednesday was chosen as it sits midweek, away from Sunday ZFS scrubs and the rolling every-3-day backup jobs.</p>
<p class="result pass">Both units now show TRIM Report in OMV scheduler alongside all other scheduled jobs.</p>
</div>
</div>
<!-- SECTION 6 — SCHEDULED JOB REFERENCE -->
<div class="section">
<div class="section-title" data-num="6">Final OMV scheduled jobs — both units</div>
<p style="margin-bottom:0.75rem;"><strong>NAS08</strong></p>
<table>
<thead>
<tr><th>Schedule</th><th>Script</th><th>Tag</th></tr>
</thead>
<tbody>
<tr><td>Every 3 days at 2:00 AM</td><td><code>/usr/scripts/omv/nas08-backup.sh</code></td><td>NAS08 Backup</td></tr>
<tr><td>Daily at 12:00 AM</td><td><code>/usr/scripts/zfs/nas08_zfs_report.sh</code></td><td>ZFS Status Report</td></tr>
<tr><td>Sunday at 3:00 AM</td><td><code>/usr/scripts/zfs/nas08_zfs_scrub.sh</code></td><td>ZFS Scrub</td></tr>
<tr><td>Wednesday at 1:00 AM</td><td><code>/usr/scripts/omv/fstrim-report.sh</code></td><td>TRIM Report</td></tr>
</tbody>
</table>
<p style="margin:1.25rem 0 0.75rem;"><strong>NAS16</strong></p>
<table>
<thead>
<tr><th>Schedule</th><th>Script</th><th>Tag</th></tr>
</thead>
<tbody>
<tr><td>Every 3 days at 2:00 AM</td><td><code>/usr/scripts/omv/nas16-backup.sh</code></td><td>NAS16 Backup</td></tr>
<tr><td>Every 2 days at 2:30 AM</td><td><code>bash /usr/scripts/nas/nas08_to_nas16_sync.sh</code></td><td>NAS Drive Backup</td></tr>
<tr><td>Daily at 12:00 AM</td><td><code>/usr/scripts/zfs/nas16_zfs_report.sh</code></td><td>ZFS Status Report</td></tr>
<tr><td>Sunday at 3:00 AM</td><td><code>/usr/scripts/zfs/nas16_zfs_scrub.sh</code></td><td>ZFS Scrub</td></tr>
<tr><td>Every 15 minutes</td><td><code>/usr/scripts/ddns/ddns_update.sh</code></td><td>DDNS Update</td></tr>
<tr><td>Wednesday at 1:30 AM</td><td><code>/usr/scripts/omv/fstrim-report.sh</code></td><td>TRIM Report</td></tr>
</tbody>
</table>
</div>
<!-- SECTION 7 — LESSONS LEARNED -->
<div class="section">
<div class="section-title" data-num="7">Lessons learned</div>
<table>
<thead>
<tr><th>Lesson</th><th>Detail</th></tr>
</thead>
<tbody>
<tr>
<td>Always investigate before configuring</td>
<td>Both noatime and fstrim were already handled by OMV's flashmemory plugin — checking first avoided duplicating work.</td>
</tr>
<tr>
<td>SMART attribute names are vendor-specific</td>
<td>PNY and Crucial use completely different attribute names for wear, reallocated sectors, and temperature format. Always run <code>smartctl -A /dev/sdX</code> first to map the real names.</td>
</tr>
<tr>
<td>systemd timers are invisible in OMV scheduler</td>
<td>The default fstrim.timer runs correctly but doesn't appear in OMV's job list — migrate to OMV Scheduled Jobs for full visibility alongside ZFS scrubs and backups.</td>
</tr>
<tr>
<td>Never run script without sudo for testing</td>
<td>Running fstrim-report.sh without sudo causes permission denied on /var/log/fstrim/ and returns N/A for all SMART data — always test with sudo or via systemctl start.</td>
</tr>
<tr>
<td>Use sudo nano over cp for file edits</td>
<td>Preferred method for placing and editing scripts on the NAS — faster navigation to the correct path, avoids confusion about source file location.</td>
</tr>
<tr>
<td>Inline TRIM (discard mount option) is not recommended</td>
<td>Real-time per-delete TRIM adds latency under heavy load. Periodic batch TRIM via fstrim is the correct approach for NAS workloads.</td>
</tr>
<tr>
<td>PNY CS900 has no pending/uncorrectable attributes</td>
<td>N/A for those fields is correct and expected — not a script bug. The drive simply does not expose those attributes.</td>
</tr>
</tbody>
</table>
</div>
<!-- SECTION 8 — DOCS UPDATED -->
<div class="section">
<div class="section-title" data-num="8">Documentation updated</div>
<table>
<thead>
<tr><th>File</th><th>What changed</th></tr>
</thead>
<tbody>
<tr>
<td><code>server_nas08.md</code></td>
<td>Added drive inventory table, full SSD Hardening &amp; Maintenance section (TRIM, noatime, flashmemory plugin, SMART attribute mapping), and complete OMV Scheduled Jobs table.</td>
</tr>
<tr>
<td><code>server_nas16.md</code></td>
<td>Added drive inventory table with Power_Cycle_Count monitoring note, full SSD Hardening &amp; Maintenance section, and complete OMV Scheduled Jobs table including NAS drive backup sync job.</td>
</tr>
</tbody>
</table>
</div>
<div class="footer">
<span>KingDezigns Home Network</span>
<span>Generated 2026-05-23</span>
</div>
</div>
</body>
</html>