feat: DNS-free WAN IP detection + add calibre to records

Use Cloudflare 1.1.1.1/cdn-cgi/trace as primary IP source (fixed IP, no resolver dependency — survives AdGuard/upstream DNS outages). Add calibre to DNS_RECORDS: its A record points at the dynamic WAN IP but was not guarded, so it would go stale on the next WAN change.
This commit is contained in:
Eddy de Vink 2026-09-25 10:44:53 +02:00
commit 4f9b2c064e
3 changed files with 15 additions and 8 deletions

View file

@ -15,7 +15,7 @@ MATRIX_TOKEN_FILE=/home/aiaiaiaiagent/whatsed-new/secrets/sherpa-token
MATRIX_ROOM_FILE=/home/aiaiaiaiagent/whatsed-new/secrets/eddy-report-room 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,nextcloud,plausible,weegschaal,www,whatsed,auth.whatsed,calibre
# EuroDynDNS (domeinen in de EuroDNS-zone, bv. eddydev.ink) # EuroDynDNS (domeinen in de EuroDNS-zone, bv. eddydev.ink)
# Gebruikt de EuroDNS site-login / site-password (dyndns2), NIET een API-key. # Gebruikt de EuroDNS site-login / site-password (dyndns2), NIET een API-key.

View file

@ -66,7 +66,7 @@ sudo systemctl enable --now wan-ip-guard.timer
| `MATRIX_API_ENDPOINT` | Matrix homeserver API | `https://whatsed.eddydevink.nl` | | `MATRIX_API_ENDPOINT` | Matrix homeserver API | `https://whatsed.eddydevink.nl` |
| `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,whatsed,auth.whatsed,calibre` |
| `EURODYNDNS_USER` | EuroDNS site-login (dyndns2) | `your_login` | | `EURODYNDNS_USER` | EuroDNS site-login (dyndns2) | `your_login` |
| `EURODYNDNS_PASS` | EuroDNS site-password (dyndns2) | `your_password` | | `EURODYNDNS_PASS` | EuroDNS site-password (dyndns2) | `your_password` |
| `EURODYNDNS_HOSTS` | EuroDynDNS-hostnames (domeinen in EuroDNS-zone) | `eddydev.ink,www.eddydev.ink` | | `EURODYNDNS_HOSTS` | EuroDynDNS-hostnames (domeinen in EuroDNS-zone) | `eddydev.ink,www.eddydev.ink` |
@ -116,7 +116,7 @@ wan-ip-guard.service: Failed to spawn 'start' task: Permission denied
## Hoe het werkt ## Hoe het werkt
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 `https://1.1.1.1/cdn-cgi/trace` (Cloudflare, DNS-vrij — werkt als de lokale resolver uitvalt) met fallback naar `ifconfig.me`, `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. **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. 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.

View file

@ -43,16 +43,23 @@ async function fetchWithTimeout(url, options = {}, timeout = 15000) {
} }
async function getWanIp() { async function getWanIp() {
// Primary source is DNS-free: Cloudflare's 1.1.1.1 serves /cdn-cgi/trace over
// HTTPS on a fixed IP, so detection keeps working when the local resolver
// (AdGuard on this host) is down. The echo services below are fallbacks.
const services = [ const services = [
'https://ifconfig.me', {
'https://api.ipify.org', url: 'https://1.1.1.1/cdn-cgi/trace',
'https://icanhazip.com', parse: t => (t.split('\n').find(l => l.startsWith('ip=')) || '').split('=')[1],
},
{ url: 'https://ifconfig.me', parse: t => t },
{ url: 'https://api.ipify.org', parse: t => t },
{ url: 'https://icanhazip.com', parse: t => t },
]; ];
for (const url of services) { for (const { url, parse } of services) {
try { try {
const resp = await fetchWithTimeout(url, {}, 10000); const resp = await fetchWithTimeout(url, {}, 10000);
if (resp.ok) { if (resp.ok) {
const ip = (await resp.text()).trim(); const ip = (parse(await resp.text()) || '').trim();
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) { if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
log(`WAN IP obtained from ${url}: ${ip}`); log(`WAN IP obtained from ${url}: ${ip}`);
return ip; return ip;