@@ -6,8 +6,13 @@ import (
6
6
"github.com/codegangsta/cli"
7
7
"github.com/fatih/color"
8
8
"golang.org/x/crypto/ssh"
9
+ "golang.org/x/crypto/ssh/agent"
9
10
"io/ioutil"
11
+ "net"
10
12
"os"
13
+ // Use of the os/user package prevents cross-compilation
14
+ "os/user" // <- https://github.com/golang/go/issues/6376
15
+ "path/filepath"
11
16
"strings"
12
17
)
13
18
@@ -16,7 +21,7 @@ func main() {
16
21
app .HideVersion = true
17
22
app .Name = "mssh"
18
23
app .Usage = "Run SSH commands on multiple machines"
19
- app .Version = "0.0.2"
24
+ app .Version = "0.0.2.1 "
20
25
app .Flags = []cli.Flag {
21
26
cli.StringFlag {
22
27
Name : "user,u" ,
@@ -43,7 +48,7 @@ func main() {
43
48
},
44
49
cli.BoolTFlag {
45
50
Name : "color,c" ,
46
- Usage : "Print cmd output in color (true by default )" ,
51
+ Usage : "Print cmd output in color (use -c=false to disable )" ,
47
52
},
48
53
}
49
54
app .Action = defaultAction
@@ -52,41 +57,99 @@ func main() {
52
57
53
58
func defaultAction (c * cli.Context ) {
54
59
hosts := c .StringSlice ("server" )
60
+
61
+ currentUser , err := user .Current ()
62
+ if err != nil {
63
+ log .Errorf ("Could not get current user: %v" , err )
64
+ }
55
65
u := c .String ("user" )
66
+ // If no flag, get current username
67
+ if len (u ) == 0 {
68
+ u = currentUser .Username
69
+ if len (u ) == 0 {
70
+ log .Errorln ("No username specified!" )
71
+ }
72
+ }
56
73
74
+ // Getting keys:
75
+ // --key flag overrides all
76
+ // If no --key is defined, check for ssh-agent on $SSH_AUTH_SOCK
77
+ // If no ssh-agent defined, look or key in ~/.ssh/id_rsa
78
+ // fail if none of the above work
57
79
key := c .String ("key" )
80
+ auths := []ssh.AuthMethod {}
58
81
if len (key ) == 0 {
59
- // If no key provided use ~/.ssh/id_rsa
60
- key = os .Getenv ("HOME" ) + "/.ssh/id_rsa"
82
+ // If no key provided see if there's an ssh-agent running
83
+ if len (os .Getenv ("SSH_AUTH_SOCK" )) > 0 {
84
+ log .Println ("Attempting to use existing ssh-agent" )
85
+ conn , err := net .Dial ("unix" , os .Getenv ("SSH_AUTH_SOCK" ))
86
+ if err != nil {
87
+ return
88
+ }
89
+ defer conn .Close ()
90
+ ag := agent .NewClient (conn )
91
+ auths = []ssh.AuthMethod {ssh .PublicKeysCallback (ag .Signers )}
92
+ } else {
93
+ // Otherwise use ~/.ssh/id_rsa
94
+ key = filepath .FromSlash (currentUser .HomeDir + "/.ssh/id_rsa" )
95
+ if ! fileExists (key ) {
96
+ log .Errorln ("Must specify a key, ~/.ssh/id_rsa does not exist and no ssh-agent is available!" )
97
+ cli .ShowAppHelp (c )
98
+ os .Exit (2 )
99
+ }
100
+ pemBytes , err := ioutil .ReadFile (key )
101
+ if err != nil {
102
+ log .Errorf ("%v" , err )
103
+ }
104
+ signer , err := ssh .ParsePrivateKey (pemBytes )
105
+ if err != nil {
106
+ log .Errorf ("%v" , err )
107
+ }
108
+ auths = []ssh.AuthMethod {ssh .PublicKeys (signer )}
109
+ }
110
+ } else {
61
111
if ! fileExists (key ) {
62
- log .Errorln ("Must specify a key, ~/.ssh/id_rsa does not exist!" )
63
- cli .ShowAppHelp (c )
64
- os .Exit (2 )
112
+ log .Errorln ("Specified key does not exist!" )
113
+ os .Exit (1 )
114
+ }
115
+ pemBytes , err := ioutil .ReadFile (key )
116
+ if err != nil {
117
+ log .Errorf ("%v" , err )
118
+ }
119
+ signer , err := ssh .ParsePrivateKey (pemBytes )
120
+ if err != nil {
121
+ log .Errorf ("%v" , err )
65
122
}
123
+ auths = []ssh.AuthMethod {ssh .PublicKeys (signer )}
66
124
}
67
125
68
- // user and host(s) required
126
+ // host(s) is required
69
127
if len (hosts ) == 0 {
70
- log .Errorln ("At least one host is required" )
128
+ log .Warnln ("At least one host is required" )
71
129
cli .ShowAppHelp (c )
72
130
os .Exit (2 )
73
131
}
132
+
133
+ // At least one command is required
74
134
if len (c .Args ().First ()) == 0 {
135
+ log .Warnln ("At least one command is required" )
75
136
cli .ShowAppHelp (c )
76
137
os .Exit (2 )
77
138
}
139
+
140
+ // append list of commands to argList
78
141
argList := append ([]string {c .Args ().First ()}, c .Args ().Tail ()... )
79
142
chLen := len (hosts ) * len (argList )
80
143
done := make (chan bool , chLen )
81
144
for _ , h := range hosts {
82
145
// Hosts are handled asynchronously
83
- go func (c * cli.Context , host string , cmds []string ) {
146
+ go func (c * cli.Context , host string , cmds []string , auth []ssh. AuthMethod ) {
84
147
// Set the default port if none specified
85
148
if len (strings .Split (host , ":" )) == 1 {
86
149
host = host + ":22"
87
150
}
88
151
for _ , cmd := range cmds {
89
- combined , err := runRemoteCmd (u , host , key , cmd )
152
+ combined , err := runRemoteCmd (u , host , auth , cmd )
90
153
out := tail (string (combined ), c .Int ("lines" ))
91
154
if err != nil {
92
155
pretty := prettyOutput (
@@ -104,7 +167,7 @@ func defaultAction(c *cli.Context) {
104
167
}
105
168
done <- true
106
169
}
107
- }(c , h , argList )
170
+ }(c , h , argList , auths )
108
171
}
109
172
110
173
// Drain chan before exiting
@@ -150,18 +213,10 @@ func tail(s string, n int) string {
150
213
return strings .Join (lines [len (lines )- (n ):], "\n " )
151
214
}
152
215
153
- func runRemoteCmd (user string , addr string , key string , cmd string ) (out []byte , err error ) {
154
- pemBytes , err := ioutil .ReadFile (key )
155
- if err != nil {
156
- return
157
- }
158
- signer , err := ssh .ParsePrivateKey (pemBytes )
159
- if err != nil {
160
- return
161
- }
216
+ func runRemoteCmd (user string , addr string , auth []ssh.AuthMethod , cmd string ) (out []byte , err error ) {
162
217
client , err := ssh .Dial ("tcp" , addr , & ssh.ClientConfig {
163
218
User : user ,
164
- Auth : []ssh. AuthMethod { ssh . PublicKeys ( signer )} ,
219
+ Auth : auth ,
165
220
})
166
221
if err != nil {
167
222
return
@@ -176,7 +231,7 @@ func runRemoteCmd(user string, addr string, key string, cmd string) (out []byte,
176
231
return
177
232
}
178
233
179
- // FileExists returns a bool if os.Stat returns an IsNotExist error
234
+ // fileExists returns a bool if os.Stat returns an IsNotExist error
180
235
func fileExists (name string ) bool {
181
236
if _ , err := os .Stat (name ); err != nil {
182
237
if os .IsNotExist (err ) {
0 commit comments