Remove Windows support
Unfortunately, I don't have the expertise or bandwidth to maintain the Windows support in Litestream. I'm open to re-adding support in the future but right now it is hindering development and is not well-tested or well-used.
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/benbjohnson/litestream"
|
"github.com/benbjohnson/litestream"
|
||||||
@@ -67,13 +68,6 @@ func NewMain(stdin io.Reader, stdout, stderr io.Writer) *Main {
|
|||||||
|
|
||||||
// Run executes the program.
|
// Run executes the program.
|
||||||
func (m *Main) Run(ctx context.Context, args []string) (err error) {
|
func (m *Main) Run(ctx context.Context, args []string) (err error) {
|
||||||
// Execute replication command if running as a Windows service.
|
|
||||||
if isService, err := isWindowsService(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if isService {
|
|
||||||
return runWindowsService(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy "LITESTEAM" environment credentials.
|
// Copy "LITESTEAM" environment credentials.
|
||||||
applyLitestreamEnv()
|
applyLitestreamEnv()
|
||||||
|
|
||||||
@@ -98,7 +92,7 @@ func (m *Main) Run(ctx context.Context, args []string) (err error) {
|
|||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
signalCh := make(chan os.Signal, 1)
|
signalCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(signalCh, notifySignals...)
|
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
if err := c.Run(ctx); err != nil {
|
if err := c.Run(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -685,7 +679,7 @@ func DefaultConfigPath() string {
|
|||||||
if v := os.Getenv("LITESTREAM_CONFIG"); v != "" {
|
if v := os.Getenv("LITESTREAM_CONFIG"); v != "" {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
return defaultConfigPath
|
return "/etc/litestream.yml"
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerConfigFlag(fs *flag.FlagSet, configPath *string, noExpandEnv *bool) {
|
func registerConfigFlag(fs *flag.FlagSet, configPath *string, noExpandEnv *bool) {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultConfigPath = "/etc/litestream.yml"
|
|
||||||
|
|
||||||
func isWindowsService() (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runWindowsService(ctx context.Context) error {
|
|
||||||
panic("cannot run windows service as unix process")
|
|
||||||
}
|
|
||||||
|
|
||||||
var notifySignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.org/x/sys/windows/svc"
|
|
||||||
"golang.org/x/sys/windows/svc/eventlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultConfigPath = `C:\Litestream\litestream.yml`
|
|
||||||
|
|
||||||
// serviceName is the Windows Service name.
|
|
||||||
const serviceName = "Litestream"
|
|
||||||
|
|
||||||
// isWindowsService returns true if currently executing within a Windows service.
|
|
||||||
func isWindowsService() (bool, error) {
|
|
||||||
return svc.IsWindowsService()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runWindowsService(ctx context.Context) error {
|
|
||||||
// Attempt to install new log service. This will fail if already installed.
|
|
||||||
// We don't log the error because we don't have anywhere to log until we open the log.
|
|
||||||
_ = eventlog.InstallAsEventCreate(serviceName, eventlog.Error|eventlog.Warning|eventlog.Info)
|
|
||||||
|
|
||||||
elog, err := eventlog.Open(serviceName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer elog.Close()
|
|
||||||
|
|
||||||
// Set eventlog as log writer while running.
|
|
||||||
log.SetOutput((*eventlogWriter)(elog))
|
|
||||||
defer log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
log.Print("Litestream service starting")
|
|
||||||
|
|
||||||
if err := svc.Run(serviceName, &windowsService{ctx: ctx}); err != nil {
|
|
||||||
return errExit
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Print("Litestream service stopped")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// windowsService is an interface adapter for svc.Handler.
|
|
||||||
type windowsService struct {
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, statusCh chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Notify Windows that the service is starting up.
|
|
||||||
statusCh <- svc.Status{State: svc.StartPending}
|
|
||||||
|
|
||||||
// Instantiate replication command and load configuration.
|
|
||||||
c := NewReplicateCommand()
|
|
||||||
if c.Config, err = ReadConfigFile(DefaultConfigPath(), true); err != nil {
|
|
||||||
log.Printf("cannot load configuration: %s", err)
|
|
||||||
return true, 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute replication command.
|
|
||||||
if err := c.Run(s.ctx); err != nil {
|
|
||||||
log.Printf("cannot replicate: %s", err)
|
|
||||||
statusCh <- svc.Status{State: svc.StopPending}
|
|
||||||
return true, 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify Windows that the service is now running.
|
|
||||||
statusCh <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case req := <-r:
|
|
||||||
switch req.Cmd {
|
|
||||||
case svc.Stop:
|
|
||||||
c.Close()
|
|
||||||
statusCh <- svc.Status{State: svc.StopPending}
|
|
||||||
return false, windows.NO_ERROR
|
|
||||||
case svc.Interrogate:
|
|
||||||
statusCh <- req.CurrentStatus
|
|
||||||
default:
|
|
||||||
log.Printf("Litestream service received unexpected change request cmd: %d", req.Cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure implementation implements io.Writer interface.
|
|
||||||
var _ io.Writer = (*eventlogWriter)(nil)
|
|
||||||
|
|
||||||
// eventlogWriter is an adapter for using eventlog.Log as an io.Writer.
|
|
||||||
type eventlogWriter eventlog.Log
|
|
||||||
|
|
||||||
func (w *eventlogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
elog := (*eventlog.Log)(w)
|
|
||||||
return 0, elog.Info(1, string(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
var notifySignals = []os.Signal{os.Interrupt}
|
|
||||||
1
go.mod
1
go.mod
@@ -13,7 +13,6 @@ require (
|
|||||||
github.com/prometheus/client_golang v1.12.1
|
github.com/prometheus/client_golang v1.12.1
|
||||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
|
||||||
google.golang.org/api v0.66.0
|
google.golang.org/api v0.66.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func MkdirAll(path string, mode os.FileMode, uid, gid int) error {
|
|||||||
|
|
||||||
if j > 1 {
|
if j > 1 {
|
||||||
// Create parent.
|
// Create parent.
|
||||||
err = MkdirAll(fixRootDirectory(path[:j-1]), mode, uid, gid)
|
err = MkdirAll(path[:j-1], mode, uid, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -154,6 +154,15 @@ func MkdirAll(path string, mode os.FileMode, uid, gid int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fileinfo returns syscall fields from a FileInfo object.
|
||||||
|
func Fileinfo(fi os.FileInfo) (uid, gid int) {
|
||||||
|
if fi == nil {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
stat := fi.Sys().(*syscall.Stat_t)
|
||||||
|
return int(stat.Uid), int(stat.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
// ParseSnapshotPath parses the index from a snapshot filename. Used by path-based replicas.
|
// ParseSnapshotPath parses the index from a snapshot filename. Used by path-based replicas.
|
||||||
func ParseSnapshotPath(s string) (index int, err error) {
|
func ParseSnapshotPath(s string) (index int, err error) {
|
||||||
a := snapshotPathRegex.FindStringSubmatch(s)
|
a := snapshotPathRegex.FindStringSubmatch(s)
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fileinfo returns syscall fields from a FileInfo object.
|
|
||||||
func Fileinfo(fi os.FileInfo) (uid, gid int) {
|
|
||||||
if fi == nil {
|
|
||||||
return -1, -1
|
|
||||||
}
|
|
||||||
stat := fi.Sys().(*syscall.Stat_t)
|
|
||||||
return int(stat.Uid), int(stat.Gid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixRootDirectory(p string) string {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fileinfo returns syscall fields from a FileInfo object.
|
|
||||||
func Fileinfo(fi os.FileInfo) (uid, gid int) {
|
|
||||||
return -1, -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixRootDirectory is copied from the standard library for use with mkdirAll()
|
|
||||||
func fixRootDirectory(p string) string {
|
|
||||||
if len(p) == len(`\\?\c:`) {
|
|
||||||
if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' {
|
|
||||||
return p + `\`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user