# DWS Dynamic DNS Service A self-hosted Dynamic DNS (DDNS) provider built with Go, featuring a minimalist web UI and NIC V2 (DynDNS2) protocol support for router compatibility. ## Overview This service allows users to claim subdomains under `space.dws.rip` and update them via the standard DynDNS2 protocol, compatible with UniFi, pfSense, EdgeRouters, and other consumer routers. ## Architecture ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ Router │ ──────► │ DWS Bridge │ ──────► │ Technitium DNS │ │ (DynDNS2) │ HTTP │ (Go) │ API │ (Authoritative)│ └─────────────┘ └──────────────┘ └─────────────────┘ │ ▼ ┌──────────────┐ │ SQLite │ │ (token→sub) │ └──────────────┘ ``` ## Features - **Token-based authentication** - No user accounts, just secure tokens - **NIC V2 Protocol** - Compatible with most routers - **Automatic wildcard records** - `*.your-space.space.dws.rip` also works - **Rate limiting** - 10 req/min per IP, 1 req/min per token - **Clean web UI** - Simple space claiming interface - **Router config examples** - UniFi, pfSense, cURL snippets ## Quick Start ### Prerequisites - Docker and Docker Compose - Running Technitium DNS server with API access - DNS delegation for `space.dws.rip` pointing to Technitium ### DNS Setup In your parent `dws.rip` zone: ```text space.dws.rip. IN NS dns.dws.rip. dns.dws.rip. IN A ``` ### Configuration Create a `.env` file: ```bash # Required: Technitium DNS API endpoint TECHNITIUM_URL=https://dns.dws.rip # Authentication (choose one method) # Method 1: API Token (recommended) TECHNITIUM_TOKEN=your-api-token-here # Method 2: Username/Password TECHNITIUM_USERNAME=admin TECHNITIUM_PASSWORD=your-password # Optional: Domain configuration BASE_DOMAIN=dws.rip SPACE_SUBDOMAIN=space # Optional: Rate limiting RATE_LIMIT_PER_IP=10 RATE_LIMIT_PER_TOKEN=1 # Optional: Trusted proxies (comma-separated) TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12 ``` ### Deployment #### Docker Compose (Recommended for single node) ```bash # Clone and start git clone https://git.dws.rip/DWS/dyn.git cd dyn docker-compose up -d # View logs docker-compose logs -f # Stop docker-compose down ``` #### Kubernetes / K3s For production deployments on Kubernetes or K3s clusters: ```bash # Clone repository git clone https://git.dws.rip/DWS/dyn.git cd dyn # Create your secrets file cp k8s/overlays/production/secrets.example.yaml k8s/overlays/production/secrets.yaml # Edit secrets.yaml with your actual Technitium credentials # Deploy with kustomize kubectl apply -k k8s/overlays/production # Or with kubectl 1.14+ (built-in kustomize) kubectl apply -k k8s/overlays/production # Check deployment status kubectl get pods -n dyn-ddns kubectl logs -n dyn-ddns -l app=dyn-ddns # Delete deployment kubectl delete -k k8s/overlays/production ``` **Kustomize Overlays:** - `k8s/overlays/production` - Production setup (2 replicas, higher resource limits) - `k8s/overlays/staging` - Staging environment (1 replica, relaxed rate limits) **Requirements:** - Cert-manager (for TLS certificates via Let's Encrypt) - Traefik or NGINX ingress controller - Persistent storage class (for SQLite database) See [k8s/README.md](k8s/README.md) for detailed Kubernetes deployment documentation. ## API Endpoints ### Web UI - `GET /` - Claim space interface ### API - `GET /api/check?subdomain=` - Check subdomain availability - `POST /api/claim` - Claim a subdomain (returns token once) ### DynDNS2 Protocol - `GET /api/nic/update?hostname=&myip=` - Auth: Basic auth with username `none` and password = token - Returns: `good `, `nochg `, `badauth`, `nohost`, `911` ## Router Configuration ### UniFi / UDM ``` Service: dyndns Hostname: your-space.space.dws.rip Username: none Password: Server: dyn.dws.rip/api/nic/update ``` ### pfSense / OPNsense ``` Service Type: Custom Update URL: https://dyn.dws.rip/api/nic/update?hostname=%h&myip=%i Username: none Password: ``` ### EdgeRouter ```bash set service dns dynamic interface eth0 service custom-dyn host-name your-space.space.dws.rip set service dns dynamic interface eth0 service custom-dyn login none set service dns dynamic interface eth0 service custom-dyn password set service dns dynamic interface eth0 service custom-dyn protocol dyndns2 set service dns dynamic interface eth0 service custom-dyn server dyn.dws.rip ``` ### Manual (cURL) ```bash curl -u "none:" \ "https://dyn.dws.rip/api/nic/update?hostname=your-space.space.dws.rip&myip=1.2.3.4" ``` ## Database Schema ```sql CREATE TABLE spaces ( token TEXT PRIMARY KEY, subdomain TEXT UNIQUE NOT NULL, last_ip TEXT, updated_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` ## Security - **32-byte random tokens** - Generated with `crypto/rand` - **Token displayed once** - No recovery mechanism (claim new space if lost) - **Rate limiting** - Prevents brute force and abuse - **No PII stored** - Just token-to-subdomain mapping - **Input validation** - Subdomains validated (alphanumeric + hyphen only) ## Environment Variables | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `TECHNITIUM_URL` | Yes | - | Technitium API URL | | `TECHNITIUM_TOKEN` | No* | - | API token for auth | | `TECHNITIUM_USERNAME` | No* | - | Basic auth username | | `TECHNITIUM_PASSWORD` | No* | - | Basic auth password | | `BASE_DOMAIN` | No | `dws.rip` | Root domain | | `SPACE_SUBDOMAIN` | No | `space` | Subdomain for user spaces | | `DATABASE_PATH` | No | `./dyn.db` | SQLite database path | | `SERVER_PORT` | No | `8080` | HTTP server port | | `RATE_LIMIT_PER_IP` | No | `10` | Requests per minute per IP | | `RATE_LIMIT_PER_TOKEN` | No | `1` | Updates per minute per token | | `TRUSTED_PROXIES` | No | - | Comma-separated CIDRs | *Either `TECHNITIUM_TOKEN` OR both `TECHNITIUM_USERNAME` and `TECHNITIUM_PASSWORD` required. ## Development ```bash # Local development go mod tidy go run cmd/server/main.go # Build go build -o server cmd/server/main.go # Test go test ./... ``` ## License MIT ## Credits Inspired by [benjaminbear/docker-ddns-server](https://github.com/benjaminbear/docker-ddns-server)