feat: add support for running CoreDNS as a Windows service (#7962)

* feat: add support for running CoreDNS as a Windows service

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

* Use non-deprecated service check function.

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

* refactor: remove deprecated build tags and clean up imports in service files

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

* ci: add Windows test workflow and fix log field access in service_windows.go

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

* test: implement cross-platform file permission restriction for Windows compatibility in run_test.go

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

* refactor: remove Windows-specific icacls test logic and restrict unreadable file test to non-Windows platforms

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

* docs: add documentation for -windows-service flag in man page

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>

---------

Signed-off-by: John-Michael Mulesa <jmulesa@gmail.com>
This commit is contained in:
John-Michael Mulesa
2026-03-26 15:10:53 -04:00
committed by GitHub
parent 12131b7455
commit 1c15569168
6 changed files with 117 additions and 15 deletions

View File

@@ -28,6 +28,31 @@ jobs:
( cd core; go test -race ./... )
( cd coremain; go test -race ./... )
test-windows:
name: Test Windows
runs-on: windows-latest
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: .go-version
id: go
- name: Build
run: go build -v ./...
- name: Test
shell: bash
run: |
( cd request; go test -race ./... )
( cd core; go test -race ./... )
( cd coremain; go test -race ./... )
test-plugins:
name: Test Plugins
runs-on: ubuntu-latest

View File

@@ -39,6 +39,9 @@ Available options:
**-version**
: show version and quit.
**-windows-service**
: run as a Windows service (only available on Windows builds, default false).
## Authors
CoreDNS Authors.

View File

@@ -80,7 +80,7 @@ func Run() {
}
// Twiddle your thumbs
instance.Wait()
runService(instance)
}
// mustLogFatal wraps log.Fatal() in a way that ensures the

View File

@@ -149,21 +149,23 @@ func TestDefaultLoader(t *testing.T) {
}
}
// Create a file but make it unreadable
tmpFile := filepath.Join(tmpDir, "Corefile")
if err := os.WriteFile(tmpFile, []byte("test"), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
if err := os.Chmod(tmpFile, 0000); err != nil {
t.Fatalf("Failed to change permissions: %v", err)
}
if runtime.GOOS != "windows" {
// Create a file but make it unreadable
tmpFile := filepath.Join(tmpDir, "Corefile")
if err := os.WriteFile(tmpFile, []byte("test"), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
if err := os.Chmod(tmpFile, 0000); err != nil {
t.Fatalf("Failed to change permissions: %v", err)
}
input, err = defaultLoader("dns")
if err == nil {
t.Error("Expected error for unreadable Corefile but got none")
}
if input != nil {
t.Error("Expected nil input for unreadable Corefile")
input, err = defaultLoader("dns")
if err == nil {
t.Error("Expected error for unreadable Corefile but got none")
}
if input != nil {
t.Error("Expected nil input for unreadable Corefile")
}
}
}

View File

@@ -0,0 +1,9 @@
//go:build !windows
package coremain
import "github.com/coredns/caddy"
func runService(instance *caddy.Instance) {
instance.Wait()
}

View File

@@ -0,0 +1,63 @@
//go:build windows
package coremain
import (
"flag"
"log"
"github.com/coredns/caddy"
"golang.org/x/sys/windows/svc"
)
var windowsService bool
func init() {
flag.BoolVar(&windowsService, "windows-service", false, "Run as a Windows service")
}
type corednsService struct {
instance *caddy.Instance
}
func (s *corednsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
changes <- svc.Status{State: svc.StartPending}
changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
for req := range r {
switch req.Cmd {
case svc.Interrogate:
changes <- req.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
if s.instance != nil {
s.instance.Stop()
}
return false, 0
default:
log.Printf("unexpected control request #%d", req.Cmd)
}
}
return false, 0
}
func runService(instance *caddy.Instance) {
if windowsService {
isService, err := svc.IsWindowsService()
if err != nil {
log.Fatalf("failed to determine if running as service: %v", err)
}
if isService {
err = svc.Run("CoreDNS", &corednsService{instance: instance})
if err != nil {
log.Fatalf("failed to start service: %v", err)
}
return
} else {
log.Printf("Windows service flag provided, but not running as a Windows service.")
}
}
instance.Wait()
}