diff --git a/README.md b/README.md new file mode 100644 index 0000000..aebf448 --- /dev/null +++ b/README.md @@ -0,0 +1,168 @@ +# Porkbun DDNS — Dynamic DNS para carloselugo.com + +**Fecha:** Abril 2026 +**Servidor:** labmini-01 (Dell Optiplex) +**Dominio:** carloselugo.com +**Ubicación del script:** `/opt/ddns/ddns_porkbun.sh` + +--- + +## Objetivo + +Detectar cambios en la IP pública del homelab y actualizar automáticamente los registros DNS en Porkbun — sin depender de un servicio DDNS de terceros. + +--- + +## Arquitectura + +``` +cron (cada 30 min) + → ddns_porkbun.sh + → obtiene IP pública (api.ipify.org) + → compara con state file (~/.porkbun-ddns.last-ip) + → si cambió → Porkbun API (editByNameType) + → actualiza registros A +``` + +--- + +## Registros DNS actualizados + +| Subdominio | FQDN | Tipo | +|---|---|---| +| `@` | `carloselugo.com` | A | +| `gamesever1` | `gamesever1.carloselugo.com` | A | +| `git` | `git.carloselugo.com` | A | + +> **Nota:** `*.carloselugo.com` tiene un CNAME apuntando a `carloselugo.com`, por lo que cualquier subdominio nuevo hereda la IP del root automáticamente sin necesitar su propio registro A. + +--- + +## Archivos + +| Archivo | Ruta | +|---|---| +| Script | `/opt/ddns/ddns_porkbun.sh` | +| State file | `/home/netintel/.porkbun-ddns.last-ip` | +| Log | `/home/netintel/.porkbun-ddns.log` | + +--- + +## Configuración del script + +```bash +API_KEY="pk1_..." # Porkbun API key +SECRET_KEY="sk1_..." # Porkbun secret key +DOMAIN="carloselugo.com" +SUBDOMAINS=("@" "gamesever1" "git") +STATE_FILE="/home/netintel/.porkbun-ddns.last-ip" +LOG_FILE="/home/netintel/.porkbun-ddns.log" +``` + +--- + +## Endpoint API usado + +``` +POST /dns/editByNameType/{domain}/A/{subdomain} +``` + +Se usa `editByNameType` en lugar de `edit` por ID porque es más robusto — no requiere hacer un retrieve previo para obtener el record ID, y evita el riesgo de crear registros duplicados accidentalmente. + +Para el root (`@`) el endpoint es: +``` +POST /dns/editByNameType/carloselugo.com/A +``` + +Para subdomains: +``` +POST /dns/editByNameType/carloselugo.com/A/git +``` + +--- + +## Prerequisitos en Porkbun + +1. Generar API key y secret en **Account Settings → API Access** +2. Habilitar API access para el dominio en **Domain Management → carloselugo.com → API Access** (toggle) + +> Sin el toggle por dominio, el API devuelve `DOMAIN_IS_NOT_OPTED_IN_TO_API_ACCESS` aunque las keys sean correctas. + +--- + +## Lógica del script + +1. Obtiene la IP pública via `api.ipify.org` (fallback: `ifconfig.me`, `icanhazip.com`) +2. Lee la última IP conocida del state file +3. Si son iguales → exit silencioso (no llama al API) +4. Si son diferentes → por cada subdominio: + - Obtiene la IP actual en DNS via `retrieveByNameType` + - Si ya coincide → skip + - Si no → llama a `editByNameType` con la nueva IP +5. Guarda la nueva IP en el state file + +--- + +## Cron + +```bash +# crontab -e (usuario netintel) +*/30 * * * * /opt/ddns/ddns_porkbun.sh +``` + +Corre cada 30 minutos. Solo genera output en el log cuando la IP cambia. + +--- + +## Verificar funcionamiento + +```bash +# Correr manualmente +/opt/ddns/ddns_porkbun.sh + +# Ver log +tail -f ~/.porkbun-ddns.log + +# Ver última IP registrada +cat ~/.porkbun-ddns.last-ip + +# Forzar una IP específica (solo script de prueba) +/opt/ddns/ddns_porkbun_test.sh -f 1.2.3.4 +``` + +--- + +## Ejemplo de log + +``` +# Primera ejecución +[2026-04-28 11:44:54] IP cambió: (primera ejecución) → 24.139.235.46 +[2026-04-28 11:44:55] [@] Ya actualizado (24.139.235.46) +[2026-04-28 11:44:56] [gamesever1] Ya actualizado (24.139.235.46) +[2026-04-28 11:44:57] [git] Ya actualizado (24.139.235.46) + +# Cuando la IP cambia +[2026-04-28 12:00:00] IP cambió: 24.139.235.46 → 24.139.236.10 +[2026-04-28 12:00:01] [@] ✓ 24.139.235.46 → 24.139.236.10 +[2026-04-28 12:00:02] [gamesever1] ✓ 24.139.235.46 → 24.139.236.10 +[2026-04-28 12:00:03] [git] ✓ 24.139.235.46 → 24.139.236.10 +[2026-04-28 12:00:03] Listo. 3 registro(s) actualizado(s). + +# Cuando no hay cambio (silencioso, no aparece en log) +``` + +--- + +## Troubleshooting + +**El script no puede escribir el log** +Verificar que las rutas de `STATE_FILE` y `LOG_FILE` sean escribibles por el usuario que corre el cron. Usar rutas en `$HOME` si no se corre como root. + +**Error: `DOMAIN_IS_NOT_OPTED_IN_TO_API_ACCESS`** +El toggle de API access del dominio en Porkbun está desactivado. Activarlo en Domain Management. + +**El API conecta por IPv6 y falla** +El servidor tiene conectividad IPv6. Todos los `curl` usan `-4` para forzar IPv4 y evitar timeouts silenciosos. + +**El record desaparece después de editar** +No usar `dns/edit` por ID cuando hay Cloudflare proxy habilitado en Porkbun — puede borrar el record. Usar siempre `editByNameType`. \ No newline at end of file