feat: log eddydev.ink via EuroDynDNS in WAN IP Guard

- Voeg EURODYNDNS_HOSTS/USER/PASS config toe en update de A-records van
  EuroDNS-zone-domeinen via de EuroDynDNS dyndns2 endpoint bij IP-wijziging.
- Resultaten mee in rapport; state alleen saven als ALLE updates (DirectAdmin
  + EuroDynDNS) slagen.
- Voor eddydev.ink/www.eddydev.ink na stale DNS -> failed certbot renew
  (HTTP-01 via oud WAN IP); zie vault Nginx notitie.
This commit is contained in:
Eddy de Vink 2026-09-25 10:03:28 +02:00
commit b8e865c83e
3 changed files with 48 additions and 2 deletions

View file

@ -17,5 +17,11 @@ MATRIX_ROOM_FILE=/home/aiaiaiaiagent/whatsed-new/secrets/eddy-report-room
# DNS records to update (comma-separated, use domain name for apex) # DNS records to update (comma-separated, use domain name for apex)
DNS_RECORDS=example.com,git,www,weegschaal,whatsed,auth.whatsed DNS_RECORDS=example.com,git,www,weegschaal,whatsed,auth.whatsed
# EuroDynDNS (domeinen in de EuroDNS-zone, bv. eddydev.ink)
# Gebruikt de EuroDNS site-login / site-password (dyndns2), NIET een API-key.
EURODYNDNS_USER=your_eurodns_site_login
EURODYNDNS_PASS=your_eurodns_site_password
EURODYNDNS_HOSTS=eddydev.ink,www.eddydev.ink
# State file path (where to store last known IP) # State file path (where to store last known IP)
STATE_FILE=/var/lib/wan-ip-guard/current_ip STATE_FILE=/var/lib/wan-ip-guard/current_ip

View file

@ -67,6 +67,9 @@ sudo systemctl enable --now wan-ip-guard.timer
| `MATRIX_TOKEN_FILE` | Bestand met Sherpa compat-token | `/home/aiaiaiaiagent/whatsed-new/secrets/sherpa-token` | | `MATRIX_TOKEN_FILE` | Bestand met Sherpa compat-token | `/home/aiaiaiaiagent/whatsed-new/secrets/sherpa-token` |
| `MATRIX_ROOM_FILE` | Bestand met DM-room voor Eddy | `/home/aiaiaiaiagent/whatsed-new/secrets/eddy-report-room` | | `MATRIX_ROOM_FILE` | Bestand met DM-room voor Eddy | `/home/aiaiaiaiagent/whatsed-new/secrets/eddy-report-room` |
| `DNS_RECORDS` | Komma-gescheiden records | `example.com,git,www` | | `DNS_RECORDS` | Komma-gescheiden records | `example.com,git,www` |
| `EURODYNDNS_USER` | EuroDNS site-login (dyndns2) | `your_login` |
| `EURODYNDNS_PASS` | EuroDNS site-password (dyndns2) | `your_password` |
| `EURODYNDNS_HOSTS` | EuroDynDNS-hostnames (domeinen in EuroDNS-zone) | `eddydev.ink,www.eddydev.ink` |
| `STATE_FILE` | Pad naar state file | `/var/lib/wan-ip-guard/current_ip` | | `STATE_FILE` | Pad naar state file | `/var/lib/wan-ip-guard/current_ip` |
WAN IP Guard verstuurt het rapport naast WhatsApp ook via de Matrix-bot `@sherpa:whatsed.eddydevink.nl` naar de bestaande DM-room met `@eddy:whatsed.eddydevink.nl`. Het compat-token en room-ID staan uitsluitend in de aangegeven bestanden en worden niet in de repository opgeslagen. WAN IP Guard verstuurt het rapport naast WhatsApp ook via de Matrix-bot `@sherpa:whatsed.eddydevink.nl` naar de bestaande DM-room met `@eddy:whatsed.eddydevink.nl`. Het compat-token en room-ID staan uitsluitend in de aangegeven bestanden en worden niet in de repository opgeslagen.
@ -116,8 +119,9 @@ wan-ip-guard.service: Failed to spawn 'start' task: Permission denied
1. **IP detectie:** Haalt het WAN IP op via `ifconfig.me` met fallback naar `api.ipify.org` en `icanhazip.com` 1. **IP detectie:** Haalt het WAN IP op via `ifconfig.me` met fallback naar `api.ipify.org` en `icanhazip.com`
2. **State vergelijking:** Vergelijkt met het laatst opgeslagen IP in de state file 2. **State vergelijking:** Vergelijkt met het laatst opgeslagen IP in de state file
3. **DNS update:** Voor elke record worden eerst alle bestaande A-records met de exacte oude waarde verwijderd en daarna één nieuw A-record toegevoegd via DirectAdmin API. De delete-aanroep gebruikt `action=select` en een URL-gecodeerde waarde `name=<recordnaam>&value=<oude-ip>` in parameter `arecs0`. De waarde moet dus niet `recordnaam=ip` zijn. 3. **DNS update:** Voor elke record worden eerst alle bestaande A-records met de exacte oude waarde verwijderd en daarna één nieuw A-record toegevoegd via DirectAdmin API. De delete-aanroep gebruikt `action=select` en een URL-gecodeerde waarde `name=<recordnaam>&value=<oude-ip>` in parameter `arecs0`. De waarde moet dus niet `recordnaam=ip` zijn.
4. **State persist:** Het nieuwe IP wordt pas opgeslagen als **alle** DNS-updates gelukt zijn. Bij gedeeltelijke mislukking retried de volgende timer-run. 4. **EuroDynDNS:** Hostnames in `EURODYNDNS_HOSTS` (domeinen die in de EuroDNS-zone staan, bv. `eddydev.ink`) worden bij een IP-wijziging via de EuroDynDNS-update-URL bijgewerkt: `https://update.eurodyndns.org/update/?hostname=<fqdn>&myip=<ip>` met HTTP Basic auth (`EURODYNDNS_USER`/`EURODYNDNS_PASS`, de EuroDNS site-login/password). Een antwoord `good`/`nochg` telt als succes; een foutcode (`badauth`, `!yours`, `notfqdn`, …) als mislukking.
5. **Notificatie:** Stuurt WhatsApp bericht met oud/nieuw IP en status per record. 5. **State persist:** Het nieuwe IP wordt pas opgeslagen als **alle** DNS-updates (DirectAdmin én EuroDynDNS) gelukt zijn. Bij gedeeltelijke mislukking retried de volgende timer-run.
6. **Notificatie:** Stuurt WhatsApp bericht met oud/nieuw IP en status per record.
### DirectAdmin A-record verwijderen ### DirectAdmin A-record verwijderen

View file

@ -19,6 +19,9 @@ const CONFIG = {
matrixTokenFile: process.env.MATRIX_TOKEN_FILE || '/home/aiaiaiaiagent/whatsed-new/secrets/sherpa-token', matrixTokenFile: process.env.MATRIX_TOKEN_FILE || '/home/aiaiaiaiagent/whatsed-new/secrets/sherpa-token',
matrixRoomFile: process.env.MATRIX_ROOM_FILE || '/home/aiaiaiaiagent/whatsed-new/secrets/eddy-report-room', matrixRoomFile: process.env.MATRIX_ROOM_FILE || '/home/aiaiaiaiagent/whatsed-new/secrets/eddy-report-room',
dnsRecords: (process.env.DNS_RECORDS || '').split(',').map(s => s.trim()).filter(Boolean), dnsRecords: (process.env.DNS_RECORDS || '').split(',').map(s => s.trim()).filter(Boolean),
euroDynDnsUser: process.env.EURODYNDNS_USER,
euroDynDnsPass: process.env.EURODYNDNS_PASS,
euroDynDnsHosts: (process.env.EURODYNDNS_HOSTS || '').split(',').map(s => s.trim()).filter(Boolean),
}; };
function log(msg) { function log(msg) {
@ -117,6 +120,17 @@ async function addDnsRecord(name, ip) {
await daRequest(endpoint); await daRequest(endpoint);
} }
async function updateEuroDynDns(hostname, ip) {
const url = `https://update.eurodyndns.org/update/?hostname=${encodeURIComponent(hostname)}&myip=${encodeURIComponent(ip)}`;
const auth = Buffer.from(`${CONFIG.euroDynDnsUser}:${CONFIG.euroDynDnsPass}`).toString('base64');
const resp = await fetchWithTimeout(url, {
headers: { Authorization: `Basic ${auth}` },
});
const text = (await resp.text()).trim();
const ok = resp.ok && /^(good|nochg)\b/.test(text);
return { ok, text };
}
async function sendWhatsApp(message) { async function sendWhatsApp(message) {
const url = `${CONFIG.whatsappApi}/client/sendMessage/${CONFIG.whatsappSession}`; const url = `${CONFIG.whatsappApi}/client/sendMessage/${CONFIG.whatsappSession}`;
const chatId = `${CONFIG.adminPhone}@c.us`; const chatId = `${CONFIG.adminPhone}@c.us`;
@ -205,6 +219,28 @@ async function main() {
} }
} }
// EuroDynDNS (domeinen in de EuroDNS-zone, bv. eddydev.ink)
if (CONFIG.euroDynDnsHosts.length > 0 && !(CONFIG.euroDynDnsUser && CONFIG.euroDynDnsPass)) {
logError('EURODYNDNS_HOSTS is set but EURODYNDNS_USER/PASS are missing. Skipping EuroDynDNS updates.');
results.push(`❌ EuroDynDNS: missing EURODYNDNS_USER/PASS`);
} else {
for (const hostname of CONFIG.euroDynDnsHosts) {
try {
const { ok, text } = await updateEuroDynDns(hostname, currentIp);
if (ok) {
results.push(`✅ ${hostname} (EuroDynDNS)`);
log(`EuroDynDNS updated ${hostname} -> ${currentIp}: ${text}`);
} else {
results.push(`❌ ${hostname} (EuroDynDNS): ${text}`);
logError(`EuroDynDNS failed for ${hostname}: ${text}`);
}
} catch (e) {
results.push(`❌ ${hostname} (EuroDynDNS): ${e.message}`);
logError(`EuroDynDNS error for ${hostname}: ${e.message}`);
}
}
}
const successCount = results.filter(r => r.startsWith('✅')).length; const successCount = results.filter(r => r.startsWith('✅')).length;
const failCount = results.filter(r => r.startsWith('❌')).length; const failCount = results.filter(r => r.startsWith('❌')).length;