[TAG] 2-cent tip: Pushing files to multiple hosts
Ben Okopnik
ben at callahans.org
Mon Feb 2 18:20:58 MSK 2004
When I teach a class, I often need to push one or more files to my
students' systems. Previously, I would write a "for-do-done" loop and
use "scp" to get the files across, laboriously logging in and exiting
out of each system every time I wanted to do a transfer - painfully
clunky.
Then I did some searching on the Net and found "sshtool" by "noconflic".
Written in Expect, it allows multiple host logins and copying. However,
it did not have a "no password" mode (i.e., logging in when
".ssh/authorized_keys" contains your key) and read the list of hosts
from a list defined within the program. I've modified it to read an
external file called "pushlist" and added a "no password" mode; this
last, of course, requires that you first push a "~/.ssh/authorized_keys"
to the host list.
===== SNIP HERE sshtool ================================================
#!/usr/bin/expect --
#
# Original idea and code by noconflic
# nocon at darkflame.net
# http://nocon.darkflame.net
# Wed Mar 12 16:01:47 CST 2003
#
# Modified Sat Dec 13 16:04:08 EST 2003 by Ben Okopnik <ben at callahans.org>
# - Added the "no password" options for "authorized_keys" logins
# - Set "hosts" list to read the contents of the "pushlist" file
# (this makes it easy to use different sets of hosts)
# Set the path to your local scp/ssh commands
set sshcmd /usr/bin/ssh
set sshcpy /usr/bin/scp
set timeout "-1"
# Read in the list of hosts
if { [catch "open pushlist" FID] == 0 } {
set hosts [read $FID]
close $FID
}
proc usage {} {
send_user "Usage: sshtool \[option\] <username>\n\n"
send_user "Option: (one of the following)\n"
send_user " -u execute command on hosts as a normal user.\n"
send_user " -U execute command on hosts as a normal user WITHOUT a password.\n"
send_user " -r execute command on hosts as root (uses sudo).\n"
send_user " -c copy file(s) or directory to hosts.\n"
send_user " -C copy file(s) or directory to hosts WITHOUT a password.\n"
send_user " -h this help\n\n"
send_user "<username> username of login account.\n\n"
exit
}
proc getpass {} {
stty -echo
send_user "SSH Password: "
expect_user -re "(.*)\n"
set password(pass) $expect_out(1,string)
send_user "\n"
stty echo
return $password(pass)
}
proc getcommand {} {
send_user "Enter Command: "
expect_user -re "(.*)\n"
set command(cmd) $expect_out(1,string)
return $command(cmd)
}
proc getfiledir {} {
send_user "Local directory or file: "
expect_user -re "(.*)\n"
set dirfile(dfile) $expect_out(1,string)
return $dirfile(dfile)
}
proc doit {user pass cmmnd files su f} {
global hosts sshcmd sshcpy sucmd
foreach host [set hosts] {
send_user "Connecting to: $host...\n"
if {$su == 1} {
spawn $sshcmd $user@$host $cmmnd
expect "password:"
send $pass\r
interact
}
if {$su == 2} {
spawn $sshcmd $user@$host $sucmd $cmmnd
expect "password:"
send $pass\r
expect "Password:"
send $pass\r
interact
}
if {$f == 1} {
spawn $sshcpy -r $files $user@$host:~/
expect "password:"
send $pass\r
interact
}
sleep 1
}
}
proc doit_nopass {user cmmnd files su f} {
global hosts sshcmd sshcpy sucmd
foreach host [set hosts] {
send_user "Connecting to: $host...\n"
if {$su == 1} {
spawn $sshcmd $user@$host $cmmnd
interact
}
if {$su == 2} {
spawn $sshcmd $user@$host $sucmd $cmmnd
interact
}
if {$f == 1} {
spawn $sshcpy -r $files $user@$host:~/
interact
}
sleep 1
}
}
# Program body
if {[llength $argv]!=2} usage
set sucmd sudo
set user [lindex $argv 1]
set su 0
set files 0
set f 0
set cmmnd 0
while {[llength $argv]>0} {
set flag [lindex $argv 0]
switch -- $flag \
"-u" {
send_user "Running command(s) on hosts as a normal user...\n"
set su 1
set pass [getpass]
set cmmnd [getcommand]
doit $user $pass $cmmnd $files $su $f
set argv [lrange $argv 2 end]
} "-U" {
send_user "Running command(s) on hosts as a normal user WITHOUT a password...\n"
set su 1
set cmmnd [getcommand]
doit_nopass $user $cmmnd $files $su $f
set argv [lrange $argv 2 end]
} "-r" {
send_user "Running command(s) on hosts as root...\n"
set su 2
set pass [getpass]
set cmmnd [getcommand]
doit $user $pass $cmmnd $files $su $f
set argv [lrange $argv 2 end]
} "-C" {
send_user "Sending file(s) to hosts WITHOUT a password...\n"
set f 1
set files [getfiledir]
doit_nopass $user $cmmnd $files $su $f
set argv [lrange $argv 2 end]
} "-c" {
send_user "Sending file(s) to hosts...\n"
set f 1
set pass [getpass]
set files [getfiledir]
doit $user $pass $cmmnd $files $su $f
set argv [lrange $argv 2 end]
} "-h" {
usage
set argv [lrange $argv 2 end]
} default {
usage
}
}
send_user "SSHTool Finished.\n"
exit
===== SNIP HERE ========================================================
First, create your "pushlist", possibly from an "/etc/hosts" on one of
the local machines. It should contain all your target hosts, one line
per host. Next, create your ".ssh/authorized_keys" in the directory
where you keep "sshtool" by copying your public keys into it:
ben at Fenrir:~/sshtool$ mkdir .ssh; cat ~/ssh/*pub > .ssh/authorized_keys
Then, push it out to your hosts (NOTE: this *replaces* the remote hosts'
"authorized_keys" files!):
# Log in as user "student" and send the local file
ben at Fenrir:~/sshtool$ ./sshtool -c .ssh/authorized_keys student
After this, I can push out any file or list of files simply by typing
ben at Fenrir:~/sshtool$ ./sshtool -C .ssh/authorized_keys student
I can also execute a command on all the systems via the "-U" option.
Note: I'm not an Expect programmer; otherwise, "sshtool" would accept a
"local:remote" syntax so files wouldn't need to be in identical
locations. It would also allow you to specify per-host usernames in the
push list (not an option I need, but something to make it more
flexible.) Anyone adding these features - please send me a copy. :)))
* Ben Okopnik * okopnik.freeshell.org * Technical Editor, Linux Gazette *
-*- See the Linux Gazette in its new home: <http://linuxgazette.net/> -*-
More information about the TAG
mailing list