update caching and server creation

This commit is contained in:
Fran Jurmanović
2025-06-01 19:48:39 +02:00
parent 8a3b11b1ef
commit d57013bb50
26 changed files with 888 additions and 249 deletions

35
go.mod
View File

@@ -2,23 +2,28 @@ module acc-server-manager
go 1.22.3 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 ( require (
github.com/KyleBanks/depth v1.2.1 // indirect 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/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/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference 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/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // 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/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/compress v1.17.0 // indirect
github.com/mailru/easyjson v0.7.7 // 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-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // 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/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/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/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.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/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.23.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 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
View File

@@ -1,43 +1,21 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 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 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 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 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 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 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= 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 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 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 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 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 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ=
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8= 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 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= 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/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 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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-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 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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/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 h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A=
github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4= 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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= 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/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 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= 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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= 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 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 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 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= 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/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.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 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 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 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 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 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= 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=

View File

@@ -20,14 +20,14 @@ type ServerController struct {
// *Fiber.RouterGroup: Fiber Router Group // *Fiber.RouterGroup: Fiber Router Group
// Returns: // Returns:
// *ServerController: Controller for "Server" interactions // *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{ ac := &ServerController{
service: as, service: as,
} }
routeGroups.Server.Get("/", ac.getAll) routeGroups.Server.Get("/", ac.getAll)
routeGroups.Server.Get("/:id", ac.getById) routeGroups.Server.Get("/:id", ac.getById)
routeGroups.Server.Post("/", ac.createServer)
return ac return ac
} }
@@ -67,3 +67,26 @@ func (ac *ServerController) getById(c *fiber.Ctx) error {
} }
return c.JSON(ServerModel) 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)
}

View File

@@ -1,6 +1,7 @@
package model package model
import ( import (
"acc-server-manager/local/utl/logging"
"sync" "sync"
"time" "time"
) )
@@ -89,6 +90,7 @@ type LookupCache struct {
// NewLookupCache creates a new lookup cache // NewLookupCache creates a new lookup cache
func NewLookupCache() *LookupCache { func NewLookupCache() *LookupCache {
logging.Debug("Initializing new LookupCache")
return &LookupCache{ return &LookupCache{
data: make(map[string]interface{}), data: make(map[string]interface{}),
} }
@@ -100,6 +102,11 @@ func (c *LookupCache) Get(key string) (interface{}, bool) {
defer c.RUnlock() defer c.RUnlock()
value, exists := c.data[key] 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 return value, exists
} }
@@ -109,6 +116,7 @@ func (c *LookupCache) Set(key string, value interface{}) {
defer c.Unlock() defer c.Unlock()
c.data[key] = value c.data[key] = value
logging.Debug("Cache SET for key: %s", key)
} }
// Clear removes all entries from the cache // Clear removes all entries from the cache
@@ -117,6 +125,7 @@ func (c *LookupCache) Clear() {
defer c.Unlock() defer c.Unlock()
c.data = make(map[string]interface{}) c.data = make(map[string]interface{})
logging.Debug("Cache CLEARED")
} }
// ConfigEntry represents a cached configuration entry with its update time // 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) { func getConfigFromCache[T any](cache map[string]*ConfigEntry[T], serverID string, expirationTime time.Duration) (*T, bool) {
if entry, ok := cache[serverID]; ok { if entry, ok := cache[serverID]; ok {
if time.Since(entry.UpdatedAt) < expirationTime { if time.Since(entry.UpdatedAt) < expirationTime {
logging.Debug("Config cache HIT for server ID: %s", serverID)
return &entry.Data, true 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 return nil, false
} }
@@ -141,6 +154,7 @@ func updateConfigInCache[T any](cache map[string]*ConfigEntry[T], serverID strin
Data: data, Data: data,
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
} }
logging.Debug("Config cache SET for server ID: %s", serverID)
} }
// ServerConfigCache manages cached server configurations // ServerConfigCache manages cached server configurations
@@ -156,6 +170,7 @@ type ServerConfigCache struct {
// NewServerConfigCache creates a new server configuration cache // NewServerConfigCache creates a new server configuration cache
func NewServerConfigCache(config CacheConfig) *ServerConfigCache { func NewServerConfigCache(config CacheConfig) *ServerConfigCache {
logging.Debug("Initializing new ServerConfigCache with expiration time: %v, throttle time: %v", config.ExpirationTime, config.ThrottleTime)
return &ServerConfigCache{ return &ServerConfigCache{
configuration: make(map[string]*ConfigEntry[Configuration]), configuration: make(map[string]*ConfigEntry[Configuration]),
assistRules: make(map[string]*ConfigEntry[AssistRules]), assistRules: make(map[string]*ConfigEntry[AssistRules]),
@@ -170,6 +185,7 @@ func NewServerConfigCache(config CacheConfig) *ServerConfigCache {
func (c *ServerConfigCache) GetConfiguration(serverID string) (*Configuration, bool) { func (c *ServerConfigCache) GetConfiguration(serverID string) (*Configuration, bool) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
logging.Debug("Attempting to get configuration from cache for server ID: %s", serverID)
return getConfigFromCache(c.configuration, serverID, c.config.ExpirationTime) 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) { func (c *ServerConfigCache) GetAssistRules(serverID string) (*AssistRules, bool) {
c.RLock() c.RLock()
defer c.RUnlock() 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) 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) { func (c *ServerConfigCache) GetEvent(serverID string) (*EventConfig, bool) {
c.RLock() c.RLock()
defer c.RUnlock() 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) 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) { func (c *ServerConfigCache) GetEventRules(serverID string) (*EventRules, bool) {
c.RLock() c.RLock()
defer c.RUnlock() 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) 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) { func (c *ServerConfigCache) GetSettings(serverID string) (*ServerSettings, bool) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
logging.Debug("Attempting to get settings from cache for server ID: %s", serverID)
return getConfigFromCache(c.settings, serverID, c.config.ExpirationTime) 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) { func (c *ServerConfigCache) UpdateConfiguration(serverID string, config Configuration) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Updating configuration cache for server ID: %s", serverID)
updateConfigInCache(c.configuration, serverID, config) 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) { func (c *ServerConfigCache) UpdateAssistRules(serverID string, rules AssistRules) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Updating assist rules cache for server ID: %s", serverID)
updateConfigInCache(c.assistRules, serverID, rules) 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) { func (c *ServerConfigCache) UpdateEvent(serverID string, event EventConfig) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Updating event config cache for server ID: %s", serverID)
updateConfigInCache(c.event, serverID, event) 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) { func (c *ServerConfigCache) UpdateEventRules(serverID string, rules EventRules) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Updating event rules cache for server ID: %s", serverID)
updateConfigInCache(c.eventRules, serverID, rules) 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) { func (c *ServerConfigCache) UpdateSettings(serverID string, settings ServerSettings) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Updating settings cache for server ID: %s", serverID)
updateConfigInCache(c.settings, serverID, settings) updateConfigInCache(c.settings, serverID, settings)
} }
@@ -241,6 +266,7 @@ func (c *ServerConfigCache) InvalidateServerCache(serverID string) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Invalidating all cache entries for server ID: %s", serverID)
delete(c.configuration, serverID) delete(c.configuration, serverID)
delete(c.assistRules, serverID) delete(c.assistRules, serverID)
delete(c.event, serverID) delete(c.event, serverID)
@@ -253,6 +279,7 @@ func (c *ServerConfigCache) Clear() {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
logging.Debug("Clearing all server config cache entries")
c.configuration = make(map[string]*ConfigEntry[Configuration]) c.configuration = make(map[string]*ConfigEntry[Configuration])
c.assistRules = make(map[string]*ConfigEntry[AssistRules]) c.assistRules = make(map[string]*ConfigEntry[AssistRules])
c.event = make(map[string]*ConfigEntry[EventConfig]) c.event = make(map[string]*ConfigEntry[EventConfig])

View File

@@ -3,6 +3,7 @@ package model
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"strconv" "strconv"
"time" "time"
) )
@@ -106,6 +107,26 @@ type Configuration struct {
ConfigVersion IntString `json:"configVersion"` 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 { func (i *IntString) UnmarshalJSON(b []byte) error {
var str string var str string
if err := json.Unmarshal(b, &str); err == nil { if err := json.Unmarshal(b, &str); err == nil {
@@ -136,4 +157,36 @@ func (i IntString) ToString() string {
func (i IntString) ToInt() (int) { func (i IntString) ToInt() (int) {
return int(i) 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
} }

View File

@@ -22,10 +22,11 @@ type Server struct {
Status ServiceStatus `json:"status" gorm:"-"` Status ServiceStatus `json:"status" gorm:"-"`
IP string `gorm:"not null" json:"-"` IP string `gorm:"not null" json:"-"`
Port int `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 ServiceName string `gorm:"not null" json:"serviceName"` // Windows service name
State ServerState `gorm:"-" json:"state"` State ServerState `gorm:"-" json:"state"`
DateCreated time.Time `json:"dateCreated"` DateCreated time.Time `json:"dateCreated"`
FromSteamCMD bool `gorm:"not null; default:true" json:"-"`
} }
type PlayerState struct { type PlayerState struct {
@@ -91,8 +92,8 @@ func (s *Server) BeforeCreate(tx *gorm.DB) error {
if s.ServiceName == "" { if s.ServiceName == "" {
s.ServiceName = s.GenerateServiceName() s.ServiceName = s.GenerateServiceName()
} }
if s.ConfigPath == "" { if s.Path == "" {
s.ConfigPath = s.GenerateConfigPath() s.Path = s.GenerateServerPath(BaseServerPath)
} }
// Set creation date if not set // Set creation date if not set
@@ -113,13 +114,34 @@ func (s *Server) GenerateServiceName() string {
return fmt.Sprintf("%s-%d", ServiceNamePrefix, time.Now().UnixNano()) return fmt.Sprintf("%s-%d", ServiceNamePrefix, time.Now().UnixNano())
} }
// GenerateConfigPath creates the config path based on the service name // GenerateServerPath creates the config path based on the service name
func (s *Server) GenerateConfigPath() string { func (s *Server) GenerateServerPath(steamCMDPath string) string {
// Ensure service name is set // Ensure service name is set
if s.ServiceName == "" { if s.ServiceName == "" {
s.ServiceName = s.GenerateServiceName() 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 { func (s *Server) Validate() error {

View File

@@ -16,4 +16,5 @@ func InitializeRepositories(c *dig.Container) {
c.Provide(NewConfigRepository) c.Provide(NewConfigRepository)
c.Provide(NewLookupRepository) c.Provide(NewLookupRepository)
c.Provide(NewSteamCredentialsRepository) c.Provide(NewSteamCredentialsRepository)
c.Provide(NewSystemConfigRepository)
} }

View 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
}

View File

@@ -19,7 +19,8 @@ type ApiService struct {
} }
func NewApiService(repository *repository.ApiRepository, func NewApiService(repository *repository.ApiRepository,
serverRepository *repository.ServerRepository) *ApiService { serverRepository *repository.ServerRepository,
systemConfigService *SystemConfigService) *ApiService {
return &ApiService{ return &ApiService{
repository: repository, repository: repository,
serverRepository: serverRepository, serverRepository: serverRepository,
@@ -28,7 +29,7 @@ func NewApiService(repository *repository.ApiRepository,
ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks
DefaultStatus: model.StatusRunning, // Default to running if throttled 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) { 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) { 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 { if err != nil {
return "", err return "", err
} }
@@ -138,7 +139,7 @@ func (as *ApiService) StartServer(serviceName string) (string, error) {
} }
func (as *ApiService) StopServer(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 { if err != nil {
return "", err return "", err
} }
@@ -153,7 +154,7 @@ func (as *ApiService) StopServer(serviceName string) (string, error) {
} }
func (as *ApiService) RestartServer(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 { if err != nil {
return "", err return "", err
} }

View File

@@ -6,6 +6,7 @@ import (
"acc-server-manager/local/utl/common" "acc-server-manager/local/utl/common"
"acc-server-manager/local/utl/logging" "acc-server-manager/local/utl/logging"
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -70,6 +71,7 @@ type ConfigService struct {
} }
func NewConfigService(repository *repository.ConfigRepository, serverRepository *repository.ServerRepository) *ConfigService { func NewConfigService(repository *repository.ConfigRepository, serverRepository *repository.ServerRepository) *ConfigService {
logging.Debug("Initializing ConfigService with 5m expiration and 1s throttle")
return &ConfigService{ return &ConfigService{
repository: repository, repository: repository,
serverRepository: serverRepository, serverRepository: serverRepository,
@@ -97,14 +99,19 @@ func (as *ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface
configFile := ctx.Params("file") configFile := ctx.Params("file")
override := ctx.QueryBool("override", false) 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 { if err != nil {
logging.Error("Server not found") logging.Error("Server not found")
return nil, fiber.NewError(404, "Server not found") return nil, fmt.Errorf("server not found")
} }
// Read existing config // Read existing config
configPath := filepath.Join(server.ConfigPath, "\\server\\cfg", configFile) configPath := filepath.Join(server.GetConfigPath(), configFile)
oldData, err := os.ReadFile(configPath) oldData, err := os.ReadFile(configPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -150,8 +157,6 @@ func (as *ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface
return nil, err return nil, err
} }
context := ctx.UserContext()
if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil { if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil {
return nil, err return nil, err
} }
@@ -162,7 +167,7 @@ func (as *ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface
as.serverService.StartAccServerRuntime(server) as.serverService.StartAccServerRuntime(server)
// Log change // Log change
return as.repository.UpdateConfig(context, &model.Config{ return as.repository.UpdateConfig(ctx, &model.Config{
ServerID: uint(serverID), ServerID: uint(serverID),
ConfigFile: configFile, ConfigFile: configFile,
OldConfig: string(oldDataUTF8), OldConfig: string(oldDataUTF8),
@@ -183,6 +188,8 @@ func (as *ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
configFile := ctx.Params("file") configFile := ctx.Params("file")
serverIDStr := strconv.Itoa(serverID) serverIDStr := strconv.Itoa(serverID)
logging.Debug("Getting config for server ID: %d, file: %s", serverID, configFile)
server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID) server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID)
if err != nil { if err != nil {
logging.Error("Server not found") logging.Error("Server not found")
@@ -193,56 +200,77 @@ func (as *ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
switch configFile { switch configFile {
case ConfigurationJson: case ConfigurationJson:
if cached, ok := as.configCache.GetConfiguration(serverIDStr); ok { if cached, ok := as.configCache.GetConfiguration(serverIDStr); ok {
logging.Debug("Returning cached configuration for server ID: %s", serverIDStr)
return cached, nil return cached, nil
} }
case AssistRulesJson: case AssistRulesJson:
if cached, ok := as.configCache.GetAssistRules(serverIDStr); ok { if cached, ok := as.configCache.GetAssistRules(serverIDStr); ok {
logging.Debug("Returning cached assist rules for server ID: %s", serverIDStr)
return cached, nil return cached, nil
} }
case EventJson: case EventJson:
if cached, ok := as.configCache.GetEvent(serverIDStr); ok { if cached, ok := as.configCache.GetEvent(serverIDStr); ok {
logging.Debug("Returning cached event config for server ID: %s", serverIDStr)
return cached, nil return cached, nil
} }
case EventRulesJson: case EventRulesJson:
if cached, ok := as.configCache.GetEventRules(serverIDStr); ok { if cached, ok := as.configCache.GetEventRules(serverIDStr); ok {
logging.Debug("Returning cached event rules for server ID: %s", serverIDStr)
return cached, nil return cached, nil
} }
case SettingsJson: case SettingsJson:
if cached, ok := as.configCache.GetSettings(serverIDStr); ok { if cached, ok := as.configCache.GetSettings(serverIDStr); ok {
logging.Debug("Returning cached settings for server ID: %s", serverIDStr)
return cached, nil 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 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 return nil, err
} }
// Cache the result based on config file type // Cache the loaded config
switch configFile { switch configFile {
case ConfigurationJson: case ConfigurationJson:
if config, ok := decoded.(model.Configuration); ok { as.configCache.UpdateConfiguration(serverIDStr, *config.(*model.Configuration))
as.configCache.UpdateConfiguration(serverIDStr, config)
}
case AssistRulesJson: case AssistRulesJson:
if rules, ok := decoded.(model.AssistRules); ok { as.configCache.UpdateAssistRules(serverIDStr, *config.(*model.AssistRules))
as.configCache.UpdateAssistRules(serverIDStr, rules)
}
case EventJson: case EventJson:
if event, ok := decoded.(model.EventConfig); ok { as.configCache.UpdateEvent(serverIDStr, *config.(*model.EventConfig))
as.configCache.UpdateEvent(serverIDStr, event)
}
case EventRulesJson: case EventRulesJson:
if rules, ok := decoded.(model.EventRules); ok { as.configCache.UpdateEventRules(serverIDStr, *config.(*model.EventRules))
as.configCache.UpdateEventRules(serverIDStr, rules)
}
case SettingsJson: case SettingsJson:
if settings, ok := decoded.(model.ServerSettings); ok { as.configCache.UpdateSettings(serverIDStr, *config.(*model.ServerSettings))
as.configCache.UpdateSettings(serverIDStr, settings)
}
} }
return decoded, nil logging.Debug("Successfully loaded and cached config for server ID: %s, file: %s", serverIDStr, configFile)
return config, nil
} }
// GetConfigs // 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) { func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configurations, error) {
serverIDStr := strconv.Itoa(int(server.ID)) 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{} configs := &model.Configurations{}
// Load configuration // Load configuration
@@ -270,7 +298,7 @@ func (as *ConfigService) LoadConfigs(server *model.Server) (*model.Configuration
configs.Configuration = *cached configs.Configuration = *cached
} else { } else {
logging.Debug("Loading configuration from disk for server %s", serverIDStr) 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 { if err != nil {
logging.Error("Failed to load configuration for server %s: %v", serverIDStr, err) logging.Error("Failed to load configuration for server %s: %v", serverIDStr, err)
return nil, fmt.Errorf("failed to load configuration: %v", 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 configs.AssistRules = *cached
} else { } else {
logging.Debug("Loading assist rules from disk for server %s", serverIDStr) 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 { if err != nil {
logging.Error("Failed to load assist rules for server %s: %v", serverIDStr, err) logging.Error("Failed to load assist rules for server %s: %v", serverIDStr, err)
return nil, fmt.Errorf("failed to load assist rules: %v", 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 configs.Event = *cached
} else { } else {
logging.Debug("Loading event config from disk for server %s", serverIDStr) 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 { if err != nil {
logging.Error("Failed to load event config for server %s: %v", serverIDStr, err) logging.Error("Failed to load event config for server %s: %v", serverIDStr, err)
return nil, fmt.Errorf("failed to load event config: %v", 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 configs.EventRules = *cached
} else { } else {
logging.Debug("Loading event rules from disk for server %s", serverIDStr) 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 { if err != nil {
logging.Error("Failed to load event rules for server %s: %v", serverIDStr, err) logging.Error("Failed to load event rules for server %s: %v", serverIDStr, err)
return nil, fmt.Errorf("failed to load event rules: %v", 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 configs.Settings = *cached
} else { } else {
logging.Debug("Loading settings from disk for server %s", serverIDStr) 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 { if err != nil {
logging.Error("Failed to load settings for server %s: %v", serverIDStr, err) logging.Error("Failed to load settings for server %s: %v", serverIDStr, err)
return nil, fmt.Errorf("failed to load settings: %v", 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) { 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) oldData, err := os.ReadFile(configPath)
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
@@ -419,7 +447,7 @@ func (as *ConfigService) GetEventConfig(server *model.Server) (*model.EventConfi
return cached, nil return cached, nil
} }
event, err := mustDecode[model.EventConfig](EventJson, server.ConfigPath) event, err := mustDecode[model.EventConfig](EventJson, server.GetConfigPath())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -433,10 +461,27 @@ func (as *ConfigService) GetConfiguration(server *model.Server) (*model.Configur
return cached, nil return cached, nil
} }
config, err := mustDecode[model.Configuration](ConfigurationJson, server.ConfigPath) config, err := mustDecode[model.Configuration](ConfigurationJson, server.GetConfigPath())
if err != nil { if err != nil {
return nil, err return nil, err
} }
as.configCache.UpdateConfiguration(serverIDStr, config) as.configCache.UpdateConfiguration(serverIDStr, config)
return &config, nil 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
}

View File

@@ -21,16 +21,16 @@ func NewFirewallService() *FirewallService {
func (s *FirewallService) CreateServerRules(serverName string, tcpPorts, udpPorts []int) error { func (s *FirewallService) CreateServerRules(serverName string, tcpPorts, udpPorts []int) error {
for _, port := range tcpPorts { for _, port := range tcpPorts {
ruleName := fmt.Sprintf("%s-TCP-%d", serverName, port) ruleName := fmt.Sprintf("\"%s-TCP-%d\"", serverName, port)
builder := command.NewCommandBuilder(). builder := command.NewCommandBuilder().
Add("advfirewall"). Add("advfirewall").
Add("firewall"). Add("firewall").
Add("add"). Add("add").
Add("rule"). Add("rule").
AddPair("name", ruleName). AddFlag("name", ruleName).
AddPair("dir", "in"). AddFlag("dir", "in").
AddPair("action", "allow"). AddFlag("action", "allow").
AddPair("protocol", "TCP"). AddFlag("protocol", "TCP").
AddFlag("localport", port) AddFlag("localport", port)
if err := s.executor.ExecuteWithBuilder(builder); err != nil { if err := s.executor.ExecuteWithBuilder(builder); err != nil {
@@ -46,10 +46,10 @@ func (s *FirewallService) CreateServerRules(serverName string, tcpPorts, udpPort
Add("firewall"). Add("firewall").
Add("add"). Add("add").
Add("rule"). Add("rule").
AddPair("name", ruleName). AddFlag("name", ruleName).
AddPair("dir", "in"). AddFlag("dir", "in").
AddPair("action", "allow"). AddFlag("action", "allow").
AddPair("protocol", "UDP"). AddFlag("protocol", "UDP").
AddFlag("localport", port) AddFlag("localport", port)
if err := s.executor.ExecuteWithBuilder(builder); err != nil { 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 { func (s *FirewallService) DeleteServerRules(serverName string, tcpPorts, udpPorts []int) error {
for _, port := range tcpPorts { for _, port := range tcpPorts {
ruleName := fmt.Sprintf("%s-TCP-%d", serverName, port) ruleName := fmt.Sprintf("\"%s-TCP-%d\"", serverName, port)
builder := command.NewCommandBuilder(). builder := command.NewCommandBuilder().
Add("advfirewall"). Add("advfirewall").
Add("firewall"). Add("firewall").
Add("delete"). Add("delete").
Add("rule"). Add("rule").
AddPair("name", ruleName) AddFlag("name", ruleName)
if err := s.executor.ExecuteWithBuilder(builder); err != nil { if err := s.executor.ExecuteWithBuilder(builder); err != nil {
return fmt.Errorf("failed to delete TCP firewall rule for port %d: %v", port, err) 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 { for _, port := range udpPorts {
ruleName := fmt.Sprintf("%s-UDP-%d", serverName, port) ruleName := fmt.Sprintf("\"%s-UDP-%d\"", serverName, port)
builder := command.NewCommandBuilder(). builder := command.NewCommandBuilder().
Add("advfirewall"). Add("advfirewall").
Add("firewall"). Add("firewall").
Add("delete"). Add("delete").
Add("rule"). Add("rule").
AddPair("name", ruleName) AddFlag("name", ruleName)
if err := s.executor.ExecuteWithBuilder(builder); err != nil { if err := s.executor.ExecuteWithBuilder(builder); err != nil {
return fmt.Errorf("failed to delete UDP firewall rule for port %d: %v", port, err) return fmt.Errorf("failed to delete UDP firewall rule for port %d: %v", port, err)

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"acc-server-manager/local/model" "acc-server-manager/local/model"
"acc-server-manager/local/repository" "acc-server-manager/local/repository"
"acc-server-manager/local/utl/logging"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@@ -12,10 +13,11 @@ type LookupService struct {
cache *model.LookupCache cache *model.LookupCache
} }
func NewLookupService(repository *repository.LookupRepository) *LookupService { func NewLookupService(repository *repository.LookupRepository, cache *model.LookupCache) *LookupService {
logging.Debug("Initializing LookupService")
return &LookupService{ return &LookupService{
repository: repository, repository: repository,
cache: model.NewLookupCache(), cache: cache,
} }
} }
@@ -24,6 +26,7 @@ func (s *LookupService) GetTracks(ctx *fiber.Ctx) (interface{}, error) {
return cached, nil return cached, nil
} }
logging.Debug("Loading tracks from database")
tracks := s.repository.GetTracks(ctx.UserContext()) tracks := s.repository.GetTracks(ctx.UserContext())
s.cache.Set("tracks", tracks) s.cache.Set("tracks", tracks)
return tracks, nil return tracks, nil
@@ -34,6 +37,7 @@ func (s *LookupService) GetCarModels(ctx *fiber.Ctx) (interface{}, error) {
return cached, nil return cached, nil
} }
logging.Debug("Loading car models from database")
cars := s.repository.GetCarModels(ctx.UserContext()) cars := s.repository.GetCarModels(ctx.UserContext())
s.cache.Set("cars", cars) s.cache.Set("cars", cars)
return cars, nil return cars, nil
@@ -44,6 +48,7 @@ func (s *LookupService) GetDriverCategories(ctx *fiber.Ctx) (interface{}, error)
return cached, nil return cached, nil
} }
logging.Debug("Loading driver categories from database")
categories := s.repository.GetDriverCategories(ctx.UserContext()) categories := s.repository.GetDriverCategories(ctx.UserContext())
s.cache.Set("drivers", categories) s.cache.Set("drivers", categories)
return categories, nil return categories, nil
@@ -54,6 +59,7 @@ func (s *LookupService) GetCupCategories(ctx *fiber.Ctx) (interface{}, error) {
return cached, nil return cached, nil
} }
logging.Debug("Loading cup categories from database")
categories := s.repository.GetCupCategories(ctx.UserContext()) categories := s.repository.GetCupCategories(ctx.UserContext())
s.cache.Set("cups", categories) s.cache.Set("cups", categories)
return categories, nil return categories, nil
@@ -64,6 +70,7 @@ func (s *LookupService) GetSessionTypes(ctx *fiber.Ctx) (interface{}, error) {
return cached, nil return cached, nil
} }
logging.Debug("Loading session types from database")
types := s.repository.GetSessionTypes(ctx.UserContext()) types := s.repository.GetSessionTypes(ctx.UserContext())
s.cache.Set("sessions", types) s.cache.Set("sessions", types)
return types, nil return types, nil
@@ -71,14 +78,17 @@ func (s *LookupService) GetSessionTypes(ctx *fiber.Ctx) (interface{}, error) {
// ClearCache clears all cached lookup data // ClearCache clears all cached lookup data
func (s *LookupService) ClearCache() { func (s *LookupService) ClearCache() {
logging.Debug("Clearing all lookup cache data")
s.cache.Clear() s.cache.Clear()
} }
// PreloadCache loads all lookup data into cache // PreloadCache loads all lookup data into cache
func (s *LookupService) PreloadCache(ctx *fiber.Ctx) { func (s *LookupService) PreloadCache(ctx *fiber.Ctx) {
logging.Debug("Preloading all lookup cache data")
s.GetTracks(ctx) s.GetTracks(ctx)
s.GetCarModels(ctx) s.GetCarModels(ctx)
s.GetDriverCategories(ctx) s.GetDriverCategories(ctx)
s.GetCupCategories(ctx) s.GetCupCategories(ctx)
s.GetSessionTypes(ctx) s.GetSessionTypes(ctx)
logging.Debug("Completed preloading lookup cache data")
} }

View File

@@ -12,22 +12,30 @@ import (
"sync" "sync"
"time" "time"
"acc-server-manager/local/utl/network"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
const (
DefaultStartPort = 9600
RequiredPortCount = 1 // Update this if ACC needs more ports
)
type ServerService struct { type ServerService struct {
repository *repository.ServerRepository repository *repository.ServerRepository
stateHistoryRepo *repository.StateHistoryRepository stateHistoryRepo *repository.StateHistoryRepository
apiService *ApiService apiService *ApiService
instances sync.Map // Track instances per server
configService *ConfigService 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 lastInsertTimes sync.Map // Track last insert time per server
debouncers sync.Map // Track debounce timers per server debouncers sync.Map // Track debounce timers per server
logTailers sync.Map // Track log tailers per server logTailers sync.Map // Track log tailers per server
sessionIDs sync.Map // Track current session ID per server sessionIDs sync.Map // Track current session ID per server
steamService *SteamService
windowsService *WindowsService
firewallService *FirewallService
} }
type pendingState struct { 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 // Start tailing in a goroutine that handles file creation/deletion
go func() { go func() {
logPath := filepath.Join(server.ConfigPath, "\\server\\log\\server.log") logPath := filepath.Join(server.GetLogPath(), "server.log")
tailer := tracking.NewLogTailer(logPath, instance.HandleLogLine) tailer := tracking.NewLogTailer(logPath, instance.HandleLogLine)
s.logTailers.Store(server.ID, tailer) 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 { func NewServerService(
steamService := NewSteamService(steamCredentialsRepo) repository *repository.ServerRepository,
stateHistoryRepo *repository.StateHistoryRepository,
apiService *ApiService,
configService *ConfigService,
steamService *SteamService,
windowsService *WindowsService,
firewallService *FirewallService,
systemConfigService *SystemConfigService,
) *ServerService {
service := &ServerService{ service := &ServerService{
repository: repository, repository: repository,
stateHistoryRepo: stateHistoryRepo,
apiService: apiService, apiService: apiService,
configService: configService, configService: configService,
stateHistoryRepo: stateHistoryRepo,
steamService: steamService, steamService: steamService,
windowsService: NewWindowsService(), windowsService: windowsService,
firewallService: NewFirewallService(), firewallService: firewallService,
systemConfigService: systemConfigService,
} }
// Initialize instances for all servers // Initialize server instances
servers, err := repository.GetAll(context.Background(), &model.ServerFilter{}) servers, err := repository.GetAll(context.Background(), &model.ServerFilter{})
if err != nil { if err != nil {
logging.Error("Failed to get servers: %v", err) 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 // Get configs using helper methods
event, err := s.configService.GetEventConfig(server) event, err := s.configService.GetEventConfig(server)
if err != nil { if err != nil {
event = &model.EventConfig{}
logging.Error("Failed to get event config for server %d: %v", server.ID, err) logging.Error("Failed to get event config for server %d: %v", server.ID, err)
return
} }
configuration, err := s.configService.GetConfiguration(server) configuration, err := s.configService.GetConfiguration(server)
if err != nil { if err != nil {
configuration = &model.Configuration{}
logging.Error("Failed to get configuration for server %d: %v", server.ID, err) logging.Error("Failed to get configuration for server %d: %v", server.ID, err)
return
} }
if instance, ok := s.instances.Load(server.ID); ok { 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) { func (s *ServerService) handleStateChange(server *model.Server, state *model.ServerState) {
// Update session duration when session changes // Update session duration when session changes
s.updateSessionDuration(server, state.Session) s.updateSessionDuration(server, state.Session)
@@ -309,34 +337,47 @@ func (s *ServerService) CreateServer(ctx *fiber.Ctx, server *model.Server) error
} }
// Install server using SteamCMD // 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) return fmt.Errorf("failed to install server: %v", err)
} }
// Create Windows service // Create Windows service with correct paths
execPath := filepath.Join(server.ConfigPath, "accServer.exe") execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
if err := s.windowsService.CreateService(server.ServiceName, execPath, server.ConfigPath, nil); err != nil { serverWorkingDir := filepath.Join(server.GetServerPath(), "server")
if err := s.windowsService.CreateService(ctx.UserContext(), server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
// Cleanup on failure // Cleanup on failure
s.steamService.UninstallServer(server.ConfigPath) s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to create Windows service: %v", err) return fmt.Errorf("failed to create Windows service: %v", err)
} }
// Create firewall rules s.configureFirewall(server)
tcpPorts := []int{9600} // Add all required TCP ports ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
udpPorts := []int{9600} // Add all required UDP ports 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 { if err := s.firewallService.CreateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
// Cleanup on failure // Cleanup on failure
s.windowsService.DeleteService(server.ServiceName) s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName)
s.steamService.UninstallServer(server.ConfigPath) s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to create firewall rules: %v", err) 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 // Insert server into database
if err := s.repository.Insert(ctx.UserContext(), server); err != nil { if err := s.repository.Insert(ctx.UserContext(), server); err != nil {
// Cleanup on failure // Cleanup on failure
s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts) s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
s.windowsService.DeleteService(server.ServiceName) s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName)
s.steamService.UninstallServer(server.ConfigPath) s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to insert server into database: %v", err) 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 // 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) logging.Error("Failed to delete Windows service: %v", err)
} }
// Remove firewall rules // Remove firewall rules
tcpPorts := []int{9600} // Add all required TCP ports configuration, err := s.configService.GetConfiguration(server)
udpPorts := []int{9600} // Add all required UDP ports 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 { if err := s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
logging.Error("Failed to delete firewall rules: %v", err) logging.Error("Failed to delete firewall rules: %v", err)
} }
// Uninstall server files // 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) 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 // Update server files if path changed
if existingServer.ConfigPath != server.ConfigPath { if existingServer.Path != server.Path {
if err := s.steamService.InstallServer(ctx.UserContext(), server.ConfigPath); err != nil { if err := s.steamService.InstallServer(ctx.UserContext(), server.Path); err != nil {
return fmt.Errorf("failed to install server to new location: %v", err) return fmt.Errorf("failed to install server to new location: %v", err)
} }
// Clean up old installation // 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) logging.Error("Failed to remove old server installation: %v", err)
} }
} }
// Update Windows service if necessary // Update Windows service if necessary
if existingServer.ServiceName != server.ServiceName || existingServer.ConfigPath != server.ConfigPath { if existingServer.ServiceName != server.ServiceName || existingServer.Path != server.Path {
execPath := filepath.Join(server.ConfigPath, "accServer.exe") execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
if err := s.windowsService.UpdateService(server.ServiceName, execPath, server.ConfigPath, nil); err != nil { 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) return fmt.Errorf("failed to update Windows service: %v", err)
} }
} }
// Update firewall rules if service name changed // Update firewall rules if service name changed
if existingServer.ServiceName != server.ServiceName { if existingServer.ServiceName != server.ServiceName {
tcpPorts := []int{9600} // Add all required TCP ports if err := s.configureFirewall(server); err != nil {
udpPorts := []int{9600} // Add all required UDP ports
if err := s.firewallService.UpdateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
return fmt.Errorf("failed to update firewall rules: %v", err) return fmt.Errorf("failed to update firewall rules: %v", err)
} }
} }
@@ -436,5 +481,50 @@ func (s *ServerService) UpdateServer(ctx *fiber.Ctx, server *model.Server) error
// Restart server runtime // Restart server runtime
s.StartAccServerRuntime(server) s.StartAccServerRuntime(server)
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 return nil
} }

View File

@@ -1,6 +1,7 @@
package service package service
import ( import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository" "acc-server-manager/local/repository"
"acc-server-manager/local/utl/logging" "acc-server-manager/local/utl/logging"
"context" "context"
@@ -14,25 +15,51 @@ import (
// Args: // Args:
// *dig.Container: Dig Container // *dig.Container: Dig Container
func InitializeServices(c *dig.Container) { func InitializeServices(c *dig.Container) {
logging.Debug("Initializing repositories")
repository.InitializeRepositories(c) 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(NewServerService)
c.Provide(NewStateHistoryService) c.Provide(NewStateHistoryService)
c.Provide(NewApiService) c.Provide(NewApiService)
c.Provide(NewConfigService) c.Provide(NewConfigService)
c.Provide(NewLookupService) 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) api.SetServerService(server)
config.SetServerService(server) config.SetServerService(server)
logging.Debug("Initializing lookup data cache")
// Initialize lookup data using repository directly // Initialize lookup data using repository directly
lookup.cache.Set("tracks", lookup.repository.GetTracks(context.Background())) lookup.cache.Set("tracks", lookup.repository.GetTracks(context.Background()))
lookup.cache.Set("cars", lookup.repository.GetCarModels(context.Background())) lookup.cache.Set("cars", lookup.repository.GetCarModels(context.Background()))
lookup.cache.Set("drivers", lookup.repository.GetDriverCategories(context.Background())) lookup.cache.Set("drivers", lookup.repository.GetDriverCategories(context.Background()))
lookup.cache.Set("cups", lookup.repository.GetCupCategories(context.Background())) lookup.cache.Set("cups", lookup.repository.GetCupCategories(context.Background()))
lookup.cache.Set("sessions", lookup.repository.GetSessionTypes(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 { if err != nil {
logging.Panic("unable to initialize services: " + err.Error()) logging.Panic("unable to initialize services: " + err.Error())
} }
logging.Debug("Completed service initialization")
} }

View File

@@ -12,22 +12,23 @@ import (
) )
const ( const (
SteamCMDPath = "steamcmd"
ACCServerAppID = "1430110" ACCServerAppID = "1430110"
) )
type SteamService struct { type SteamService struct {
executor *command.CommandExecutor executor *command.CommandExecutor
repository *repository.SteamCredentialsRepository repository *repository.SteamCredentialsRepository
configService *SystemConfigService
} }
func NewSteamService(repository *repository.SteamCredentialsRepository) *SteamService { func NewSteamService(repository *repository.SteamCredentialsRepository, configService *SystemConfigService) *SteamService {
return &SteamService{ return &SteamService{
executor: &command.CommandExecutor{ executor: &command.CommandExecutor{
ExePath: "powershell", ExePath: "powershell",
LogOutput: true, 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) 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 // Check if SteamCMD exists
if _, err := os.Stat(SteamCMDPath); !os.IsNotExist(err) { if _, err := os.Stat(steamCMDPath); !os.IsNotExist(err) {
return nil 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 // Download and install SteamCMD
logging.Info("Downloading SteamCMD...") logging.Info("Downloading SteamCMD...")
if err := s.executor.Execute("-Command", if err := s.executor.Execute("-Command",
@@ -58,7 +72,7 @@ func (s *SteamService) ensureSteamCMD() error {
// Extract SteamCMD // Extract SteamCMD
logging.Info("Extracting SteamCMD...") logging.Info("Extracting SteamCMD...")
if err := s.executor.Execute("-Command", 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) 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 { 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 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 // 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) 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) 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 // Build SteamCMD command
args := []string{ args := []string{
"-nologo", "-nologo",
"-noprofile", "-noprofile",
filepath.Join(SteamCMDPath, "steamcmd.exe"), steamCMDPath,
"+force_install_dir", installPath, "+force_install_dir", absPath,
"+login", "+login",
} }
@@ -108,11 +135,24 @@ func (s *SteamService) InstallServer(ctx context.Context, installPath string) er
) )
// Run SteamCMD // 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 { 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 return nil
} }

View 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
}

View File

@@ -3,35 +3,47 @@ package service
import ( import (
"acc-server-manager/local/utl/command" "acc-server-manager/local/utl/command"
"acc-server-manager/local/utl/logging" "acc-server-manager/local/utl/logging"
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings" "strings"
) )
const ( const (
NSSMPath = ".\\nssm" NSSMPath = ".\\nssm.exe"
) )
type WindowsService struct { type WindowsService struct {
executor *command.CommandExecutor executor *command.CommandExecutor
configService *SystemConfigService
} }
func NewWindowsService() *WindowsService { func NewWindowsService(configService *SystemConfigService) *WindowsService {
return &WindowsService{ return &WindowsService{
executor: &command.CommandExecutor{ executor: &command.CommandExecutor{
ExePath: "powershell", ExePath: "powershell",
LogOutput: true, LogOutput: true,
}, },
configService: configService,
} }
} }
// executeNSSM runs an NSSM command through PowerShell with elevation // 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 // 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...) output, err := s.executor.ExecuteWithOutput(nssmArgs...)
if err != nil { 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 return "", err
} }
@@ -45,42 +57,54 @@ func (s *WindowsService) executeNSSM(args ...string) (string, error) {
// Service Installation/Configuration Methods // Service Installation/Configuration Methods
func (s *WindowsService) CreateService(serviceName, execPath, workingDir string, args []string) error { func (s *WindowsService) CreateService(ctx context.Context, serviceName, execPath, workingDir string, args []string) error {
// Ensure paths are absolute // Ensure paths are absolute and properly formatted for Windows
absExecPath, err := filepath.Abs(execPath) absExecPath, err := filepath.Abs(execPath)
if err != nil { if err != nil {
return fmt.Errorf("failed to get absolute path for executable: %v", err) return fmt.Errorf("failed to get absolute path for executable: %v", err)
} }
absExecPath = filepath.Clean(absExecPath)
absWorkingDir, err := filepath.Abs(workingDir) absWorkingDir, err := filepath.Abs(workingDir)
if err != nil { if err != nil {
return fmt.Errorf("failed to get absolute path for working directory: %v", err) 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 // 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) 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 // Set arguments if provided
if len(args) > 0 { if len(args) > 0 {
cmdArgs := append([]string{"set", serviceName, "AppParameters"}, args...) 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) 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) logging.Info("Created Windows service: %s", serviceName)
return nil return nil
} }
func (s *WindowsService) DeleteService(serviceName string) error { func (s *WindowsService) DeleteService(ctx context.Context, serviceName string) error {
if _, err := s.executeNSSM("remove", serviceName, "confirm"); err != nil { if _, err := s.executeNSSM(ctx, "remove", serviceName, "confirm"); err != nil {
return fmt.Errorf("failed to remove service: %v", err) return fmt.Errorf("failed to remove service: %v", err)
} }
@@ -88,36 +112,36 @@ func (s *WindowsService) DeleteService(serviceName string) error {
return nil 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 // First remove the existing service
if err := s.DeleteService(serviceName); err != nil { if err := s.DeleteService(ctx, serviceName); err != nil {
return err return err
} }
// Then create it again with new parameters // 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 // Service Control Methods
func (s *WindowsService) Status(serviceName string) (string, error) { func (s *WindowsService) Status(ctx context.Context, serviceName string) (string, error) {
return s.executeNSSM("status", serviceName) return s.executeNSSM(ctx, "status", serviceName)
} }
func (s *WindowsService) Start(serviceName string) (string, error) { func (s *WindowsService) Start(ctx context.Context, serviceName string) (string, error) {
return s.executeNSSM("start", serviceName) return s.executeNSSM(ctx, "start", serviceName)
} }
func (s *WindowsService) Stop(serviceName string) (string, error) { func (s *WindowsService) Stop(ctx context.Context, serviceName string) (string, error) {
return s.executeNSSM("stop", serviceName) 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 // First stop the service
if _, err := s.Stop(serviceName); err != nil { if _, err := s.Stop(ctx, serviceName); err != nil {
return "", err return "", err
} }
// Then start it again // Then start it again
return s.Start(serviceName) return s.Start(ctx, serviceName)
} }

View File

@@ -3,6 +3,7 @@ package db
import ( import (
"acc-server-manager/local/model" "acc-server-manager/local/model"
"acc-server-manager/local/utl/logging" "acc-server-manager/local/utl/logging"
"time"
"go.uber.org/dig" "go.uber.org/dig"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
@@ -60,10 +61,17 @@ func Migrate(db *gorm.DB) {
if err != nil { if err != nil {
logging.Panic("failed to migrate model.StateHistory") 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"}) db.FirstOrCreate(&model.ApiModel{Api: "Works"})
Seed(db) Seed(db)
} }
func Seed(db *gorm.DB) error { func Seed(db *gorm.DB) error {
@@ -85,15 +93,38 @@ func Seed(db *gorm.DB) error {
if err := seedServers(db); err != nil { if err := seedServers(db); err != nil {
return err 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 return nil
} }
func seedServers(db *gorm.DB) error { func seedServers(db *gorm.DB) error {
servers := []model.Server{ servers := []model.Server{
{ID: 1, Name: "ACC Server - Barcelona", ServiceName: "ACC-Barcelona", ConfigPath: "C:\\steamcmd\\acc"}, {ID: 1, Name: "ACC Server - Barcelona", ServiceName: "ACC-Barcelona", Path: "C:\\steamcmd\\acc", FromSteamCMD: true},
{ID: 2, Name: "ACC Server - Monza", ServiceName: "ACC-Monza", ConfigPath: "C:\\steamcmd\\acc2"}, {ID: 2, Name: "ACC Server - Monza", ServiceName: "ACC-Monza", Path: "C:\\steamcmd\\acc2", FromSteamCMD: true},
{ID: 3, Name: "ACC Server - Spa", ServiceName: "ACC-Spa", ConfigPath: "C:\\steamcmd\\acc3"}, {ID: 3, Name: "ACC Server - Spa", ServiceName: "ACC-Spa", Path: "C:\\steamcmd\\acc3", FromSteamCMD: true},
{ID: 4, Name: "ACC Server - League", ServiceName: "ACC-League", ConfigPath: "C:\\steamcmd\\acc-league"}, {ID: 4, Name: "ACC Server - League", ServiceName: "ACC-League", Path: "C:\\steamcmd\\acc-league", FromSteamCMD: true},
} }
for _, track := range servers { for _, track := range servers {
@@ -203,3 +234,41 @@ func seedSessionTypes(db *gorm.DB) error {
} }
return nil 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
}

View File

@@ -136,8 +136,9 @@ func Panic(format string) {
// RecoverAndLog recovers from panics and logs them // RecoverAndLog recovers from panics and logs them
func RecoverAndLog() { 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 // Get stack trace
buf := make([]byte, 4096) buf := make([]byte, 4096)
n := runtime.Stack(buf, false) n := runtime.Stack(buf, false)

87
local/utl/network/port.go Normal file
View 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)
}

View File

@@ -1,4 +1,4 @@
package regexHandler package regex_handler
import ( import (
"acc-server-manager/local/model" "acc-server-manager/local/model"

View File

@@ -2,7 +2,7 @@ package tracking
import ( import (
"acc-server-manager/local/model" "acc-server-manager/local/model"
"acc-server-manager/local/utl/regexHandler" "acc-server-manager/local/utl/regex_handler"
"bufio" "bufio"
"os" "os"
"strconv" "strconv"
@@ -36,13 +36,13 @@ func NewAccServerInstance(server *model.Server, onStateChange func(*model.Server
} }
type StateRegexHandler struct { type StateRegexHandler struct {
*regexHandler.RegexHandler *regex_handler.RegexHandler
test string test string
} }
func NewRegexHandler(str string, test string) *StateRegexHandler { func NewRegexHandler(str string, test string) *StateRegexHandler {
return &StateRegexHandler{ return &StateRegexHandler{
RegexHandler: regexHandler.New(str), RegexHandler: regex_handler.New(str),
test: test, test: test,
} }
} }