168 lines
4.5 KiB
Markdown
168 lines
4.5 KiB
Markdown
# 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`. |