diff --git a/Makefile b/Makefile index 36c523c..1aa7f2d 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ q3: gen gen: ## Generate and embed templates @go run tools/genstatic.go public public -VERSION ?= v1.0.0 +VERSION ?= v1.0.1 IMAGE ?= docker.io/criticalstack/quake:$(VERSION) .PHONY: build diff --git a/cmd/q3/app/server/server.go b/cmd/q3/app/server/server.go index 6119ecb..2557631 100644 --- a/cmd/q3/app/server/server.go +++ b/cmd/q3/app/server/server.go @@ -3,13 +3,11 @@ package server import ( "context" "fmt" - "io/ioutil" "net/url" - "path/filepath" + "time" "github.com/pkg/errors" "github.com/spf13/cobra" - "sigs.k8s.io/yaml" quakeclient "github.com/criticalstack/quake-kube/internal/quake/client" "github.com/criticalstack/quake-kube/internal/quake/content" @@ -26,7 +24,7 @@ var opts struct { AcceptEula bool AssetsDir string ConfigFile string - Maps string + WatchInterval time.Duration } func NewCommand() *cobra.Command { @@ -63,45 +61,19 @@ func NewCommand() *cobra.Command { if err := httputil.GetUntil(opts.ContentServer, ctx.Done()); err != nil { return err } + + // TODO(chrism): only download what is in map config if err := content.CopyAssets(csurl, opts.AssetsDir); err != nil { return err } - if err := writeDefaultServerConfig(filepath.Join(opts.AssetsDir, "baseq3/server.cfg")); err != nil { - return err - } - if opts.ConfigFile != "" { - data, err := ioutil.ReadFile(opts.ConfigFile) - if err != nil { - return err - } - if err := ioutil.WriteFile(filepath.Join(opts.AssetsDir, "baseq3/server.cfg"), data, 0644); err != nil { - return err - } - } - - if err := writeDefaultMapConfig(filepath.Join(opts.AssetsDir, "baseq3/maps.cfg")); err != nil { - return err - } - if opts.Maps != "" { - data, err := ioutil.ReadFile(opts.Maps) - if err != nil { - return err - } - var maps quakeserver.Maps - if err := yaml.Unmarshal(data, &maps); err != nil { - return err - } - data, err = maps.Marshal() - if err != nil { - return err - } - if err := ioutil.WriteFile(filepath.Join(opts.AssetsDir, "baseq3/maps.cfg"), data, 0644); err != nil { - return err - } - } go func() { - if err := quakeserver.Start(ctx, opts.AssetsDir); err != nil { + s := quakeserver.Server{ + Dir: opts.AssetsDir, + WatchInterval: opts.WatchInterval, + ConfigFile: opts.ConfigFile, + } + if err := s.Start(ctx); err != nil { panic(err) } }() @@ -129,26 +101,6 @@ func NewCommand() *cobra.Command { cmd.Flags().StringVar(&opts.AssetsDir, "assets-dir", "assets", "location for game files") cmd.Flags().StringVar(&opts.ClientAddr, "client-addr", "", "client address :") cmd.Flags().StringVar(&opts.ServerAddr, "server-addr", "", "dedicated server :") - cmd.Flags().StringVar(&opts.Maps, "maps", "", "map rotation") + cmd.Flags().DurationVar(&opts.WatchInterval, "watch-interval", 15*time.Second, "dedicated server :") return cmd } - -func writeDefaultMapConfig(path string) error { - maps := quakeserver.Maps{ - {Name: "q3dm7", Type: quakeserver.FreeForAll}, - {Name: "q3dm17", Type: quakeserver.FreeForAll}, - } - data, err := maps.Marshal() - if err != nil { - return err - } - return ioutil.WriteFile(path, data, 0644) -} - -func writeDefaultServerConfig(path string) error { - data, err := quakeserver.Default().Marshal() - if err != nil { - return err - } - return ioutil.WriteFile(path, data, 0644) -} diff --git a/example.yaml b/example.yaml index dad4f41..b388d17 100644 --- a/example.yaml +++ b/example.yaml @@ -16,12 +16,10 @@ spec: - command: - q3 - server - - --config=/config/server.cfg + - --config=/config/config.yaml - --content-server=http://localhost:9090 - - --maps=/config/maps.yaml - --agree-eula - image: docker.io/criticalstack/quake:v1.0.0 - imagePullPolicy: Always + image: docker.io/criticalstack/quake:v1.0.1 name: server ports: - containerPort: 8080 @@ -39,7 +37,7 @@ spec: - q3 - content - --seed-content-url=http://content.quakejs.com - image: docker.io/criticalstack/quake:v1.0.0 + image: docker.io/criticalstack/quake:v1.0.1 name: content-server ports: - containerPort: 9090 @@ -49,7 +47,7 @@ spec: volumes: - name: quake3-server-config configMap: - name: default-quake3-server-config + name: quake3-server-config - name: quake3-content emptyDir: {} --- @@ -78,21 +76,23 @@ spec: apiVersion: v1 kind: ConfigMap metadata: - name: default-quake3-server-config + name: quake3-server-config data: - server.cfg: | - seta sv_hostname "quakekube" - seta g_log "" - seta sv_maxclients 12 - seta g_motd "Welcome to Critical Stack" - seta g_quadfactor 3 - seta timelimit 15 - seta fraglimit 25 - seta g_weaponrespawn 3 - seta g_inactivity 600 - seta g_forcerespawn 0 - seta rconpassword "changeme" - maps.yaml: | + config.yaml: | + fragLimit: 25 + timeLimit: 15m + game: + motd: "Welcome to Critical Stack" + type: FreeForAll + forceRespawn: false + inactivity: 10m + quadFactor: 3 + weaponRespawn: 3 + server: + hostname: "quakekube" + maxClients: 12 + password: "changeme" + maps: - name: q3dm7 type: FreeForAll - name: q3dm17 diff --git a/go.mod b/go.mod index 1206fe4..e1c59df 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,13 @@ go 1.14 require ( github.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292 - github.com/google/go-cmp v0.2.0 + github.com/google/go-cmp v0.3.0 github.com/gorilla/websocket v1.4.0 github.com/labstack/echo/v4 v4.1.16 github.com/pkg/errors v0.9.1 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20200627165143-92b8a710ab6c // indirect github.com/spf13/cobra v1.0.0 + k8s.io/apimachinery v0.18.6 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index d976246..106d18a 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,9 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -22,35 +25,61 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -64,6 +93,7 @@ github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZ github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -74,8 +104,19 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -105,12 +146,15 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -132,35 +176,44 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAak golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -173,11 +226,23 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/quake/server/config.go b/internal/quake/server/config.go index f43ab3b..0bcd9b6 100644 --- a/internal/quake/server/config.go +++ b/internal/quake/server/config.go @@ -9,6 +9,7 @@ import ( "time" "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type GameType int @@ -57,21 +58,24 @@ func (gt *GameType) UnmarshalText(data []byte) error { } type Config struct { - FragLimit int `name:"fraglimit"` - TimeLimit time.Duration `name:"timelimit"` + FragLimit int `name:"fraglimit"` + TimeLimit metav1.Duration `name:"timelimit"` - GameConfig - ServerConfig + GameConfig `json:"game"` + FileServerConfig `json:"fs"` + ServerConfig `json:"server"` + + Maps } type GameConfig struct { - ForceRespawn bool `name:"g_forcerespawn"` - GameType GameType `name:"g_gametype"` - Inactivity time.Duration `name:"g_inactivity"` - Log string `name:"g_log"` - MOTD string `name:"g_motd"` - QuadFactor int `name:"g_quadfactor"` - WeaponRespawn int `name:"g_weaponrespawn"` + ForceRespawn bool `name:"g_forcerespawn"` + GameType GameType `json:"type" name:"g_gametype"` + Inactivity metav1.Duration `name:"g_inactivity"` + Log string `name:"g_log"` + MOTD string `name:"g_motd"` + QuadFactor int `name:"g_quadfactor"` + WeaponRespawn int `name:"g_weaponrespawn"` } type FileServerConfig struct { @@ -117,6 +121,14 @@ func writeStruct(v reflect.Value) ([]byte, error) { return nil, err } b.Write(data) + case reflect.Slice: + switch val := fv.Interface().(type) { + case Maps: + data, _ := val.Marshal() + b.Write(data) + default: + panic(fmt.Errorf("received unknown type %T", val)) + } default: tv, ok := v.Type().Field(i).Tag.Lookup("name") if !ok { @@ -142,7 +154,7 @@ func toString(name string, v reflect.Value) string { return val case int: return strconv.Itoa(val) - case time.Duration: + case metav1.Duration: switch name { case "TimeLimit": return fmt.Sprintf("%d", int(val.Minutes())) @@ -156,6 +168,9 @@ func toString(name string, v reflect.Value) string { return "0" case GameType: return fmt.Sprintf("%d", val) + case Maps: + data, _ := val.Marshal() + return string(data) default: panic(fmt.Errorf("received unknown type %T", v.Interface())) } @@ -163,15 +178,15 @@ func toString(name string, v reflect.Value) string { func Default() *Config { return &Config{ - TimeLimit: 15 * time.Minute, FragLimit: 25, + TimeLimit: metav1.Duration{Duration: 15 * time.Minute}, GameConfig: GameConfig{ Log: "", MOTD: "Welcome to Critical Stack", QuadFactor: 3, GameType: FreeForAll, WeaponRespawn: 3, - Inactivity: 10 * time.Minute, + Inactivity: metav1.Duration{Duration: 10 * time.Minute}, ForceRespawn: false, }, ServerConfig: ServerConfig{ @@ -179,9 +194,22 @@ func Default() *Config { Hostname: "quakekube", Password: "changeme", }, + Maps: Maps{ + {Name: "q3dm7", Type: FreeForAll}, + {Name: "q3dm17", Type: FreeForAll}, + }, } } +type Map struct { + Name string `json:"name"` + Type GameType `json:"type"` + + CaptureLimit int `json:"captureLimit"` + FragLimit int `json:"fragLimit"` + TimeLimit time.Duration `json:"timeLimit"` +} + type Maps []Map func (maps Maps) Marshal() ([]byte, error) { @@ -210,12 +238,3 @@ func (maps Maps) Marshal() ([]byte, error) { b.WriteString("vstr d0") return b.Bytes(), nil } - -type Map struct { - Name string `json:"name"` - Type GameType `json:"type"` - - CaptureLimit int `json:"captureLimit"` - FragLimit int `json:"fragLimit"` - TimeLimit time.Duration `json:"timeLimit"` -} diff --git a/internal/quake/server/config_test.go b/internal/quake/server/config_test.go index 582c453..891a0e9 100644 --- a/internal/quake/server/config_test.go +++ b/internal/quake/server/config_test.go @@ -8,35 +8,22 @@ import ( "sigs.k8s.io/yaml" ) -const expectedConfig = `seta fraglimit "25" -seta timelimit "15" -seta g_forcerespawn "0" -seta g_gametype "0" -seta g_inactivity "600" -seta g_log "" -seta g_motd "Welcome to Critical Stack" -seta g_quadfactor "3" -seta g_weaponrespawn "3" -seta sv_allowDownload "0" -seta sv_hostname "quakekube" -seta sv_maxclients "12" -seta rconpassword "changeme" -` - -func TestConfigMarshal(t *testing.T) { - c := Default() - - data, err := c.Marshal() - if err != nil { - t.Fatal(err) - } - fmt.Printf("data = %s\n", data) - if diff := cmp.Diff(string(data), expectedConfig); diff != "" { - t.Fatalf(diff) - } -} - -const mapConfig = `- name: q3dm7 +const config = ` +fragLimit: 25 +timeLimit: 15m +game: + motd: "Welcome to Critical Stack" + type: FreeForAll + forceRespawn: false + inactivity: 10m + quadFactor: 3 + weaponRespawn: 3 +server: + hostname: "quakekube" + maxClients: 12 + password: "changeme" +maps: +- name: q3dm7 type: FreeForAll - name: q3dm17 type: FreeForAll @@ -52,7 +39,24 @@ const mapConfig = `- name: q3dm7 type: Tournament ` -const expectedMapConfig = `set d0 "seta g_gametype 0 ; map q3dm7 ; set nextmap vstr d1" +const expectedConfig = `seta fraglimit "25" +seta g_forcerespawn "0" +seta g_gametype "0" +seta g_log "" +seta g_motd "Welcome to Critical Stack" +seta g_quadfactor "3" +seta g_weaponrespawn "3" +seta fs_basegame "" +seta fs_basepath "" +seta fs_copyfiles "0" +seta fs_debug "0" +seta fs_game "" +seta fs_homepath "" +seta sv_allowDownload "0" +seta sv_hostname "quakekube" +seta sv_maxclients "12" +seta rconpassword "changeme" +set d0 "seta g_gametype 0 ; map q3dm7 ; set nextmap vstr d1" set d1 "seta g_gametype 0 ; map q3dm17 ; set nextmap vstr d2" set d2 "seta g_gametype 4 ; capturelimit 8 ; map q3wctf1 ; set nextmap vstr d3" set d3 "seta g_gametype 1 ; map q3tourney2 ; set nextmap vstr d4" @@ -60,21 +64,18 @@ set d4 "seta g_gametype 4 ; capturelimit 8 ; map q3wctf3 ; set nextmap vstr d5" set d5 "seta g_gametype 1 ; map ztn3tourney1 ; set nextmap vstr d0" vstr d0` -func TestMapRead(t *testing.T) { - var maps Maps - if err := yaml.Unmarshal([]byte(mapConfig), &maps); err != nil { +func TestConfigMarshal(t *testing.T) { + var cfg *Config + if err := yaml.Unmarshal([]byte(config), &cfg); err != nil { t.Fatal(err) } - - for _, m := range maps { - fmt.Printf("m = %+v\n", m) - } - data, err := maps.Marshal() + data, err := cfg.Marshal() if err != nil { t.Fatal(err) } - - if diff := cmp.Diff(string(data), expectedMapConfig); diff != "" { + fmt.Printf("%s\n", data) + if diff := cmp.Diff(string(data), expectedConfig); diff != "" { t.Fatalf(diff) } + } diff --git a/internal/quake/server/server.go b/internal/quake/server/server.go index 3a914af..cd67122 100644 --- a/internal/quake/server/server.go +++ b/internal/quake/server/server.go @@ -2,17 +2,126 @@ package server import ( "context" + "io/ioutil" + "log" "os" - "os/exec" + "path/filepath" + "time" + + "sigs.k8s.io/yaml" + + "github.com/criticalstack/quake-kube/internal/util/exec" ) -func Start(ctx context.Context, dir string) error { - cmd := exec.CommandContext(ctx, "ioq3ded", "+set", "dedicated", "1", "+exec", "server.cfg", "+exec", "maps.cfg") - cmd.Dir = dir +type Server struct { + Dir string + WatchInterval time.Duration + ConfigFile string +} + +func (s *Server) Start(ctx context.Context) error { + cmd := exec.CommandContext(ctx, "ioq3ded", "+set", "dedicated", "1", "+exec", "server.cfg") + cmd.Dir = s.Dir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + + if s.ConfigFile == "" { + cfg := Default() + data, err := cfg.Marshal() + if err != nil { + return err + } + if err := ioutil.WriteFile(filepath.Join(s.Dir, "baseq3/server.cfg"), data, 0644); err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + return cmd.Wait() + } + + if err := s.reload(); err != nil { + return err + } if err := cmd.Start(); err != nil { return err } - return cmd.Wait() + + go func() { + if err := cmd.Wait(); err != nil { + log.Println(err) + } + }() + + ch, err := s.watch(ctx) + if err != nil { + return err + } + + for { + select { + case <-ch: + if err := s.reload(); err != nil { + return err + } + if err := cmd.Restart(ctx); err != nil { + return err + } + go func() { + if err := cmd.Wait(); err != nil { + log.Println(err) + } + }() + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (s *Server) reload() error { + data, err := ioutil.ReadFile(s.ConfigFile) + if err != nil { + return err + } + var cfg *Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return err + } + data, err = cfg.Marshal() + if err != nil { + return err + } + return ioutil.WriteFile(filepath.Join(s.Dir, "baseq3/server.cfg"), data, 0644) +} + +func (s *Server) watch(ctx context.Context) (<-chan struct{}, error) { + if s.WatchInterval == 0 { + s.WatchInterval = 15 * time.Second + } + cur, err := os.Stat(s.ConfigFile) + if err != nil { + return nil, err + } + + ch := make(chan struct{}) + + go func() { + ticker := time.NewTicker(s.WatchInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if fi, err := os.Stat(s.ConfigFile); err == nil { + if fi.ModTime().After(cur.ModTime()) { + ch <- struct{}{} + } + cur = fi + } + case <-ctx.Done(): + return + } + } + }() + return ch, nil } diff --git a/internal/util/exec/exec.go b/internal/util/exec/exec.go new file mode 100644 index 0000000..c1369b2 --- /dev/null +++ b/internal/util/exec/exec.go @@ -0,0 +1,30 @@ +package exec + +import ( + "context" + "os/exec" +) + +type Cmd struct { + *exec.Cmd +} + +func (cmd *Cmd) Restart(ctx context.Context) error { + if cmd.Process != nil { + if err := cmd.Process.Kill(); err != nil { + return err + } + } + newCmd := exec.CommandContext(ctx, cmd.Args[0], cmd.Args[1:]...) + newCmd.Dir = cmd.Dir + newCmd.Env = cmd.Env + newCmd.Stdin = cmd.Stdin + newCmd.Stdout = cmd.Stdout + newCmd.Stderr = cmd.Stderr + cmd.Cmd = newCmd + return cmd.Start() +} + +func CommandContext(ctx context.Context, name string, args ...string) *Cmd { + return &Cmd{Cmd: exec.CommandContext(ctx, name, args...)} +}