Compare commits

..

3 Commits

Author SHA1 Message Date
c5279243c0 Add custom filter for DWS and Tanishq Dubey trademarks
- Create custom_filter.go with DWS/Tanishq Dubey term detection
- Block variations including:
  - DWS, Dubey Web Services, DWS Engineering LLC
  - Tanishq Dubey, tdubey
  - Leet speak variations (dub3y, t4nishq, dw5, etc.)
  - Combined terms (dubeydns, dws-ddns, etc.)
- Update frontend to show 'reserved' message for blocked terms
- Filters are case-insensitive and handle separators

Protects brand identity and personal name from being used
in user subdomains
2026-02-01 17:17:52 -05:00
f96aaf1e96 Add profanity filter for subdomain validation
- Integrate github.com/TwiN/go-away for content filtering
- Check subdomains for inappropriate content during validation
- Update frontend to display 'inappropriate content' message
- Blocks profane subdomains from being claimed

Uses go-away's built-in profanity dictionary to detect:
- Leet speak substitutions (e.g., @73447h013)
- Obfuscated profanity
- Common inappropriate terms
2026-02-01 16:45:29 -05:00
f3f1c0a0c8 Add Kubernetes/K3s deployment manifests and documentation
- Complete k8s manifests with Kustomize support
- Production and staging overlays
- ConfigMap/Secret management
- Ingress with TLS (Traefik/NGINX)
- Persistent storage for SQLite
- Comprehensive k8s README with operations guide
- Updated main README with k8s deployment instructions
- Gitignore for k8s secrets

Usage:
  kubectl apply -k k8s/overlays/production
2026-02-01 16:40:16 -05:00
21 changed files with 906 additions and 9 deletions

4
.gitignore vendored
View File

@@ -44,3 +44,7 @@ vendor/
# Temporary files
*.tmp
*.temp
# Kubernetes secrets (never commit these!)
k8s/overlays/*/secrets.yaml
!k8s/overlays/*/secrets.example.yaml

View File

@@ -77,9 +77,11 @@ 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 <repo>
git clone https://git.dws.rip/DWS/dyn.git
cd dyn
docker-compose up -d
@@ -90,6 +92,44 @@ docker-compose logs -f
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

17
go.mod
View File

@@ -1,6 +1,6 @@
module git.dws.rip/DWS/dyn
go 1.23.0
go 1.24.4
require (
github.com/gin-gonic/gin v1.11.0
@@ -8,6 +8,7 @@ require (
)
require (
github.com/TwiN/go-away v1.8.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
@@ -31,12 +32,12 @@ require (
github.com/ugorji/go/codec v1.3.0 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
)

16
go.sum
View File

@@ -1,3 +1,5 @@
github.com/TwiN/go-away v1.8.1 h1:zbbr0ISBkDSbnUFHrnRUhbCR/7+9ONMWtIi1BiQWX8Y=
github.com/TwiN/go-away v1.8.1/go.mod h1:nSQEvd/FYBNmnC27RGJdPi91LXYMG8SrRc1o1w+VmKY=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
@@ -69,19 +71,33 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,147 @@
package handlers
import (
"strings"
"unicode"
)
// CustomFilter contains terms related to DWS and Tanishq Dubey that should be blocked
type CustomFilter struct {
blockedTerms []string
}
// NewCustomFilter creates a new custom filter with DWS and personal name variations
func NewCustomFilter() *CustomFilter {
return &CustomFilter{
blockedTerms: []string{
// DWS variations
"dws",
"dubey",
"dubeyweb",
"dubeywebservices",
"dubeyweb services",
"dubey-engineering",
"dubeyengineering",
"dwsengineering",
"dws-engineering",
"dws-engineering-llc",
"dwsengineeringllc",
"dwsllc",
"dws-llc",
"webservices",
"web-services",
"dubeycorp",
"dubey-corp",
"dubeyinc",
"dubey-inc",
// Tanishq Dubey variations
"tanishq",
"tanishqdubey",
"tanishq-dubey",
"tdubey",
"t-dubey",
"tanishq-d",
"tdub",
"tanish",
"dubey-t",
"dubeytanishq",
"dubey-tanishq",
// Leet speak variations
"dub3y",
"dub3yweb",
"t4nishq",
"t4n1shq",
"tan1shq",
"dub3y3ng1n33r1ng",
"dw5",
"dw$",
"dub3yc0rp",
// Common combinations
"dubeydns",
"dubey-ddns",
"dwsdns",
"dws-ddns",
"tanishqdns",
"tanishq-ddns",
"tdubeydns",
"tdubey-ddns",
},
}
}
// IsBlocked checks if the given text contains any blocked terms
func (cf *CustomFilter) IsBlocked(text string) bool {
normalized := cf.normalize(text)
for _, term := range cf.blockedTerms {
if strings.Contains(normalized, term) {
return true
}
}
return false
}
// normalize prepares text for comparison by:
// - Converting to lowercase
// - Removing common separators
// - Converting leet speak to normal text
func (cf *CustomFilter) normalize(text string) string {
// Convert to lowercase
text = strings.ToLower(text)
// Remove separators
replacer := strings.NewReplacer(
"-", "",
"_", "",
".", "",
" ", "",
)
text = replacer.Replace(text)
// Convert leet speak
text = cf.leetToNormal(text)
return text
}
// leetToNormal converts common leet speak characters to normal letters
func (cf *CustomFilter) leetToNormal(text string) string {
replacements := map[rune]rune{
'0': 'o',
'1': 'i',
'3': 'e',
'4': 'a',
'5': 's',
'6': 'g',
'7': 't',
'8': 'b',
'9': 'g',
'@': 'a',
'$': 's',
'!': 'i',
'|': 'i',
'+': 't',
}
result := make([]rune, len(text))
for i, char := range text {
if replacement, ok := replacements[char]; ok {
result[i] = replacement
} else {
result[i] = unicode.ToLower(char)
}
}
return string(result)
}
// GetBlockedTerms returns a copy of the blocked terms list (for testing/debugging)
func (cf *CustomFilter) GetBlockedTerms() []string {
terms := make([]string, len(cf.blockedTerms))
copy(terms, cf.blockedTerms)
return terms
}

View File

@@ -12,9 +12,15 @@ import (
"git.dws.rip/DWS/dyn/internal/database"
"git.dws.rip/DWS/dyn/internal/dns"
"git.dws.rip/DWS/dyn/internal/models"
"github.com/TwiN/go-away"
"github.com/gin-gonic/gin"
)
var (
profanityDetector = goaway.NewProfanityDetector()
customFilter = NewCustomFilter()
)
type WebHandler struct {
db *database.DB
config *config.Config
@@ -49,6 +55,16 @@ func (h *WebHandler) ClaimSpace(c *gin.Context) {
return
}
if profanityDetector.IsProfane(subdomain) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Subdomain contains inappropriate content"})
return
}
if customFilter.IsBlocked(subdomain) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Subdomain is reserved"})
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@@ -86,6 +102,24 @@ func (h *WebHandler) CheckSubdomain(c *gin.Context) {
return
}
if profanityDetector.IsProfane(subdomain) {
c.JSON(http.StatusOK, gin.H{
"available": false,
"subdomain": subdomain,
"reason": "inappropriate",
})
return
}
if customFilter.IsBlocked(subdomain) {
c.JSON(http.StatusOK, gin.H{
"available": false,
"subdomain": subdomain,
"reason": "reserved",
})
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

365
k8s/README.md Normal file
View File

@@ -0,0 +1,365 @@
# Kubernetes Deployment
This directory contains Kubernetes manifests for deploying the DWS Dynamic DNS service on K3s, Kubernetes, or any K8s-compatible platform.
## Directory Structure
```
k8s/
├── base/ # Base manifests (don't edit directly)
│ ├── deployment.yaml # Deployment, Service, PVC
│ ├── configmap.yaml # Non-sensitive configuration
│ ├── secrets.yaml # Sensitive configuration (placeholders)
│ ├── ingress.yaml # Ingress with TLS
│ └── kustomization.yaml # Base kustomization
├── overlays/
│ ├── production/ # Production environment
│ │ ├── kustomization.yaml # Production-specific settings
│ │ ├── deployment-patch.yaml # Resource adjustments
│ │ ├── namespace.yaml # Production namespace
│ │ ├── secrets.yaml # Production secrets (gitignored)
│ │ └── secrets.example.yaml # Example secrets template
│ │
│ └── staging/ # Staging environment
│ ├── kustomization.yaml # Staging-specific settings
│ ├── deployment-patch.yaml # Single replica, lower resources
│ ├── namespace.yaml # Staging namespace
│ ├── secrets.yaml # Staging secrets (gitignored)
│ └── secrets.example.yaml # Example secrets template
└── README.md # This file
```
## Quick Start
### Prerequisites
- Kubernetes 1.21+ or K3s cluster
- kubectl configured with cluster access
- cert-manager installed (for TLS certificates)
- Ingress controller (Traefik, NGINX, etc.)
- Storage class for persistent volumes
### Production Deployment
```bash
# 1. Clone and enter directory
git clone https://git.dws.rip/DWS/dyn.git
cd dyn/k8s
# 2. Create production secrets
cp overlays/production/secrets.example.yaml overlays/production/secrets.yaml
# 3. Edit secrets with your Technitium credentials
# Replace 'your-production-api-token-here' with actual token
nano overlays/production/secrets.yaml
# 4. Deploy to production
kubectl apply -k overlays/production
# 5. Verify deployment
kubectl get pods -n dyn-ddns
kubectl get svc -n dyn-ddns
kubectl get ingress -n dyn-ddns
# 6. Check logs
kubectl logs -n dyn-ddns -l app.kubernetes.io/name=dyn-ddns -f
```
### Staging Deployment
```bash
# Similar process for staging
cp overlays/staging/secrets.example.yaml overlays/staging/secrets.yaml
# Edit with staging credentials
kubectl apply -k overlays/staging
```
## Configuration
### Secrets (Required)
Create `overlays/production/secrets.yaml`:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Choose ONE authentication method:
# Method 1: API Token (recommended)
TECHNITIUM_TOKEN: "your-actual-api-token-here"
# Method 2: Username/Password
# TECHNITIUM_USERNAME: "admin"
# TECHNITIUM_PASSWORD: "your-password"
# Optional: Trusted proxies
# TRUSTED_PROXIES: "10.0.0.0/8,172.16.0.0/12"
```
**Important:** Never commit secrets.yaml to git. It's already in .gitignore.
### ConfigMap (Optional Overrides)
Edit `overlays/production/kustomization.yaml`:
```yaml
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://dns.dws.rip
- BASE_DOMAIN=dws.rip
- SPACE_SUBDOMAIN=space
- RATE_LIMIT_PER_IP=10
- RATE_LIMIT_PER_TOKEN=1
```
### Ingress Customization
By default, the ingress is configured for **Traefik** with cert-manager:
```yaml
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
```
For **NGINX** ingress, change annotations in `base/ingress.yaml`:
```yaml
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
```
## Architecture
### Components
1. **Deployment** - Runs the DDNS bridge container
- Replicas: 2 (production), 1 (staging)
- Resource limits configurable per overlay
- Health checks (liveness & readiness probes)
2. **Service** - ClusterIP exposing port 80
3. **Ingress** - TLS termination at edge
- Host: dyn.dws.rip (customize as needed)
- Automatic certificate via cert-manager
4. **PersistentVolumeClaim** - SQLite database storage
- Size: 1Gi (adjustable)
- AccessMode: ReadWriteOnce
### Resource Requirements
**Production:**
- CPU: 200m request / 1000m limit
- Memory: 128Mi request / 512Mi limit
- Replicas: 2
**Staging:**
- CPU: 100m request / 500m limit
- Memory: 64Mi request / 256Mi limit
- Replicas: 1
## Operations
### View Logs
```bash
# All pods
kubectl logs -n dyn-ddns -l app.kubernetes.io/name=dyn-ddns
# Specific pod
kubectl logs -n dyn-ddns -f deployment/prod-dyn-ddns
# Previous container logs (after restart)
kubectl logs -n dyn-ddns --previous deployment/prod-dyn-ddns
```
### Scale Deployment
```bash
# Scale to 3 replicas
kubectl scale deployment -n dyn-ddns prod-dyn-ddns --replicas=3
# Edit deployment directly
kubectl edit deployment -n dyn-ddns prod-dyn-ddns
```
### Database Backup
The SQLite database is stored in the persistent volume at `/data/dyn.db`:
```bash
# Find the pod
POD=$(kubectl get pod -n dyn-ddns -l app.kubernetes.io/name=dyn-ddns -o jsonpath='{.items[0].metadata.name}')
# Copy database locally
kubectl cp dyn-ddns/$POD:/data/dyn.db ./dyn-backup-$(date +%Y%m%d).db
# Or exec into pod
kubectl exec -it -n dyn-ddns $POD -- sh
# Then: sqlite3 /data/dyn.db "SELECT * FROM spaces;"
```
### Update Deployment
```bash
# Update to latest image
kubectl rollout restart deployment -n dyn-ddns prod-dyn-ddns
# Or set specific image tag
kubectl set image deployment -n dyn-ddns prod-dyn-ddns dyn-ddns=git.dws.rip/DWS/dyn:v1.0.0
# Monitor rollout
kubectl rollout status deployment -n dyn-ddns prod-dyn-ddns
```
### Troubleshooting
**Pod stuck in Pending:**
```bash
kubectl describe pod -n dyn-ddns <pod-name>
# Check: Storage class available? PV provisioned?
```
**Pod crash looping:**
```bash
kubectl logs -n dyn-ddns --previous <pod-name>
# Check: Secrets configured? Technitium URL reachable?
```
**Ingress not working:**
```bash
kubectl describe ingress -n dyn-ddns prod-dyn-ddns
kubectl get certificate -n dyn-ddns
# Check: DNS pointing to ingress controller? Cert-manager working?
```
## Advanced Usage
### Multi-Environment Setup
Deploy to multiple environments:
```bash
# Staging
kubectl apply -k overlays/staging
# Production
kubectl apply -k overlays/production
# Verify both
kubectl get pods --all-namespaces -l app.kubernetes.io/name=dyn-ddns
```
### Custom Overlay
Create your own overlay for specific needs:
```bash
mkdir overlays/custom
cat > overlays/custom/kustomization.yaml << 'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: my-namespace
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://my-dns-server.example.com
- RATE_LIMIT_PER_IP=5
EOF
kubectl apply -k overlays/custom
```
### Monitoring
The deployment exposes standard metrics. Add Prometheus scraping:
```yaml
# Add to deployment-patch.yaml
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
```
## Security Considerations
1. **Secrets Management**
- Never commit secrets to git
- Consider using external secrets operator (Vault, Sealed Secrets)
- Rotate Technitium API tokens regularly
2. **Network Policies**
```yaml
# Example network policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dyn-ddns-netpol
namespace: dyn-ddns
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: dyn-ddns
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to: [] # Allow all egress (for Technitium API)
```
3. **Pod Security**
- Container runs as non-root (in Dockerfile)
- Read-only root filesystem recommended
- Drop all capabilities
## Maintenance
### Regular Tasks
1. **Backup database weekly**
2. **Monitor rate limit metrics**
3. **Review access logs**
4. **Update base image for security patches**
### Version Upgrades
1. Update image tag in overlay kustomization
2. Apply changes: `kubectl apply -k overlays/production`
3. Verify rollout: `kubectl rollout status ...`
4. Monitor for errors in logs
## Support
For issues specific to Kubernetes deployment:
1. Check pod logs: `kubectl logs ...`
2. Describe resources: `kubectl describe ...`
3. Check ingress controller logs
4. Verify cert-manager is issuing certificates

13
k8s/base/configmap.yaml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: dyn-ddns-config
data:
SERVER_PORT: "8080"
DATABASE_PATH: "/data/dyn.db"
BASE_DOMAIN: "dws.rip"
SPACE_SUBDOMAIN: "space"
RATE_LIMIT_PER_IP: "10"
RATE_LIMIT_PER_TOKEN: "1"
# TECHNITIUM_URL: "https://dns.dws.rip"
# Add your Technitium URL above or in overlays

86
k8s/base/deployment.yaml Normal file
View File

@@ -0,0 +1,86 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dyn-ddns
labels:
app: dyn-ddns
spec:
replicas: 1
selector:
matchLabels:
app: dyn-ddns
template:
metadata:
labels:
app: dyn-ddns
spec:
containers:
- name: dyn-ddns
image: git.dws.rip/DWS/dyn:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: dyn-ddns-config
- secretRef:
name: dyn-ddns-secrets
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
volumes:
- name: data
persistentVolumeClaim:
claimName: dyn-ddns-data
---
apiVersion: v1
kind: Service
metadata:
name: dyn-ddns
labels:
app: dyn-ddns
spec:
selector:
app: dyn-ddns
ports:
- port: 80
targetPort: 8080
name: http
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dyn-ddns-data
labels:
app: dyn-ddns
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

30
k8s/base/ingress.yaml Normal file
View File

@@ -0,0 +1,30 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dyn-ddns
annotations:
# Traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# NGINX (uncomment if using NGINX ingress)
# kubernetes.io/ingress.class: nginx
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- dyn.dws.rip
secretName: dyn-ddns-tls
rules:
- host: dyn.dws.rip
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dyn-ddns
port:
number: 80

View File

@@ -0,0 +1,17 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- configmap.yaml
- ingress.yaml
commonLabels:
app.kubernetes.io/name: dyn-ddns
app.kubernetes.io/part-of: dws-dns
namespace: default
images:
- name: git.dws.rip/DWS/dyn
newTag: latest

17
k8s/base/secrets.yaml Normal file
View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Choose ONE authentication method:
# Method 1: API Token (recommended)
# TECHNITIUM_TOKEN: "your-api-token-here"
# Method 2: Username/Password
# TECHNITIUM_USERNAME: "admin"
# TECHNITIUM_PASSWORD: "your-password"
# Optional: Trusted proxies (comma-separated)
# TRUSTED_PROXIES: "10.0.0.0/8,172.16.0.0/12"

View File

@@ -0,0 +1,16 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dyn-ddns
spec:
template:
spec:
containers:
- name: dyn-ddns
resources:
requests:
memory: "128Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "1000m"

View File

@@ -0,0 +1,28 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- ../../base
- secrets.yaml
namePrefix: prod-
namespace: dyn-ddns
commonLabels:
environment: production
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://dns.dws.rip
- RATE_LIMIT_PER_IP=10
- RATE_LIMIT_PER_TOKEN=1
patchesStrategicMerge:
- deployment-patch.yaml
replicas:
- name: dyn-ddns
count: 2

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: dyn-ddns
labels:
app.kubernetes.io/name: dyn-ddns
app.kubernetes.io/part-of: dws-dns

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Replace with your actual Technitium API token
TECHNITIUM_TOKEN: "your-production-api-token-here"
# Or use username/password (not recommended for production)
# TECHNITIUM_USERNAME: "admin"
# TECHNITIUM_PASSWORD: "your-password"

View File

@@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dyn-ddns
spec:
replicas: 1
template:
spec:
containers:
- name: dyn-ddns
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"

View File

@@ -0,0 +1,24 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- ../../base
- secrets.yaml
namePrefix: staging-
namespace: dyn-ddns-staging
commonLabels:
environment: staging
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://dns-staging.dws.rip
- RATE_LIMIT_PER_IP=100
- RATE_LIMIT_PER_TOKEN=10
patchesStrategicMerge:
- deployment-patch.yaml

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: dyn-ddns-staging
labels:
app.kubernetes.io/name: dyn-ddns
app.kubernetes.io/part-of: dws-dns

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Replace with your staging Technitium credentials
TECHNITIUM_TOKEN: "your-staging-api-token-here"

View File

@@ -33,6 +33,14 @@ document.addEventListener('DOMContentLoaded', function() {
availabilityStatus.textContent = '✓ Available';
availabilityStatus.className = 'status available';
claimBtn.disabled = false;
} else if (data.reason === 'inappropriate') {
availabilityStatus.textContent = '✗ Contains inappropriate content';
availabilityStatus.className = 'status taken';
claimBtn.disabled = true;
} else if (data.reason === 'reserved') {
availabilityStatus.textContent = '✗ This subdomain is reserved';
availabilityStatus.className = 'status taken';
claimBtn.disabled = true;
} else {
availabilityStatus.textContent = '✗ Already taken';
availabilityStatus.className = 'status taken';