No description
  • Go 96.2%
  • Makefile 3%
  • Dockerfile 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
morten-olsen ca291f2524
All checks were successful
ci/woodpecker/manual/renovate Pipeline was successful
ci/woodpecker/cron/renovate Pipeline was successful
ci: enable scheduled source dependency maintenance (#1)
2026-10-26 23:37:10 +01:00
.github/workflows productionized version 2025-12-31 00:22:33 +01:00
.woodpecker ci: enable scheduled source dependency maintenance (#1) 2026-10-26 23:37:10 +01:00
cmd/dns-pihole productionized version 2025-12-31 00:22:33 +01:00
deploy productionized version 2025-12-31 00:22:33 +01:00
internal productionized version 2025-12-31 00:22:33 +01:00
pkg/models productionized version 2025-12-31 00:22:33 +01:00
.dockerignore productionized version 2025-12-31 00:22:33 +01:00
.gitignore productionized version 2025-12-31 00:22:33 +01:00
.golangci.yml init 2025-12-31 00:03:21 +01:00
docker-compose.test.yml init 2025-12-31 00:03:21 +01:00
Dockerfile productionized version 2025-12-31 00:22:33 +01:00
e2e_test.go productionized version 2025-12-31 00:22:33 +01:00
go.mod init 2025-12-31 00:03:21 +01:00
go.sum init 2025-12-31 00:03:21 +01:00
Makefile productionized version 2025-12-31 00:22:33 +01:00
mise.toml init 2025-12-31 00:03:21 +01:00
README.md ci: enable scheduled source dependency maintenance (#1) 2026-10-26 23:37:10 +01:00
renovate.json ci: enable scheduled source dependency maintenance (#1) 2026-10-26 23:37:10 +01:00

Pi-hole DNS Backend

A DNS provider backend for the homelab-dns-operator that integrates with Pi-hole's custom DNS API.

Overview

This backend implements the DNS Operator webhook protocol, allowing the DNS operator to manage DNS records through Pi-hole's custom DNS feature. It provides REST endpoints for creating, updating, deleting, and querying DNS records.

Features

  • ✅ Full implementation of DNS Operator webhook protocol
  • ✅ Support for A and AAAA records
  • ✅ HMAC authentication support
  • ✅ Health check endpoint
  • ✅ Idempotent operations
  • ✅ Comprehensive error handling

Dependency updates

Renovate runs through Woodpecker every Sunday at 03:45 UTC on main; the manual pipeline event supports an immediate run. renovate.json covers Dockerfile bases, Go dependencies, Compose images and actions, with container digest pinning. Review source updates before merging. The core repository owns the deployed backend image pin independently of source dependency maintenance.

Prerequisites

  • Go 1.21 or later (managed via mise)
  • Pi-hole instance with API access enabled
  • Pi-hole API password (configured via FTLCONF_webserver_api_password in Docker or web interface)

Configuration

The backend is configured via environment variables:

Variable Description Required Default
PORT HTTP server port No 7100
PIHOLE_URL Base URL of Pi-hole instance Yes -
PIHOLE_PASSWORD Pi-hole API password Yes -
HMAC_SECRET HMAC secret for request authentication No -

Getting Pi-hole API Password

The backend uses password-based authentication with Pi-hole's REST API. You can set the password in one of two ways:

Docker Deployment

Set the FTLCONF_webserver_api_password environment variable when starting Pi-hole:

docker run -e FTLCONF_webserver_api_password="your-password" pihole/pihole

Web Interface

  1. Log into your Pi-hole web interface
  2. Navigate to Settings → API / Web interface
  3. Set or change the API password
  4. Use this password in the PIHOLE_PASSWORD environment variable

Building

Using mise

# Install dependencies
make deps

# Build the application
make build

Manual build

go build -o dns-pihole ./cmd/dns-pihole

Docker Image

Pre-built multi-architecture Docker images are available on GitHub Container Registry:

docker pull ghcr.io/morten-olsen/homelab-dns-pihole:latest

Images are automatically built for linux/amd64 and linux/arm64 architectures.

Running

Local development

export PIHOLE_URL="http://pihole.local:80"
export PIHOLE_PASSWORD="your-api-password"
export HMAC_SECRET="your-hmac-secret"  # Optional
export PORT="7100"  # Optional

./dns-pihole

Docker

# Build the image
make docker-build

# Run the container
docker run -p 7100:7100 \
  -e PIHOLE_URL=http://pihole:80 \
  -e PIHOLE_PASSWORD=your-password \
  -e HMAC_SECRET=your-secret \
  dns-pihole:latest

Kubernetes

See the deploy/ directory for Kubernetes deployment examples.

API Endpoints

Health Check

GET /health

Returns the health status of the backend and Pi-hole connectivity.

Response:

{
  "status": "healthy",
  "message": "DNS server is operational",
  "timestamp": "2025-12-30T10:00:00Z"
}

Create/Update Record

POST /records

Creates or updates a DNS record.

Request:

{
  "record": {
    "type": "A",
    "domain": "example.com",
    "subdomain": "www",
    "values": ["192.168.1.100"],
    "ttl": 600
  },
  "operation": "upsert"
}

Response:

{
  "success": true,
  "record": {
    "type": "A",
    "domain": "example.com",
    "subdomain": "www",
    "fqdn": "www.example.com",
    "values": ["192.168.1.100"],
    "ttl": 600
  },
  "message": "Record created successfully"
}

Get Record

GET /records/{type}/{domain}/{subdomain}

Retrieves a DNS record.

Response:

{
  "success": true,
  "record": {
    "type": "A",
    "domain": "example.com",
    "subdomain": "www",
    "fqdn": "www.example.com",
    "values": ["192.168.1.100"],
    "ttl": 600
  }
}

Delete Record

DELETE /records/{type}/{domain}/{subdomain}

Deletes a DNS record.

Response:

{
  "success": true,
  "message": "Record deleted successfully"
}

Limitations

  • CNAME Records: Pi-hole's custom DNS feature does not support CNAME records. The backend will return an error if you attempt to create a CNAME record. Use A records instead.
  • Multiple IPs: Pi-hole custom DNS only supports a single IP address per domain. If multiple values are provided, only the first one will be used.
  • TTL: Pi-hole custom DNS does not support custom TTL values. The TTL field in requests is accepted but not applied.

Testing

The project includes comprehensive unit tests with 72%+ code coverage and end-to-end tests against a real Pi-hole instance.

Running Tests

# Run unit tests
make test

# Run tests with coverage report
make test-coverage

# Run tests with race detector
make test-race

# Run e2e tests (requires Docker)
make test-e2e

# Run all tests (unit + e2e)
make test-all

Unit Test Coverage

The unit test suite covers:

  • ✅ HTTP handlers (health, upsert, get, delete)
  • ✅ Pi-hole API client integration (mocked)
  • ✅ HMAC authentication and verification
  • ✅ Request validation (IPv4, IPv6, CNAME)
  • ✅ Error handling and edge cases
  • ✅ Server middleware and routing

End-to-End Tests

The e2e tests run against a real Pi-hole instance in Docker:

  • ✅ Health check against real Pi-hole
  • ✅ Create and retrieve A/AAAA records
  • ✅ Update existing records
  • ✅ Delete records (including idempotent behavior)
  • ✅ Full HTTP endpoint integration

The e2e tests use Docker Compose to spin up a Pi-hole instance for testing. See docker-compose.test.yml for the test environment configuration.

Manual Testing

# Test health endpoint
curl http://localhost:7100/health

# Test creating a record (with HMAC if configured)
curl -X POST http://localhost:7100/records \
  -H "Content-Type: application/json" \
  -H "X-DNS-Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -H "X-DNS-Nonce: $(uuidgen)" \
  -H "X-DNS-Signature: <calculated-hmac-signature>" \
  -d '{
    "record": {
      "type": "A",
      "domain": "example.com",
      "subdomain": "test",
      "values": ["192.168.1.100"]
    },
    "operation": "upsert"
  }'

Security

  • HMAC Authentication: When HMAC_SECRET is configured, all requests must include valid HMAC signatures in the X-DNS-Signature header.
  • API Password: The Pi-hole API password should be stored securely (e.g., Kubernetes secrets).
  • Network: Consider running the backend in the same network as Pi-hole or using TLS for communication.

CI/CD

The project uses GitHub Actions for continuous integration and deployment:

Test Workflow (.github/workflows/test.yml)

Runs on every push and pull request:

  • ✅ Unit tests with race detector (Go 1.21 and 1.22)
  • ✅ Linting with golangci-lint
  • ✅ End-to-end tests against Dockerized Pi-hole
  • ✅ Code coverage reports uploaded as artifacts

Build Workflow (.github/workflows/build.yml)

Builds and pushes multi-architecture Docker images:

  • ✅ Multi-architecture builds (linux/amd64, linux/arm64)
  • ✅ Pushes to GitHub Container Registry (ghcr.io)
  • ✅ Automatic tagging:
    • latest for main branch
    • main-<sha> for commits
    • Semantic version tags (v1.0.0, v1.0, v1) for releases
  • ✅ Docker layer caching for faster builds
  • ✅ OCI image labels with metadata

Image Registry: ghcr.io/morten-olsen/homelab-dns-pihole

See .github/workflows/README.md for detailed workflow documentation.

Development

Linting

The project uses golangci-lint for code quality checks.

# Install golangci-lint (if not already installed)
make install-lint

# Run linters
make lint

# Run linters and auto-fix issues
make lint-fix

The linting configuration is in .golangci.yml. Common checks include:

  • Error handling verification
  • Code simplification
  • Security checks
  • Unused code detection
  • Code formatting

Project Structure

The project follows the standard Go project layout:

.
├── cmd/
│   └── dns-pihole/
│       └── main.go              # Application entry point
├── internal/
│   ├── auth/
│   │   ├── hmac.go              # HMAC authentication
│   │   └── hmac_test.go         # HMAC tests
│   ├── handlers/
│   │   └── handlers.go          # HTTP request handlers
│   ├── pihole/
│   │   ├── client.go            # Pi-hole API client
│   │   ├── interface.go         # Pi-hole client interface
│   │   ├── mock.go              # Mock Pi-hole client for testing
│   │   └── pihole_test.go       # Pi-hole client tests
│   └── server/
│       ├── server.go            # HTTP server setup and routing
│       └── server_test.go       # Server tests
├── pkg/
│   └── models/
│       └── records.go           # Shared data models
├── deploy/                      # Kubernetes deployment manifests
├── e2e_test.go                  # End-to-end tests
├── go.mod                       # Go module definition
├── Dockerfile                   # Docker build configuration
├── Makefile                     # Build automation
└── README.md                    # Project documentation

License

See LICENSE file for details.

Contributing

Contributions are welcome! Please open an issue or submit a pull request.