From 3f1feacacffeee543b69aa3b549f219531a22912 Mon Sep 17 00:00:00 2001 From: sbarbett Date: Wed, 6 Aug 2025 21:25:25 +0000 Subject: [PATCH] feat: added Docker image configuration for testing with Unbound --- .dockerignore | 10 ++++ Dockerfile | 32 +++++++++++++ README-Docker.md | 98 +++++++++++++++++++++++++++++++++++++++ README.md | 16 +++++++ docker-compose.yml | 49 ++++++++++++++++++++ internal/config/config.go | 12 +++++ 6 files changed, 217 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 README-Docker.md create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..68654ea --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +README.md +LICENSE +docs/ +examples/ +.github/ +.pre-commit-config.yaml +.goreleaser.yml +*.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ff85214 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Build stage +FROM golang:1.24-alpine AS builder + +WORKDIR /app + +# Copy go mod files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy source code +COPY . . + +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o sinkzone . + +# Final stage +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +# Copy the binary from builder stage +COPY --from=builder /app/sinkzone . + +# Expose ports for DNS resolver and API +EXPOSE 5353 8080 + +# Run the resolver with API and DNS ports +CMD ["./sinkzone", "resolver", "--api-port", "8080", "--port", "5353"] \ No newline at end of file diff --git a/README-Docker.md b/README-Docker.md new file mode 100644 index 0000000..7280123 --- /dev/null +++ b/README-Docker.md @@ -0,0 +1,98 @@ +# Sinkzone with Unbound Docker Setup + +A Docker Compose setup for Sinkzone with Unbound as the upstream DNS resolver. + +## Quick Start + +1. **Create environment file:** + ```bash + cat > .env << 'EOF' + DOCKER_NETWORK_SUBNET=172.30.0.0/16 + SINKZONE_IP=172.30.0.1 + UNBOUND_IP=172.30.0.2 + SINKZONE_UPSTREAM_NAMESERVERS=unbound + EOF + ``` + +2. **Start services:** + ```bash + docker compose up -d + ``` + +3. **Test DNS resolution:** + ```bash + dig @127.0.0.1 -p 5353 google.com + ``` + +## Usage + +### DNS Resolution +- **Sinkzone**: `dig @127.0.0.1 -p 5353 google.com` +- **Direct Unbound**: `dig @127.0.0.1 -p 5335 google.com` + +### API Access +```bash +# Health check +curl http://localhost:8080/health + +# View DNS queries +curl http://localhost:8080/api/queries + +# Enable focus mode +curl -X POST http://localhost:8080/api/focus \ + -H "Content-Type: application/json" \ + -d '{"enabled": true, "duration": "1h"}' +``` + +### Sinkzone Commands via Docker Exec + +```bash +# Terminal UI +docker compose exec -it sinkzone ./sinkzone tui --api-url http://localhost:8080 + +# Monitor DNS requests +docker compose exec -it sinkzone ./sinkzone monitor --api-url http://localhost:8080 + +# Add domain to allowlist +docker compose exec -it sinkzone ./sinkzone allowlist add google.com + +# Remove domain from allowlist +docker compose exec -it sinkzone ./sinkzone allowlist remove google.com + +# List allowlist +docker compose exec -it sinkzone ./sinkzone allowlist list + +# Enable focus mode +docker compose exec -it sinkzone ./sinkzone focus start + +# Disable focus mode +docker compose exec -it sinkzone ./sinkzone focus --disable + +# Check status +docker compose exec -it sinkzone ./sinkzone status +``` + +## Ports + +- **5353**: Sinkzone DNS server +- **8080**: Sinkzone API server +- **5335**: Unbound DNS server (direct access) + +## Architecture + +``` +Client → Sinkzone (5353) → Unbound (172.30.0.2) → Internet +``` + +## Troubleshooting + +```bash +# View logs +docker compose logs -f + +# Restart services +docker compose restart + +# Rebuild and restart +docker compose up -d --build +``` \ No newline at end of file diff --git a/README.md b/README.md index ab7bc7c..33758aa 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,22 @@ go build -o sinkzone . --- +
+👉 Docker Setup + +For Docker-based deployment with Unbound as the upstream DNS resolver, see [README-Docker.md](README-Docker.md) for complete instructions. + +```bash +# Quick start with Docker +git clone https://github.com/berbyte/sinkzone.git +cd sinkzone +docker compose up -d +``` + +
+ +--- + ## Documentation diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a35b82a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +--- +networks: + dns_net: + driver: bridge + ipam: + config: + - subnet: ${DOCKER_NETWORK_SUBNET:-172.30.0.0/16} # Custom subnet for the Docker network + +services: + sinkzone: + build: . + container_name: sinkzone + restart: unless-stopped + ports: + # DNS Ports + - "5353:5353/tcp" + - "5353:5353/udp" + # For production use, set to port 53 + #- "53:53/tcp" + #- "53:53/udp" + # API Port + - "8080:8080/tcp" + environment: + # Set sinkzone to use unbound as upstream DNS + SINKZONE_UPSTREAM_NAMESERVERS: "unbound" + cap_add: + - NET_ADMIN # Required for network configuration + depends_on: + - unbound + networks: + dns_net: + + unbound: + container_name: unbound + image: mvance/unbound:latest # Official Unbound container + command: > + sh -c "if [ ! -f /opt/unbound/etc/unbound/unbound.conf ]; then \ + echo 'server:' > /opt/unbound/etc/unbound/unbound.conf && \ + echo ' interface: 0.0.0.0' >> /opt/unbound/etc/unbound/unbound.conf && \ + echo ' access-control: 172.30.0.0/16 allow' >> /opt/unbound/etc/unbound/unbound.conf; \ + fi; \ + touch /opt/unbound/etc/unbound/a-records.conf /opt/unbound/etc/unbound/srv-records.conf /opt/unbound/etc/unbound/forward-records.conf && \ + exec unbound -d" + ports: + - "5335:53/tcp" + - "5335:53/udp" + restart: unless-stopped + networks: + dns_net: \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index fb9a60f..d237f4b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -45,6 +45,18 @@ func Load() (*Config, error) { return nil, fmt.Errorf("failed to save default config: %w", err) } } + + // Check for environment variable override + if envNameservers := os.Getenv("SINKZONE_UPSTREAM_NAMESERVERS"); envNameservers != "" { + // Split by comma if multiple nameservers are provided + nameservers := strings.Split(envNameservers, ",") + // Trim whitespace from each nameserver + for i, ns := range nameservers { + nameservers[i] = strings.TrimSpace(ns) + } + cfg.UpstreamNameservers = nameservers + } + return cfg, nil }