diff --git a/.gitignore b/.gitignore index a7cb6ca..c2c76a8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ dist/ bin/ debug.test snap.login +.gocache/ +.gopath/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2bb5e61 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- `cmd/gopssh/main.go` provides the CLI entrypoint, with supporting helpers under `cmd/gopssh/gopssh/`. +- Core parallel SSH logic, worker pools, and agent wrappers live in `pkg/pssh/`, which also houses unit tests and fixtures in `pkg/pssh/test/`. +- Packaging utilities for Homebrew, RPM, and DEB builds are grouped under `pack/`, while release metadata lives in `releasenote.template.md`. +- The top-level `Makefile` orchestrates builds, tests, linting, and coverage aggregation; keep changes aligned with its targets. + +## Build, Test, and Development Commands +- `make build` compiles `cmd/gopssh` into a local `gopssh` binary; it is the default target and cleans stale artifacts. +- `make test` runs race-enabled tests for both the CLI and library packages and merges coverage into `coverage.txt`. +- `make lint` invokes the pinned `golangci-lint` binary from `./bin`; run `make setup` once to install it (requires network access). +- For release builds, mirror the README example: `go build -ldflags "-X main.version=$(TAG) ..."` so version metadata stays accurate. + +## Coding Style & Naming Conventions +- Follow Go defaults: tabs for indentation, `gofmt` + `goimports` formatting, exported symbols in PascalCase, internals in camelCase. +- Keep files self-contained and small; place shared concurrency helpers in `pkg/pssh` rather than duplicating logic in `cmd/`. +- Run `make fmt` (or its underlying `gofmt`/`goimports` loop) before committing to avoid lint noise. + +## Testing Guidelines +- Name tests `*_test.go` with functions `TestXxx`; table-driven cases fit well for SSH session permutations. +- Use `go test ./cmd/gopssh ./pkg/pssh -race` for quick local runs; rely on `make test` when you need coverage artifacts. +- Place reusable fixtures under `pkg/pssh/test` and keep them deterministic to prevent race test flakiness. + +## Commit & Pull Request Guidelines +- Recent history mixes concise imperative commits (`fix worker hang`) with Conventional Commits (`fix: update dependency`); prefer the latter style for clarity. +- Ensure commits stay focused and include context for packaging or release changes (e.g., note RPM spec updates). +- Before opening a PR, run `make build test lint`, attach the resulting summary, link related issues, and describe any manual SSH verification performed. + +## Release & Packaging Notes +- Build artifacts land in `.bin/`; confirm the binary version with `./gopssh -version` before running `pack/debpack` or `pack/rpmpack`. +- Update `releasenote.template.md` alongside packaging changes so CI-driven releases stay coherent. diff --git a/cmd/gopssh/main.go b/cmd/gopssh/main.go index 9ae6770..1df7e68 100644 --- a/cmd/gopssh/main.go +++ b/cmd/gopssh/main.go @@ -6,16 +6,15 @@ import ( "io" "log" "os" + "strings" "time" "github.com/masahide/gopssh/pkg/pssh" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/crypto/ssh" + "golang.org/x/term" ) const ( - defaultKexFlags = "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,curve25519-sha256@libssh.org" - defaultCiphersFlags = "arcfour256,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr" - defaultMacsFlags = "hmac-sha1-96,hmac-sha1,hmac-sha2-256,hmac-sha2-256-etm@openssh.com" // https://man.openbsd.org/ssh_config#IdentityFile defaultIdentityFiles = "~/.ssh/id_dsa,~/.ssh/id_ecdsa,~/.ssh/id_ed25519,~/.ssh/id_rsa" defaultMaxAgent = 50 @@ -25,6 +24,34 @@ const ( // nolint: gochecknoglobals var ( + defaultMacsFlags = []string{ + ssh.InsecureHMACSHA196, + ssh.HMACSHA1, + ssh.HMACSHA256ETM, + ssh.HMACSHA512ETM, + ssh.HMACSHA256, + ssh.HMACSHA512, + } + defaultKexAlgos = []string{ + ssh.InsecureKeyExchangeDH1SHA1, + ssh.InsecureKeyExchangeDH14SHA1, + ssh.InsecureKeyExchangeDHGEXSHA1, + ssh.KeyExchangeMLKEM768X25519, + ssh.KeyExchangeCurve25519, + ssh.KeyExchangeECDHP256, + ssh.KeyExchangeECDHP384, + ssh.KeyExchangeECDHP521, + ssh.KeyExchangeDH14SHA256, + } + defaultCiphersFlags = []string{ + ssh.InsecureCipherRC4256, + ssh.CipherAES128GCM, + ssh.CipherAES256GCM, + ssh.CipherChaCha20Poly1305, + ssh.CipherAES128CTR, + ssh.CipherAES192CTR, + ssh.CipherAES256CTR, + } version = "dev" commit = "none" date = "unknown" @@ -32,9 +59,9 @@ var ( ) func newConfig() *pssh.Config { - kexFlag := defaultKexFlags - ciphersFlag := defaultCiphersFlags - macsFlag := defaultMacsFlags + kexAlgos := strings.Join(defaultKexAlgos, ",") + ciphersFlag := strings.Join(defaultCiphersFlags, ",") + macsFlag := strings.Join(defaultMacsFlags, ",") identityFiles := defaultIdentityFiles c := pssh.Config{ Concurrency: 0, @@ -59,19 +86,19 @@ func newConfig() *pssh.Config { flag.BoolVar(&c.IgnoreHostKey, "k", c.IgnoreHostKey, "Do not check the host key") flag.BoolVar(&c.Debug, "debug", c.Debug, "debug outputs") flag.DurationVar(&c.Timeout, "timeout", c.Timeout, "maximum amount of time for the TCP connection to establish.") - flag.StringVar(&kexFlag, "kex", kexFlag, "allowed key exchanges algorithms") + flag.StringVar(&kexAlgos, "kex", kexAlgos, "allowed key exchanges algorithms") flag.StringVar(&ciphersFlag, "ciphers", ciphersFlag, "allowed cipher algorithms") flag.StringVar(&macsFlag, "macs", macsFlag, "allowed MAC algorithms") flag.StringVar(&identityFiles, "i", identityFiles, "identity files") flag.Parse() - c.Kex = pssh.ToSlice(kexFlag) + c.Kex = pssh.ToSlice(kexAlgos) c.Ciphers = pssh.ToSlice(ciphersFlag) c.Macs = pssh.ToSlice(macsFlag) c.IdentityFileOnly = identityFiles != defaultIdentityFiles c.IdentFiles = pssh.ToSlice(identityFiles) // see: https://qiita.com/tanksuzuki/items/e712717675faf4efb07a#パイプで渡された時だけ処理する - c.StdinFlag = !terminal.IsTerminal(0) + c.StdinFlag = !term.IsTerminal(0) return &c } diff --git a/go.mod b/go.mod index 9dd3f2d..3eb6a08 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,27 @@ module github.com/masahide/gopssh -go 1.23.0 +go 1.25 -toolchain go1.24.2 +toolchain go1.25.3 require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/dzeromsk/debpack v0.0.0-20190912160929-4b3d7b5dd69b github.com/fatih/color v1.18.0 - github.com/google/rpmpack v0.6.0 + github.com/google/rpmpack v0.7.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/pkg/errors v0.9.1 - golang.org/x/crypto v0.37.0 + golang.org/x/crypto v0.43.0 + golang.org/x/term v0.36.0 ) require ( github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.1 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/ulikunitz/xz v0.5.12 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/term v0.31.0 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + golang.org/x/sys v0.37.0 // indirect ) diff --git a/go.sum b/go.sum index ec7a59c..13cdcb4 100644 --- a/go.sum +++ b/go.sum @@ -6,46 +6,30 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/dzeromsk/debpack v0.0.0-20190912160929-4b3d7b5dd69b h1:dPL3hXMmS1fDUcnOmD7OYIr3J5P3TeIlEPYixIhe8u4= github.com/dzeromsk/debpack v0.0.0-20190912160929-4b3d7b5dd69b/go.mod h1:uFUKzp8opkz50jcHuTEG8ZrivmQ++kmTUJs7MkKvxvA= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/rpmpack v0.6.0 h1:LoQuqlw6kHRwg25n3M0xtYrW+z2pTkR0ae1xx11hRw8= -github.com/google/rpmpack v0.6.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= +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/rpmpack v0.7.1 h1:YdWh1IpzOjBz60Wvdw0TU0A5NWP+JTVHA5poDqwMO2o= +github.com/google/rpmpack v0.7.1/go.mod h1:h1JL16sUTWCLI/c39ox1rDaTBo3BXUQGjczVJyK4toU= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -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/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= 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/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= diff --git a/pkg/pssh/agent_wrap.go b/pkg/pssh/agent_wrap.go index 4768486..6ac0551 100644 --- a/pkg/pssh/agent_wrap.go +++ b/pkg/pssh/agent_wrap.go @@ -64,7 +64,7 @@ func (cp *connPools) newKeyAgent() (*keyAgent, error) { var err error ka.authConn, err = cp.dialSocket() if err != nil { - return nil, fmt.Errorf("Failed dial sockFile: %s,err:%w", cp.sockFile, err) + return nil, fmt.Errorf("failed dial sockFile: %s, err:%w", cp.sockFile, err) } if ka.authConn == nil { return nil, fmt.Errorf("dial sockFile: %s authConn==null", cp.sockFile) diff --git a/pkg/pssh/pssh.go b/pkg/pssh/pssh.go index 3f51939..7f108e8 100644 --- a/pkg/pssh/pssh.go +++ b/pkg/pssh/pssh.go @@ -6,7 +6,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" "net" "os" @@ -185,7 +184,7 @@ var re = regexp.MustCompile(":.+") func readHosts(fileName string) ([]string, error) { // nolint: gosec - data, err := ioutil.ReadFile(fileName) + data, err := os.ReadFile(fileName) if err != nil { return nil, err } @@ -265,7 +264,7 @@ func (p *Pssh) Run() int { stdin := []byte{} if p.StdinFlag { - if stdin, err = ioutil.ReadAll(os.Stdin); err != nil { + if stdin, err = io.ReadAll(os.Stdin); err != nil { log.Fatal(err) } } @@ -449,7 +448,7 @@ func (p *Pssh) readIdentFiles() [][]byte { for _, filePath := range p.IdentFiles { // nolint: gosec filePath = strings.Replace(filePath, "~", home, one) - buffer, err := ioutil.ReadFile(filePath) + buffer, err := os.ReadFile(filePath) if err != nil { continue }