From 960b278f0079791b503511c9901fe7a276adca28 Mon Sep 17 00:00:00 2001 From: IdanK Date: Sat, 1 Aug 2026 15:29:36 +0300 Subject: [PATCH 1/4] Update manifest --- cmd/main.go | 4 ++++ internal/module/manager.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index 352cc9c..256b088 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,6 +6,7 @@ import ( "errors" "flag" "fmt" + "goscouter/internal/management" "io" "os" "os/signal" @@ -52,6 +53,9 @@ func main() { func run(target string) error { printBanner() + if err := management.InstallModule(context.Background(), "https://github.com/GoScouter/nginx-module"); err != nil { + panic(err) + } if err := versions.SuggestUpdate(VERSION); err != nil { return fmt.Errorf("update check: %w", err) diff --git a/internal/module/manager.go b/internal/module/manager.go index 98c1358..a410bd4 100644 --- a/internal/module/manager.go +++ b/internal/module/manager.go @@ -127,7 +127,7 @@ func ExternalDir() (string, error) { if err != nil { return "", err } - return filepath.Join(cacheDir, "gs"), nil + return filepath.Join(cacheDir, "gs", "modules"), nil } func (m *Manager) LoadExternals(ctx context.Context) error { From b5cbcfb99d431bcd8faa173be80bdbb21af4c4ab Mon Sep 17 00:00:00 2001 From: IdanK Date: Sat, 1 Aug 2026 16:37:46 +0300 Subject: [PATCH 2/4] Implement install & uninstall methods --- cmd/main.go | 28 +++- go.mod | 20 +++ go.sum | 101 +++++++++++++- internal/management/install.go | 220 +++++++++++++++++++++++++++++++ internal/management/uninstall.go | 70 ++++++++++ internal/module/graph.go | 4 + pkg/modules/manifest.go | 16 +++ 7 files changed, 457 insertions(+), 2 deletions(-) create mode 100644 internal/management/install.go create mode 100644 internal/management/uninstall.go create mode 100644 pkg/modules/manifest.go diff --git a/cmd/main.go b/cmd/main.go index 256b088..dbf2b7c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -33,10 +33,36 @@ var interrupted atomic.Bool func main() { version := flag.Bool("version", false, "Returns goscouter cli version") targetSite := flag.String("target", "", "The site to target") + + install := flag.String("install", "", "Module to install") + uninstall := flag.String("uninstall", "", "Module to uninstall (author:name)") + flag.Parse() + if *install != "" { + if err := management.InstallModule(context.Background(), *install); err != nil { + fmt.Fprintf(os.Stderr, "Failed to install modules %s. %v\n", *install, err) + os.Exit(1) + return + } + + os.Exit(0) + return + } + + if *uninstall != "" { + if err := management.UninstallModule(context.Background(), *uninstall); err != nil { + fmt.Fprintf(os.Stderr, "Failed to uninstall module %s. %v\n", *uninstall, err) + os.Exit(1) + return + } + + os.Exit(0) + return + } + if *version { - fmt.Println(versionString()) + fmt.Println("Version: " + versionString()) return } diff --git a/go.mod b/go.mod index 133b153..1d172f7 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,33 @@ go 1.26.4 require ( github.com/GoScouter/sdk v1.0.9 + github.com/go-git/go-git/v5 v5.19.2 github.com/google/go-github v17.0.0+incompatible github.com/stevenle/topsort v0.2.0 golang.org/x/term v0.45.0 ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.9.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/pjbgf/sha1cd v0.6.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/sys v0.47.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 445435f..baed647 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,115 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/GoScouter/sdk v1.0.9 h1:3Dy2l6/oMkdD01TtW2ZOgg7S7iOQFXW2bEIg2AoToZM= github.com/GoScouter/sdk v1.0.9/go.mod h1:MvG1fWNCbXBz7rXjZA2JMD0KyNOiWJJFrb9ti3b9H8U= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +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/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= +github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= +github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.6.0/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/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= +github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/stevenle/topsort v0.2.0 h1:LLWgtp34HPX6/RBDRS0kElVxGOTzGBLI1lSAa5Lb46k= github.com/stevenle/topsort v0.2.0/go.mod h1:ck2WG2/ZrOr6dLApQ/5Xrqy5wv3T0qhKYWE7r9tkibc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/management/install.go b/internal/management/install.go new file mode 100644 index 0000000..8f77837 --- /dev/null +++ b/internal/management/install.go @@ -0,0 +1,220 @@ +package management + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "goscouter/internal/module" + "goscouter/internal/style" + "goscouter/internal/utils" + "goscouter/pkg/modules" + "io" + "net/http" + "os" + "path" + "path/filepath" + "regexp" + "runtime" + "strings" + "time" + + "github.com/GoScouter/sdk" + "github.com/go-git/go-git/v5" +) + +var modulePattern = regexp.MustCompile(`^(https?://[^@]+)(?:@([^@]+))?$`) + +const infoTimeout = 5 * time.Second + +func InstallModule(ctx context.Context, input string) error { + matches := modulePattern.FindStringSubmatch(input) + if matches == nil { + return fmt.Errorf("invalid module") + } + + url := matches[1] + version := matches[2] + + dir, err := os.MkdirTemp("/tmp", "module-*") + if err != nil { + return err + } + defer os.RemoveAll(dir) + + step("Fetching manifest from %s", style.Bold(url)) + _, err = git.PlainClone(dir, false, &git.CloneOptions{ + URL: url, + }) + if err != nil { + return err + } + + data, err := os.ReadFile(filepath.Join(dir, "manifest.json")) + if err != nil { + return err + } + + var manifest modules.Manifest + err = json.Unmarshal(data, &manifest) + if err != nil { + return err + } + + if version == "latest" || version == "" { + version = manifest.Latest + } + + vers, ok := manifest.Versions[version] + if !ok { + return fmt.Errorf("cannot find matching version (%s)", version) + } + + platform := runtime.GOOS + "-" + runtime.GOARCH + artifact, ok := vers.Artifacts[platform] + if !ok { + return fmt.Errorf("cannot find matching artifact for your architecture") + } + + if artifact.Checksum == "" { + return fmt.Errorf("manifest lists no checksum for %s", platform) + } + + step("Resolved %s %s for %s", style.Bold(manifest.Name), style.Bold(version), platform) + + modulesDir, err := module.ExternalDir() + if err != nil { + return err + } + if err := os.MkdirAll(modulesDir, 0o755); err != nil { + return err + } + + filename := path.Base(artifact.Url) + filePath := filepath.Join(modulesDir, filename) + + if installed, err := utils.CalculateChecksum(filePath); err == nil { + if strings.EqualFold(installed, artifact.Checksum) { + return fmt.Errorf("%s %s is already installed at %s", manifest.Name, version, filePath) + } + } else if !os.IsNotExist(err) { + return err + } + + step("Downloading %s", style.Dim(artifact.Url)) + resp, err := http.Get(artifact.Url) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("no binary was found (HTTP %d)", resp.StatusCode) + } + + staged := filepath.Join(modulesDir, fmt.Sprintf(".%s.download.%d", filename, os.Getpid())) + out, err := os.OpenFile(staged, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) + if err != nil { + return err + } + + hash := sha256.New() + if _, err := io.Copy(io.MultiWriter(out, hash), resp.Body); err != nil { + out.Close() + os.Remove(staged) + return err + } + if err := out.Close(); err != nil { + os.Remove(staged) + return err + } + + got := hex.EncodeToString(hash.Sum(nil)) + if !strings.EqualFold(got, artifact.Checksum) { + os.Remove(staged) + return fmt.Errorf( + "checksum mismatch for %s\n expected: %s\n actual: %s\nThe download may be corrupt or tampered with — not installing", + filename, artifact.Checksum, got, + ) + } + + step("Checksum verified %s", style.Dim(got)) + + manager := module.NewManager() + if err := manager.LoadExternals(ctx); err != nil { + os.Remove(staged) + return err + } + + if err := os.Chmod(staged, 0o755); err != nil { + os.Remove(staged) + return err + } + + info, err := moduleInfo(ctx, staged) + if err != nil { + os.Remove(staged) + return err + } + + key := module.Key(module.Namespace(info)) + step("Module identified as %s", style.Bold(key)) + + if manager.GetInternal(key) != nil { + os.Remove(staged) + return fmt.Errorf("%s is a built-in module", key) + } + + var outdated string + if other := manager.GetExternal(key); other != nil && other.Path != filePath { + if strings.EqualFold(other.Checksum, artifact.Checksum) { + os.Remove(staged) + return fmt.Errorf("%s %s is already installed at %s", key, version, other.Path) + } + outdated = other.Path + notice("Replacing older install at %s", style.Dim(other.Path)) + } + + if err := os.Rename(staged, filePath); err != nil { + os.Remove(staged) + return err + } + + if outdated != "" { + os.Remove(outdated) + } + + step("Installed %s %s to %s", style.Bold(key), style.Bold(version), style.Dim(filePath)) + + return nil +} + +func step(format string, a ...any) { + fmt.Printf("%s\r\n", style.Foundf(format, a...)) +} + +func notice(format string, a ...any) { + fmt.Printf("%s\r\n", style.Alertf(format, a...)) +} + +func moduleInfo(ctx context.Context, path string) (sdk.ModuleInfo, error) { + bin, err := sdk.Open(path) + if err != nil { + return sdk.ModuleInfo{}, err + } + defer bin.Close() + + ctx, cancel := context.WithTimeout(ctx, infoTimeout) + defer cancel() + + info, err := bin.Info(ctx) + if err != nil { + return sdk.ModuleInfo{}, fmt.Errorf("reading info: %w", err) + } + if !info.IsValid() { + return sdk.ModuleInfo{}, fmt.Errorf("%s reports invalid info", filepath.Base(path)) + } + + return info, nil +} diff --git a/internal/management/uninstall.go b/internal/management/uninstall.go new file mode 100644 index 0000000..8132406 --- /dev/null +++ b/internal/management/uninstall.go @@ -0,0 +1,70 @@ +package management + +import ( + "context" + "fmt" + "goscouter/internal/module" + "goscouter/internal/style" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/GoScouter/sdk" +) + +var namePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`) + +func UninstallModule(ctx context.Context, input string) error { + author, name, ok := strings.Cut(strings.TrimSpace(input), ":") + if !ok || !namePattern.MatchString(author) || !namePattern.MatchString(name) { + return fmt.Errorf("invalid module, expected author:name (got %q)", input) + } + + key := module.Key(sdk.ModuleNamespace{Author: author, Name: name}) + + if module.IsInternalAuthor(author) { + return fmt.Errorf("%s is a built-in module and cannot be uninstalled", key) + } + + modulesDir, err := module.ExternalDir() + if err != nil { + return err + } + + step("Looking for %s in %s", style.Bold(key), style.Dim(modulesDir)) + + manager := module.NewManager() + if err := manager.LoadExternals(ctx); err != nil { + return err + } + + if manager.GetInternal(key) != nil { + return fmt.Errorf("%s is a built-in module and cannot be uninstalled", key) + } + + external := manager.GetExternal(key) + if external == nil { + return fmt.Errorf("%s is not installed", key) + } + + target, err := filepath.EvalSymlinks(external.Path) + if err != nil { + return err + } + root, err := filepath.EvalSymlinks(modulesDir) + if err != nil { + return err + } + if filepath.Dir(target) != root { + return fmt.Errorf("refusing to remove %s: it lives outside %s", target, root) + } + + if err := os.Remove(target); err != nil { + return err + } + + step("Uninstalled %s from %s", style.Bold(key), style.Dim(target)) + + return nil +} diff --git a/internal/module/graph.go b/internal/module/graph.go index 25c85a5..b688c6c 100644 --- a/internal/module/graph.go +++ b/internal/module/graph.go @@ -16,6 +16,10 @@ func Namespace(info sdk.ModuleInfo) sdk.ModuleNamespace { return sdk.ModuleNamespace{Name: info.Name, Author: info.Author} } +func IsInternalAuthor(author string) bool { + return author == internalAuthor +} + const everything = "*" type Graph struct { diff --git a/pkg/modules/manifest.go b/pkg/modules/manifest.go new file mode 100644 index 0000000..6edc785 --- /dev/null +++ b/pkg/modules/manifest.go @@ -0,0 +1,16 @@ +package modules + +type Manifest struct { + Name string `json:"name"` + Latest string `json:"latest"` + Versions map[string]Version `json:"versions"` +} + +type Version struct { + Artifacts map[string]Artifact `json:"artifacts"` +} + +type Artifact struct { + Url string `json:"url"` + Checksum string `json:"checksum"` +} From 2cee9886a2f93308ceda68b64df5b63183a45683 Mon Sep 17 00:00:00 2001 From: IdanK Date: Sat, 1 Aug 2026 16:42:56 +0300 Subject: [PATCH 3/4] Update README.md --- README.md | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cf286f4..c745fd7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

- Documentation + Website · Downloads · @@ -24,6 +24,22 @@ --- +## Table of Contents + +- [Overview](#overview) +- [Key Features](#key-features) +- [Platform Support](#platform-support) +- [Installation](#installation) + - [Install script](#install-script) + - [Manual download](#manual-download) + - [Build from source](#build-from-source) +- [Management modules](#management-modules) + - [Install](#install) + - [Uninstall](#uninstall) +- [Usage](#usage) + - [Example session](#example-session) +- [Contributing](#contributing) + ## Overview **GoScouter** is a modular toolkit for scouting, probing, and analyzing @@ -36,7 +52,7 @@ Built with a focus on speed and extensibility, GoScouter is designed to grow with your workflow: enable only the modules you need, script it into your pipelines, and extend it with your own probes. -> ⚠️ **Use responsibly.** GoScouter is intended for authorized security +> **Use responsibly.** GoScouter is intended for authorized security > testing, research, and network administration only. Always ensure you have > explicit permission to scan and probe the networks and hosts you target. @@ -54,11 +70,11 @@ pipelines, and extend it with your own probes. machine-friendly formats for scripting and automation. ## Platform Support - -| Platform | Supported | Notes | -| -------- | :-------: | ------------------------------ | -| Linux | ✅ | Primary development platform | -| macOS | ✅ | Intel & Apple Silicon | +| Platform | Status | +| -------- | :----: | +| Linux | OK | +| macOS | OK | +| Windows | N/A | ## Installation @@ -133,6 +149,19 @@ platform, use the `release-build` target: make release-build GOOS=linux GOARCH=amd64 # dist/gs-linux-amd64 make release-build GOOS=darwin GOARCH=arm64 # dist/gs-darwin-arm64 ``` +## Management modules + +### Install +```bash +gs --install +``` + +If version attribute is absent then it takes the `latest` version of the module, that is specified in `manifest.json` + +### Uninstall +```bash +gs --uninstall +``` ## Usage From a5abf8da3e99522ec5b54f96b82560467449a77e Mon Sep 17 00:00:00 2001 From: IdanK Date: Sat, 1 Aug 2026 16:48:38 +0300 Subject: [PATCH 4/4] Add CHANGELOG.md --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9effc2..02f3782 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,12 +134,44 @@ jobs: working-directory: dist/bin run: sha256sum gs-* > checksums.txt + - name: Extract changelog section + env: + VERSION: ${{ github.event.inputs.version }} + run: | + set -euo pipefail + section="${VERSION#v}" + + # Print everything between "## [

]" and the next "## [" heading, + # stopping early at the link-reference definitions at the bottom. + awk -v ver="$section" ' + /^## \[/ { + if (found) exit + heading = $0 + sub(/^## \[/, "", heading) + sub(/\].*$/, "", heading) + if (heading == ver) found = 1 + next + } + found && /^\[[^]]+\]:/ { exit } + found { print } + ' CHANGELOG.md \ + | sed -e '/./,$!d' | tac | sed -e '/./,$!d' | tac > release-notes.md + + if [ ! -s release-notes.md ]; then + echo "::error::No CHANGELOG.md section for '${section}'. Add a '## [${section}] - ' entry before releasing." + exit 1 + fi + + echo "Release notes for ${VERSION}:" + cat release-notes.md + - name: Publish release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.event.inputs.version }} name: ${{ github.event.inputs.version }} + body_path: release-notes.md generate_release_notes: true files: | dist/bin/gs-* - dist/checksums.txt + dist/bin/checksums.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8f1ea99 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +All notable changes to GoScouter are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Module management from the CLI: `gs --install ` and + `gs --uninstall `. When the version is omitted, the `latest` + version declared in the module's `manifest.json` is used. +- Module dependency graph resolution, so modules can depend on other modules + and are loaded in the right order. +- PDF summary output for network scans. +- Community and contribution scaffolding: `CONTRIBUTING.md`, issue templates, + and a PR review request workflow. + +### Changed + +- Rewrote the module subsystem around a central manager and runner, replacing + the previous per-command install/uninstall/scan implementations. +- Replaced the standalone `internal/logger` package with output helpers in + `internal/style`, unifying how commands and modules print to the terminal. +- Reworked DNS, HTTP, and subdomain record handling in `pkg/records` and + `pkg/subdomains`. +- Documentation updates to `README.md` covering installation, module + management, and usage. + +### Removed + +- `internal/logger` — superseded by the styled output helpers. + +## [1.0.0] - 2026-07-18 + +First stable release. + +### Added + +- Interactive shell interface: run `gs --target ` to drop into the `gs>` + prompt, with built-in `help`, `info`, `install`, and `exit` commands. +- Modular architecture — DNS, HTTP, and subdomain probing each live in their + own module, loaded on demand. +- Cross-platform binaries for Linux and macOS. +- Install script (`scripts/install.sh`) that selects the right prebuilt binary + for the platform, verifies its SHA-256 checksum, and installs it to + `/usr/local/bin` or `~/.local/bin`. +- `make build` and `make release-build` targets for building from source and + cross-compiling release artifacts. + +### Changed + +- Complete rewrite of the project on top of the standard Go project layout + (`cmd/`, `internal/`, `pkg/`). + +[Unreleased]: https://github.com/GoScouter/GoScouter/compare/1.0.0...HEAD +[1.0.0]: https://github.com/GoScouter/GoScouter/compare/2026-b1-rc1...1.0.0