diff --git a/.github/workflows/v10-deployment-pipeline.yml b/.github/workflows/v10-deployment-pipeline.yml index 3e6c9c17c..cc3eee7ab 100644 --- a/.github/workflows/v10-deployment-pipeline.yml +++ b/.github/workflows/v10-deployment-pipeline.yml @@ -67,7 +67,7 @@ jobs: - name: Check out code into the right branch uses: actions/checkout@v4 - - name: Build Linux Agent + - name: Build Linux Binaries (Agent & Updater) env: GOOS: linux GOARCH: amd64 @@ -75,7 +75,10 @@ jobs: cd ${{ github.workspace }}/agent go build -o utmstack_agent_service -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . - - name: Build Windows Agent (amd64) + cd ${{ github.workspace }}/agent/updater + go build -o utmstack_updater_service . + + - name: Build Windows Binaries (amd64) env: GOOS: windows GOARCH: amd64 @@ -83,7 +86,10 @@ jobs: cd ${{ github.workspace }}/agent go build -o utmstack_agent_service.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . - - name: Build Windows Agent (arm64) + cd ${{ github.workspace }}/agent/updater + go build -o utmstack_updater_service.exe . + + - name: Build Windows Binaries (arm64) env: GOOS: windows GOARCH: arm64 @@ -91,12 +97,20 @@ jobs: cd ${{ github.workspace }}/agent go build -o utmstack_agent_service_arm64.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + cd ${{ github.workspace }}/agent/updater + go build -o utmstack_updater_service_arm64.exe . + - name: Sign Windows Agents run: | cd ${{ github.workspace }}/agent signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_agent_service.exe" signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_agent_service_arm64.exe" + cd ${{ github.workspace }}/agent/updater + signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_updater_service.exe" + signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f "${{ vars.SIGN_CERT }}" /csp "eToken Base Cryptographic Provider" /k "[{{${{ secrets.SIGN_KEY }}}}]=${{ secrets.SIGN_CONTAINER }}" "utmstack_updater_service_arm64.exe" + + - name: Upload signed binaries as artifacts uses: actions/upload-artifact@v4 with: @@ -105,6 +119,9 @@ jobs: ${{ github.workspace }}/agent/utmstack_agent_service ${{ github.workspace }}/agent/utmstack_agent_service.exe ${{ github.workspace }}/agent/utmstack_agent_service_arm64.exe + ${{ github.workspace }}/agent/updater/utmstack_updater_service + ${{ github.workspace }}/agent/updater/utmstack_updater_service.exe + ${{ github.workspace }}/agent/updater/utmstack_updater_service_arm64.exe retention-days: 1 build_agent_manager: @@ -140,6 +157,10 @@ jobs: cp "${{ github.workspace }}/agent/utmstack_agent_service.exe" ./dependencies/agent/ cp "${{ github.workspace }}/agent/utmstack_agent_service_arm64.exe" ./dependencies/agent/ cp "${{ github.workspace }}/agent/version.json" ./dependencies/agent/ + + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service.exe" ./dependencies/agent/ + cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_arm64.exe" ./dependencies/agent/ - name: Login to GitHub Container Registry uses: docker/login-action@v3 diff --git a/agent/config/const.go b/agent/config/const.go index 94960361f..f7527d214 100644 --- a/agent/config/const.go +++ b/agent/config/const.go @@ -13,6 +13,10 @@ type ProtoPort struct { TCP string } +const ( + SERVICE_UPDATER_NAME = "UTMStackUpdater" +) + var ( REPLACE_KEY string @@ -41,7 +45,6 @@ var ( IntegrationKeyPath = filepath.Join(utils.GetMyPath(), "certs", "integration.key") IntegrationCAPath = filepath.Join(utils.GetMyPath(), "certs", "integration-ca.crt") - // MaxConnectionTime = 120 * time.Second // SERV_NAME = "UTMStackAgent" // SERV_LOG = "utmstack_agent.log" diff --git a/agent/config/linux_amd64.go b/agent/config/linux_amd64.go index 76c777a2b..dcd1170c4 100644 --- a/agent/config/linux_amd64.go +++ b/agent/config/linux_amd64.go @@ -4,7 +4,7 @@ package config var ( - UpdaterSelf = "utmstack_updater_self%s" ServiceFile = "utmstack_agent_service%s" + UpdaterFile = "utmstack_updater_service%s" DependFiles = []string{"utmstack_agent_dependencies_linux.zip"} ) diff --git a/agent/config/linux_arm64.go b/agent/config/linux_arm64.go index 138c70002..3e0e5e3be 100644 --- a/agent/config/linux_arm64.go +++ b/agent/config/linux_arm64.go @@ -4,7 +4,7 @@ package config var ( - UpdaterSelf = "utmstack_updater_self_arm64%s" ServiceFile = "utmstack_agent_service_arm64%s" + UpdaterFile = "utmstack_updater_service_arm64%s" DependFiles = []string{"utmstack_agent_dependencies_linux_arm64.zip"} ) diff --git a/agent/config/macos.go b/agent/config/macos.go index feef8b2fb..ed03e70d8 100644 --- a/agent/config/macos.go +++ b/agent/config/macos.go @@ -4,7 +4,7 @@ package config var ( - UpdaterSelf = "utmstack_updater_self%s" ServiceFile = "utmstack_agent_service%s" + UpdaterFile = "utmstack_updater_service%s" DependFiles = []string{} ) diff --git a/agent/config/windows_amd64.go b/agent/config/windows_amd64.go index af4490cd0..f055d2dca 100644 --- a/agent/config/windows_amd64.go +++ b/agent/config/windows_amd64.go @@ -4,7 +4,7 @@ package config var ( - UpdaterSelf = "utmstack_updater_self%s.exe" ServiceFile = "utmstack_agent_service%s.exe" + UpdaterFile = "utmstack_updater_service%s.exe" DependFiles = []string{"utmstack_agent_dependencies_windows.zip"} ) diff --git a/agent/config/windows_arm64.go b/agent/config/windows_arm64.go index aa136d400..9bd38ffca 100644 --- a/agent/config/windows_arm64.go +++ b/agent/config/windows_arm64.go @@ -4,7 +4,7 @@ package config var ( - UpdaterSelf = "utmstack_updater_self_arm64%s.exe" ServiceFile = "utmstack_agent_service_arm64%s.exe" + UpdaterFile = "utmstack_updater_service_arm64%s.exe" DependFiles = []string{"utmstack_agent_dependencies_windows_arm64.zip"} ) diff --git a/agent/logs/utmstack_agent.log b/agent/logs/utmstack_agent.log deleted file mode 100644 index 602489253..000000000 --- a/agent/logs/utmstack_agent.log +++ /dev/null @@ -1 +0,0 @@ -2025-04-16T13:37:14.854518Z ERROR f18ed04e-83e4-49d7-b82b-b96385b8b0bd /Users/osmany/Projects/UTMStack/agent/serv/service.go 36 error getting config: error reading config file: open /Users/osmany/Projects/UTMStack/agent/config.yml: no such file or directory diff --git a/agent/models/version.go b/agent/models/version.go index ed01f06e4..1ee6e0648 100644 --- a/agent/models/version.go +++ b/agent/models/version.go @@ -1,5 +1,6 @@ package models type Version struct { - Version string `json:"version"` + Version string `json:"version"` + UpdaterVersion string `json:"updater_version"` } diff --git a/agent/self/config/const.go b/agent/self/config/const.go deleted file mode 100644 index 2d89a1ee5..000000000 --- a/agent/self/config/const.go +++ /dev/null @@ -1,6 +0,0 @@ -package config - -const ( - SERV_NAME = "UTMStackAgent" - SERV_LOG = "utmstack_updater_self.log" -) diff --git a/agent/self/go.mod b/agent/self/go.mod deleted file mode 100644 index f9ba38c2e..000000000 --- a/agent/self/go.mod +++ /dev/null @@ -1,39 +0,0 @@ -module github.com/utmstack/UTMStack/agent/self - -go 1.23.0 - -toolchain go1.23.4 - -require github.com/threatwinds/logger v1.2.1 - -require ( - github.com/bytedance/sonic v1.12.1 // indirect - github.com/bytedance/sonic/loader v0.2.0 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.5 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.10.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.8 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.9.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.36.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/agent/self/go.sum b/agent/self/go.sum deleted file mode 100644 index 7e6037bf1..000000000 --- a/agent/self/go.sum +++ /dev/null @@ -1,94 +0,0 @@ -github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= -github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= -github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/threatwinds/logger v1.2.1 h1:uN7efZaHobMX3DRi6GOPtxESPxt5xj0bNflnmgklwII= -github.com/threatwinds/logger v1.2.1/go.mod h1:eevkhjP9wSpRekRIgi4ZAq7DMdlUMy+Shwx/QNDvOHg= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= -golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/agent/self/main.go b/agent/self/main.go deleted file mode 100644 index 51f057b6a..000000000 --- a/agent/self/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "path/filepath" - "time" - - "github.com/utmstack/UTMStack/agent/self/config" - "github.com/utmstack/UTMStack/agent/self/update" - "github.com/utmstack/UTMStack/agent/self/utils" -) - -func main() { - path := utils.GetMyPath() - utils.InitLogger(filepath.Join(path, "logs", config.SERV_LOG)) - - utils.SelfLogger.Info("Updating %s...", config.SERV_NAME) - - if isRunning, err := utils.CheckIfServiceIsActive(config.SERV_NAME); err != nil { - utils.SelfLogger.Fatal("error checking %s service: %v", config.SERV_NAME, err) - } else if isRunning { - err = utils.StopService(config.SERV_NAME) - if err != nil { - utils.SelfLogger.Fatal("error stopping %s service: %v", config.SERV_NAME, err) - } - utils.SelfLogger.Info("%s stopped correctly", config.SERV_NAME) - } - - time.Sleep(10 * time.Second) - - err := update.RunUpdate() - if err != nil { - utils.SelfLogger.Fatal("error updating new %s service: %v", config.SERV_NAME, err) - } - utils.SelfLogger.Info("New %s downloaded correctly", config.SERV_NAME) - - err = utils.RestartService(config.SERV_NAME) - if err != nil { - utils.SelfLogger.Fatal("error restarting %s service: %v", config.SERV_NAME, err) - } - utils.SelfLogger.Info("%s updated correctly", config.SERV_NAME) -} diff --git a/agent/self/update/update.go b/agent/self/update/update.go deleted file mode 100644 index 22011d831..000000000 --- a/agent/self/update/update.go +++ /dev/null @@ -1,28 +0,0 @@ -package update - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/utmstack/UTMStack/agent/self/config" - "github.com/utmstack/UTMStack/agent/self/utils" -) - -func RunUpdate() error { - path := utils.GetMyPath() - - newbin := fmt.Sprintf(config.ServiceFile, "_new") - oldbin := fmt.Sprintf(config.ServiceFile, "") - err := os.Remove(filepath.Join(path, oldbin)) - if err != nil { - return fmt.Errorf("error removing old %s: %v", oldbin, err) - } - - err = os.Rename(filepath.Join(path, newbin), filepath.Join(path, oldbin)) - if err != nil { - return fmt.Errorf("error renaming new %s: %v", newbin, err) - } - - return nil -} diff --git a/agent/self/utils/files.go b/agent/self/utils/files.go deleted file mode 100644 index 24baf7dae..000000000 --- a/agent/self/utils/files.go +++ /dev/null @@ -1,15 +0,0 @@ -package utils - -import ( - "os" - "path/filepath" -) - -func GetMyPath() string { - ex, err := os.Executable() - if err != nil { - return "" - } - exPath := filepath.Dir(ex) - return exPath -} diff --git a/agent/serv/clean-old.go b/agent/serv/clean-old.go index 6369a9ebd..06317deab 100644 --- a/agent/serv/clean-old.go +++ b/agent/serv/clean-old.go @@ -1,76 +1,30 @@ package serv import ( - "fmt" - "os" - "path/filepath" - "runtime" - "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/utils" ) func CleanOldServices(cnf *config.Config) { - oldVersion := false - - isUpdaterINstalled, err := utils.CheckIfServiceIsInstalled("UTMStackUpdater") - if err != nil { - utils.Logger.LogF(100, "error checking if service is installed: %v", err) - } - - if isUpdaterINstalled { - oldVersion = true - err = utils.StopService("UTMStackUpdater") - if err != nil { - utils.Logger.LogF(100, "error stopping service: %v", err) - } - - err = utils.UninstallService("UTMStackUpdater") - if err != nil { - utils.Logger.LogF(100, "error uninstalling service: %v", err) - } - } - isRedlineInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackRedline") if err != nil { - utils.Logger.LogF(100, "error checking if service is installed: %v", err) + utils.Logger.LogF(100, "error checking if UTMStackRedline service is installed: %v", err) + return } if isRedlineInstalled { - oldVersion = true + utils.Logger.Info("old UTMStackRedline service found, removing...") + err = utils.StopService("UTMStackRedline") if err != nil { - utils.Logger.LogF(100, "error stopping service: %v", err) + utils.Logger.LogF(100, "error stopping UTMStackRedline service: %v", err) } err = utils.UninstallService("UTMStackRedline") if err != nil { - utils.Logger.LogF(100, "error uninstalling service: %v", err) - } - } - - if oldVersion { - utils.Logger.Info("old version of agent found, downloading new version") - if runtime.GOOS != "darwin" { - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.UpdaterSelf, "")), map[string]string{}, fmt.Sprintf(config.UpdaterSelf, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.Logger.LogF(100, "error downloading updater: %v", err) - return - } - } - - oldFilePath := filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterSelf, "")) - newFilePath := filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterSelf, "_new")) - - utils.Logger.LogF(100, "renaming %s to %s", newFilePath, oldFilePath) - err := os.Remove(oldFilePath) - if err != nil { - utils.Logger.LogF(100, "error removing old updater: %v", err) - } - err = os.Rename(newFilePath, oldFilePath) - if err != nil { - utils.Logger.LogF(100, "error renaming updater: %v", err) + utils.Logger.LogF(100, "error uninstalling UTMStackRedline service: %v", err) + } else { + utils.Logger.Info("UTMStackRedline service removed successfully") } - } else { - utils.Logger.LogF(100, "no old version of agent found") } } diff --git a/agent/serv/service.go b/agent/serv/service.go index 8206cb220..1016ec933 100644 --- a/agent/serv/service.go +++ b/agent/serv/service.go @@ -2,8 +2,11 @@ package serv import ( "context" + "fmt" "os" "os/signal" + "path/filepath" + "runtime" "strconv" "syscall" @@ -36,6 +39,9 @@ func (p *program) run() { utils.Logger.Fatal("error getting config: %v", err) } + // Ensure updater service is installed + ensureUpdaterServiceInstalled() + CleanOldServices(cnf) ctx, cancel := context.WithCancel(context.Background()) @@ -60,3 +66,52 @@ func (p *program) run() { signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) <-signals } + +func ensureUpdaterServiceInstalled() { + isInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackUpdater") + if err != nil { + utils.Logger.ErrorF("error checking if updater service is installed: %v", err) + return + } + + if isInstalled { + utils.Logger.Info("updater service is already installed") + return + } + + utils.Logger.Info("updater service not found, installing...") + + updaterPath := filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "")) + + if !utils.CheckIfPathExist(updaterPath) { + + cnf, err := config.GetCurrentConfig() + if err != nil { + utils.Logger.ErrorF("error getting config to download updater: %v", err) + return + } + + updaterBinary := fmt.Sprintf(config.UpdaterFile, "") + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, updaterBinary), map[string]string{}, updaterBinary, utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.Logger.ErrorF("error downloading updater binary: %v", err) + return + } + + if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + if err := utils.Execute("chmod", utils.GetMyPath(), "755", updaterBinary); err != nil { + utils.Logger.ErrorF("error setting permissions on updater: %v", err) + return + } + } + + utils.Logger.Info("updater binary downloaded successfully") + } + + err = utils.Execute(updaterPath, utils.GetMyPath(), "install") + if err != nil { + utils.Logger.ErrorF("error installing updater service: %v", err) + return + } + + utils.Logger.Info("updater service installed successfully") +} diff --git a/agent/serv/uninstall.go b/agent/serv/uninstall.go index 644bc713a..e789c4ea8 100644 --- a/agent/serv/uninstall.go +++ b/agent/serv/uninstall.go @@ -9,8 +9,20 @@ func UninstallService() { if err != nil { utils.Logger.Fatal("error stopping UTMStackAgent: %v", err) } + err = utils.StopService("UTMStackUpdater") + if err != nil { + utils.Logger.Fatal("error stopping UTMStackUpdater: %v", err) + } + err = utils.UninstallService("UTMStackAgent") + if err != nil { + utils.Logger.Fatal("error uninstalling UTMStackAgent: %v", err) + } err = utils.UninstallService("UTMStackAgent") if err != nil { utils.Logger.Fatal("error uninstalling UTMStackAgent: %v", err) } + err = utils.UninstallService("UTMStackUpdater") + if err != nil { + utils.Logger.Fatal("error uninstalling UTMStackUpdater: %v", err) + } } diff --git a/agent/updater/config/config.go b/agent/updater/config/config.go new file mode 100644 index 000000000..8daefeb23 --- /dev/null +++ b/agent/updater/config/config.go @@ -0,0 +1,54 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "sync" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Server string `json:"server" yaml:"server"` + SkipCertValidation bool `json:"insecure" yaml:"insecure"` +} + +var ( + cnf = Config{} + confOnce sync.Once +) + +func GetCurrentConfig() (*Config, error) { + var errR error + confOnce.Do(func() { + ex, err := os.Executable() + if err != nil { + errR = fmt.Errorf("error getting executable path: %v", err) + return + } + exPath := filepath.Dir(ex) + + configPath := filepath.Join(exPath, "config.yml") + content, err := os.ReadFile(configPath) + if err != nil { + errR = fmt.Errorf("error reading config file: %v", err) + return + } + + var loadedConfig Config + err = yaml.Unmarshal(content, &loadedConfig) + if err != nil { + errR = fmt.Errorf("error parsing config file: %v", err) + return + } + + cnf.Server = loadedConfig.Server + cnf.SkipCertValidation = loadedConfig.SkipCertValidation + }) + + if errR != nil { + return nil, errR + } + return &cnf, nil +} diff --git a/agent/updater/config/const.go b/agent/updater/config/const.go new file mode 100644 index 000000000..5032035e0 --- /dev/null +++ b/agent/updater/config/const.go @@ -0,0 +1,21 @@ +package config + +import ( + "path/filepath" + + "github.com/utmstack/UTMStack/agent/updater/utils" +) + +const ( + SERV_LOG = "utmstack_updater.log" + SERV_AGENT_NAME = "UTMStackAgent" +) + +var ( + DependUrl = "https://%s:%s/private/dependencies/agent/%s" + AgentManagerPort = "9000" + LogAuthProxyPort = "50051" + DependenciesPort = "9001" + + VersionPath = filepath.Join(utils.GetMyPath(), "version.json") +) diff --git a/agent/self/config/linux_amd64.go b/agent/updater/config/linux_amd64.go similarity index 63% rename from agent/self/config/linux_amd64.go rename to agent/updater/config/linux_amd64.go index 2bd4ccf23..026f6c3f9 100644 --- a/agent/self/config/linux_amd64.go +++ b/agent/updater/config/linux_amd64.go @@ -5,4 +5,5 @@ package config var ( ServiceFile = "utmstack_agent_service%s" + DependFiles = []string{"utmstack_agent_dependencies_linux.zip"} ) diff --git a/agent/self/config/linux_arm64.go b/agent/updater/config/linux_arm64.go similarity index 63% rename from agent/self/config/linux_arm64.go rename to agent/updater/config/linux_arm64.go index 35fbef145..d77ff9f09 100644 --- a/agent/self/config/linux_arm64.go +++ b/agent/updater/config/linux_arm64.go @@ -5,4 +5,5 @@ package config var ( ServiceFile = "utmstack_agent_service_arm64%s" + DependFiles = []string{"utmstack_agent_dependencies_linux_arm64.zip"} ) diff --git a/agent/self/config/macos.go b/agent/updater/config/macos.go similarity index 79% rename from agent/self/config/macos.go rename to agent/updater/config/macos.go index 9804f368b..ecb80457a 100644 --- a/agent/self/config/macos.go +++ b/agent/updater/config/macos.go @@ -5,4 +5,5 @@ package config var ( ServiceFile = "utmstack_agent_service%s" + DependFiles = []string{} ) diff --git a/agent/self/config/win_amd64.go b/agent/updater/config/windows_amd64.go similarity index 64% rename from agent/self/config/win_amd64.go rename to agent/updater/config/windows_amd64.go index 87bcb5eea..adad86d9f 100644 --- a/agent/self/config/win_amd64.go +++ b/agent/updater/config/windows_amd64.go @@ -5,4 +5,5 @@ package config var ( ServiceFile = "utmstack_agent_service%s.exe" + DependFiles = []string{"utmstack_agent_dependencies_windows.zip"} ) diff --git a/agent/self/config/win_arm64.go b/agent/updater/config/windows_arm64.go similarity index 63% rename from agent/self/config/win_arm64.go rename to agent/updater/config/windows_arm64.go index ccedb2f95..4705118c1 100644 --- a/agent/self/config/win_arm64.go +++ b/agent/updater/config/windows_arm64.go @@ -5,4 +5,5 @@ package config var ( ServiceFile = "utmstack_agent_service_arm64%s.exe" + DependFiles = []string{"utmstack_agent_dependencies_windows_arm64.zip"} ) diff --git a/agent/updater/go.mod b/agent/updater/go.mod new file mode 100644 index 000000000..8b6343295 --- /dev/null +++ b/agent/updater/go.mod @@ -0,0 +1,46 @@ +module github.com/utmstack/UTMStack/agent/updater + +go 1.25.0 + +require ( + github.com/kardianos/service v1.2.2 + github.com/threatwinds/go-sdk v1.0.40 + github.com/threatwinds/logger v1.2.2 + gopkg.in/yaml.v3 v3.0.1 + +) + +require ( + github.com/bytedance/sonic v1.13.3 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-gonic/gin v1.10.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.14 // indirect + golang.org/x/arch v0.18.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/agent/updater/go.sum b/agent/updater/go.sum new file mode 100644 index 000000000..23d1acc42 --- /dev/null +++ b/agent/updater/go.sum @@ -0,0 +1,111 @@ +github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= +github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= +github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/threatwinds/go-sdk v1.0.40 h1:g0XS8WiGVGD6djucY0ZuckpZljBbXDuqGJ1dKOjce3I= +github.com/threatwinds/go-sdk v1.0.40/go.mod h1:ZLcT5kB0s2xFEUVJoFpwlLucnbQ8VBNU44aoKKC/j/g= +github.com/threatwinds/logger v1.2.2 h1:sVuT8yhbecPqP4tT8EwHfp1czNC6e1wdkE1ihNnuBdA= +github.com/threatwinds/logger v1.2.2/go.mod h1:Amq0QI1y7fkTpnBUgeGVu2Z/C4u4ys2pNLUOuj3UAAU= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw= +github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= +golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/agent/updater/main.go b/agent/updater/main.go new file mode 100644 index 000000000..9aae73b21 --- /dev/null +++ b/agent/updater/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/utmstack/UTMStack/agent/updater/config" + "github.com/utmstack/UTMStack/agent/updater/service" + "github.com/utmstack/UTMStack/agent/updater/utils" +) + +func main() { + path := utils.GetMyPath() + utils.InitLogger(filepath.Join(path, "logs", config.SERV_LOG)) + + if len(os.Args) > 1 { + switch os.Args[1] { + case "install": + fmt.Println("Installing UTMStack Updater service...") + + fmt.Print(("Creating service ... ")) + service.InstallService() + fmt.Println("[OK]") + + fmt.Println("UTMStackUpdater service installed correctly") + return + case "uninstall": + fmt.Println("Uninstalling UTMStack Updater service...") + service.UninstallService() + fmt.Println("Service uninstalled successfully") + return + case "start": + fmt.Println("Starting UTMStack Updater service...") + return + case "stop": + fmt.Println("Stopping UTMStack Updater service...") + // Will be handled by systemd + return + } + } + + service.RunService() +} diff --git a/agent/updater/models/version.go b/agent/updater/models/version.go new file mode 100644 index 000000000..ed01f06e4 --- /dev/null +++ b/agent/updater/models/version.go @@ -0,0 +1,5 @@ +package models + +type Version struct { + Version string `json:"version"` +} diff --git a/agent/self/rsrc_windows_386.syso b/agent/updater/rsrc_windows_386.syso similarity index 100% rename from agent/self/rsrc_windows_386.syso rename to agent/updater/rsrc_windows_386.syso diff --git a/agent/self/rsrc_windows_amd64.syso b/agent/updater/rsrc_windows_amd64.syso similarity index 100% rename from agent/self/rsrc_windows_amd64.syso rename to agent/updater/rsrc_windows_amd64.syso diff --git a/agent/self/rsrc_windows_arm64.syso b/agent/updater/rsrc_windows_arm64.syso similarity index 100% rename from agent/self/rsrc_windows_arm64.syso rename to agent/updater/rsrc_windows_arm64.syso diff --git a/agent/updater/service/config.go b/agent/updater/service/config.go new file mode 100644 index 000000000..0946f296c --- /dev/null +++ b/agent/updater/service/config.go @@ -0,0 +1,17 @@ +package service + +import ( + "github.com/kardianos/service" +) + +// GetConfigServ creates and returns a pointer to a service configuration structure. +func GetConfigServ() *service.Config { + svcConfig := &service.Config{ + Name: "UTMStackUpdater", + DisplayName: "UTMStack Updater", + Description: "UTMStack Agent Updater Service", + // No arguments needed - service will just run + } + + return svcConfig +} diff --git a/agent/updater/service/install.go b/agent/updater/service/install.go new file mode 100644 index 000000000..f566ea2a7 --- /dev/null +++ b/agent/updater/service/install.go @@ -0,0 +1,52 @@ +package service + +import ( + "fmt" + "os" + + "github.com/kardianos/service" + "github.com/utmstack/UTMStack/agent/updater/utils" +) + +func InstallService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + fmt.Println("\nError creating new service: ", err) + os.Exit(1) + } + err = newService.Install() + if err != nil { + fmt.Println("\nError installing new service: ", err) + os.Exit(1) + } + + err = newService.Start() + if err != nil { + fmt.Println("\nError starting new service: ", err) + os.Exit(1) + } + utils.UpdaterLogger.Info("updater service installed succefull") +} + +func UninstallService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + fmt.Println("\nError creating new service: ", err) + os.Exit(1) + } + + err = newService.Stop() + if err != nil { + fmt.Println("\nWarning stopping service: ", err) + } + + err = newService.Uninstall() + if err != nil { + fmt.Println("\nError uninstalling service: ", err) + os.Exit(1) + } +} diff --git a/agent/updater/service/service.go b/agent/updater/service/service.go new file mode 100644 index 000000000..837037b09 --- /dev/null +++ b/agent/updater/service/service.go @@ -0,0 +1,43 @@ +package service + +import ( + "github.com/kardianos/service" + "github.com/utmstack/UTMStack/agent/updater/config" + "github.com/utmstack/UTMStack/agent/updater/updates" + "github.com/utmstack/UTMStack/agent/updater/utils" +) + +type program struct{} + +func (p *program) Start(s service.Service) error { + go p.run() + return nil +} + +func (p *program) Stop(s service.Service) error { + return nil +} + +func (p *program) run() { + cnf, err := config.GetCurrentConfig() + if err != nil { + utils.UpdaterLogger.ErrorF("error getting config: %v", err) + return + } + + updates.UpdateDependencies(cnf) +} + +func RunService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + utils.UpdaterLogger.Fatal("error creating service: %v", err) + } + + err = newService.Run() + if err != nil { + utils.UpdaterLogger.Fatal("error running service: %v", err) + } +} diff --git a/agent/updater/updates/update.go b/agent/updater/updates/update.go new file mode 100644 index 000000000..80f8779db --- /dev/null +++ b/agent/updater/updates/update.go @@ -0,0 +1,154 @@ +package updates + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "time" + + "github.com/utmstack/UTMStack/agent/updater/config" + "github.com/utmstack/UTMStack/agent/updater/models" + "github.com/utmstack/UTMStack/agent/updater/utils" +) + +const ( + checkEvery = 5 * time.Minute +) + +var currentVersion = models.Version{} + +func UpdateDependencies(cnf *config.Config) { + if utils.CheckIfPathExist(config.VersionPath) { + err := utils.ReadJson(config.VersionPath, ¤tVersion) + if err != nil { + utils.UpdaterLogger.ErrorF("error reading version file: %v", err) + } + } + + for { + time.Sleep(checkEvery) + + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json"), map[string]string{}, "version_new.json", utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.UpdaterLogger.ErrorF("error downloading version.json: %v", err) + continue + } + newVersion := models.Version{} + err := utils.ReadJson(filepath.Join(utils.GetMyPath(), "version_new.json"), &newVersion) + if err != nil { + utils.UpdaterLogger.ErrorF("error reading version file: %v", err) + continue + } + + if newVersion.Version != currentVersion.Version { + utils.UpdaterLogger.Info("New version of agent found: %s", newVersion.Version) + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.ServiceFile, "")), map[string]string{}, fmt.Sprintf(config.ServiceFile, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.UpdaterLogger.ErrorF("error downloading agent: %v", err) + continue + } + + if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + if err = utils.Execute("chmod", utils.GetMyPath(), "-R", "755", filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))); err != nil { + utils.UpdaterLogger.ErrorF("error executing chmod: %v", err) + } + } + + utils.UpdaterLogger.Info("Starting update process...") + err = runUpdateProcess() + if err != nil { + utils.UpdaterLogger.ErrorF("error updating service: %v", err) + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + os.Remove(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))) + } else { + utils.UpdaterLogger.Info("Update completed successfully") + if utils.CheckIfPathExist(config.VersionPath) { + err := utils.ReadJson(config.VersionPath, ¤tVersion) + if err != nil { + utils.UpdaterLogger.ErrorF("error reading updated version file: %v", err) + } + } + } + } else { + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + } + } +} + +func runUpdateProcess() error { + path := utils.GetMyPath() + + newBin := fmt.Sprintf(config.ServiceFile, "_new") + oldBin := fmt.Sprintf(config.ServiceFile, "") + backupBin := fmt.Sprintf(config.ServiceFile, ".old") + + agentNew := filepath.Join(path, newBin) + if _, err := os.Stat(agentNew); err != nil { + return fmt.Errorf("no _new binary found to update") + } + + if err := utils.StopService(config.SERV_AGENT_NAME); err != nil { + return fmt.Errorf("error stopping agent: %v", err) + } + + time.Sleep(10 * time.Second) + + backupPath := filepath.Join(path, backupBin) + if utils.CheckIfPathExist(backupPath) { + utils.UpdaterLogger.Info("Removing previous backup: %s", backupPath) + if err := os.Remove(backupPath); err != nil { + utils.UpdaterLogger.ErrorF("could not remove old backup: %v", err) + } + } + + if err := os.Rename(filepath.Join(path, oldBin), backupPath); err != nil { + return fmt.Errorf("error backing up old binary: %v", err) + } + + if err := os.Rename(filepath.Join(path, newBin), filepath.Join(path, oldBin)); err != nil { + os.Rename(backupPath, filepath.Join(path, oldBin)) + return fmt.Errorf("error renaming new binary: %v", err) + } + + if err := utils.StartService(config.SERV_AGENT_NAME); err != nil { + rollbackAgent(oldBin, backupBin, path) + return fmt.Errorf("error starting agent: %v", err) + } + + time.Sleep(30 * time.Second) + + isHealthy, err := utils.CheckIfServiceIsActive(config.SERV_AGENT_NAME) + if err != nil || !isHealthy { + utils.UpdaterLogger.Info("New version failed health check, rolling back...") + rollbackAgent(oldBin, backupBin, path) + return fmt.Errorf("rollback completed: new version failed health check") + } + + utils.UpdaterLogger.Info("Health check passed for agent") + + versionNewPath := filepath.Join(path, "version_new.json") + versionPath := filepath.Join(path, "version.json") + if utils.CheckIfPathExist(versionNewPath) { + if err := os.Rename(versionNewPath, versionPath); err != nil { + utils.UpdaterLogger.ErrorF("error updating version file: %v", err) + } else { + utils.UpdaterLogger.Info("Version file updated successfully") + } + } + + return nil +} + +func rollbackAgent(currentBin, backupBin, path string) { + utils.UpdaterLogger.Info("Rolling back agent to previous version...") + + utils.StopService(config.SERV_AGENT_NAME) + time.Sleep(5 * time.Second) + + os.Remove(filepath.Join(path, currentBin)) + os.Rename(filepath.Join(path, backupBin), filepath.Join(path, currentBin)) + + utils.StartService(config.SERV_AGENT_NAME) + os.Remove(filepath.Join(path, "version_new.json")) + + utils.UpdaterLogger.Info("Rollback completed for agent") +} diff --git a/agent/self/utils/cmd.go b/agent/updater/utils/cmd.go similarity index 59% rename from agent/self/utils/cmd.go rename to agent/updater/utils/cmd.go index b1881855f..eae4140d9 100644 --- a/agent/self/utils/cmd.go +++ b/agent/updater/utils/cmd.go @@ -3,23 +3,9 @@ package utils import ( "errors" "os/exec" - "unicode/utf8" -) -func CleanString(s string) string { - v := make([]rune, 0, len(s)) - for i, r := range s { - if r == utf8.RuneError { - _, size := utf8.DecodeRuneInString(s[i:]) - if size == 1 { - v = append(v, '?') - continue - } - } - v = append(v, r) - } - return string(v) -} + twsdk "github.com/threatwinds/go-sdk/entities" +) func ExecuteWithResult(c string, dir string, arg ...string) (string, bool) { cmd := exec.Command(c, arg...) @@ -34,7 +20,13 @@ func ExecuteWithResult(c string, dir string, arg ...string) (string, bool) { return string(out[:]) + err.Error(), true } - validUtf8Out := CleanString(string(out[:])) + if string(out[:]) == "" { + return "Command executed successfully but no output", false + } + validUtf8Out, _, err := twsdk.ValidateString(string(out[:]), false) + if err != nil { + return string(out[:]) + err.Error(), true + } return validUtf8Out, false } diff --git a/agent/updater/utils/download.go b/agent/updater/utils/download.go new file mode 100644 index 000000000..055c44881 --- /dev/null +++ b/agent/updater/utils/download.go @@ -0,0 +1,48 @@ +package utils + +import ( + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "path/filepath" +) + +func DownloadFile(url string, headers map[string]string, fileName string, path string, skipTlsVerification bool) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("error creating new request: %v", err) + } + for key, value := range headers { + req.Header.Add(key, value) + } + + client := &http.Client{} + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerification}, + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("error sending request: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status %d; got %d", http.StatusOK, resp.StatusCode) + } + + out, err := os.Create(filepath.Join(path, fileName)) + if err != nil { + return fmt.Errorf("error creating file: %v", err) + } + defer func() { _ = out.Close() }() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("error copying file: %v", err) + } + + return nil +} diff --git a/agent/updater/utils/files.go b/agent/updater/utils/files.go new file mode 100644 index 000000000..6b0180721 --- /dev/null +++ b/agent/updater/utils/files.go @@ -0,0 +1,92 @@ +package utils + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" +) + +func GetMyPath() string { + ex, err := os.Executable() + if err != nil { + return "" + } + exPath := filepath.Dir(ex) + return exPath +} + +func CreatePathIfNotExist(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + if err := os.MkdirAll(path, 0755); err != nil { + return fmt.Errorf("error creating path: %v", err) + } + } else if err != nil { + return fmt.Errorf("error checking path: %v", err) + } + return nil +} + +func CheckIfPathExist(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} + +func ReadJson(fileName string, data interface{}) error { + content, err := os.ReadFile(fileName) + if err != nil { + return err + } + + err = json.Unmarshal(content, data) + if err != nil { + return err + } + + return nil +} + +func WriteStringToFile(fileName string, body string) error { + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer func() { _ = file.Close() }() + + _, err = file.WriteString(body) + return err +} + +func WriteJSON(path string, data interface{}) error { + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + + err = WriteStringToFile(path, string(jsonData)) + if err != nil { + return err + } + + return nil +} + +func copyFile(src, dst string) error { + sourceFile, err := os.Open(src) + if err != nil { + return err + } + defer sourceFile.Close() + + destFile, err := os.Create(dst) + if err != nil { + return err + } + defer destFile.Close() + + _, err = io.Copy(destFile, sourceFile) + return err +} diff --git a/agent/self/utils/logger.go b/agent/updater/utils/logger.go similarity index 79% rename from agent/self/utils/logger.go rename to agent/updater/utils/logger.go index e2e8c7fdf..fc0cbb757 100644 --- a/agent/self/utils/logger.go +++ b/agent/updater/utils/logger.go @@ -7,13 +7,13 @@ import ( ) var ( - SelfLogger *logger.Logger + UpdaterLogger *logger.Logger loggerOnceInstance sync.Once ) func InitLogger(filename string) { loggerOnceInstance.Do(func() { - SelfLogger = logger.NewLogger( + UpdaterLogger = logger.NewLogger( &logger.Config{Format: "text", Level: 100, Output: filename, Retries: 3, Wait: 5}, ) }) diff --git a/agent/self/utils/services.go b/agent/updater/utils/services.go similarity index 81% rename from agent/self/utils/services.go rename to agent/updater/utils/services.go index b6009d27f..183980245 100644 --- a/agent/self/utils/services.go +++ b/agent/updater/utils/services.go @@ -110,3 +110,26 @@ func StopService(name string) error { } return nil } + +func StartService(name string) error { + path := GetMyPath() + switch runtime.GOOS { + case "windows": + err := Execute("sc", path, "start", name) + if err != nil { + return fmt.Errorf("error starting service: %v", err) + } + case "linux": + err := Execute("systemctl", path, "start", name) + if err != nil { + return fmt.Errorf("error starting service: %v", err) + } + case "darwin": + plistPath := fmt.Sprintf("/Library/LaunchDaemons/%s.plist", name) + err := Execute("launchctl", path, "load", plistPath) + if err != nil { + return fmt.Errorf("error starting macOS service: %v", err) + } + } + return nil +} diff --git a/agent/updater/utils/zip.go b/agent/updater/utils/zip.go new file mode 100644 index 000000000..e8c8381b8 --- /dev/null +++ b/agent/updater/utils/zip.go @@ -0,0 +1,42 @@ +package utils + +import ( + "archive/zip" + "io" + "os" + "path" + "path/filepath" +) + +func Unzip(zipFile, destPath string) error { + archive, err := zip.OpenReader(zipFile) + if err != nil { + return err + } + defer archive.Close() + + for _, f := range archive.File { + filePath := path.Join(destPath, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(filePath, os.ModePerm) + continue + } + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + return err + } + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + fileInArchive, err := f.Open() + if err != nil { + return err + } + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + return err + } + dstFile.Close() + fileInArchive.Close() + } + return nil +} diff --git a/agent/updates/dependencies.go b/agent/updates/dependencies.go index 5b6e13b5d..914740be4 100644 --- a/agent/updates/dependencies.go +++ b/agent/updates/dependencies.go @@ -16,6 +16,17 @@ func DownloadFirstDependencies(address string, authKey string, insecure bool) er return fmt.Errorf("error downloading version.json : %v", err) } + updaterBinary := fmt.Sprintf(config.UpdaterFile, "") + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, updaterBinary), map[string]string{}, updaterBinary, utils.GetMyPath(), insecure); err != nil { + return fmt.Errorf("error downloading updater binary %s: %v", updaterBinary, err) + } + + if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + if err := utils.Execute("chmod", utils.GetMyPath(), "755", updaterBinary); err != nil { + return fmt.Errorf("error setting permissions on updater: %v", err) + } + } + dependFiles := config.DependFiles for _, file := range dependFiles { if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, file), map[string]string{}, file, utils.GetMyPath(), insecure); err != nil { @@ -38,12 +49,6 @@ func handleDependenciesPostDownload(dependencies []string) error { return fmt.Errorf("error unzipping dependencies: %v", err) } - if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err := utils.Execute("chmod", utils.GetMyPath(), "-R", "755", fmt.Sprintf(config.UpdaterSelf, "")); err != nil { - return fmt.Errorf("error executing chmod on %s: %v", fmt.Sprintf(config.UpdaterSelf, ""), err) - } - } - if err := os.Remove(filepath.Join(utils.GetMyPath(), file)); err != nil { return fmt.Errorf("error removing file %s: %v", file, err) } diff --git a/agent/updates/update.go b/agent/updates/update.go index 0663ed57f..0d743734d 100644 --- a/agent/updates/update.go +++ b/agent/updates/update.go @@ -40,29 +40,115 @@ func UpdateDependencies(cnf *config.Config) { continue } - if newVersion.Version != currentVersion.Version { - utils.Logger.Info("New version of agent found: %s", newVersion.Version) - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.ServiceFile, "")), map[string]string{}, fmt.Sprintf(config.ServiceFile, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { - utils.Logger.ErrorF("error downloading agent: %v", err) - continue - } - - currentVersion = newVersion - err = utils.WriteJSON(config.VersionPath, ¤tVersion) - if err != nil { - utils.Logger.ErrorF("error writing version file: %v", err) + if newVersion.UpdaterVersion != currentVersion.UpdaterVersion { + utils.Logger.Info("New version of updater found: %s", newVersion.UpdaterVersion) + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.UpdaterFile, "")), map[string]string{}, fmt.Sprintf(config.UpdaterFile, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.Logger.ErrorF("error downloading updater: %v", err) continue } if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - if err = utils.Execute("chmod", utils.GetMyPath(), "-R", "755", filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))); err != nil { + if err = utils.Execute("chmod", utils.GetMyPath(), "-R", "755", filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "_new"))); err != nil { utils.Logger.ErrorF("error executing chmod: %v", err) } } - utils.Execute(fmt.Sprintf(config.UpdaterSelf, ""), utils.GetMyPath()) + utils.Logger.Info("Starting updater update process...") + err = runUpdateProcess() + if err != nil { + utils.Logger.ErrorF("error updating updater: %v", err) + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + os.Remove(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.UpdaterFile, "_new"))) + } else { + utils.Logger.Info("Updater update completed successfully") + if utils.CheckIfPathExist(config.VersionPath) { + err := utils.ReadJson(config.VersionPath, ¤tVersion) + if err != nil { + utils.Logger.ErrorF("error reading updated version file: %v", err) + } + } + } + } else { + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) } + } +} + +func runUpdateProcess() error { + path := utils.GetMyPath() + + newBin := fmt.Sprintf(config.UpdaterFile, "_new") + oldBin := fmt.Sprintf(config.UpdaterFile, "") + backupBin := fmt.Sprintf(config.UpdaterFile, ".old") + + updaterNew := filepath.Join(path, newBin) + if _, err := os.Stat(updaterNew); err != nil { + return fmt.Errorf("no _new binary found to update") + } + + if err := utils.StopService(config.SERVICE_UPDATER_NAME); err != nil { + return fmt.Errorf("error stopping updater: %v", err) + } + + time.Sleep(10 * time.Second) + + backupPath := filepath.Join(path, backupBin) + if utils.CheckIfPathExist(backupPath) { + utils.Logger.Info("Removing previous backup: %s", backupPath) + if err := os.Remove(backupPath); err != nil { + utils.Logger.ErrorF("could not remove old backup: %v", err) + } + } - os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + if err := os.Rename(filepath.Join(path, oldBin), backupPath); err != nil { + return fmt.Errorf("error backing up old binary: %v", err) } + + if err := os.Rename(filepath.Join(path, newBin), filepath.Join(path, oldBin)); err != nil { + os.Rename(backupPath, filepath.Join(path, oldBin)) + return fmt.Errorf("error renaming new binary: %v", err) + } + + if err := utils.StartService(config.SERVICE_UPDATER_NAME); err != nil { + rollbackUpdater(oldBin, backupBin, path) + return fmt.Errorf("error starting updater: %v", err) + } + + time.Sleep(30 * time.Second) + + isHealthy, err := utils.CheckIfServiceIsActive(config.SERVICE_UPDATER_NAME) + if err != nil || !isHealthy { + utils.Logger.Info("New version failed health check, rolling back...") + rollbackUpdater(oldBin, backupBin, path) + return fmt.Errorf("rollback completed: new version failed health check") + } + + utils.Logger.Info("Health check passed for updater") + + versionNewPath := filepath.Join(path, "version_new.json") + versionPath := filepath.Join(path, "version.json") + if utils.CheckIfPathExist(versionNewPath) { + if err := os.Rename(versionNewPath, versionPath); err != nil { + utils.Logger.ErrorF("error updating version file: %v", err) + } else { + utils.Logger.Info("Version file updated successfully") + } + } + + return nil +} + +func rollbackUpdater(currentBin, backupBin, path string) { + utils.Logger.Info("Rolling back updater to previous version...") + + utils.StopService(config.SERVICE_UPDATER_NAME) + time.Sleep(5 * time.Second) + + os.Remove(filepath.Join(path, currentBin)) + os.Rename(filepath.Join(path, backupBin), filepath.Join(path, currentBin)) + + utils.StartService(config.SERVICE_UPDATER_NAME) + os.Remove(filepath.Join(path, "version_new.json")) + + utils.Logger.Info("Rollback completed for updater") } diff --git a/agent/utils/services.go b/agent/utils/services.go index 04a9d9401..a0a828d3f 100644 --- a/agent/utils/services.go +++ b/agent/utils/services.go @@ -4,8 +4,66 @@ import ( "fmt" "os" "runtime" + "strings" ) +func CheckIfServiceIsActive(serv string) (bool, error) { + var errB bool + var output string + path := GetMyPath() + + switch runtime.GOOS { + case "windows": + output, errB = ExecuteWithResult("sc", path, "query", serv) + case "linux": + output, errB = ExecuteWithResult("systemctl", path, "is-active", serv) + case "darwin": + output, errB = ExecuteWithResult("launchctl", path, "list", serv) + default: + return false, fmt.Errorf("unknown operating system") + } + + if errB { + return false, nil + } + + serviceStatus := strings.ToLower(strings.TrimSpace(output)) + + switch runtime.GOOS { + case "windows": + return strings.Contains(serviceStatus, "running"), nil + case "linux": + return serviceStatus == "active", nil + case "darwin": + return true, nil + default: + return false, fmt.Errorf("unsupported operating system") + } +} + +func StartService(name string) error { + path := GetMyPath() + switch runtime.GOOS { + case "windows": + err := Execute("sc", path, "start", name) + if err != nil { + return fmt.Errorf("error starting service: %v", err) + } + case "linux": + err := Execute("systemctl", path, "start", name) + if err != nil { + return fmt.Errorf("error starting service: %v", err) + } + case "darwin": + plistPath := fmt.Sprintf("/Library/LaunchDaemons/%s.plist", name) + err := Execute("launchctl", path, "load", plistPath) + if err != nil { + return fmt.Errorf("error starting macOS service: %v", err) + } + } + return nil +} + func StopService(name string) error { path := GetMyPath() switch runtime.GOOS { diff --git a/agent/version.json b/agent/version.json index 2a8d9f74f..219ee83b4 100644 --- a/agent/version.json +++ b/agent/version.json @@ -1,3 +1,4 @@ { - "version": "10.8.1" + "version": "10.9.0", + "updater_version": "1.0.0" }