Initial commit: DDNS service with NIC V2 protocol support

Features:
- Token-based subdomain claiming
- NIC V2 (DynDNS2) protocol implementation
- Technitium DNS integration
- Rate limiting (10 req/min IP, 1 req/min token)
- Web UI for space claiming
- Docker/Docker Compose support
- Compatible with UniFi, pfSense, EdgeRouter

Module: git.dws.rip/DWS/dyn
This commit is contained in:
2026-02-01 16:37:09 -05:00
commit 2470f121e2
16 changed files with 1835 additions and 0 deletions

83
internal/config/config.go Normal file
View File

@@ -0,0 +1,83 @@
package config
import (
"os"
"strconv"
"strings"
)
type Config struct {
ServerPort string
DatabasePath string
TechnitiumURL string
TechnitiumUsername string
TechnitiumPassword string
TechnitiumToken string
BaseDomain string
SpaceSubdomain string
RateLimitPerIP int
RateLimitPerToken int
TrustedProxies []string
}
func Load() *Config {
cfg := &Config{
ServerPort: getEnv("SERVER_PORT", "8080"),
DatabasePath: getEnv("DATABASE_PATH", "./dyn.db"),
TechnitiumURL: getEnv("TECHNITIUM_URL", ""),
TechnitiumUsername: getEnv("TECHNITIUM_USERNAME", ""),
TechnitiumPassword: getEnv("TECHNITIUM_PASSWORD", ""),
TechnitiumToken: getEnv("TECHNITIUM_TOKEN", ""),
BaseDomain: getEnv("BASE_DOMAIN", "dws.rip"),
SpaceSubdomain: getEnv("SPACE_SUBDOMAIN", "space"),
RateLimitPerIP: getEnvAsInt("RATE_LIMIT_PER_IP", 10),
RateLimitPerToken: getEnvAsInt("RATE_LIMIT_PER_TOKEN", 1),
TrustedProxies: getEnvAsSlice("TRUSTED_PROXIES", []string{}),
}
return cfg
}
func (c *Config) Validate() []string {
var errors []string
if c.TechnitiumURL == "" {
errors = append(errors, "TECHNITIUM_URL is required")
}
if c.TechnitiumToken == "" && (c.TechnitiumUsername == "" || c.TechnitiumPassword == "") {
errors = append(errors, "Either TECHNITIUM_TOKEN or TECHNITIUM_USERNAME/PASSWORD must be provided")
}
return errors
}
func (c *Config) GetZone() string {
if c.SpaceSubdomain == "" {
return c.BaseDomain
}
return c.SpaceSubdomain + "." + c.BaseDomain
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvAsInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intVal, err := strconv.Atoi(value); err == nil {
return intVal
}
}
return defaultValue
}
func getEnvAsSlice(key string, defaultValue []string) []string {
if value := os.Getenv(key); value != "" {
return strings.Split(value, ",")
}
return defaultValue
}