1003 lines
39 KiB
HTML
1003 lines
39 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>KingDezigns — NAS16 Automated Backup Setup 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); }
|
||
|
||
.note {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
margin-top: 0.5rem;
|
||
padding-left: 2px;
|
||
}
|
||
.note::before { content: '📌 '; }
|
||
|
||
.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;
|
||
}
|
||
|
||
.phase-label {
|
||
font-family: var(--mono);
|
||
font-size: 10px;
|
||
font-weight: 500;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: var(--text-primary);
|
||
background: var(--surface-alt);
|
||
border: 1px solid var(--border-strong);
|
||
border-radius: 4px;
|
||
padding: 5px 12px;
|
||
margin: 1.5rem 0 0.6rem 0;
|
||
display: inline-block;
|
||
}
|
||
|
||
.script-box { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; margin-bottom: 1rem; }
|
||
.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 — NAS16 Automated Backup Setup Summary</h1>
|
||
<div class="meta">
|
||
<span>📅 2026-05-12</span>
|
||
<span>🖥 NAS16 / Raspberry Pi 5</span>
|
||
<span>⚙️ OpenMediaVault (OMV)</span>
|
||
<span>🌐 Apache · PHP · MariaDB · Webmin</span>
|
||
<span>💾 ZFS RAIDZ2 — Penta SATA HAT</span>
|
||
</div>
|
||
</header>
|
||
|
||
|
||
<!-- SECTION 1 — THE GOAL -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="1">The goal</div>
|
||
|
||
<p>NAS16 serves live websites via Apache/PHP and hosts their databases in MariaDB. A software or hardware failure — corrupt SD card, bad OMV update, runaway script — would take down all hosted sites and lose database state with no documented recovery path and no saved configuration.</p>
|
||
|
||
<div class="badge-row">
|
||
<span class="badge amber">No OS backup</span>
|
||
<span class="badge amber">No database backup</span>
|
||
<span class="badge amber">No automated recovery plan</span>
|
||
<span class="badge amber">Web server config undocumented</span>
|
||
</div>
|
||
|
||
<p>The goal was to create a fully automated, scheduled backup of the OS and configuration layer — including all databases — that could restore NAS16 to full operation after a failure with no manual intervention during normal operation. Website files on the NAS drives are intentionally excluded; those are covered separately by NAS-to-NAS redundancy.</p>
|
||
|
||
<div class="callout amber" style="margin-top:1rem;">
|
||
<div class="clue-label">⚠ Critical — store this document off NAS16</div>
|
||
<p>Keep a copy of this document on your workstation, a USB drive, or in print. If NAS16 is down you cannot read files stored on it.</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- SECTION 2 — WHAT IS BACKED UP -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="2">What is backed up</div>
|
||
|
||
<div class="script-box">
|
||
<div class="script-row">
|
||
<div class="script-label">Backup Script Location</div>
|
||
<pre>/usr/scripts/omv/nas16-backup.sh</pre>
|
||
</div>
|
||
<div class="script-row">
|
||
<div class="script-label">What It Backs Up</div>
|
||
<pre>OMV config — omv-confbak export + raw /etc/openmediavault/config.xml
|
||
/etc — full system config (network, fstab, cron, etc.)
|
||
Apache config — sites-available, sites-enabled, mods-enabled, conf-available,
|
||
conf-enabled, apache2.conf
|
||
PHP config — all installed PHP version ini + FPM pool configs (/etc/php/*)
|
||
MariaDB databases — mysqldump of every user database (.sql.gz per database)
|
||
system schemas excluded (information_schema, performance_schema,
|
||
sys, mysql)
|
||
Webmin config — /etc/webmin (Webmin and Adminer settings)
|
||
Crontab — root crontab + /etc/cron.d entries
|
||
MANIFEST.txt — baked-in restore instructions and version info
|
||
|
||
NOT INCLUDED (by design):
|
||
Website files on NAS drives — covered by NAS-to-NAS redundancy</pre>
|
||
</div>
|
||
<div class="script-row">
|
||
<div class="script-label">Output Paths</div>
|
||
<pre>/export/kingdezignsnas-16/Backups/NAS16/nas16-backup-YYYY-MM-DD.tar.gz
|
||
/export/kingdezignsnas-16/Backups/NAS16/backup.log</pre>
|
||
</div>
|
||
<div class="script-row">
|
||
<div class="script-label">Retention</div>
|
||
<pre>10 archives retained (~30 days coverage) — older archives auto-deleted on each run</pre>
|
||
</div>
|
||
<div class="script-row">
|
||
<div class="script-label">Expected Size</div>
|
||
<pre>Varies based on database sizes — /etc + Apache + PHP + Webmin are small.
|
||
Primary size driver is MariaDB dumps. Monitor backup.log after first run for actual size.</pre>
|
||
</div>
|
||
<div class="script-row">
|
||
<div class="script-label">Schedule via OMV UI</div>
|
||
<pre>System → Scheduled Jobs → Add
|
||
Name: NAS16 Backup
|
||
Command: sudo /usr/scripts/omv/nas16-backup.sh
|
||
Minute: 0
|
||
Hour: 2
|
||
Day of month: */3
|
||
Month: *
|
||
Day of week: *
|
||
Runs automatically at 2:00 AM every 3 days</pre>
|
||
</div>
|
||
<div class="script-row">
|
||
<div class="script-label">Manual Run</div>
|
||
<pre>sudo /usr/scripts/omv/nas16-backup.sh
|
||
# or: OMV UI → System → Scheduled Jobs → Run</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- SECTION 3 — SETUP STEPS -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="3">Initial setup steps</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">1</span> Create the scripts directory — NAS16</div>
|
||
<pre>sudo mkdir -p /usr/scripts/omv
|
||
sudo chown rufusking:rufusking /usr/scripts/omv</pre>
|
||
<p class="why">Placing scripts in /usr/scripts/omv/ avoids the root-SCP requirement of /usr/local/bin/. The rufusking user can copy files here directly.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">2</span> Create the backup destination — NAS16</div>
|
||
<pre>sudo mkdir -p /export/kingdezignsnas-16/Backups/NAS16</pre>
|
||
<p class="why">The script creates this automatically via mkdir -p on every run, but creating it manually avoids a tee log error on the very first run if the path doesn't exist yet.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">3</span> Copy the script to NAS16 — workstation</div>
|
||
<pre># From your workstation:
|
||
scp nas16-backup.sh rufusking@<NAS16-IP>:/usr/scripts/omv/nas16-backup.sh</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">4</span> Make the script executable — NAS16</div>
|
||
<pre>chmod +x /usr/scripts/omv/nas16-backup.sh</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">5</span> Run a test backup — NAS16</div>
|
||
<pre>sudo /usr/scripts/omv/nas16-backup.sh</pre>
|
||
<p>Watch for any WARNING or ERROR lines in the output. Then verify the archive was created:</p>
|
||
<pre>ls -lh /export/kingdezignsnas-16/Backups/NAS16/
|
||
cat /export/kingdezignsnas-16/Backups/NAS16/backup.log</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">6</span> Inspect archive contents — NAS16</div>
|
||
<pre>tar -tzf /export/kingdezignsnas-16/Backups/NAS16/nas16-backup-$(date +%Y-%m-%d).tar.gz | head -60</pre>
|
||
<p>You should see folders: <code>omv-config/</code> <code>etc/</code> <code>apache/</code> <code>php/</code> <code>databases/</code> <code>webmin/</code> <code>cron/</code> <code>MANIFEST.txt</code></p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">7</span> Schedule the job in OMV UI — NAS16</div>
|
||
<pre>OMV UI → System → Scheduled Jobs → Add
|
||
Enabled: Yes
|
||
Name: NAS16 Backup
|
||
Command: sudo /usr/scripts/omv/nas16-backup.sh
|
||
Minute: 0
|
||
Hour: 2
|
||
Day of month: */3
|
||
Month: *
|
||
Day of week: *
|
||
→ Save → Apply</pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<!-- SECTION 4 — FULL RECOVERY PROCEDURE -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="4">Full recovery procedure</div>
|
||
|
||
<div class="callout amber">
|
||
<div class="clue-label">⚠ Before You Start</div>
|
||
<p>You need: a workstation with internet, a replacement SD card (16GB+), Raspberry Pi Imager, SSH access, and your rufusking password. Physically connect all ZFS drives to the Penta SATA HAT before powering on.</p>
|
||
</div>
|
||
|
||
<!-- PHASE 1 -->
|
||
<div class="phase-label">Phase 1 — Flash the OS</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">1</span> Download Raspberry Pi Imager — workstation</div>
|
||
<pre>https://www.raspberrypi.com/software/</pre>
|
||
<p class="note">Download and install for your OS (Windows / Mac / Linux).</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">2</span> Flash Raspberry Pi OS Lite (64-bit) — workstation</div>
|
||
<pre>Open Raspberry Pi Imager
|
||
→ Choose Device : Raspberry Pi 5
|
||
→ Choose OS : Raspberry Pi OS Lite (64-bit) ← NOT the Desktop version
|
||
→ Choose Storage : your SD card
|
||
→ Next → Edit Settings:
|
||
Hostname : NAS16
|
||
Username : rufusking
|
||
Password : your usual password
|
||
Services : Enable SSH → Use password authentication
|
||
→ Save → Yes → Yes (confirm flash and verify)</pre>
|
||
<p class="note">Wait for the flash and verify to complete fully before removing the SD card.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">3</span> Boot the Raspberry Pi</div>
|
||
<pre>1. Insert SD card into Raspberry Pi 5
|
||
2. Connect Penta SATA HAT with all ZFS drives plugged in
|
||
3. Power on
|
||
4. Wait 2–3 minutes for first boot to complete</pre>
|
||
</div>
|
||
|
||
<!-- PHASE 2 -->
|
||
<div class="phase-label">Phase 2 — Install OpenMediaVault</div>
|
||
|
||
<div class="callout amber">
|
||
<div class="clue-label">⚠ Note</div>
|
||
<p>Verify the current OMV install script at <code>https://github.com/OpenMediaVault-Plugin-Developers/installScript</code> before running. Steps below reflect the method as of 2026-05-12.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">4</span> SSH into NAS16 — workstation</div>
|
||
<pre>ssh rufusking@<NAS16-IP></pre>
|
||
<p class="note">If the static IP is not yet assigned, log into UniFi, find NAS16 in Clients, and SSH to its temporary IP instead.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">5</span> Update the OS — NAS16</div>
|
||
<pre>sudo apt-get update && sudo apt-get upgrade -y</pre>
|
||
<p class="note">Wait for the prompt to return before continuing.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">6</span> Install OpenMediaVault — NAS16</div>
|
||
<pre>sudo wget -O - https://github.com/OpenMediaVault-Plugin-Developers/installScript/raw/master/install | sudo bash</pre>
|
||
<p class="note">Takes 10–20 minutes. The Pi may reboot automatically. If it does, SSH back in and wait for the script to finish.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">7</span> Verify OMV is running — browser on workstation</div>
|
||
<pre>http://<NAS16-IP>
|
||
|
||
Default login:
|
||
Username: admin
|
||
Password: openmediavault</pre>
|
||
<p class="note">Confirm the OMV login page loads. Do not configure anything yet — the restore handles all configuration.</p>
|
||
</div>
|
||
|
||
<!-- PHASE 3 -->
|
||
<div class="phase-label">Phase 3 — Enable PCIe for Penta SATA HAT</div>
|
||
|
||
<div class="callout coral">
|
||
<p>The Raspberry Pi 5 does not expose PCIe by default. Without enabling it, the Penta SATA HAT and all ZFS drives are completely invisible to the OS. This must be done before any ZFS commands will work.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">8</span> Edit boot config to enable PCIe — NAS16</div>
|
||
<pre>sudo nano /boot/firmware/config.txt</pre>
|
||
<p>Scroll to find the line <code>arm_boost=1</code> and add these lines immediately after it:</p>
|
||
<pre># Enable PCIe
|
||
dtparam=pciex1
|
||
dtparam=pciex1_gen=3</pre>
|
||
<p>Save and exit:</p>
|
||
<pre>Ctrl+X → Y → Enter</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">9</span> Reboot to apply PCIe config — NAS16</div>
|
||
<pre>sudo reboot</pre>
|
||
<p>Wait 2–3 minutes, then SSH back in.</p>
|
||
</div>
|
||
|
||
<!-- PHASE 4 -->
|
||
<div class="phase-label">Phase 4 — Import ZFS Pool</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">10</span> Install ZFS tools — NAS16</div>
|
||
<pre>sudo apt-get install -y zfsutils-linux</pre>
|
||
<p class="note">Required on every fresh OS install.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">11</span> Verify all drives are visible — NAS16</div>
|
||
<pre>lsblk</pre>
|
||
<p>All drives attached to the Penta SATA HAT should be listed. If any are missing, check physical connections and reboot.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">12</span> Import the ZFS pool — NAS16</div>
|
||
<pre>sudo zpool import kingdezignsnas-16</pre>
|
||
<p class="note">If the previous OS failed uncleanly, force the import:</p>
|
||
<pre># Only use -f if the standard import fails:
|
||
sudo zpool import -f kingdezignsnas-16</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">13</span> Verify pool imported correctly — NAS16</div>
|
||
<pre>sudo zpool status</pre>
|
||
<p>All drives must show <strong>ONLINE</strong> and errors should be <strong>No known data errors</strong>.</p>
|
||
<p class="note">If any drive shows FAULTED or DEGRADED, do not proceed. See ZFS Troubleshooting at the bottom of this document.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">14</span> Confirm backup files are accessible — NAS16</div>
|
||
<pre>ls /kingdezignsnas-16/Backups/NAS16/
|
||
|
||
# List archives sorted newest first:
|
||
ls -lht /kingdezignsnas-16/Backups/NAS16/nas16-backup-*.tar.gz</pre>
|
||
<p class="note">Use <code>/kingdezignsnas-16/</code> directly at this stage. The <code>/export/kingdezignsnas-16/</code> path is recreated by OMV after the config restore in Phase 6.</p>
|
||
</div>
|
||
|
||
<!-- PHASE 5 -->
|
||
<div class="phase-label">Phase 5 — Extract the Backup Archive</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">15</span> Create restore working directory — NAS16</div>
|
||
<pre>sudo mkdir -p /tmp/nas16-restore</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">16</span> Extract the most recent backup — NAS16</div>
|
||
<pre># Replace YYYY-MM-DD with the actual archive date from step 14
|
||
sudo tar -xzf /kingdezignsnas-16/Backups/NAS16/nas16-backup-YYYY-MM-DD.tar.gz \
|
||
-C /tmp/nas16-restore</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">17</span> Confirm extraction and read the manifest — NAS16</div>
|
||
<pre>ls /tmp/nas16-restore/
|
||
ls /tmp/nas16-restore/nas16-backup-*/
|
||
|
||
cat /tmp/nas16-restore/nas16-backup-*/MANIFEST.txt</pre>
|
||
<p>You should see: <code>omv-config/</code> <code>etc/</code> <code>apache/</code> <code>php/</code> <code>databases/</code> <code>webmin/</code> <code>cron/</code> <code>MANIFEST.txt</code></p>
|
||
</div>
|
||
|
||
<!-- PHASE 6 -->
|
||
<div class="phase-label">Phase 6 — Restore System Configuration</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">18</span> Restore /etc — NAS16</div>
|
||
<pre>sudo rsync -a /tmp/nas16-restore/nas16-backup-*/etc/ /etc/</pre>
|
||
<p class="note">Restores all system configuration: network settings, fstab, cron, and everything else under /etc.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">19</span> Restore OMV configuration — NAS16</div>
|
||
<pre># Copy saved OMV config back into place:
|
||
sudo cp /tmp/nas16-restore/nas16-backup-*/omv-config/config.xml.raw \
|
||
/etc/openmediavault/config.xml
|
||
|
||
# Load it into the OMV database:
|
||
sudo omv-confdbadm populate
|
||
|
||
# Re-apply all settings to the system:
|
||
sudo omv-salt deploy run all</pre>
|
||
<p class="note">The last command takes several minutes. Do not interrupt it — let it complete fully.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">20</span> Reboot and verify OMV — NAS16 then browser</div>
|
||
<pre>sudo reboot</pre>
|
||
<p>After 2–3 minutes, SSH back in. Then open a browser:</p>
|
||
<pre>http://<NAS16-IP></pre>
|
||
<p>Log in and verify shares, users, and settings are present. Confirm the ZFS pool is mounted and <code>/export/kingdezignsnas-16</code> is available under Storage → File Systems.</p>
|
||
</div>
|
||
|
||
<!-- PHASE 7 -->
|
||
<div class="phase-label">Phase 7 — Reinstall Web Stack (Apache, PHP, MariaDB, Webmin)</div>
|
||
|
||
<div class="callout amber">
|
||
<div class="clue-label">⚠ Install First, Restore Config Second</div>
|
||
<p>The web stack packages must be reinstalled before restoring configuration files. Restoring config before the packages exist will have no effect and may be overwritten by the installer.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">21</span> Install Apache and PHP — NAS16</div>
|
||
<pre>sudo apt-get install -y apache2 php libapache2-mod-php php-mysql php-cli php-curl \
|
||
php-mbstring php-xml php-zip php-gd</pre>
|
||
<p class="note">Install the same PHP version that was running before. Check the MANIFEST.txt from the archive for the exact version if unsure.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">22</span> Install MariaDB — NAS16</div>
|
||
<pre>sudo apt-get install -y mariadb-server mariadb-client</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">23</span> Secure MariaDB and verify it is running — NAS16</div>
|
||
<pre>sudo systemctl start mariadb
|
||
sudo systemctl enable mariadb
|
||
sudo systemctl status mariadb</pre>
|
||
<p class="note">Do NOT run <code>mysql_secure_installation</code> before restoring databases — the debian.cnf maintenance account needs to remain intact for the restore steps to work without a password.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">24</span> Install Webmin — NAS16</div>
|
||
<pre># Add Webmin repository and install:
|
||
curl -o setup-repos.sh https://raw.githubusercontent.com/webmin/webmin/master/setup-repos.sh
|
||
sudo sh setup-repos.sh
|
||
sudo apt-get install -y webmin</pre>
|
||
<p class="note">Verify the current install method at <code>https://webmin.com/download/</code> before running — the method may change.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">25</span> Install Adminer — NAS16</div>
|
||
<pre>sudo apt-get install -y adminer
|
||
sudo a2enconf adminer
|
||
sudo systemctl reload apache2</pre>
|
||
<p>Or manually place adminer.php in your web root if you use a custom location:</p>
|
||
<pre>sudo wget -O /var/www/html/adminer.php https://www.adminer.org/latest.php</pre>
|
||
</div>
|
||
|
||
<!-- PHASE 8 -->
|
||
<div class="phase-label">Phase 8 — Restore Web Stack Configuration</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">26</span> Restore Apache configuration — NAS16</div>
|
||
<pre>sudo rsync -a /tmp/nas16-restore/nas16-backup-*/apache/sites-available/ \
|
||
/etc/apache2/sites-available/
|
||
|
||
sudo rsync -a /tmp/nas16-restore/nas16-backup-*/apache/sites-enabled/ \
|
||
/etc/apache2/sites-enabled/
|
||
|
||
sudo rsync -a /tmp/nas16-restore/nas16-backup-*/apache/conf-available/ \
|
||
/etc/apache2/conf-available/
|
||
|
||
sudo rsync -a /tmp/nas16-restore/nas16-backup-*/apache/conf-enabled/ \
|
||
/etc/apache2/conf-enabled/
|
||
|
||
# Restore main apache2.conf:
|
||
sudo cp /tmp/nas16-restore/nas16-backup-*/apache/apache2.conf \
|
||
/etc/apache2/apache2.conf</pre>
|
||
<p class="note">Do not overwrite mods-enabled directly — Apache manages these symlinks internally. Re-enable required mods manually if needed (see step 27).</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">27</span> Re-enable Apache modules — NAS16</div>
|
||
<pre># Check what modules were enabled (from your backup):
|
||
ls /tmp/nas16-restore/nas16-backup-*/apache/mods-enabled/
|
||
|
||
# Re-enable common modules as needed:
|
||
sudo a2enmod rewrite ssl headers proxy proxy_http
|
||
sudo systemctl restart apache2</pre>
|
||
<p class="note">Enable only the mods you see in the backup snapshot. The full list is in the <code>mods-enabled/</code> folder inside the archive.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">28</span> Restore PHP configuration — NAS16</div>
|
||
<pre># Replace X.X with your PHP version (e.g. 8.2):
|
||
sudo rsync -a /tmp/nas16-restore/nas16-backup-*/php/X.X/ /etc/php/X.X/</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">29</span> Restore Webmin configuration — NAS16</div>
|
||
<pre>sudo rsync -a /tmp/nas16-restore/nas16-backup-*/webmin/ /etc/webmin/</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">30</span> Restart web stack services — NAS16</div>
|
||
<pre>sudo systemctl restart apache2
|
||
sudo systemctl restart mariadb
|
||
sudo systemctl restart webmin
|
||
|
||
# Verify all are running:
|
||
sudo systemctl status apache2 mariadb webmin</pre>
|
||
</div>
|
||
|
||
<!-- PHASE 9 -->
|
||
<div class="phase-label">Phase 9 — Restore Databases</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">31</span> List backed-up databases — NAS16</div>
|
||
<pre>ls /tmp/nas16-restore/nas16-backup-*/databases/</pre>
|
||
<p>You should see one <code>.sql.gz</code> file per database. Each file is a complete dump of that database including schema and data.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">32</span> Restore each database — NAS16</div>
|
||
<pre># For each database (replace DB_NAME with the actual name):
|
||
gunzip -c /tmp/nas16-restore/nas16-backup-*/databases/DB_NAME.sql.gz \
|
||
| sudo mysql --defaults-file=/etc/mysql/debian.cnf DB_NAME
|
||
|
||
# If the database does not exist yet, create it first:
|
||
sudo mysql --defaults-file=/etc/mysql/debian.cnf \
|
||
-e "CREATE DATABASE IF NOT EXISTS DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||
|
||
# Then restore:
|
||
gunzip -c /tmp/nas16-restore/nas16-backup-*/databases/DB_NAME.sql.gz \
|
||
| sudo mysql --defaults-file=/etc/mysql/debian.cnf DB_NAME</pre>
|
||
<p class="note">Repeat for every <code>.sql.gz</code> file in the databases/ folder. Take your time — one at a time to catch any individual errors.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">33</span> Verify databases restored correctly — NAS16</div>
|
||
<pre>sudo mysql --defaults-file=/etc/mysql/debian.cnf \
|
||
-e "SHOW DATABASES;"
|
||
|
||
# Spot-check row counts in a key table (replace DB_NAME and TABLE_NAME):
|
||
sudo mysql --defaults-file=/etc/mysql/debian.cnf \
|
||
-e "SELECT COUNT(*) FROM DB_NAME.TABLE_NAME;"</pre>
|
||
</div>
|
||
|
||
<!-- PHASE 10 -->
|
||
<div class="phase-label">Phase 10 — Final Steps & Verification</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">34</span> Restore the crontab — NAS16</div>
|
||
<pre>sudo crontab /tmp/nas16-restore/nas16-backup-*/cron/root-crontab.txt
|
||
|
||
# Verify it was applied:
|
||
sudo crontab -l</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">35</span> Reschedule the backup job in OMV UI — NAS16</div>
|
||
<pre>OMV UI → System → Scheduled Jobs → Add
|
||
Enabled: Yes
|
||
Name: NAS16 Backup
|
||
Command: sudo /usr/scripts/omv/nas16-backup.sh
|
||
Minute: 0
|
||
Hour: 2
|
||
Day of month: */3
|
||
Month: *
|
||
Day of week: *
|
||
→ Save → Apply</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">36</span> Copy the backup script back to NAS16 — workstation</div>
|
||
<pre>scp nas16-backup.sh rufusking@<NAS16-IP>:/usr/scripts/omv/nas16-backup.sh
|
||
chmod +x /usr/scripts/omv/nas16-backup.sh</pre>
|
||
<p class="note">The script lives outside /etc so it is not automatically restored by the /etc rsync. Always re-deploy it manually.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">37</span> Verify all websites load — browser</div>
|
||
<pre># Test each hosted domain or IP in your browser.
|
||
# Check Apache error log if anything fails:
|
||
sudo tail -50 /var/log/apache2/error.log</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">38</span> Clean up the restore working directory — NAS16</div>
|
||
<pre>sudo rm -rf /tmp/nas16-restore</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">39</span> Run a fresh backup immediately to create a new baseline — NAS16</div>
|
||
<pre># Option A — OMV UI:
|
||
System → Scheduled Jobs → NAS16 Backup → Run
|
||
|
||
# Option B — SSH on NAS16:
|
||
sudo /usr/scripts/omv/nas16-backup.sh
|
||
|
||
# Verify it completed successfully:
|
||
ls -lh /export/kingdezignsnas-16/Backups/NAS16/
|
||
cat /export/kingdezignsnas-16/Backups/NAS16/backup.log</pre>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">40</span> Run a ZFS scrub to verify data integrity — NAS16</div>
|
||
<pre>sudo zpool scrub kingdezignsnas-16
|
||
|
||
# Check results a few minutes later:
|
||
sudo zpool status</pre>
|
||
<p>A healthy result:</p>
|
||
<pre>scan: scrub repaired 0B in 00:xx:xx with 0 errors</pre>
|
||
</div>
|
||
|
||
<!-- ZFS TROUBLESHOOTING -->
|
||
<div class="phase-label">ZFS Troubleshooting</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">—</span> Pool shows DEGRADED — 1 drive failed</div>
|
||
<pre>sudo zpool status ← failed drive shows FAULTED or UNAVAIL</pre>
|
||
<p>RAIDZ2 tolerates up to 2 simultaneous drive failures. The pool is still fully readable and writable. Complete the full restore procedure first, then replace the failed drive.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">—</span> Pool shows DEGRADED — 2 drives failed</div>
|
||
<pre>sudo zpool status ← two drives show FAULTED or UNAVAIL</pre>
|
||
<p>Data is at risk but still accessible. Complete the restore immediately and replace both drives as fast as possible.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">—</span> Pool cannot be imported — not found</div>
|
||
<pre># Scan all connected devices for importable pools:
|
||
sudo zpool import
|
||
|
||
# If kingdezignsnas-16 appears in the list, force import:
|
||
sudo zpool import -f kingdezignsnas-16</pre>
|
||
<p class="note">If no pools are found at all, PCIe is likely not enabled. Verify Steps 8–9 (boot config) were applied correctly and reboot again before retrying.</p>
|
||
</div>
|
||
|
||
<div class="step">
|
||
<div class="step-header"><span class="step-num">—</span> Pool imported but /export/kingdezignsnas-16 is missing</div>
|
||
<pre># Access backup files directly via the ZFS mount point:
|
||
ls /kingdezignsnas-16/Backups/NAS16/</pre>
|
||
<p class="note"><code>/export/kingdezignsnas-16/</code> is created by OMV when it mounts the filesystem. Use <code>/kingdezignsnas-16/</code> as the base path for all restore commands until OMV is fully restored.</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<!-- SECTION 5 — KEY DIFFERENCES FROM NAS08 -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="5">Key differences from NAS08</div>
|
||
<table>
|
||
<thead>
|
||
<tr><th>Area</th><th>NAS08</th><th>NAS16</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Primary role</td>
|
||
<td>Docker containers (Pi-hole, Plex, Vaultwarden, Nextcloud, NPM)</td>
|
||
<td>Web server (Apache/PHP), databases (MariaDB), Webmin admin</td>
|
||
</tr>
|
||
<tr>
|
||
<td>What's backed up beyond /etc + OMV</td>
|
||
<td>Docker Compose files, Pi-hole data, Plex config/metadata</td>
|
||
<td>Apache vhosts, PHP config, MariaDB dumps, Webmin config</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Website files</td>
|
||
<td>N/A</td>
|
||
<td>Intentionally excluded — stored on NAS drives, covered by redundancy</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Database backup</td>
|
||
<td>None (no databases)</td>
|
||
<td>Full mysqldump of all user databases, .sql.gz per database</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Backup destination</td>
|
||
<td>/export/kingdezigns-all/Backups/NAS08/</td>
|
||
<td>/export/kingdezignsnas-16/Backups/NAS16/</td>
|
||
</tr>
|
||
<tr>
|
||
<td>ZFS pool name</td>
|
||
<td>kingdezignsnas</td>
|
||
<td>kingdezignsnas-16 <em>(assumed — verify with <code>sudo zpool list</code>)</em></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Recovery phases</td>
|
||
<td>9 phases, 34 steps</td>
|
||
<td>10 phases, 40 steps</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- SECTION 6 — NOTES & ASSUMPTIONS -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="6">Notes & assumptions to verify</div>
|
||
|
||
<div class="callout amber">
|
||
<div class="clue-label">⚠ Verify these before first run</div>
|
||
<p>The script was written based on information provided and follows Raspberry Pi OS / Debian conventions. Verify the following on NAS16 before treating any backup as production-ready.</p>
|
||
</div>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr><th>Item</th><th>Assumed value</th><th>How to verify</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>ZFS pool name</td>
|
||
<td><code>kingdezignsnas-16</code></td>
|
||
<td><code>sudo zpool list</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Backup destination path</td>
|
||
<td><code>/export/kingdezignsnas-16/Backups/NAS16/</code></td>
|
||
<td><code>ls /export/</code> — confirm share name matches</td>
|
||
</tr>
|
||
<tr>
|
||
<td>MariaDB auth method</td>
|
||
<td>Uses <code>/etc/mysql/debian.cnf</code> (maintenance account, no password when run as root)</td>
|
||
<td>Run: <code>sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "SHOW DATABASES;"</code> — should work without password</td>
|
||
</tr>
|
||
<tr>
|
||
<td>PHP version(s)</td>
|
||
<td>Auto-detected from <code>/etc/php/*/</code></td>
|
||
<td><code>php -v</code> and <code>ls /etc/php/</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Apache config location</td>
|
||
<td><code>/etc/apache2/</code> (standard Debian)</td>
|
||
<td><code>apache2 -V</code> — confirms config path</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Webmin config location</td>
|
||
<td><code>/etc/webmin/</code></td>
|
||
<td><code>ls /etc/webmin/</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<!-- SECTION 7 — NETWORK MAP UPDATES -->
|
||
<div class="section">
|
||
<div class="section-title" data-num="7">Network map — items to update</div>
|
||
<table>
|
||
<thead>
|
||
<tr><th>Item</th><th>Old value</th><th>New value</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>NAS16 — Backup status</td>
|
||
<td><code>None</code></td>
|
||
<td><code>Automated — every 3 days — 30-day retention</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>NAS16 — Backup destination</td>
|
||
<td><code>None</code></td>
|
||
<td><code>/export/kingdezignsnas-16/Backups/NAS16/</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>NAS16 — Backup script</td>
|
||
<td><code>None</code></td>
|
||
<td><code>/usr/scripts/omv/nas16-backup.sh</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>NAS16 — PCIe requirement</td>
|
||
<td><code>not documented</code></td>
|
||
<td><code>dtparam=pciex1 + dtparam=pciex1_gen=3 required in /boot/firmware/config.txt on fresh OS</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>NAS16 — Web stack</td>
|
||
<td><code>not documented</code></td>
|
||
<td><code>Apache · PHP · MariaDB · Webmin · Adminer</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>NAS16 — Recovery plan</td>
|
||
<td><code>None</code></td>
|
||
<td><code>NAS16-Backup-Summary.html — 40 steps across 10 phases + ZFS troubleshooting</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<div class="footer">
|
||
<span>KingDezigns Home Network</span>
|
||
<span>Generated 2026-05-12</span>
|
||
</div>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|