407 lines
24 KiB
HTML
407 lines
24 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 — DDNS Updater 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 { 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); display: inline-block; }
|
|||
|
|
.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); }
|
|||
|
|
.badge-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 1rem; }
|
|||
|
|
.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: '↳ note: '; 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.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.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; }
|
|||
|
|
.flow { display: flex; flex-direction: column; gap: 0; }
|
|||
|
|
.flow-row { display: flex; align-items: stretch; gap: 0; }
|
|||
|
|
.flow-node { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 0.65rem 1rem; font-family: var(--mono); font-size: 12px; flex-shrink: 0; display: flex; align-items: center; gap: 8px; }
|
|||
|
|
.flow-node.trigger { border-color: var(--border-strong); background: var(--surface-alt); font-weight: 500; }
|
|||
|
|
.flow-node.action { border-color: var(--green-border); background: var(--green-bg); color: var(--green-text); }
|
|||
|
|
.flow-node.warn { border-color: var(--amber-border); background: var(--amber-bg); color: var(--amber-text); }
|
|||
|
|
.flow-node.fail { border-color: var(--red-border); background: var(--red-bg); color: var(--red-text); }
|
|||
|
|
.flow-node.silent { border-color: var(--border); background: var(--surface-alt); color: var(--text-muted); }
|
|||
|
|
.flow-node.daily { border-color: #a89ccc; background: #f0edf8; color: #3d2d6b; }
|
|||
|
|
.flow-arrow { display: flex; flex-direction: column; align-items: center; padding: 3px 0; color: var(--text-muted); font-family: var(--mono); font-size: 11px; gap: 1px; }
|
|||
|
|
.flow-branch { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; margin: 0.3rem 0; }
|
|||
|
|
.flow-branch-col { display: flex; flex-direction: column; gap: 0.3rem; }
|
|||
|
|
.branch-label { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); padding: 0 2px; margin-bottom: 2px; }
|
|||
|
|
.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 — DDNS Updater Summary</h1>
|
|||
|
|
<div class="meta">
|
|||
|
|
<span>📅 May 19, 2025</span>
|
|||
|
|
<span>🖥 NAS16 (Raspberry Pi)</span>
|
|||
|
|
<span>⚙️ OpenMediaVault</span>
|
|||
|
|
<span>☁️ DreamHost DNS API</span>
|
|||
|
|
<span>🌐 20 A Records · 4 Domains</span>
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 1 — WHAT WAS BUILT ===================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="1">What was built</div>
|
|||
|
|
<p>A self-contained Bash script that keeps all KingDezigns DreamHost A records synchronized with the current home WAN IP. It runs every 15 minutes via OMV Scheduled Tasks. It only calls the DreamHost API when the IP actually changes (or during a nightly sanity check), so there is zero risk of being banned for spamming.</p>
|
|||
|
|
<div class="badge-row">
|
|||
|
|
<span class="badge green">20 A records managed</span>
|
|||
|
|
<span class="badge green">4 domains</span>
|
|||
|
|
<span class="badge green">Max 2 API calls per event</span>
|
|||
|
|
<span class="badge green">HTML email notifications</span>
|
|||
|
|
<span class="badge">Every 15 min via cron</span>
|
|||
|
|
<span class="badge">Daily sanity check 00:00–00:14</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 2 — LOGIC FLOW MAP ===================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="2">Logic flow map</div>
|
|||
|
|
|
|||
|
|
<div class="flow">
|
|||
|
|
|
|||
|
|
<div class="flow-node trigger">⏱ Script runs (every 15 min via OMV cron)</div>
|
|||
|
|
<div class="flow-arrow">↓</div>
|
|||
|
|
|
|||
|
|
<div class="flow-node">🌐 Get current WAN IP from api.ipify.org / ifconfig.me / icanhazip.com</div>
|
|||
|
|
<div class="flow-arrow">↓</div>
|
|||
|
|
|
|||
|
|
<div class="flow-node">📄 Read cached previous IP from <code>/var/log/ddns/last_wan_ip.txt</code></div>
|
|||
|
|
<div class="flow-arrow">↓</div>
|
|||
|
|
|
|||
|
|
<div class="flow-node">🕛 Is the time between 00:00 and 00:14? (daily sanity-check window)</div>
|
|||
|
|
<div class="flow-arrow">↓</div>
|
|||
|
|
|
|||
|
|
<!-- Branch: IP changed vs same -->
|
|||
|
|
<div class="flow-branch">
|
|||
|
|
<div class="flow-branch-col">
|
|||
|
|
<div class="branch-label">IP changed OR daily window</div>
|
|||
|
|
<div class="flow-node action">▶ Query DreamHost — check each A record</div>
|
|||
|
|
<div class="flow-node action">📡 Call dns-remove_record + dns-add_record for each mismatched record</div>
|
|||
|
|
<div class="flow-node">⏳ Wait 3–5 minutes (random)</div>
|
|||
|
|
<div class="flow-node">🔍 Re-query DreamHost — verify each record</div>
|
|||
|
|
|
|||
|
|
<!-- Sub-branch: all ok vs still failing -->
|
|||
|
|
<div class="flow-branch" style="margin-top:0.3rem;">
|
|||
|
|
<div class="flow-branch-col">
|
|||
|
|
<div class="branch-label">Still wrong</div>
|
|||
|
|
<div class="flow-node warn">🔁 Attempt 2 of 2 — update remaining records</div>
|
|||
|
|
<div class="flow-node">⏳ Wait 3–5 min · Re-verify</div>
|
|||
|
|
<div class="flow-branch">
|
|||
|
|
<div class="flow-branch-col">
|
|||
|
|
<div class="branch-label">Still failing</div>
|
|||
|
|
<div class="flow-node fail">✗ STOP (max attempts reached)</div>
|
|||
|
|
<div class="flow-node fail">📧 Email: partial failure + manual fix instructions + WAN IP</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-branch-col">
|
|||
|
|
<div class="branch-label">Now correct</div>
|
|||
|
|
<div class="flow-node action">✓ Mark success</div>
|
|||
|
|
<div class="flow-node action">📧 Email: all records updated</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="flow-branch-col">
|
|||
|
|
<div class="branch-label">All correct</div>
|
|||
|
|
<div class="flow-node action">✓ All records verified</div>
|
|||
|
|
<div class="flow-node action">📧 Email: success — all records updated</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-node">💾 Write new WAN IP to cache file</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flow-branch-col">
|
|||
|
|
<div class="branch-label">IP unchanged + not daily window</div>
|
|||
|
|
<div class="flow-node silent">💾 Overwrite cache with same IP (refreshes timestamp)</div>
|
|||
|
|
<div class="flow-node silent">🔇 Exit silently — no API calls, no email</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="callout amber" style="margin-top:1rem;">
|
|||
|
|
<div class="clue-label">⚠ DreamHost API — how updates work</div>
|
|||
|
|
<p>DreamHost has no "update" DNS command. To change an A record you must <strong>remove the old value then add the new one</strong>. The script always fetches the live DreamHost value immediately before each remove/add pair so it never tries to remove a value that isn't there.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 3 — DOMAINS & RECORDS ================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="3">Domains & records managed</div>
|
|||
|
|
<table>
|
|||
|
|
<thead><tr><th>Record (DreamHost "record" field)</th><th>Zone / Domain</th><th>Type</th></tr></thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr><td><code>albranch.org</code></td><td>albranch.org</td><td>A (root / @)</td></tr>
|
|||
|
|
<tr><td><code>www.albranch.org</code></td><td>albranch.org</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>indigorhayne.com</code></td><td>indigorhayne.com</td><td>A (root / @)</td></tr>
|
|||
|
|
<tr><td><code>www.indigorhayne.com</code></td><td>indigorhayne.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>rufusking.com</code></td><td>rufusking.com</td><td>A (root / @)</td></tr>
|
|||
|
|
<tr><td><code>www.rufusking.com</code></td><td>rufusking.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>kingdezigns.com</code></td><td>kingdezigns.com</td><td>A (root / @)</td></tr>
|
|||
|
|
<tr><td><code>www.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>*.web.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A (wildcard)</td></tr>
|
|||
|
|
<tr><td><code>cloud.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>files08.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>files16.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>has.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>nginx.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>omv08.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>omv16.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>pihole.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>plex.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>vault.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
<tr><td><code>webmin.kingdezigns.com</code></td><td>kingdezigns.com</td><td>A</td></tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 4 — DEPLOYMENT STEPS ================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="4">Deployment — step by step</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">1</span> Install dependencies</div>
|
|||
|
|
<pre>sudo apt-get update
|
|||
|
|
sudo apt-get install -y curl python3 mailutils</pre>
|
|||
|
|
<p class="why"><code>curl</code> fetches WAN IP and calls the DreamHost API. <code>python3</code> parses the JSON responses. <code>mailutils</code> provides the <code>mail</code> command for sending HTML notifications.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">2</span> Copy the script to its permanent location</div>
|
|||
|
|
<pre>sudo cp ddns_update.sh /usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
<p class="result neutral">This puts it in a root-only system bin directory alongside other admin utilities.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">3</span> Set ownership and permissions</div>
|
|||
|
|
<pre>sudo chown root:root /usr/local/sbin/ddns_update.sh
|
|||
|
|
sudo chmod 700 /usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
<p class="why">The script reads/writes to <code>/var/log/ddns/</code> and calls sendmail — it must run as root. Mode 700 prevents other users from reading the embedded API key.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">4</span> Create the log directory</div>
|
|||
|
|
<pre>sudo mkdir -p /var/log/ddns
|
|||
|
|
sudo chmod 700 /var/log/ddns</pre>
|
|||
|
|
<p class="why">The script will create this automatically on first run, but creating it manually with 700 ensures no other user can read the cached IP or logs before then.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">5</span> Test a manual run</div>
|
|||
|
|
<pre>sudo /usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
<p class="why">Run once by hand to verify API connectivity, confirm all 20 records are found, and check that the email arrives at rufus.king@kingdezigns.com. Watch the output — it prints everything it does.</p>
|
|||
|
|
<p class="result neutral">On the very first run there is no cached IP, so it will treat every record as new and perform a full update cycle.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">6</span> Verify the log file after test run</div>
|
|||
|
|
<pre>sudo cat /var/log/ddns/ddns_run.log</pre>
|
|||
|
|
<p class="result neutral">You should see the WAN IP detected, DreamHost responses for each record, and "DDNS run complete" at the bottom.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">7</span> Add OMV Scheduled Task</div>
|
|||
|
|
<pre>OMV → System → Scheduled Tasks → + (Add)
|
|||
|
|
|
|||
|
|
Enabled: ✓
|
|||
|
|
Minute: */15
|
|||
|
|
Hour: *
|
|||
|
|
Day: *
|
|||
|
|
Month: *
|
|||
|
|
Weekday: *
|
|||
|
|
User: root
|
|||
|
|
Command: /usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
<p class="why">This fires the script every 15 minutes around the clock. OMV uses standard cron under the hood — you can verify with <code>sudo crontab -l</code> after saving.</p>
|
|||
|
|
<p class="result pass">Script is now live and fully automated.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 5 — SCRIPT & FILES ===================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="5">Script & files created</div>
|
|||
|
|
<div class="script-box">
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Script location</div>
|
|||
|
|
<pre>/usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">IP cache (previous WAN IP)</div>
|
|||
|
|
<pre>/var/log/ddns/last_wan_ip.txt</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Run log (last 5000 lines, auto-trimmed)</div>
|
|||
|
|
<pre>/var/log/ddns/ddns_run.log</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Notification email</div>
|
|||
|
|
<pre>rufus.king@kingdezigns.com</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Manual run command</div>
|
|||
|
|
<pre>sudo /usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Force a fresh update (clears cache)</div>
|
|||
|
|
<pre>sudo rm /var/log/ddns/last_wan_ip.txt && sudo /usr/local/sbin/ddns_update.sh</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Watch the live log</div>
|
|||
|
|
<pre>sudo tail -f /var/log/ddns/ddns_run.log</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 6 — EMAIL BEHAVIOR ===================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="6">Email notification behavior</div>
|
|||
|
|
<table>
|
|||
|
|
<thead><tr><th>Scenario</th><th>Email sent?</th><th>Subject line</th></tr></thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>IP unchanged, not daily window</td>
|
|||
|
|
<td><span class="badge">No</span></td>
|
|||
|
|
<td>Silent exit — no email</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>IP changed, all records updated successfully</td>
|
|||
|
|
<td><span class="badge green">Yes</span></td>
|
|||
|
|
<td>[DDNS] ✅ All Records Updated — <new IP></td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>IP changed, some records failed after 2 attempts</td>
|
|||
|
|
<td><span class="badge red">Yes — action needed</span></td>
|
|||
|
|
<td>[DDNS] ⚠️ PARTIAL FAILURE — Manual action needed — <new IP></td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Daily sanity check (00:00–00:14), all records match</td>
|
|||
|
|
<td><span class="badge green">Yes</span></td>
|
|||
|
|
<td>[DDNS] ✅ All Records Updated — <IP></td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Daily sanity check, mismatch found & fixed</td>
|
|||
|
|
<td><span class="badge green">Yes</span></td>
|
|||
|
|
<td>[DDNS] ✅ All Records Updated — <new IP></td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>WAN IP discovery fails (all 3 sources down)</td>
|
|||
|
|
<td><span class="badge">No</span></td>
|
|||
|
|
<td>Script aborts — will retry next 15-min run</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
<div class="callout green" style="margin-top:0.75rem;">
|
|||
|
|
<p>All emails are sent as <strong>HTML</strong> using the KingDezigns branded template — same typography and color system as this document. They include a record-by-record results table and, on failure, a direct link to the DreamHost DNS panel with the correct IP pre-noted for manual entry.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- ===== SECTION 7 — IMPORTANT NOTES =================================== -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="7">Important notes & gotchas</div>
|
|||
|
|
<table>
|
|||
|
|
<thead><tr><th>Item</th><th>Detail</th></tr></thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>API key is embedded in the script</td>
|
|||
|
|
<td>The script is chmod 700 / root only. Do not copy or share the file without scrubbing the key first.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>DreamHost has no "update" API command</td>
|
|||
|
|
<td>Every update is a remove + add pair. The script always reads the live DreamHost value first so it never removes the wrong IP.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Wildcard record <code>*.web.kingdezigns.com</code></td>
|
|||
|
|
<td>DreamHost supports wildcard A records via API. The script passes the literal string <code>*.web.kingdezigns.com</code> to <code>dns-list_records</code> and it will match correctly.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Mail must be configured on NAS16</td>
|
|||
|
|
<td>Run <code>sudo apt-get install mailutils</code> and ensure your MTA (Postfix or ssmtp) is configured to relay outbound email. Test with: <code>echo "test" | mail -s "test" rufus.king@kingdezigns.com</code></td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>First run behavior</td>
|
|||
|
|
<td>On the very first run there is no cached IP. The script treats every record as needing an update and performs a full cycle. This is expected and correct.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Daily window fires once per day</td>
|
|||
|
|
<td>The 00:00–00:14 window aligns with the every-15-min schedule — only one run per day falls in that window. No extra logic needed.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Log rotation</td>
|
|||
|
|
<td>The run log auto-trims itself to 5000 lines on every execution. No logrotate config needed.</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<div class="footer">
|
|||
|
|
<span>KingDezigns Home Network</span>
|
|||
|
|
<span>Generated May 19, 2025 — DDNS Updater build session</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</body>
|
|||
|
|
</html>
|