update caching and server creation
This commit is contained in:
35
go.mod
35
go.mod
@@ -2,23 +2,28 @@ module acc-server-manager
|
||||
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/gofiber/swagger v1.1.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c
|
||||
github.com/swaggo/swag v1.16.3
|
||||
go.uber.org/dig v1.17.1
|
||||
golang.org/x/text v0.16.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/fourcorelabs/wintoken v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.52.5 // indirect
|
||||
github.com/gofiber/swagger v1.1.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@@ -26,26 +31,12 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/skillian/getfiletime v0.0.0-20170819221534-37b64ac4de15 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||
github.com/swaggo/swag v1.16.3 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
go.uber.org/dig v1.17.1 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/sqlite v1.5.6 // indirect
|
||||
gorm.io/gorm v1.25.11 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
87
go.sum
87
go.sum
@@ -1,43 +1,21 @@
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28=
|
||||
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fourcorelabs/wintoken v1.0.0 h1:dskUYLAFHNy1cbS5MXsNFXauQzxieTrZlffQZ0Yu19I=
|
||||
github.com/fourcorelabs/wintoken v1.0.0/go.mod h1:jKyXHt079W09KwEMbUC9g+R2KDs5kVvSKPUiF5p0ejs=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ=
|
||||
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@@ -50,14 +28,10 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@@ -69,77 +43,46 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A=
|
||||
github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skillian/getfiletime v0.0.0-20170819221534-37b64ac4de15 h1:szp/LEXP+cCpLNKJ1NgC3Vnloo4TYmHv8lrzxng+cXI=
|
||||
github.com/skillian/getfiletime v0.0.0-20170819221534-37b64ac4de15/go.mod h1:QHVxyK6RvbImkJZCSS48T72l5JVj/rA0FerqWGSSvlQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
|
||||
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
|
||||
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
||||
@@ -20,14 +20,14 @@ type ServerController struct {
|
||||
// *Fiber.RouterGroup: Fiber Router Group
|
||||
// Returns:
|
||||
// *ServerController: Controller for "Server" interactions
|
||||
func NewServerController(as *service.ServerService, routeGroups *common.RouteGroups) *ServerController {
|
||||
func NewServerController(as *service.ServerService, routeGroups *common.RouteGroups,) *ServerController {
|
||||
ac := &ServerController{
|
||||
service: as,
|
||||
}
|
||||
|
||||
routeGroups.Server.Get("/", ac.getAll)
|
||||
routeGroups.Server.Get("/:id", ac.getById)
|
||||
|
||||
routeGroups.Server.Post("/", ac.createServer)
|
||||
return ac
|
||||
}
|
||||
|
||||
@@ -67,3 +67,26 @@ func (ac *ServerController) getById(c *fiber.Ctx) error {
|
||||
}
|
||||
return c.JSON(ServerModel)
|
||||
}
|
||||
|
||||
// createServer creates a new server
|
||||
//
|
||||
// @Summary Create a new server
|
||||
// @Description Create a new server
|
||||
// @Tags Server
|
||||
// @Success 200 {array} string
|
||||
// @Router /v1/server [post]
|
||||
func (ac *ServerController) createServer(c *fiber.Ctx) error {
|
||||
server := new(model.Server)
|
||||
if err := c.BodyParser(server); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
ac.service.GenerateServerPath(server)
|
||||
if err := ac.service.CreateServer(c, server); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return c.JSON(server)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -89,6 +90,7 @@ type LookupCache struct {
|
||||
|
||||
// NewLookupCache creates a new lookup cache
|
||||
func NewLookupCache() *LookupCache {
|
||||
logging.Debug("Initializing new LookupCache")
|
||||
return &LookupCache{
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
@@ -100,6 +102,11 @@ func (c *LookupCache) Get(key string) (interface{}, bool) {
|
||||
defer c.RUnlock()
|
||||
|
||||
value, exists := c.data[key]
|
||||
if exists {
|
||||
logging.Debug("Cache HIT for key: %s", key)
|
||||
} else {
|
||||
logging.Debug("Cache MISS for key: %s", key)
|
||||
}
|
||||
return value, exists
|
||||
}
|
||||
|
||||
@@ -109,6 +116,7 @@ func (c *LookupCache) Set(key string, value interface{}) {
|
||||
defer c.Unlock()
|
||||
|
||||
c.data[key] = value
|
||||
logging.Debug("Cache SET for key: %s", key)
|
||||
}
|
||||
|
||||
// Clear removes all entries from the cache
|
||||
@@ -117,6 +125,7 @@ func (c *LookupCache) Clear() {
|
||||
defer c.Unlock()
|
||||
|
||||
c.data = make(map[string]interface{})
|
||||
logging.Debug("Cache CLEARED")
|
||||
}
|
||||
|
||||
// ConfigEntry represents a cached configuration entry with its update time
|
||||
@@ -129,8 +138,12 @@ type ConfigEntry[T any] struct {
|
||||
func getConfigFromCache[T any](cache map[string]*ConfigEntry[T], serverID string, expirationTime time.Duration) (*T, bool) {
|
||||
if entry, ok := cache[serverID]; ok {
|
||||
if time.Since(entry.UpdatedAt) < expirationTime {
|
||||
logging.Debug("Config cache HIT for server ID: %s", serverID)
|
||||
return &entry.Data, true
|
||||
}
|
||||
logging.Debug("Config cache EXPIRED for server ID: %s", serverID)
|
||||
} else {
|
||||
logging.Debug("Config cache MISS for server ID: %s", serverID)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -141,6 +154,7 @@ func updateConfigInCache[T any](cache map[string]*ConfigEntry[T], serverID strin
|
||||
Data: data,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
logging.Debug("Config cache SET for server ID: %s", serverID)
|
||||
}
|
||||
|
||||
// ServerConfigCache manages cached server configurations
|
||||
@@ -156,6 +170,7 @@ type ServerConfigCache struct {
|
||||
|
||||
// NewServerConfigCache creates a new server configuration cache
|
||||
func NewServerConfigCache(config CacheConfig) *ServerConfigCache {
|
||||
logging.Debug("Initializing new ServerConfigCache with expiration time: %v, throttle time: %v", config.ExpirationTime, config.ThrottleTime)
|
||||
return &ServerConfigCache{
|
||||
configuration: make(map[string]*ConfigEntry[Configuration]),
|
||||
assistRules: make(map[string]*ConfigEntry[AssistRules]),
|
||||
@@ -170,6 +185,7 @@ func NewServerConfigCache(config CacheConfig) *ServerConfigCache {
|
||||
func (c *ServerConfigCache) GetConfiguration(serverID string) (*Configuration, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
logging.Debug("Attempting to get configuration from cache for server ID: %s", serverID)
|
||||
return getConfigFromCache(c.configuration, serverID, c.config.ExpirationTime)
|
||||
}
|
||||
|
||||
@@ -177,6 +193,7 @@ func (c *ServerConfigCache) GetConfiguration(serverID string) (*Configuration, b
|
||||
func (c *ServerConfigCache) GetAssistRules(serverID string) (*AssistRules, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
logging.Debug("Attempting to get assist rules from cache for server ID: %s", serverID)
|
||||
return getConfigFromCache(c.assistRules, serverID, c.config.ExpirationTime)
|
||||
}
|
||||
|
||||
@@ -184,6 +201,7 @@ func (c *ServerConfigCache) GetAssistRules(serverID string) (*AssistRules, bool)
|
||||
func (c *ServerConfigCache) GetEvent(serverID string) (*EventConfig, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
logging.Debug("Attempting to get event config from cache for server ID: %s", serverID)
|
||||
return getConfigFromCache(c.event, serverID, c.config.ExpirationTime)
|
||||
}
|
||||
|
||||
@@ -191,6 +209,7 @@ func (c *ServerConfigCache) GetEvent(serverID string) (*EventConfig, bool) {
|
||||
func (c *ServerConfigCache) GetEventRules(serverID string) (*EventRules, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
logging.Debug("Attempting to get event rules from cache for server ID: %s", serverID)
|
||||
return getConfigFromCache(c.eventRules, serverID, c.config.ExpirationTime)
|
||||
}
|
||||
|
||||
@@ -198,6 +217,7 @@ func (c *ServerConfigCache) GetEventRules(serverID string) (*EventRules, bool) {
|
||||
func (c *ServerConfigCache) GetSettings(serverID string) (*ServerSettings, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
logging.Debug("Attempting to get settings from cache for server ID: %s", serverID)
|
||||
return getConfigFromCache(c.settings, serverID, c.config.ExpirationTime)
|
||||
}
|
||||
|
||||
@@ -205,6 +225,7 @@ func (c *ServerConfigCache) GetSettings(serverID string) (*ServerSettings, bool)
|
||||
func (c *ServerConfigCache) UpdateConfiguration(serverID string, config Configuration) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
logging.Debug("Updating configuration cache for server ID: %s", serverID)
|
||||
updateConfigInCache(c.configuration, serverID, config)
|
||||
}
|
||||
|
||||
@@ -212,6 +233,7 @@ func (c *ServerConfigCache) UpdateConfiguration(serverID string, config Configur
|
||||
func (c *ServerConfigCache) UpdateAssistRules(serverID string, rules AssistRules) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
logging.Debug("Updating assist rules cache for server ID: %s", serverID)
|
||||
updateConfigInCache(c.assistRules, serverID, rules)
|
||||
}
|
||||
|
||||
@@ -219,6 +241,7 @@ func (c *ServerConfigCache) UpdateAssistRules(serverID string, rules AssistRules
|
||||
func (c *ServerConfigCache) UpdateEvent(serverID string, event EventConfig) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
logging.Debug("Updating event config cache for server ID: %s", serverID)
|
||||
updateConfigInCache(c.event, serverID, event)
|
||||
}
|
||||
|
||||
@@ -226,6 +249,7 @@ func (c *ServerConfigCache) UpdateEvent(serverID string, event EventConfig) {
|
||||
func (c *ServerConfigCache) UpdateEventRules(serverID string, rules EventRules) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
logging.Debug("Updating event rules cache for server ID: %s", serverID)
|
||||
updateConfigInCache(c.eventRules, serverID, rules)
|
||||
}
|
||||
|
||||
@@ -233,6 +257,7 @@ func (c *ServerConfigCache) UpdateEventRules(serverID string, rules EventRules)
|
||||
func (c *ServerConfigCache) UpdateSettings(serverID string, settings ServerSettings) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
logging.Debug("Updating settings cache for server ID: %s", serverID)
|
||||
updateConfigInCache(c.settings, serverID, settings)
|
||||
}
|
||||
|
||||
@@ -241,6 +266,7 @@ func (c *ServerConfigCache) InvalidateServerCache(serverID string) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
logging.Debug("Invalidating all cache entries for server ID: %s", serverID)
|
||||
delete(c.configuration, serverID)
|
||||
delete(c.assistRules, serverID)
|
||||
delete(c.event, serverID)
|
||||
@@ -253,6 +279,7 @@ func (c *ServerConfigCache) Clear() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
logging.Debug("Clearing all server config cache entries")
|
||||
c.configuration = make(map[string]*ConfigEntry[Configuration])
|
||||
c.assistRules = make(map[string]*ConfigEntry[AssistRules])
|
||||
c.event = make(map[string]*ConfigEntry[EventConfig])
|
||||
|
||||
@@ -3,6 +3,7 @@ package model
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -106,6 +107,26 @@ type Configuration struct {
|
||||
ConfigVersion IntString `json:"configVersion"`
|
||||
}
|
||||
|
||||
type SystemConfig struct {
|
||||
ID uint `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
DefaultValue string `json:"defaultValue"`
|
||||
Description string `json:"description"`
|
||||
DateModified string `json:"dateModified"`
|
||||
}
|
||||
|
||||
// Known configuration keys
|
||||
const (
|
||||
ConfigKeySteamCMDPath = "steamcmd_path"
|
||||
ConfigKeyNSSMPath = "nssm_path"
|
||||
)
|
||||
|
||||
// Cache keys
|
||||
const (
|
||||
CacheKeySystemConfig = "system_config_%s" // Format with config key
|
||||
)
|
||||
|
||||
func (i *IntString) UnmarshalJSON(b []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(b, &str); err == nil {
|
||||
@@ -137,3 +158,35 @@ func (i IntString) ToString() string {
|
||||
func (i IntString) ToInt() (int) {
|
||||
return int(i)
|
||||
}
|
||||
|
||||
func (c *SystemConfig) Validate() error {
|
||||
if c.Key == "" {
|
||||
return fmt.Errorf("key is required")
|
||||
}
|
||||
|
||||
// Validate paths exist for certain config keys
|
||||
switch c.Key {
|
||||
case ConfigKeySteamCMDPath, ConfigKeyNSSMPath:
|
||||
if c.Value == "" {
|
||||
if c.DefaultValue == "" {
|
||||
return fmt.Errorf("value or default value is required for path configuration")
|
||||
}
|
||||
// Use default value if value is empty
|
||||
c.Value = c.DefaultValue
|
||||
}
|
||||
|
||||
// Check if path exists
|
||||
if _, err := os.Stat(c.Value); os.IsNotExist(err) {
|
||||
return fmt.Errorf("path does not exist: %s", c.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SystemConfig) GetEffectiveValue() string {
|
||||
if c.Value != "" {
|
||||
return c.Value
|
||||
}
|
||||
return c.DefaultValue
|
||||
}
|
||||
@@ -22,10 +22,11 @@ type Server struct {
|
||||
Status ServiceStatus `json:"status" gorm:"-"`
|
||||
IP string `gorm:"not null" json:"-"`
|
||||
Port int `gorm:"not null" json:"-"`
|
||||
ConfigPath string `gorm:"not null" json:"configPath"` // e.g. "/acc/servers/server1/"
|
||||
Path string `gorm:"not null" json:"path"` // e.g. "/acc/servers/server1/"
|
||||
ServiceName string `gorm:"not null" json:"serviceName"` // Windows service name
|
||||
State ServerState `gorm:"-" json:"state"`
|
||||
DateCreated time.Time `json:"dateCreated"`
|
||||
FromSteamCMD bool `gorm:"not null; default:true" json:"-"`
|
||||
}
|
||||
|
||||
type PlayerState struct {
|
||||
@@ -91,8 +92,8 @@ func (s *Server) BeforeCreate(tx *gorm.DB) error {
|
||||
if s.ServiceName == "" {
|
||||
s.ServiceName = s.GenerateServiceName()
|
||||
}
|
||||
if s.ConfigPath == "" {
|
||||
s.ConfigPath = s.GenerateConfigPath()
|
||||
if s.Path == "" {
|
||||
s.Path = s.GenerateServerPath(BaseServerPath)
|
||||
}
|
||||
|
||||
// Set creation date if not set
|
||||
@@ -113,13 +114,34 @@ func (s *Server) GenerateServiceName() string {
|
||||
return fmt.Sprintf("%s-%d", ServiceNamePrefix, time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// GenerateConfigPath creates the config path based on the service name
|
||||
func (s *Server) GenerateConfigPath() string {
|
||||
// GenerateServerPath creates the config path based on the service name
|
||||
func (s *Server) GenerateServerPath(steamCMDPath string) string {
|
||||
// Ensure service name is set
|
||||
if s.ServiceName == "" {
|
||||
s.ServiceName = s.GenerateServiceName()
|
||||
}
|
||||
return filepath.Join(BaseServerPath, s.ServiceName)
|
||||
if (steamCMDPath == "") {
|
||||
steamCMDPath = BaseServerPath
|
||||
}
|
||||
return filepath.Join(steamCMDPath, "servers", s.ServiceName)
|
||||
}
|
||||
|
||||
func (s *Server) GetServerPath() string {
|
||||
if (!s.FromSteamCMD) {
|
||||
return s.Path
|
||||
}
|
||||
return filepath.Join(s.Path, "server")
|
||||
}
|
||||
|
||||
func (s *Server) GetConfigPath() string {
|
||||
return filepath.Join(s.GetServerPath(), "cfg")
|
||||
}
|
||||
|
||||
func (s *Server) GetLogPath() string {
|
||||
if (!s.FromSteamCMD) {
|
||||
return s.Path
|
||||
}
|
||||
return filepath.Join(s.GetServerPath(), "log")
|
||||
}
|
||||
|
||||
func (s *Server) Validate() error {
|
||||
|
||||
@@ -16,4 +16,5 @@ func InitializeRepositories(c *dig.Container) {
|
||||
c.Provide(NewConfigRepository)
|
||||
c.Provide(NewLookupRepository)
|
||||
c.Provide(NewSteamCredentialsRepository)
|
||||
c.Provide(NewSystemConfigRepository)
|
||||
}
|
||||
|
||||
58
local/repository/system_config_repository.go
Normal file
58
local/repository/system_config_repository.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SystemConfigRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSystemConfigRepository(db *gorm.DB) *SystemConfigRepository {
|
||||
return &SystemConfigRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SystemConfigRepository) Initialize(ctx context.Context) error {
|
||||
// Migration and seeding are now handled in the db package
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SystemConfigRepository) Get(ctx context.Context, key string) (*model.SystemConfig, error) {
|
||||
var config model.SystemConfig
|
||||
err := r.db.Where("key = ?", key).First(&config).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (r *SystemConfigRepository) GetAll(ctx context.Context) (*[]model.SystemConfig, error) {
|
||||
var configs []model.SystemConfig
|
||||
if err := r.db.Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &configs, nil
|
||||
}
|
||||
|
||||
func (r *SystemConfigRepository) Update(ctx context.Context, config *model.SystemConfig) error {
|
||||
if err := config.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.DateModified = time.Now().UTC().Format(time.RFC3339)
|
||||
return r.db.Model(&model.SystemConfig{}).
|
||||
Where("key = ?", config.Key).
|
||||
Updates(map[string]interface{}{
|
||||
"value": config.Value,
|
||||
"date_modified": config.DateModified,
|
||||
}).Error
|
||||
}
|
||||
@@ -19,7 +19,8 @@ type ApiService struct {
|
||||
}
|
||||
|
||||
func NewApiService(repository *repository.ApiRepository,
|
||||
serverRepository *repository.ServerRepository) *ApiService {
|
||||
serverRepository *repository.ServerRepository,
|
||||
systemConfigService *SystemConfigService) *ApiService {
|
||||
return &ApiService{
|
||||
repository: repository,
|
||||
serverRepository: serverRepository,
|
||||
@@ -28,7 +29,7 @@ func NewApiService(repository *repository.ApiRepository,
|
||||
ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks
|
||||
DefaultStatus: model.StatusRunning, // Default to running if throttled
|
||||
}),
|
||||
windowsService: NewWindowsService(),
|
||||
windowsService: NewWindowsService(systemConfigService),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +121,11 @@ func (as *ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
|
||||
}
|
||||
|
||||
func (as *ApiService) StatusServer(serviceName string) (string, error) {
|
||||
return as.windowsService.Status(serviceName)
|
||||
return as.windowsService.Status(context.Background(), serviceName)
|
||||
}
|
||||
|
||||
func (as *ApiService) StartServer(serviceName string) (string, error) {
|
||||
status, err := as.windowsService.Start(serviceName)
|
||||
status, err := as.windowsService.Start(context.Background(), serviceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func (as *ApiService) StartServer(serviceName string) (string, error) {
|
||||
}
|
||||
|
||||
func (as *ApiService) StopServer(serviceName string) (string, error) {
|
||||
status, err := as.windowsService.Stop(serviceName)
|
||||
status, err := as.windowsService.Stop(context.Background(), serviceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -153,7 +154,7 @@ func (as *ApiService) StopServer(serviceName string) (string, error) {
|
||||
}
|
||||
|
||||
func (as *ApiService) RestartServer(serviceName string) (string, error) {
|
||||
status, err := as.windowsService.Restart(serviceName)
|
||||
status, err := as.windowsService.Restart(context.Background(), serviceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"acc-server-manager/local/utl/common"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -70,6 +71,7 @@ type ConfigService struct {
|
||||
}
|
||||
|
||||
func NewConfigService(repository *repository.ConfigRepository, serverRepository *repository.ServerRepository) *ConfigService {
|
||||
logging.Debug("Initializing ConfigService with 5m expiration and 1s throttle")
|
||||
return &ConfigService{
|
||||
repository: repository,
|
||||
serverRepository: serverRepository,
|
||||
@@ -97,14 +99,19 @@ func (as *ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface
|
||||
configFile := ctx.Params("file")
|
||||
override := ctx.QueryBool("override", false)
|
||||
|
||||
server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID)
|
||||
return as.updateConfigInternal(ctx.UserContext(), serverID, configFile, body, override)
|
||||
}
|
||||
|
||||
// updateConfigInternal handles the actual config update logic without Fiber dependencies
|
||||
func (as *ConfigService) updateConfigInternal(ctx context.Context, serverID int, configFile string, body *map[string]interface{}, override bool) (*model.Config, error) {
|
||||
server, err := as.serverRepository.GetByID(ctx, serverID)
|
||||
if err != nil {
|
||||
logging.Error("Server not found")
|
||||
return nil, fiber.NewError(404, "Server not found")
|
||||
return nil, fmt.Errorf("server not found")
|
||||
}
|
||||
|
||||
// Read existing config
|
||||
configPath := filepath.Join(server.ConfigPath, "\\server\\cfg", configFile)
|
||||
configPath := filepath.Join(server.GetConfigPath(), configFile)
|
||||
oldData, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -150,8 +157,6 @@ func (as *ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface
|
||||
return nil, err
|
||||
}
|
||||
|
||||
context := ctx.UserContext()
|
||||
|
||||
if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,7 +167,7 @@ func (as *ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface
|
||||
as.serverService.StartAccServerRuntime(server)
|
||||
|
||||
// Log change
|
||||
return as.repository.UpdateConfig(context, &model.Config{
|
||||
return as.repository.UpdateConfig(ctx, &model.Config{
|
||||
ServerID: uint(serverID),
|
||||
ConfigFile: configFile,
|
||||
OldConfig: string(oldDataUTF8),
|
||||
@@ -183,6 +188,8 @@ func (as *ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
|
||||
configFile := ctx.Params("file")
|
||||
serverIDStr := strconv.Itoa(serverID)
|
||||
|
||||
logging.Debug("Getting config for server ID: %d, file: %s", serverID, configFile)
|
||||
|
||||
server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID)
|
||||
if err != nil {
|
||||
logging.Error("Server not found")
|
||||
@@ -193,56 +200,77 @@ func (as *ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
|
||||
switch configFile {
|
||||
case ConfigurationJson:
|
||||
if cached, ok := as.configCache.GetConfiguration(serverIDStr); ok {
|
||||
logging.Debug("Returning cached configuration for server ID: %s", serverIDStr)
|
||||
return cached, nil
|
||||
}
|
||||
case AssistRulesJson:
|
||||
if cached, ok := as.configCache.GetAssistRules(serverIDStr); ok {
|
||||
logging.Debug("Returning cached assist rules for server ID: %s", serverIDStr)
|
||||
return cached, nil
|
||||
}
|
||||
case EventJson:
|
||||
if cached, ok := as.configCache.GetEvent(serverIDStr); ok {
|
||||
logging.Debug("Returning cached event config for server ID: %s", serverIDStr)
|
||||
return cached, nil
|
||||
}
|
||||
case EventRulesJson:
|
||||
if cached, ok := as.configCache.GetEventRules(serverIDStr); ok {
|
||||
logging.Debug("Returning cached event rules for server ID: %s", serverIDStr)
|
||||
return cached, nil
|
||||
}
|
||||
case SettingsJson:
|
||||
if cached, ok := as.configCache.GetSettings(serverIDStr); ok {
|
||||
logging.Debug("Returning cached settings for server ID: %s", serverIDStr)
|
||||
return cached, nil
|
||||
}
|
||||
}
|
||||
|
||||
decoded, err := DecodeFileName(configFile)(server.ConfigPath)
|
||||
logging.Debug("Cache miss for server ID: %s, file: %s - loading from disk", serverIDStr, configFile)
|
||||
|
||||
// Not in cache, load from disk
|
||||
configPath := filepath.Join(server.GetConfigPath(), configFile)
|
||||
decoder := DecodeFileName(configFile)
|
||||
if decoder == nil {
|
||||
return nil, errors.New("invalid config file")
|
||||
}
|
||||
|
||||
config, err := decoder(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
logging.Debug("Config file not found, creating default for server ID: %s, file: %s", serverIDStr, configFile)
|
||||
// Return empty config if file doesn't exist
|
||||
switch configFile {
|
||||
case ConfigurationJson:
|
||||
return &model.Configuration{}, nil
|
||||
case AssistRulesJson:
|
||||
return &model.AssistRules{}, nil
|
||||
case EventJson:
|
||||
return &model.EventConfig{}, nil
|
||||
case EventRulesJson:
|
||||
return &model.EventRules{}, nil
|
||||
case SettingsJson:
|
||||
return &model.ServerSettings{}, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache the result based on config file type
|
||||
// Cache the loaded config
|
||||
switch configFile {
|
||||
case ConfigurationJson:
|
||||
if config, ok := decoded.(model.Configuration); ok {
|
||||
as.configCache.UpdateConfiguration(serverIDStr, config)
|
||||
}
|
||||
as.configCache.UpdateConfiguration(serverIDStr, *config.(*model.Configuration))
|
||||
case AssistRulesJson:
|
||||
if rules, ok := decoded.(model.AssistRules); ok {
|
||||
as.configCache.UpdateAssistRules(serverIDStr, rules)
|
||||
}
|
||||
as.configCache.UpdateAssistRules(serverIDStr, *config.(*model.AssistRules))
|
||||
case EventJson:
|
||||
if event, ok := decoded.(model.EventConfig); ok {
|
||||
as.configCache.UpdateEvent(serverIDStr, event)
|
||||
}
|
||||
as.configCache.UpdateEvent(serverIDStr, *config.(*model.EventConfig))
|
||||
case EventRulesJson:
|
||||
if rules, ok := decoded.(model.EventRules); ok {
|
||||
as.configCache.UpdateEventRules(serverIDStr, rules)
|
||||
}
|
||||
as.configCache.UpdateEventRules(serverIDStr, *config.(*model.EventRules))
|
||||
case SettingsJson:
|
||||
if settings, ok := decoded.(model.ServerSettings); ok {
|
||||
as.configCache.UpdateSettings(serverIDStr, settings)
|
||||
}
|
||||
as.configCache.UpdateSettings(serverIDStr, *config.(*model.ServerSettings))
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
logging.Debug("Successfully loaded and cached config for server ID: %s, file: %s", serverIDStr, configFile)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetConfigs
|
||||
@@ -261,7 +289,7 @@ func (as *ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, erro
|
||||
|
||||
func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configurations, error) {
|
||||
serverIDStr := strconv.Itoa(int(server.ID))
|
||||
logging.Info("Loading configs for server ID: %s at path: %s", serverIDStr, server.ConfigPath)
|
||||
logging.Info("Loading configs for server ID: %s at path: %s", serverIDStr, server.GetConfigPath())
|
||||
configs := &model.Configurations{}
|
||||
|
||||
// Load configuration
|
||||
@@ -270,7 +298,7 @@ func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configuration
|
||||
configs.Configuration = *cached
|
||||
} else {
|
||||
logging.Debug("Loading configuration from disk for server %s", serverIDStr)
|
||||
config, err := mustDecode[model.Configuration](ConfigurationJson, server.ConfigPath)
|
||||
config, err := mustDecode[model.Configuration](ConfigurationJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
logging.Error("Failed to load configuration for server %s: %v", serverIDStr, err)
|
||||
return nil, fmt.Errorf("failed to load configuration: %v", err)
|
||||
@@ -285,7 +313,7 @@ func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configuration
|
||||
configs.AssistRules = *cached
|
||||
} else {
|
||||
logging.Debug("Loading assist rules from disk for server %s", serverIDStr)
|
||||
rules, err := mustDecode[model.AssistRules](AssistRulesJson, server.ConfigPath)
|
||||
rules, err := mustDecode[model.AssistRules](AssistRulesJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
logging.Error("Failed to load assist rules for server %s: %v", serverIDStr, err)
|
||||
return nil, fmt.Errorf("failed to load assist rules: %v", err)
|
||||
@@ -300,7 +328,7 @@ func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configuration
|
||||
configs.Event = *cached
|
||||
} else {
|
||||
logging.Debug("Loading event config from disk for server %s", serverIDStr)
|
||||
event, err := mustDecode[model.EventConfig](EventJson, server.ConfigPath)
|
||||
event, err := mustDecode[model.EventConfig](EventJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
logging.Error("Failed to load event config for server %s: %v", serverIDStr, err)
|
||||
return nil, fmt.Errorf("failed to load event config: %v", err)
|
||||
@@ -316,7 +344,7 @@ func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configuration
|
||||
configs.EventRules = *cached
|
||||
} else {
|
||||
logging.Debug("Loading event rules from disk for server %s", serverIDStr)
|
||||
rules, err := mustDecode[model.EventRules](EventRulesJson, server.ConfigPath)
|
||||
rules, err := mustDecode[model.EventRules](EventRulesJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
logging.Error("Failed to load event rules for server %s: %v", serverIDStr, err)
|
||||
return nil, fmt.Errorf("failed to load event rules: %v", err)
|
||||
@@ -331,7 +359,7 @@ func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configuration
|
||||
configs.Settings = *cached
|
||||
} else {
|
||||
logging.Debug("Loading settings from disk for server %s", serverIDStr)
|
||||
settings, err := mustDecode[model.ServerSettings](SettingsJson, server.ConfigPath)
|
||||
settings, err := mustDecode[model.ServerSettings](SettingsJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
logging.Error("Failed to load settings for server %s: %v", serverIDStr, err)
|
||||
return nil, fmt.Errorf("failed to load settings: %v", err)
|
||||
@@ -359,7 +387,7 @@ func readAndDecode[T interface{}](path string, configFile string) (T, error) {
|
||||
}
|
||||
|
||||
func readFile(path string, configFile string) ([]byte, error) {
|
||||
configPath := filepath.Join(path, "server", "cfg", configFile)
|
||||
configPath := filepath.Join(path, configFile)
|
||||
oldData, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
@@ -419,7 +447,7 @@ func (as *ConfigService) GetEventConfig(server *model.Server) (*model.EventConfi
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
event, err := mustDecode[model.EventConfig](EventJson, server.ConfigPath)
|
||||
event, err := mustDecode[model.EventConfig](EventJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -433,10 +461,27 @@ func (as *ConfigService) GetConfiguration(server *model.Server) (*model.Configur
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
config, err := mustDecode[model.Configuration](ConfigurationJson, server.ConfigPath)
|
||||
config, err := mustDecode[model.Configuration](ConfigurationJson, server.GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
as.configCache.UpdateConfiguration(serverIDStr, config)
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// SaveConfiguration saves the configuration for a server
|
||||
func (as *ConfigService) SaveConfiguration(server *model.Server, config *model.Configuration) error {
|
||||
// Convert config to map for UpdateConfig
|
||||
configMap := make(map[string]interface{})
|
||||
configBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal configuration: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(configBytes, &configMap); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal configuration: %v", err)
|
||||
}
|
||||
|
||||
// Update the configuration using the internal method
|
||||
_, err = as.updateConfigInternal(context.Background(), int(server.ID), ConfigurationJson, &configMap, true)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -21,16 +21,16 @@ func NewFirewallService() *FirewallService {
|
||||
|
||||
func (s *FirewallService) CreateServerRules(serverName string, tcpPorts, udpPorts []int) error {
|
||||
for _, port := range tcpPorts {
|
||||
ruleName := fmt.Sprintf("%s-TCP-%d", serverName, port)
|
||||
ruleName := fmt.Sprintf("\"%s-TCP-%d\"", serverName, port)
|
||||
builder := command.NewCommandBuilder().
|
||||
Add("advfirewall").
|
||||
Add("firewall").
|
||||
Add("add").
|
||||
Add("rule").
|
||||
AddPair("name", ruleName).
|
||||
AddPair("dir", "in").
|
||||
AddPair("action", "allow").
|
||||
AddPair("protocol", "TCP").
|
||||
AddFlag("name", ruleName).
|
||||
AddFlag("dir", "in").
|
||||
AddFlag("action", "allow").
|
||||
AddFlag("protocol", "TCP").
|
||||
AddFlag("localport", port)
|
||||
|
||||
if err := s.executor.ExecuteWithBuilder(builder); err != nil {
|
||||
@@ -46,10 +46,10 @@ func (s *FirewallService) CreateServerRules(serverName string, tcpPorts, udpPort
|
||||
Add("firewall").
|
||||
Add("add").
|
||||
Add("rule").
|
||||
AddPair("name", ruleName).
|
||||
AddPair("dir", "in").
|
||||
AddPair("action", "allow").
|
||||
AddPair("protocol", "UDP").
|
||||
AddFlag("name", ruleName).
|
||||
AddFlag("dir", "in").
|
||||
AddFlag("action", "allow").
|
||||
AddFlag("protocol", "UDP").
|
||||
AddFlag("localport", port)
|
||||
|
||||
if err := s.executor.ExecuteWithBuilder(builder); err != nil {
|
||||
@@ -63,13 +63,13 @@ func (s *FirewallService) CreateServerRules(serverName string, tcpPorts, udpPort
|
||||
|
||||
func (s *FirewallService) DeleteServerRules(serverName string, tcpPorts, udpPorts []int) error {
|
||||
for _, port := range tcpPorts {
|
||||
ruleName := fmt.Sprintf("%s-TCP-%d", serverName, port)
|
||||
ruleName := fmt.Sprintf("\"%s-TCP-%d\"", serverName, port)
|
||||
builder := command.NewCommandBuilder().
|
||||
Add("advfirewall").
|
||||
Add("firewall").
|
||||
Add("delete").
|
||||
Add("rule").
|
||||
AddPair("name", ruleName)
|
||||
AddFlag("name", ruleName)
|
||||
|
||||
if err := s.executor.ExecuteWithBuilder(builder); err != nil {
|
||||
return fmt.Errorf("failed to delete TCP firewall rule for port %d: %v", port, err)
|
||||
@@ -78,13 +78,13 @@ func (s *FirewallService) DeleteServerRules(serverName string, tcpPorts, udpPort
|
||||
}
|
||||
|
||||
for _, port := range udpPorts {
|
||||
ruleName := fmt.Sprintf("%s-UDP-%d", serverName, port)
|
||||
ruleName := fmt.Sprintf("\"%s-UDP-%d\"", serverName, port)
|
||||
builder := command.NewCommandBuilder().
|
||||
Add("advfirewall").
|
||||
Add("firewall").
|
||||
Add("delete").
|
||||
Add("rule").
|
||||
AddPair("name", ruleName)
|
||||
AddFlag("name", ruleName)
|
||||
|
||||
if err := s.executor.ExecuteWithBuilder(builder); err != nil {
|
||||
return fmt.Errorf("failed to delete UDP firewall rule for port %d: %v", port, err)
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
@@ -12,10 +13,11 @@ type LookupService struct {
|
||||
cache *model.LookupCache
|
||||
}
|
||||
|
||||
func NewLookupService(repository *repository.LookupRepository) *LookupService {
|
||||
func NewLookupService(repository *repository.LookupRepository, cache *model.LookupCache) *LookupService {
|
||||
logging.Debug("Initializing LookupService")
|
||||
return &LookupService{
|
||||
repository: repository,
|
||||
cache: model.NewLookupCache(),
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +26,7 @@ func (s *LookupService) GetTracks(ctx *fiber.Ctx) (interface{}, error) {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading tracks from database")
|
||||
tracks := s.repository.GetTracks(ctx.UserContext())
|
||||
s.cache.Set("tracks", tracks)
|
||||
return tracks, nil
|
||||
@@ -34,6 +37,7 @@ func (s *LookupService) GetCarModels(ctx *fiber.Ctx) (interface{}, error) {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading car models from database")
|
||||
cars := s.repository.GetCarModels(ctx.UserContext())
|
||||
s.cache.Set("cars", cars)
|
||||
return cars, nil
|
||||
@@ -44,6 +48,7 @@ func (s *LookupService) GetDriverCategories(ctx *fiber.Ctx) (interface{}, error)
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading driver categories from database")
|
||||
categories := s.repository.GetDriverCategories(ctx.UserContext())
|
||||
s.cache.Set("drivers", categories)
|
||||
return categories, nil
|
||||
@@ -54,6 +59,7 @@ func (s *LookupService) GetCupCategories(ctx *fiber.Ctx) (interface{}, error) {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading cup categories from database")
|
||||
categories := s.repository.GetCupCategories(ctx.UserContext())
|
||||
s.cache.Set("cups", categories)
|
||||
return categories, nil
|
||||
@@ -64,6 +70,7 @@ func (s *LookupService) GetSessionTypes(ctx *fiber.Ctx) (interface{}, error) {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading session types from database")
|
||||
types := s.repository.GetSessionTypes(ctx.UserContext())
|
||||
s.cache.Set("sessions", types)
|
||||
return types, nil
|
||||
@@ -71,14 +78,17 @@ func (s *LookupService) GetSessionTypes(ctx *fiber.Ctx) (interface{}, error) {
|
||||
|
||||
// ClearCache clears all cached lookup data
|
||||
func (s *LookupService) ClearCache() {
|
||||
logging.Debug("Clearing all lookup cache data")
|
||||
s.cache.Clear()
|
||||
}
|
||||
|
||||
// PreloadCache loads all lookup data into cache
|
||||
func (s *LookupService) PreloadCache(ctx *fiber.Ctx) {
|
||||
logging.Debug("Preloading all lookup cache data")
|
||||
s.GetTracks(ctx)
|
||||
s.GetCarModels(ctx)
|
||||
s.GetDriverCategories(ctx)
|
||||
s.GetCupCategories(ctx)
|
||||
s.GetSessionTypes(ctx)
|
||||
logging.Debug("Completed preloading lookup cache data")
|
||||
}
|
||||
|
||||
@@ -12,22 +12,30 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"acc-server-manager/local/utl/network"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultStartPort = 9600
|
||||
RequiredPortCount = 1 // Update this if ACC needs more ports
|
||||
)
|
||||
|
||||
type ServerService struct {
|
||||
repository *repository.ServerRepository
|
||||
stateHistoryRepo *repository.StateHistoryRepository
|
||||
apiService *ApiService
|
||||
instances sync.Map // Track instances per server
|
||||
configService *ConfigService
|
||||
steamService *SteamService
|
||||
windowsService *WindowsService
|
||||
firewallService *FirewallService
|
||||
systemConfigService *SystemConfigService
|
||||
instances sync.Map // Track instances per server
|
||||
lastInsertTimes sync.Map // Track last insert time per server
|
||||
debouncers sync.Map // Track debounce timers per server
|
||||
logTailers sync.Map // Track log tailers per server
|
||||
sessionIDs sync.Map // Track current session ID per server
|
||||
steamService *SteamService
|
||||
windowsService *WindowsService
|
||||
firewallService *FirewallService
|
||||
}
|
||||
|
||||
type pendingState struct {
|
||||
@@ -43,7 +51,7 @@ func (s *ServerService) ensureLogTailing(server *model.Server, instance *trackin
|
||||
|
||||
// Start tailing in a goroutine that handles file creation/deletion
|
||||
go func() {
|
||||
logPath := filepath.Join(server.ConfigPath, "\\server\\log\\server.log")
|
||||
logPath := filepath.Join(server.GetLogPath(), "server.log")
|
||||
tailer := tracking.NewLogTailer(logPath, instance.HandleLogLine)
|
||||
s.logTailers.Store(server.ID, tailer)
|
||||
|
||||
@@ -52,20 +60,28 @@ func (s *ServerService) ensureLogTailing(server *model.Server, instance *trackin
|
||||
}()
|
||||
}
|
||||
|
||||
func NewServerService(repository *repository.ServerRepository, stateHistoryRepo *repository.StateHistoryRepository, apiService *ApiService, configService *ConfigService, steamCredentialsRepo *repository.SteamCredentialsRepository) *ServerService {
|
||||
steamService := NewSteamService(steamCredentialsRepo)
|
||||
|
||||
func NewServerService(
|
||||
repository *repository.ServerRepository,
|
||||
stateHistoryRepo *repository.StateHistoryRepository,
|
||||
apiService *ApiService,
|
||||
configService *ConfigService,
|
||||
steamService *SteamService,
|
||||
windowsService *WindowsService,
|
||||
firewallService *FirewallService,
|
||||
systemConfigService *SystemConfigService,
|
||||
) *ServerService {
|
||||
service := &ServerService{
|
||||
repository: repository,
|
||||
stateHistoryRepo: stateHistoryRepo,
|
||||
apiService: apiService,
|
||||
configService: configService,
|
||||
stateHistoryRepo: stateHistoryRepo,
|
||||
steamService: steamService,
|
||||
windowsService: NewWindowsService(),
|
||||
firewallService: NewFirewallService(),
|
||||
windowsService: windowsService,
|
||||
firewallService: firewallService,
|
||||
systemConfigService: systemConfigService,
|
||||
}
|
||||
|
||||
// Initialize instances for all servers
|
||||
// Initialize server instances
|
||||
servers, err := repository.GetAll(context.Background(), &model.ServerFilter{})
|
||||
if err != nil {
|
||||
logging.Error("Failed to get servers: %v", err)
|
||||
@@ -144,14 +160,14 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
|
||||
// Get configs using helper methods
|
||||
event, err := s.configService.GetEventConfig(server)
|
||||
if err != nil {
|
||||
event = &model.EventConfig{}
|
||||
logging.Error("Failed to get event config for server %d: %v", server.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
configuration, err := s.configService.GetConfiguration(server)
|
||||
if err != nil {
|
||||
configuration = &model.Configuration{}
|
||||
logging.Error("Failed to get configuration for server %d: %v", server.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if instance, ok := s.instances.Load(server.ID); ok {
|
||||
@@ -181,6 +197,18 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerService) GenerateServerPath(server *model.Server) {
|
||||
// Get the base steamcmd path
|
||||
steamCMDPath, err := s.systemConfigService.GetSteamCMDDirPath(context.Background())
|
||||
if err != nil {
|
||||
logging.Error("Failed to get steamcmd path: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
server.Path = server.GenerateServerPath(steamCMDPath)
|
||||
}
|
||||
|
||||
|
||||
func (s *ServerService) handleStateChange(server *model.Server, state *model.ServerState) {
|
||||
// Update session duration when session changes
|
||||
s.updateSessionDuration(server, state.Session)
|
||||
@@ -309,34 +337,47 @@ func (s *ServerService) CreateServer(ctx *fiber.Ctx, server *model.Server) error
|
||||
}
|
||||
|
||||
// Install server using SteamCMD
|
||||
if err := s.steamService.InstallServer(ctx.UserContext(), server.ConfigPath); err != nil {
|
||||
if err := s.steamService.InstallServer(ctx.UserContext(), server.GetServerPath()); err != nil {
|
||||
return fmt.Errorf("failed to install server: %v", err)
|
||||
}
|
||||
|
||||
// Create Windows service
|
||||
execPath := filepath.Join(server.ConfigPath, "accServer.exe")
|
||||
if err := s.windowsService.CreateService(server.ServiceName, execPath, server.ConfigPath, nil); err != nil {
|
||||
// Create Windows service with correct paths
|
||||
execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
|
||||
serverWorkingDir := filepath.Join(server.GetServerPath(), "server")
|
||||
if err := s.windowsService.CreateService(ctx.UserContext(), server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
|
||||
// Cleanup on failure
|
||||
s.steamService.UninstallServer(server.ConfigPath)
|
||||
s.steamService.UninstallServer(server.Path)
|
||||
return fmt.Errorf("failed to create Windows service: %v", err)
|
||||
}
|
||||
|
||||
// Create firewall rules
|
||||
tcpPorts := []int{9600} // Add all required TCP ports
|
||||
udpPorts := []int{9600} // Add all required UDP ports
|
||||
s.configureFirewall(server)
|
||||
ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find available ports: %v", err)
|
||||
}
|
||||
|
||||
// Use the first port for both TCP and UDP
|
||||
serverPort := ports[0]
|
||||
tcpPorts := []int{serverPort}
|
||||
udpPorts := []int{serverPort}
|
||||
if err := s.firewallService.CreateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
|
||||
// Cleanup on failure
|
||||
s.windowsService.DeleteService(server.ServiceName)
|
||||
s.steamService.UninstallServer(server.ConfigPath)
|
||||
s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName)
|
||||
s.steamService.UninstallServer(server.Path)
|
||||
return fmt.Errorf("failed to create firewall rules: %v", err)
|
||||
}
|
||||
|
||||
// Update server configuration with the allocated port
|
||||
if err := s.updateServerPort(server, serverPort); err != nil {
|
||||
return fmt.Errorf("failed to update server configuration: %v", err)
|
||||
}
|
||||
|
||||
// Insert server into database
|
||||
if err := s.repository.Insert(ctx.UserContext(), server); err != nil {
|
||||
// Cleanup on failure
|
||||
s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
|
||||
s.windowsService.DeleteService(server.ServiceName)
|
||||
s.steamService.UninstallServer(server.ConfigPath)
|
||||
s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName)
|
||||
s.steamService.UninstallServer(server.Path)
|
||||
return fmt.Errorf("failed to insert server into database: %v", err)
|
||||
}
|
||||
|
||||
@@ -354,19 +395,24 @@ func (s *ServerService) DeleteServer(ctx *fiber.Ctx, serverID int) error {
|
||||
}
|
||||
|
||||
// Stop and remove Windows service
|
||||
if err := s.windowsService.DeleteService(server.ServiceName); err != nil {
|
||||
if err := s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName); err != nil {
|
||||
logging.Error("Failed to delete Windows service: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Remove firewall rules
|
||||
tcpPorts := []int{9600} // Add all required TCP ports
|
||||
udpPorts := []int{9600} // Add all required UDP ports
|
||||
configuration, err := s.configService.GetConfiguration(server)
|
||||
if err != nil {
|
||||
logging.Error("Failed to get configuration for server %d: %v", server.ID, err)
|
||||
}
|
||||
tcpPorts := []int{configuration.TcpPort.ToInt()}
|
||||
udpPorts := []int{configuration.UdpPort.ToInt()}
|
||||
if err := s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
|
||||
logging.Error("Failed to delete firewall rules: %v", err)
|
||||
}
|
||||
|
||||
// Uninstall server files
|
||||
if err := s.steamService.UninstallServer(server.ConfigPath); err != nil {
|
||||
if err := s.steamService.UninstallServer(server.Path); err != nil {
|
||||
logging.Error("Failed to uninstall server: %v", err)
|
||||
}
|
||||
|
||||
@@ -401,29 +447,28 @@ func (s *ServerService) UpdateServer(ctx *fiber.Ctx, server *model.Server) error
|
||||
}
|
||||
|
||||
// Update server files if path changed
|
||||
if existingServer.ConfigPath != server.ConfigPath {
|
||||
if err := s.steamService.InstallServer(ctx.UserContext(), server.ConfigPath); err != nil {
|
||||
if existingServer.Path != server.Path {
|
||||
if err := s.steamService.InstallServer(ctx.UserContext(), server.Path); err != nil {
|
||||
return fmt.Errorf("failed to install server to new location: %v", err)
|
||||
}
|
||||
// Clean up old installation
|
||||
if err := s.steamService.UninstallServer(existingServer.ConfigPath); err != nil {
|
||||
if err := s.steamService.UninstallServer(existingServer.Path); err != nil {
|
||||
logging.Error("Failed to remove old server installation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update Windows service if necessary
|
||||
if existingServer.ServiceName != server.ServiceName || existingServer.ConfigPath != server.ConfigPath {
|
||||
execPath := filepath.Join(server.ConfigPath, "accServer.exe")
|
||||
if err := s.windowsService.UpdateService(server.ServiceName, execPath, server.ConfigPath, nil); err != nil {
|
||||
if existingServer.ServiceName != server.ServiceName || existingServer.Path != server.Path {
|
||||
execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
|
||||
serverWorkingDir := server.GetServerPath()
|
||||
if err := s.windowsService.UpdateService(ctx.UserContext(), server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
|
||||
return fmt.Errorf("failed to update Windows service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update firewall rules if service name changed
|
||||
if existingServer.ServiceName != server.ServiceName {
|
||||
tcpPorts := []int{9600} // Add all required TCP ports
|
||||
udpPorts := []int{9600} // Add all required UDP ports
|
||||
if err := s.firewallService.UpdateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
|
||||
if err := s.configureFirewall(server); err != nil {
|
||||
return fmt.Errorf("failed to update firewall rules: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -438,3 +483,48 @@ func (s *ServerService) UpdateServer(ctx *fiber.Ctx, server *model.Server) error
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerService) configureFirewall(server *model.Server) error {
|
||||
// Find available ports for the server
|
||||
ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find available ports: %v", err)
|
||||
}
|
||||
|
||||
// Use the first port for both TCP and UDP
|
||||
serverPort := ports[0]
|
||||
tcpPorts := []int{serverPort}
|
||||
udpPorts := []int{serverPort}
|
||||
|
||||
logging.Info("Configuring firewall for server %d with port %d", server.ID, serverPort)
|
||||
|
||||
// Configure firewall rules
|
||||
if err := s.firewallService.UpdateServerRules(server.Name, tcpPorts, udpPorts); err != nil {
|
||||
return fmt.Errorf("failed to configure firewall: %v", err)
|
||||
}
|
||||
|
||||
// Update server configuration with the allocated port
|
||||
if err := s.updateServerPort(server, serverPort); err != nil {
|
||||
return fmt.Errorf("failed to update server configuration: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerService) updateServerPort(server *model.Server, port int) error {
|
||||
// Load current configuration
|
||||
config, err := s.configService.GetConfiguration(server)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load server configuration: %v", err)
|
||||
}
|
||||
|
||||
config.TcpPort = model.IntString(port)
|
||||
config.UdpPort = model.IntString(port)
|
||||
|
||||
// Save the updated configuration
|
||||
if err := s.configService.SaveConfiguration(server, config); err != nil {
|
||||
return fmt.Errorf("failed to save server configuration: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"context"
|
||||
@@ -14,25 +15,51 @@ import (
|
||||
// Args:
|
||||
// *dig.Container: Dig Container
|
||||
func InitializeServices(c *dig.Container) {
|
||||
logging.Debug("Initializing repositories")
|
||||
repository.InitializeRepositories(c)
|
||||
|
||||
// Provide caches
|
||||
logging.Debug("Creating lookup cache instance")
|
||||
c.Provide(func() *model.LookupCache {
|
||||
return model.NewLookupCache()
|
||||
})
|
||||
|
||||
logging.Debug("Registering services")
|
||||
// Provide services
|
||||
c.Provide(NewServerService)
|
||||
c.Provide(NewStateHistoryService)
|
||||
c.Provide(NewApiService)
|
||||
c.Provide(NewConfigService)
|
||||
c.Provide(NewLookupService)
|
||||
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, lookup *LookupService) {
|
||||
c.Provide(NewSystemConfigService)
|
||||
c.Provide(NewSteamService)
|
||||
c.Provide(NewWindowsService)
|
||||
c.Provide(NewFirewallService)
|
||||
|
||||
logging.Debug("Initializing service dependencies")
|
||||
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, lookup *LookupService, systemConfig *SystemConfigService) {
|
||||
logging.Debug("Setting up service cross-references")
|
||||
api.SetServerService(server)
|
||||
config.SetServerService(server)
|
||||
|
||||
logging.Debug("Initializing lookup data cache")
|
||||
// Initialize lookup data using repository directly
|
||||
lookup.cache.Set("tracks", lookup.repository.GetTracks(context.Background()))
|
||||
lookup.cache.Set("cars", lookup.repository.GetCarModels(context.Background()))
|
||||
lookup.cache.Set("drivers", lookup.repository.GetDriverCategories(context.Background()))
|
||||
lookup.cache.Set("cups", lookup.repository.GetCupCategories(context.Background()))
|
||||
lookup.cache.Set("sessions", lookup.repository.GetSessionTypes(context.Background()))
|
||||
logging.Debug("Completed initializing lookup data cache")
|
||||
|
||||
logging.Debug("Initializing system config service")
|
||||
// Initialize system config service
|
||||
if err := systemConfig.Initialize(context.Background()); err != nil {
|
||||
logging.Panic("failed to initialize system config service: " + err.Error())
|
||||
}
|
||||
logging.Debug("Completed initializing system config service")
|
||||
})
|
||||
if err != nil {
|
||||
logging.Panic("unable to initialize services: " + err.Error())
|
||||
}
|
||||
logging.Debug("Completed service initialization")
|
||||
}
|
||||
|
||||
@@ -12,22 +12,23 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SteamCMDPath = "steamcmd"
|
||||
ACCServerAppID = "1430110"
|
||||
)
|
||||
|
||||
type SteamService struct {
|
||||
executor *command.CommandExecutor
|
||||
repository *repository.SteamCredentialsRepository
|
||||
executor *command.CommandExecutor
|
||||
repository *repository.SteamCredentialsRepository
|
||||
configService *SystemConfigService
|
||||
}
|
||||
|
||||
func NewSteamService(repository *repository.SteamCredentialsRepository) *SteamService {
|
||||
func NewSteamService(repository *repository.SteamCredentialsRepository, configService *SystemConfigService) *SteamService {
|
||||
return &SteamService{
|
||||
executor: &command.CommandExecutor{
|
||||
ExePath: "powershell",
|
||||
LogOutput: true,
|
||||
},
|
||||
repository: repository,
|
||||
repository: repository,
|
||||
configService: configService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +43,25 @@ func (s *SteamService) SaveCredentials(ctx context.Context, creds *model.SteamCr
|
||||
return s.repository.Save(ctx, creds)
|
||||
}
|
||||
|
||||
func (s *SteamService) ensureSteamCMD() error {
|
||||
func (s *SteamService) ensureSteamCMD(ctx context.Context) error {
|
||||
// Get SteamCMD path from config
|
||||
steamCMDPath, err := s.configService.GetSteamCMDDirPath(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get SteamCMD path from config: %v", err)
|
||||
}
|
||||
|
||||
steamCMDDir := filepath.Dir(steamCMDPath)
|
||||
|
||||
// Check if SteamCMD exists
|
||||
if _, err := os.Stat(SteamCMDPath); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat(steamCMDPath); !os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if err := os.MkdirAll(steamCMDDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create SteamCMD directory: %v", err)
|
||||
}
|
||||
|
||||
// Download and install SteamCMD
|
||||
logging.Info("Downloading SteamCMD...")
|
||||
if err := s.executor.Execute("-Command",
|
||||
@@ -58,7 +72,7 @@ func (s *SteamService) ensureSteamCMD() error {
|
||||
// Extract SteamCMD
|
||||
logging.Info("Extracting SteamCMD...")
|
||||
if err := s.executor.Execute("-Command",
|
||||
"Expand-Archive -Path 'steamcmd.zip' -DestinationPath 'steamcmd'"); err != nil {
|
||||
fmt.Sprintf("Expand-Archive -Path 'steamcmd.zip' -DestinationPath '%s'", steamCMDDir)); err != nil {
|
||||
return fmt.Errorf("failed to extract SteamCMD: %v", err)
|
||||
}
|
||||
|
||||
@@ -68,12 +82,19 @@ func (s *SteamService) ensureSteamCMD() error {
|
||||
}
|
||||
|
||||
func (s *SteamService) InstallServer(ctx context.Context, installPath string) error {
|
||||
if err := s.ensureSteamCMD(); err != nil {
|
||||
if err := s.ensureSteamCMD(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert to absolute path and ensure proper Windows path format
|
||||
absPath, err := filepath.Abs(installPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path: %v", err)
|
||||
}
|
||||
absPath = filepath.Clean(absPath)
|
||||
|
||||
// Ensure install path exists
|
||||
if err := os.MkdirAll(installPath, 0755); err != nil {
|
||||
if err := os.MkdirAll(absPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create install directory: %v", err)
|
||||
}
|
||||
|
||||
@@ -83,12 +104,18 @@ func (s *SteamService) InstallServer(ctx context.Context, installPath string) er
|
||||
return fmt.Errorf("failed to get Steam credentials: %v", err)
|
||||
}
|
||||
|
||||
// Get SteamCMD path from config
|
||||
steamCMDPath, err := s.configService.GetSteamCMDPath(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get SteamCMD path from config: %v", err)
|
||||
}
|
||||
|
||||
// Build SteamCMD command
|
||||
args := []string{
|
||||
"-nologo",
|
||||
"-noprofile",
|
||||
filepath.Join(SteamCMDPath, "steamcmd.exe"),
|
||||
"+force_install_dir", installPath,
|
||||
steamCMDPath,
|
||||
"+force_install_dir", absPath,
|
||||
"+login",
|
||||
}
|
||||
|
||||
@@ -108,11 +135,24 @@ func (s *SteamService) InstallServer(ctx context.Context, installPath string) er
|
||||
)
|
||||
|
||||
// Run SteamCMD
|
||||
logging.Info("Installing ACC server to %s...", installPath)
|
||||
logging.Info("Installing ACC server to %s...", absPath)
|
||||
if err := s.executor.Execute(args...); err != nil {
|
||||
return fmt.Errorf("failed to install server: %v", err)
|
||||
return fmt.Errorf("failed to run SteamCMD: %v", err)
|
||||
}
|
||||
|
||||
// Add a delay to allow Steam to properly cleanup
|
||||
logging.Info("Waiting for Steam operations to complete...")
|
||||
if err := s.executor.Execute("-Command", "Start-Sleep -Seconds 5"); err != nil {
|
||||
logging.Warn("Failed to wait after Steam operations: %v", err)
|
||||
}
|
||||
|
||||
// Verify installation
|
||||
exePath := filepath.Join(absPath, "server", "accServer.exe")
|
||||
if _, err := os.Stat(exePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("server installation failed: accServer.exe not found in %s", absPath)
|
||||
}
|
||||
|
||||
logging.Info("Server installation completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
127
local/service/system_config_service.go
Normal file
127
local/service/system_config_service.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
type SystemConfigService struct {
|
||||
repository *repository.SystemConfigRepository
|
||||
cache *model.LookupCache
|
||||
}
|
||||
|
||||
// SystemConfigServiceParams holds the dependencies for SystemConfigService
|
||||
type SystemConfigServiceParams struct {
|
||||
dig.In
|
||||
|
||||
Repository *repository.SystemConfigRepository
|
||||
Cache *model.LookupCache
|
||||
}
|
||||
|
||||
// NewSystemConfigService creates a new SystemConfigService with dependencies injected by dig
|
||||
func NewSystemConfigService(params SystemConfigServiceParams) *SystemConfigService {
|
||||
logging.Debug("Initializing SystemConfigService")
|
||||
return &SystemConfigService{
|
||||
repository: params.Repository,
|
||||
cache: params.Cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) Initialize(ctx context.Context) error {
|
||||
logging.Debug("Initializing system config cache")
|
||||
// Cache all configs
|
||||
configs, err := s.repository.GetAll(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get configs for caching: %v", err)
|
||||
}
|
||||
|
||||
for _, config := range *configs {
|
||||
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, config.Key)
|
||||
s.cache.Set(cacheKey, &config)
|
||||
logging.Debug("Cached system config: %s", config.Key)
|
||||
}
|
||||
|
||||
logging.Debug("Completed initializing system config cache")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) GetConfig(ctx context.Context, key string) (*model.SystemConfig, error) {
|
||||
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, key)
|
||||
|
||||
// Try to get from cache first
|
||||
if cached, exists := s.cache.Get(cacheKey); exists {
|
||||
if config, ok := cached.(*model.SystemConfig); ok {
|
||||
return config, nil
|
||||
}
|
||||
logging.Debug("Invalid type in cache for key: %s", key)
|
||||
}
|
||||
|
||||
// If not in cache, get from database
|
||||
logging.Debug("Loading system config from database: %s", key)
|
||||
config, err := s.repository.Get(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
logging.Error("Configuration not found for key: %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
s.cache.Set(cacheKey, config)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) GetAllConfigs(ctx context.Context) (*[]model.SystemConfig, error) {
|
||||
logging.Debug("Loading all system configs from database")
|
||||
return s.repository.GetAll(ctx)
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) UpdateConfig(ctx context.Context, config *model.SystemConfig) error {
|
||||
if err := s.repository.Update(ctx, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update cache
|
||||
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, config.Key)
|
||||
s.cache.Set(cacheKey, config)
|
||||
logging.Debug("Updated system config in cache: %s", config.Key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) GetSteamCMDDirPath(ctx context.Context) (string, error) {
|
||||
steamCMDPath, err := s.GetSteamCMDPath(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Dir(steamCMDPath), nil
|
||||
}
|
||||
|
||||
// Helper methods for common configurations
|
||||
func (s *SystemConfigService) GetSteamCMDPath(ctx context.Context) (string, error) {
|
||||
config, err := s.GetConfig(ctx, model.ConfigKeySteamCMDPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if config == nil {
|
||||
return "", nil
|
||||
}
|
||||
return config.GetEffectiveValue(), nil
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) GetNSSMPath(ctx context.Context) (string, error) {
|
||||
config, err := s.GetConfig(ctx, model.ConfigKeyNSSMPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if config == nil {
|
||||
return "", nil
|
||||
}
|
||||
return config.GetEffectiveValue(), nil
|
||||
}
|
||||
@@ -3,35 +3,47 @@ package service
|
||||
import (
|
||||
"acc-server-manager/local/utl/command"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
NSSMPath = ".\\nssm"
|
||||
NSSMPath = ".\\nssm.exe"
|
||||
)
|
||||
|
||||
type WindowsService struct {
|
||||
executor *command.CommandExecutor
|
||||
executor *command.CommandExecutor
|
||||
configService *SystemConfigService
|
||||
}
|
||||
|
||||
func NewWindowsService() *WindowsService {
|
||||
func NewWindowsService(configService *SystemConfigService) *WindowsService {
|
||||
return &WindowsService{
|
||||
executor: &command.CommandExecutor{
|
||||
ExePath: "powershell",
|
||||
LogOutput: true,
|
||||
},
|
||||
configService: configService,
|
||||
}
|
||||
}
|
||||
|
||||
// executeNSSM runs an NSSM command through PowerShell with elevation
|
||||
func (s *WindowsService) executeNSSM(args ...string) (string, error) {
|
||||
func (s *WindowsService) executeNSSM(ctx context.Context, args ...string) (string, error) {
|
||||
// Get NSSM path from config
|
||||
nssmPath, err := s.configService.GetNSSMPath(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get NSSM path from config: %v", err)
|
||||
}
|
||||
|
||||
// Prepend NSSM path to arguments
|
||||
nssmArgs := append([]string{"-nologo", "-noprofile", NSSMPath}, args...)
|
||||
nssmArgs := append([]string{"-NoProfile", "-NonInteractive", "-Command", "& " + nssmPath}, args...)
|
||||
|
||||
output, err := s.executor.ExecuteWithOutput(nssmArgs...)
|
||||
if err != nil {
|
||||
// Log the full command and error for debugging
|
||||
logging.Error("NSSM command failed: powershell %s", strings.Join(nssmArgs, " "))
|
||||
logging.Error("NSSM error output: %s", output)
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -45,42 +57,54 @@ func (s *WindowsService) executeNSSM(args ...string) (string, error) {
|
||||
|
||||
// Service Installation/Configuration Methods
|
||||
|
||||
func (s *WindowsService) CreateService(serviceName, execPath, workingDir string, args []string) error {
|
||||
// Ensure paths are absolute
|
||||
func (s *WindowsService) CreateService(ctx context.Context, serviceName, execPath, workingDir string, args []string) error {
|
||||
// Ensure paths are absolute and properly formatted for Windows
|
||||
absExecPath, err := filepath.Abs(execPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path for executable: %v", err)
|
||||
}
|
||||
absExecPath = filepath.Clean(absExecPath)
|
||||
|
||||
absWorkingDir, err := filepath.Abs(workingDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path for working directory: %v", err)
|
||||
}
|
||||
absWorkingDir = filepath.Clean(absWorkingDir)
|
||||
|
||||
// Log the paths being used
|
||||
logging.Info("Creating service '%s' with:", serviceName)
|
||||
logging.Info(" Executable: %s", absExecPath)
|
||||
logging.Info(" Working Directory: %s", absWorkingDir)
|
||||
|
||||
// First remove any existing service with the same name
|
||||
s.executeNSSM(ctx, "remove", serviceName, "confirm")
|
||||
|
||||
// Install service
|
||||
if _, err := s.executeNSSM("install", serviceName, absExecPath); err != nil {
|
||||
if _, err := s.executeNSSM(ctx, "install", serviceName, absExecPath); err != nil {
|
||||
return fmt.Errorf("failed to install service: %v", err)
|
||||
}
|
||||
|
||||
// Set working directory
|
||||
if _, err := s.executeNSSM("set", serviceName, "AppDirectory", absWorkingDir); err != nil {
|
||||
return fmt.Errorf("failed to set working directory: %v", err)
|
||||
}
|
||||
|
||||
// Set arguments if provided
|
||||
if len(args) > 0 {
|
||||
cmdArgs := append([]string{"set", serviceName, "AppParameters"}, args...)
|
||||
if _, err := s.executeNSSM(cmdArgs...); err != nil {
|
||||
if _, err := s.executeNSSM(ctx, cmdArgs...); err != nil {
|
||||
// Try to clean up on failure
|
||||
s.executeNSSM(ctx, "remove", serviceName, "confirm")
|
||||
return fmt.Errorf("failed to set arguments: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify service was created
|
||||
if _, err := s.executeNSSM(ctx, "get", serviceName, "Application"); err != nil {
|
||||
return fmt.Errorf("service creation verification failed: %v", err)
|
||||
}
|
||||
|
||||
logging.Info("Created Windows service: %s", serviceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WindowsService) DeleteService(serviceName string) error {
|
||||
if _, err := s.executeNSSM("remove", serviceName, "confirm"); err != nil {
|
||||
func (s *WindowsService) DeleteService(ctx context.Context, serviceName string) error {
|
||||
if _, err := s.executeNSSM(ctx, "remove", serviceName, "confirm"); err != nil {
|
||||
return fmt.Errorf("failed to remove service: %v", err)
|
||||
}
|
||||
|
||||
@@ -88,36 +112,36 @@ func (s *WindowsService) DeleteService(serviceName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WindowsService) UpdateService(serviceName, execPath, workingDir string, args []string) error {
|
||||
func (s *WindowsService) UpdateService(ctx context.Context, serviceName, execPath, workingDir string, args []string) error {
|
||||
// First remove the existing service
|
||||
if err := s.DeleteService(serviceName); err != nil {
|
||||
if err := s.DeleteService(ctx, serviceName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Then create it again with new parameters
|
||||
return s.CreateService(serviceName, execPath, workingDir, args)
|
||||
return s.CreateService(ctx, serviceName, execPath, workingDir, args)
|
||||
}
|
||||
|
||||
// Service Control Methods
|
||||
|
||||
func (s *WindowsService) Status(serviceName string) (string, error) {
|
||||
return s.executeNSSM("status", serviceName)
|
||||
func (s *WindowsService) Status(ctx context.Context, serviceName string) (string, error) {
|
||||
return s.executeNSSM(ctx, "status", serviceName)
|
||||
}
|
||||
|
||||
func (s *WindowsService) Start(serviceName string) (string, error) {
|
||||
return s.executeNSSM("start", serviceName)
|
||||
func (s *WindowsService) Start(ctx context.Context, serviceName string) (string, error) {
|
||||
return s.executeNSSM(ctx, "start", serviceName)
|
||||
}
|
||||
|
||||
func (s *WindowsService) Stop(serviceName string) (string, error) {
|
||||
return s.executeNSSM("stop", serviceName)
|
||||
func (s *WindowsService) Stop(ctx context.Context, serviceName string) (string, error) {
|
||||
return s.executeNSSM(ctx, "stop", serviceName)
|
||||
}
|
||||
|
||||
func (s *WindowsService) Restart(serviceName string) (string, error) {
|
||||
func (s *WindowsService) Restart(ctx context.Context, serviceName string) (string, error) {
|
||||
// First stop the service
|
||||
if _, err := s.Stop(serviceName); err != nil {
|
||||
if _, err := s.Stop(ctx, serviceName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Then start it again
|
||||
return s.Start(serviceName)
|
||||
return s.Start(ctx, serviceName)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"time"
|
||||
|
||||
"go.uber.org/dig"
|
||||
"gorm.io/driver/sqlite"
|
||||
@@ -60,10 +61,17 @@ func Migrate(db *gorm.DB) {
|
||||
if err != nil {
|
||||
logging.Panic("failed to migrate model.StateHistory")
|
||||
}
|
||||
err = db.AutoMigrate(&model.SteamCredentials{})
|
||||
if err != nil {
|
||||
logging.Panic("failed to migrate model.SteamCredentials")
|
||||
}
|
||||
err = db.AutoMigrate(&model.SystemConfig{})
|
||||
if err != nil {
|
||||
logging.Panic("failed to migrate model.SystemConfig")
|
||||
}
|
||||
db.FirstOrCreate(&model.ApiModel{Api: "Works"})
|
||||
|
||||
Seed(db)
|
||||
|
||||
}
|
||||
|
||||
func Seed(db *gorm.DB) error {
|
||||
@@ -85,15 +93,38 @@ func Seed(db *gorm.DB) error {
|
||||
if err := seedServers(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := seedSteamCredentials(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := seedSystemConfigs(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedSteamCredentials(db *gorm.DB) error {
|
||||
credentials := []model.SteamCredentials{
|
||||
{
|
||||
ID: 1,
|
||||
Username: "test",
|
||||
Password: "test",
|
||||
DateCreated: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
for _, credential := range credentials {
|
||||
if err := db.FirstOrCreate(&credential).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedServers(db *gorm.DB) error {
|
||||
servers := []model.Server{
|
||||
{ID: 1, Name: "ACC Server - Barcelona", ServiceName: "ACC-Barcelona", ConfigPath: "C:\\steamcmd\\acc"},
|
||||
{ID: 2, Name: "ACC Server - Monza", ServiceName: "ACC-Monza", ConfigPath: "C:\\steamcmd\\acc2"},
|
||||
{ID: 3, Name: "ACC Server - Spa", ServiceName: "ACC-Spa", ConfigPath: "C:\\steamcmd\\acc3"},
|
||||
{ID: 4, Name: "ACC Server - League", ServiceName: "ACC-League", ConfigPath: "C:\\steamcmd\\acc-league"},
|
||||
{ID: 1, Name: "ACC Server - Barcelona", ServiceName: "ACC-Barcelona", Path: "C:\\steamcmd\\acc", FromSteamCMD: true},
|
||||
{ID: 2, Name: "ACC Server - Monza", ServiceName: "ACC-Monza", Path: "C:\\steamcmd\\acc2", FromSteamCMD: true},
|
||||
{ID: 3, Name: "ACC Server - Spa", ServiceName: "ACC-Spa", Path: "C:\\steamcmd\\acc3", FromSteamCMD: true},
|
||||
{ID: 4, Name: "ACC Server - League", ServiceName: "ACC-League", Path: "C:\\steamcmd\\acc-league", FromSteamCMD: true},
|
||||
}
|
||||
|
||||
for _, track := range servers {
|
||||
@@ -203,3 +234,41 @@ func seedSessionTypes(db *gorm.DB) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedSystemConfigs(db *gorm.DB) error {
|
||||
configs := []model.SystemConfig{
|
||||
{
|
||||
Key: model.ConfigKeySteamCMDPath,
|
||||
DefaultValue: "c:\\steamcmd\\steamcmd.exe",
|
||||
Description: "Path to SteamCMD executable",
|
||||
DateModified: time.Now().UTC().Format(time.RFC3339),
|
||||
},
|
||||
{
|
||||
Key: model.ConfigKeyNSSMPath,
|
||||
DefaultValue: ".\\nssm.exe",
|
||||
Description: "Path to NSSM executable",
|
||||
DateModified: time.Now().UTC().Format(time.RFC3339),
|
||||
},
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
var exists bool
|
||||
err := db.Model(&model.SystemConfig{}).
|
||||
Select("count(*) > 0").
|
||||
Where("key = ?", config.Key).
|
||||
Find(&exists).
|
||||
Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := db.Create(&config).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logging.Info("Seeded system config: %s", config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,8 +136,9 @@ func Panic(format string) {
|
||||
|
||||
// RecoverAndLog recovers from panics and logs them
|
||||
func RecoverAndLog() {
|
||||
if r := recover(); r != nil {
|
||||
if logger != nil {
|
||||
if logger != nil {
|
||||
logger.Info("Recovering from panic")
|
||||
if r := recover(); r != nil {
|
||||
// Get stack trace
|
||||
buf := make([]byte, 4096)
|
||||
n := runtime.Stack(buf, false)
|
||||
|
||||
87
local/utl/network/port.go
Normal file
87
local/utl/network/port.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IsPortAvailable checks if a port is available for both TCP and UDP
|
||||
func IsPortAvailable(port int) bool {
|
||||
return IsTCPPortAvailable(port) && IsUDPPortAvailable(port)
|
||||
}
|
||||
|
||||
// IsTCPPortAvailable checks if a TCP port is available
|
||||
func IsTCPPortAvailable(port int) bool {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
listener.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// IsUDPPortAvailable checks if a UDP port is available
|
||||
func IsUDPPortAvailable(port int) bool {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: port})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// FindAvailablePort finds an available port starting from the given port
|
||||
func FindAvailablePort(startPort int) (int, error) {
|
||||
maxPort := 65535
|
||||
for port := startPort; port <= maxPort; port++ {
|
||||
if IsPortAvailable(port) {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("no available ports found between %d and %d", startPort, maxPort)
|
||||
}
|
||||
|
||||
// FindAvailablePortRange finds a range of consecutive available ports
|
||||
func FindAvailablePortRange(startPort, count int) ([]int, error) {
|
||||
maxPort := 65535
|
||||
ports := make([]int, 0, count)
|
||||
currentPort := startPort
|
||||
|
||||
for len(ports) < count && currentPort <= maxPort {
|
||||
// Check if we have enough consecutive ports available
|
||||
available := true
|
||||
for i := 0; i < count-len(ports); i++ {
|
||||
if !IsPortAvailable(currentPort + i) {
|
||||
available = false
|
||||
currentPort += i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if available {
|
||||
for i := 0; i < count-len(ports); i++ {
|
||||
ports = append(ports, currentPort+i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ports) < count {
|
||||
return nil, fmt.Errorf("could not find %d consecutive available ports starting from %d", count, startPort)
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
// WaitForPortAvailable waits for a port to become available with timeout
|
||||
func WaitForPortAvailable(port int, timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if IsPortAvailable(port) {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for port %d to become available", port)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package regexHandler
|
||||
package regex_handler
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
@@ -2,7 +2,7 @@ package tracking
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/utl/regexHandler"
|
||||
"acc-server-manager/local/utl/regex_handler"
|
||||
"bufio"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -36,13 +36,13 @@ func NewAccServerInstance(server *model.Server, onStateChange func(*model.Server
|
||||
}
|
||||
|
||||
type StateRegexHandler struct {
|
||||
*regexHandler.RegexHandler
|
||||
*regex_handler.RegexHandler
|
||||
test string
|
||||
}
|
||||
|
||||
func NewRegexHandler(str string, test string) *StateRegexHandler {
|
||||
return &StateRegexHandler{
|
||||
RegexHandler: regexHandler.New(str),
|
||||
RegexHandler: regex_handler.New(str),
|
||||
test: test,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user