diff --git a/.github/workflows/go.test.yml b/.github/workflows/go.test.yml index 76a640e6c..2a76ff2fc 100644 --- a/.github/workflows/go.test.yml +++ b/.github/workflows/go.test.yml @@ -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 diff --git a/coredns.1.md b/coredns.1.md index 64daaca48..641877276 100644 --- a/coredns.1.md +++ b/coredns.1.md @@ -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. diff --git a/coremain/run.go b/coremain/run.go index ce30770ec..93050c07f 100644 --- a/coremain/run.go +++ b/coremain/run.go @@ -80,7 +80,7 @@ func Run() { } // Twiddle your thumbs - instance.Wait() + runService(instance) } // mustLogFatal wraps log.Fatal() in a way that ensures the diff --git a/coremain/run_test.go b/coremain/run_test.go index 84ca7dfa5..4df352d2d 100644 --- a/coremain/run_test.go +++ b/coremain/run_test.go @@ -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") + } } } diff --git a/coremain/service_other.go b/coremain/service_other.go new file mode 100644 index 000000000..023c7e701 --- /dev/null +++ b/coremain/service_other.go @@ -0,0 +1,9 @@ +//go:build !windows + +package coremain + +import "github.com/coredns/caddy" + +func runService(instance *caddy.Instance) { + instance.Wait() +} diff --git a/coremain/service_windows.go b/coremain/service_windows.go new file mode 100644 index 000000000..2a4450caa --- /dev/null +++ b/coremain/service_windows.go @@ -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() +}