diff options
author | LEVAI Daniel <leva@ecentrum.hu> | 2013-03-16 15:37:55 -0400 |
---|---|---|
committer | dsomero <xgizzmo@slackbuilds.org> | 2013-03-22 07:16:50 -0400 |
commit | 418715a80f36744a4868f82d5ca1fd42351c3ccc (patch) | |
tree | 93e283c4b72860b595f46e0752ea6e81efa95f47 /network/ClusterSSH | |
parent | f2ffa9e11687aa211f115f4cd3a8f1a107873b0a (diff) |
network/ClusterSSH: Added (run multiple ssh clients in paralell)
Signed-off-by: dsomero <xgizzmo@slackbuilds.org>
Diffstat (limited to 'network/ClusterSSH')
-rw-r--r-- | network/ClusterSSH/ClusterSSH.SlackBuild | 105 | ||||
-rw-r--r-- | network/ClusterSSH/ClusterSSH.info | 11 | ||||
-rw-r--r-- | network/ClusterSSH/README | 9 | ||||
-rw-r--r-- | network/ClusterSSH/patches/00-4.01_05.diff | 1641 | ||||
-rw-r--r-- | network/ClusterSSH/slack-desc | 19 |
5 files changed, 1785 insertions, 0 deletions
diff --git a/network/ClusterSSH/ClusterSSH.SlackBuild b/network/ClusterSSH/ClusterSSH.SlackBuild new file mode 100644 index 000000000000..245cd886b393 --- /dev/null +++ b/network/ClusterSSH/ClusterSSH.SlackBuild @@ -0,0 +1,105 @@ +#!/bin/sh +# +# Copyright (c) 2011, 2012, 2013 LEVAI Daniel +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +PRGNAM=ClusterSSH +VERSION=${VERSION:-4.01_05} +BUILD=${BUILD:-1} +TAG=${TAG:-_SBo} + +SRCNAM="App-${PRGNAM}" +SRCVER="4.01_01" + +if [ -z "$ARCH" ]; then + case "$( uname -m )" in + i?86) ARCH=i486 ;; + arm*) ARCH=arm ;; + *) ARCH=$( uname -m ) ;; + esac +fi + +CWD=$(pwd) +TMP=${TMP:-/tmp/SBo} +PKG=$TMP/package-$PRGNAM +OUTPUT=${OUTPUT:-/tmp} + +if [ "$ARCH" = "i486" ]; then + SLKCFLAGS="-O2 -march=i486 -mtune=i686" + LIBDIRSUFFIX="" +elif [ "$ARCH" = "i686" ]; then + SLKCFLAGS="-O2 -march=i686 -mtune=i686" + LIBDIRSUFFIX="" +elif [ "$ARCH" = "x86_64" ]; then + SLKCFLAGS="-O2 -fPIC" + LIBDIRSUFFIX="64" +else + SLKCFLAGS="-O2" + LIBDIRSUFFIX="" +fi + +set -e + +rm -rf $PKG +mkdir -p $TMP $PKG $OUTPUT +cd $TMP +rm -rf $SRCNAM-$SRCVER +tar xvf $CWD/$SRCNAM-$SRCVER.tar.gz +cd $SRCNAM-$SRCVER +chown -R root:root . +find . \ + \( -perm 777 -o -perm 775 -o -perm 711 -o -perm 555 -o -perm 511 \) \ + -exec chmod 755 {} \; -o \ + \( -perm 666 -o -perm 664 -o -perm 600 -o -perm 444 -o -perm 440 -o -perm 400 \) \ + -exec chmod 644 {} \; + +for diff in $CWD/patches/*.diff; do + patch -E --quiet -p1 < "${diff}" +done + +perl Build.PL \ + prefix=/usr \ + installdirs=vendor \ + destdir=$PKG +./Build +./Build test +./Build install \ + --install_path bindoc=/usr/man/man1 \ + --install_path libdoc=/usr/man/man3 + +find $PKG/usr/man -type f -exec gzip -9 {} \; +for i in $( find $PKG/usr/man -type l ) ; do ln -s $( readlink $i ).gz $i.gz ; rm $i ; done + +find $PKG -name perllocal.pod -o -name ".packlist" -o -name "*.bs" | xargs rm -f || true + +find $PKG -depth -type d -empty -delete || true + +mkdir -p $PKG/usr/doc/$PRGNAM-$VERSION +cp -a Changes README $PKG/usr/doc/$PRGNAM-$VERSION +cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild + +mkdir -p $PKG/install +cat $CWD/slack-desc > $PKG/install/slack-desc + +cd $PKG +/sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.${PKGTYPE:-tgz} diff --git a/network/ClusterSSH/ClusterSSH.info b/network/ClusterSSH/ClusterSSH.info new file mode 100644 index 000000000000..ce529b64f950 --- /dev/null +++ b/network/ClusterSSH/ClusterSSH.info @@ -0,0 +1,11 @@ +PRGNAM="ClusterSSH" +VERSION="4.01_05" +HOMEPAGE="http://clusterssh.sourceforge.net" +DOWNLOAD="http://downloads.sourceforge.net/clusterssh/App-ClusterSSH-4.01_01.tar.gz" +MD5SUM="2adcd457d0647409c8948df68d26b155" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +REQUIRES="perl-Try-Tiny perl-x11-protocol perl-File-Which perl-Test-Pod perl-Test-Pod-Coverage \ + perl-tk perl-Test-Trap perl-Exception-Class perl-Readonly perl-Test-DistManifest perl-Test-Differences" +MAINTAINER="LEVAI Daniel" +EMAIL="leva@ecentrum.hu" diff --git a/network/ClusterSSH/README b/network/ClusterSSH/README new file mode 100644 index 000000000000..992ddcc0bf7e --- /dev/null +++ b/network/ClusterSSH/README @@ -0,0 +1,9 @@ +ClusterSSH is a tool for making the same change on multiple servers at the same +time. The 'cssh' command opens an administration console and an xterm to all +specified hosts. Any text typed into the administration console is replicated +to all windows. All windows may also be typed into directly. + +This tool is intended for (but not limited to) cluster administration where the +same configuration or commands must be run on each node within the cluster. +Performing these commands all at once via this tool ensures all nodes are kept +in sync. diff --git a/network/ClusterSSH/patches/00-4.01_05.diff b/network/ClusterSSH/patches/00-4.01_05.diff new file mode 100644 index 000000000000..d7b05cf63140 --- /dev/null +++ b/network/ClusterSSH/patches/00-4.01_05.diff @@ -0,0 +1,1641 @@ +diff --git a/Build.PL b/Build.PL +index be3afb3..467c201 100644 +--- a/Build.PL ++++ b/Build.PL +@@ -6,7 +6,7 @@ use Module::Build; + my $build = Module::Build->new( + meta_merge => { + resources => { +- repository => [ ++ Repository => [ + 'http://clusterssh.git.sourceforge.net/', + 'http://github.com/duncs/clusterssh', + ], +@@ -34,6 +34,10 @@ my $build = Module::Build->new( + 'File::Which' => 0, + 'File::Temp' => 0, + 'Test::DistManifest' => 0, ++ 'Test::Differences' => 0, ++ }, ++ configure_requires => { ++ 'Module::Build' => 0, + }, + add_to_cleanup => ['App-ClusterSSH-*'], + create_makefile_pl => 'traditional', +diff --git a/Changes b/Changes +index c1fe7e9..485932d 100644 +--- a/Changes ++++ b/Changes +@@ -1,3 +1,34 @@ ++2013-03-05 Duncan Ferguson <duncan_ferguson@user.sf.net> - v4.01_05 ++- New option (-m, --unique-servers) to remove repeated servers when openeing terminals (Thanks to Oliver Meissner) ++- Drop MYMETA.yml and .json files from the distribution ++- Do not set default user name to prevent overriding ssh configuration ++ ++2013-02-26 Duncan Ferguson <duncan_ferguson@user.sf.net> - v4.01_04 ++- Fixed 'ccon' not calling the correct command (Sf bug 3605002) ++- Fixed clusters not being defined correctly within the .clusterssh/config file (Sf bug 3605675) ++ ++2013-02-15 Duncan Ferguson <duncan_ferguson@user.sf.net> - v4.01_03 ++* Correct documentation for references to $HOME/.clusterssh/config ++* Re-add user back into the configurartion file ++* Add in missing newline for some error messages ++* Allow the path to rsh/ssh/telnet to be defined in the configuration file ++* Move .csshrc to .csshrc.DISABLED since it should no longer be used ++* Error emitted when adding a host via the "Hosts" drop-down (Debian bug ID #578208) ++* Pastes uses a strange keyboard layout (Debian bug ID #364565) ++* Cope with being invoked by 'clusterssh' (Debian bug ID #644368) ++* Fix migration of .csshrc when not working as expected (Debian bug ID #673507) ++* Remove doc references to 'always_tile' as renamed 'window_tiling' (Debian bug ID #697371) ++* Updated manpage whatis entries (patch by Tony Mancill) ++* Fix watch line expression to catch 4.x series tarballs (Debian patch LP ID #1076897) ++* Allow tests to pass successfully when run as root ++* Fix cssh starting if xterm is not installed (Sf bug 3494988) ++* Set WM_CLASS on windows to 'cssh' (Sf bug 3187736) ++ ++2012-12-09 Duncan Ferguson <duncan_ferguson@user.sf.net> - v4.01_02 ++* Fix logic when using 'autoclose' on the command line or config file ++* Fix $HOME/.clusterssh/clusters being read in ++* Fix 'ctel', 'crsh' and 'ccon'so they work as expected ++ + 2011-12-09 Duncan Ferguson <duncan_ferguson@user.sf.net> - v4.01_01 + * Include missing files from release tarballs + +diff --git a/MANIFEST b/MANIFEST +index cebe4b1..a4b4983 100644 +--- a/MANIFEST ++++ b/MANIFEST +@@ -14,13 +14,11 @@ lib/App/ClusterSSH/Helper.pm + lib/App/ClusterSSH/Host.pm + lib/App/ClusterSSH/L10N.pm + lib/App/ClusterSSH/L10N/en.pm +-Makefile +-Makefile.old + Makefile.PL + MANIFEST + MANIFEST.SKIP ++META.json + META.yml +-MYMETA.json + README + t/00-load.t + t/01l10n.t +diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP +index 3753e38..4900842 100644 +--- a/MANIFEST.SKIP ++++ b/MANIFEST.SKIP +@@ -4,7 +4,11 @@ + ^Build$ + ^.git/ + ^.gitignore ++^Makefile$ ++^Makefile.old$ + ^MANIFEST\.bak$ +-^MYMETA.yml$ ++MYMETA.json ++MYMETA.yml ++pm_to_blib + .*\.swp$ + ^WIP_TASKS$ +diff --git a/META.json b/META.json +new file mode 100644 +index 0000000..26b6d74 +--- /dev/null ++++ b/META.json +@@ -0,0 +1,94 @@ ++{ ++ "abstract" : "A container for functions of the ClusterSSH programs", ++ "author" : [ ++ "Duncan Ferguson <duncan_j_ferguson@yahoo.co.uk>" ++ ], ++ "dynamic_config" : 1, ++ "generated_by" : "Module::Build version 0.4003, CPAN::Meta::Converter version 2.112621", ++ "license" : [ ++ "perl_5" ++ ], ++ "meta-spec" : { ++ "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", ++ "version" : "2" ++ }, ++ "name" : "App-ClusterSSH", ++ "prereqs" : { ++ "build" : { ++ "requires" : { ++ "File::Temp" : 0, ++ "File::Which" : 0, ++ "Readonly" : 0, ++ "Test::Differences" : 0, ++ "Test::DistManifest" : 0, ++ "Test::Pod" : 0, ++ "Test::Pod::Coverage" : 0, ++ "Test::Trap" : 0 ++ } ++ }, ++ "configure" : { ++ "requires" : { ++ "Module::Build" : 0 ++ } ++ }, ++ "runtime" : { ++ "requires" : { ++ "Exception::Class" : "1.31", ++ "Locale::Maketext" : 0, ++ "Tk" : "800.022", ++ "Try::Tiny" : 0, ++ "X11::Protocol" : "0.56", ++ "version" : 0 ++ } ++ } ++ }, ++ "provides" : { ++ "App::ClusterSSH" : { ++ "file" : "lib/App/ClusterSSH.pm", ++ "version" : "4.01_05" ++ }, ++ "App::ClusterSSH::Base" : { ++ "file" : "lib/App/ClusterSSH/Base.pm", ++ "version" : "0.02" ++ }, ++ "App::ClusterSSH::Cluster" : { ++ "file" : "lib/App/ClusterSSH/Cluster.pm", ++ "version" : "0.01" ++ }, ++ "App::ClusterSSH::Config" : { ++ "file" : "lib/App/ClusterSSH/Config.pm", ++ "version" : "0.02" ++ }, ++ "App::ClusterSSH::Helper" : { ++ "file" : "lib/App/ClusterSSH/Helper.pm", ++ "version" : "0.02" ++ }, ++ "App::ClusterSSH::Host" : { ++ "file" : "lib/App/ClusterSSH/Host.pm", ++ "version" : "0.03" ++ }, ++ "App::ClusterSSH::L10N" : { ++ "file" : "lib/App/ClusterSSH/L10N.pm", ++ "version" : 0 ++ }, ++ "App::ClusterSSH::L10N::en" : { ++ "file" : "lib/App/ClusterSSH/L10N/en.pm", ++ "version" : 0 ++ } ++ }, ++ "release_status" : "testing", ++ "resources" : { ++ "bugtracker" : { ++ "web" : "http://sourceforge.net/tracker/?group_id=89139" ++ }, ++ "homepage" : "http://clusterssh.sourceforge.net/", ++ "license" : [ ++ "http://dev.perl.org/licenses/" ++ ], ++ "x_Repository" : [ ++ "http://clusterssh.git.sourceforge.net/", ++ "http://github.com/duncs/clusterssh" ++ ] ++ }, ++ "version" : "4.01_05" ++} +diff --git a/META.yml b/META.yml +index bdf1020..7e93bb8 100644 +--- a/META.yml ++++ b/META.yml +@@ -6,13 +6,15 @@ build_requires: + File::Temp: 0 + File::Which: 0 + Readonly: 0 ++ Test::Differences: 0 + Test::DistManifest: 0 + Test::Pod: 0 + Test::Pod::Coverage: 0 + Test::Trap: 0 + configure_requires: +- Module::Build: 0.36 +-generated_by: 'Module::Build version 0.3603' ++ Module::Build: 0 ++dynamic_config: 1 ++generated_by: 'Module::Build version 0.4003, CPAN::Meta::Converter version 2.112621' + license: perl + meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html +@@ -21,7 +23,7 @@ name: App-ClusterSSH + provides: + App::ClusterSSH: + file: lib/App/ClusterSSH.pm +- version: 4.01_01 ++ version: 4.01_05 + App::ClusterSSH::Base: + file: lib/App/ClusterSSH/Base.pm + version: 0.02 +@@ -30,17 +32,19 @@ provides: + version: 0.01 + App::ClusterSSH::Config: + file: lib/App/ClusterSSH/Config.pm +- version: 0.01 ++ version: 0.02 + App::ClusterSSH::Helper: + file: lib/App/ClusterSSH/Helper.pm +- version: 0.01 ++ version: 0.02 + App::ClusterSSH::Host: + file: lib/App/ClusterSSH/Host.pm + version: 0.03 + App::ClusterSSH::L10N: + file: lib/App/ClusterSSH/L10N.pm ++ version: 0 + App::ClusterSSH::L10N::en: + file: lib/App/ClusterSSH/L10N/en.pm ++ version: 0 + requires: + Exception::Class: 1.31 + Locale::Maketext: 0 +@@ -52,7 +56,7 @@ resources: + bugtracker: http://sourceforge.net/tracker/?group_id=89139 + homepage: http://clusterssh.sourceforge.net/ + license: http://dev.perl.org/licenses/ +- repository: ++ x_Repository: + - http://clusterssh.git.sourceforge.net/ + - http://github.com/duncs/clusterssh +-version: 4.01_01 ++version: 4.01_05 +diff --git a/Makefile.PL b/Makefile.PL +index 4e126ef..059ea01 100644 +--- a/Makefile.PL ++++ b/Makefile.PL +@@ -1,32 +1,33 @@ +-# Note: this file was auto-generated by Module::Build::Compat version 0.3603 ++# Note: this file was auto-generated by Module::Build::Compat version 0.4003 + use ExtUtils::MakeMaker; + WriteMakefile + ( +- 'NAME' => 'App::ClusterSSH', +- 'VERSION_FROM' => 'lib/App/ClusterSSH.pm', +- 'PREREQ_PM' => { +- 'Exception::Class' => '1.31', +- 'File::Temp' => 0, +- 'File::Which' => 0, +- 'Locale::Maketext' => 0, +- 'Readonly' => 0, +- 'Test::DistManifest' => 0, +- 'Test::Pod' => 0, +- 'Test::Pod::Coverage' => 0, +- 'Test::Trap' => 0, +- 'Tk' => '800.022', +- 'Try::Tiny' => 0, +- 'X11::Protocol' => '0.56', +- 'version' => '0' +- }, +- 'INSTALLDIRS' => 'site', +- 'EXE_FILES' => [ +- 'bin/ccon', +- 'bin/crsh', +- 'bin/cscp', +- 'bin/cssh', +- 'bin/ctel' +- ], +- 'PL_FILES' => {} +- ) ++ 'NAME' => 'App::ClusterSSH', ++ 'VERSION_FROM' => 'lib/App/ClusterSSH.pm', ++ 'PREREQ_PM' => { ++ 'Exception::Class' => '1.31', ++ 'File::Temp' => 0, ++ 'File::Which' => 0, ++ 'Locale::Maketext' => 0, ++ 'Readonly' => 0, ++ 'Test::Differences' => 0, ++ 'Test::DistManifest' => 0, ++ 'Test::Pod' => 0, ++ 'Test::Pod::Coverage' => 0, ++ 'Test::Trap' => 0, ++ 'Tk' => '800.022', ++ 'Try::Tiny' => 0, ++ 'X11::Protocol' => '0.56', ++ 'version' => '0' ++ }, ++ 'INSTALLDIRS' => 'site', ++ 'EXE_FILES' => [ ++ 'bin/ccon', ++ 'bin/crsh', ++ 'bin/cscp', ++ 'bin/cssh', ++ 'bin/ctel' ++ ], ++ 'PL_FILES' => {} ++) + ; +diff --git a/THANKS b/THANKS +index 3af7cd0..bc2b3f7 100644 +--- a/THANKS ++++ b/THANKS +@@ -40,3 +40,4 @@ Simon Fraser + Stefan Steiner + Ryan Brown + Brandon Perkins ++Oliver Meissner +diff --git a/bin/cssh b/bin/cssh +index 3a11dc1..6b95202 100755 +--- a/bin/cssh ++++ b/bin/cssh +@@ -66,12 +66,12 @@ that host. Re-selecting it will plug it back in. + =item * + + If your window manager menu bars are obscured by terminal windows see +-the C<screen_reserve_XXXXX> options in the F<csshrc> file (see L</"FILES">). ++the C<screen_reserve_XXXXX> options in the F<$HOME/.clusterssh/config> file (see L</"FILES">). + + =item * + + If the terminals overlap too much see the C<terminal_reserve_XXXXX> +-options in the F<csshrc> file (see L</"FILES">). ++options in the F<$HOME/.clusterssh/config> file (see L</"FILES">). + + =item * + +@@ -115,7 +115,7 @@ be due to either the C<-xrm> terminal option which enables C<AllowSendEvents> + (some terminal do not require this option, other terminals have another + method for enabling it - see your terminal documention) or the + C<ConnectTimeout> ssh option (see the configuration option C<-o> or file +-C<csshrc> below to resolve this). ++C<$HOME/.clusterssh/config> below to resolve this). + + =back + +@@ -201,7 +201,7 @@ F<$HOME/.ssh/config>). + =item --output-config,-u + + Output the current configuration in the same format used by the +-F<$HOME/.csshrc> file. ++F<$HOME/.clusterssh/config> file. + + =item --port,-p <port> + +@@ -224,6 +224,10 @@ Enable|Disable window tiling (overriding the config file) + + Specify the initial part of the title used in the console and client windows + ++=item --unique-servers,-m ++ ++Connect to each host only once ++ + =item --use_all_a_records,-A + + If a hostname resolves to multiple IP addresses, toggle whether or not to +@@ -255,7 +259,7 @@ on standard port (e.g not listening on port 22) and ssh_config cannot be used. + =item <tag> ... + + Open a series of xterms defined by <tag> within either /etc/clusters or +-F<$HOME/.csshrc> (see L</"FILES">). ++F<$HOME/.clusterssh/config> (see L</"FILES">). + + Note: specifying a username on a cluster tag will override any usernames + defined in the cluster +@@ -354,13 +358,13 @@ nested, but be aware of recursive tags which are not checked for. + + Clusters may also be specified either directly (see C<clusters> configuration + options) or indirectly (see C<extra_cluster_file> configuration option) +-in the users F<$HOME/.csshrc> file. ++in the users F<$HOME/.clusterssh/config> file. + + NOTE: there is a special cluster tag called C<default> - any tags or hosts + included within this tag will be automatically opened if no other tags + are specified on the command line. + +-=item F</etc/csshrc> & F<$HOME/.csshrc> ++=item F</etc/csshrc> & F<$HOME/.clusterssh/config> + + This file contains configuration overrides - the defaults are as marked. + Default options are overwritten first by the global file, and then by the +@@ -377,10 +381,6 @@ should be written as + + =over + +-=item always_tile = yes +- +-Setting to anything other than C<yes> does not perform window tiling (see also -G). +- + =item auto_close = 5 + + Close terminal window after this many seconds. If set to 0 will instead wait +@@ -472,10 +472,21 @@ program start + Default key sequence to paste text into the console window using the mouse. + See below notes on shortcuts. + ++=item rsh = rsh ++ ++=item ssh = ssh ++ ++=item telnet = telnet ++ ++Set the path to the specific binary to use for the communication method, else ++uses the first match found in $PATH ++ + =item rsh_args = <blank> + + =item ssh_args = "-x -o ConnectTimeout=10" + ++=item telnet_args = <blank> ++ + Sets any arguments to be used with the communication method (defaults to ssh + arguments). + +@@ -725,7 +736,7 @@ If you have issues running cssh, first try: + C<< cssh -e [user@]<hostname>[:port] >> + + This performs two tests to confirm cssh is able to work properly with the +-settings provided within the F<.csshrc> file (or internal defaults). ++settings provided within the F<$HOME/.clusterssh/config> file (or internal defaults). + + 1. test the terminal window works with the options provided + +@@ -734,7 +745,7 @@ settings provided within the F<.csshrc> file (or internal defaults). + Configuration options to watch for in ssh are + + - Doesnt understand "-o ConnectTimeout=10" - remove the option +- in the F<.csshrc> file ++ in the F<$HOME/.clusterssh/config> file + + - OpenSSH-3.8 using untrusted ssh tunnels - use "-Y" instead of "-X" + or use "ForwardX11Trusted yes' in ssh_config (if you change the +@@ -751,7 +762,7 @@ C<< perl -MTk -e 'print $Tk::VERSION,$/' >> + + C<< perl -MX11::Protocol -e 'print $X11::Protocol::VERSION,$/' >> + +-C<< cat /etc/csshrc $HOME/.csshrc >> ++C<< cat /etc/csshrc $HOME/.clusterssh/config >> + + =item * + +diff --git a/lib/App/ClusterSSH.pm b/lib/App/ClusterSSH.pm +index 793de94..ec568fc 100644 +--- a/lib/App/ClusterSSH.pm ++++ b/lib/App/ClusterSSH.pm +@@ -3,7 +3,7 @@ package App::ClusterSSH; + use 5.008.004; + use warnings; + use strict; +-use version; our $VERSION = version->new('4.01_01'); ++use version; our $VERSION = version->new('4.01_05'); + + use Carp; + +@@ -116,12 +116,13 @@ my @options_spec = ( + 'font|f=s', + 'list|L', + 'use_all_a_records|A', ++ 'unique-servers|m', + ); + my %options; +-my %windows; # hash for all window definitions +-my %menus; # hash for all menu definitions +-my @servers; # array of servers provided on cmdline +-my %servers; # hash of server cx info ++my %windows; # hash for all window definitions ++my %menus; # hash for all menu definitions ++my @servers; # array of servers provided on cmdline ++my %servers; # hash of server cx info + my $xdisplay; + my %keyboardmap; + my $sysconfigdir = "/etc"; +@@ -277,53 +278,68 @@ sub load_keyboard_map() { + + logmsg( 1, "Loading keymaps and keycodes" ); + +- foreach ( 0 .. $#keyboard ) { +- if ( defined $keyboard[$_][3] ) { +- if ( defined( $keycodetosym{ $keyboard[$_][3] } ) ) { +- $keyboardmap{ $keycodetosym{ $keyboard[$_][3] } } +- = 'sa' . ( $_ + $min ); +- } +- else { +- logmsg( 2, "Unknown keycode ", $keyboard[$_][3] ) +- if ( $keyboard[$_][3] != 0 ); +- } +- } +- if ( defined $keyboard[$_][2] ) { +- if ( defined( $keycodetosym{ $keyboard[$_][2] } ) ) { +- $keyboardmap{ $keycodetosym{ $keyboard[$_][2] } } +- = 'a' . ( $_ + $min ); +- } +- else { +- logmsg( 2, "Unknown keycode ", $keyboard[$_][2] ) +- if ( $keyboard[$_][2] != 0 ); +- } +- } +- if ( defined $keyboard[$_][1] ) { +- if ( defined( $keycodetosym{ $keyboard[$_][1] } ) ) { +- $keyboardmap{ $keycodetosym{ $keyboard[$_][1] } } +- = 's' . ( $_ + $min ); +- } +- else { +- logmsg( 2, "Unknown keycode ", $keyboard[$_][1] ) +- if ( $keyboard[$_][1] != 0 ); +- } +- } +- if ( defined $keyboard[$_][0] ) { +- if ( defined( $keycodetosym{ $keyboard[$_][0] } ) ) { +- $keyboardmap{ $keycodetosym{ $keyboard[$_][0] } } +- = 'n' . ( $_ + $min ); ++ my %keyboard_modifier_priority = ( ++ 'sa' => 3, # lowest ++ 'a' => 2, ++ 's' => 1, ++ 'n' => 0, # highest ++ ); ++ ++ my %keyboard_stringlike_modifiers = reverse %keyboard_modifier_priority; ++ ++ # try to associate $keyboard=X11->GetKeyboardMapping table with X11::Keysyms ++ foreach my $i ( 0 .. $#keyboard ) { ++ for my $modifier ( 0 .. 3 ) { ++ if ( defined( $keycodetosym{ $keyboard[$i][$modifier] } ) ) { ++ ++ # keyboard layout contains the keycode at $modifier level ++ if (defined( ++ $keyboardmap{ $keycodetosym{ $keyboard[$i][$modifier] ++ } } ++ ) ++ ) ++ { ++ ++# we already have a mapping, let's see whether current one is better (lower shift state) ++ my ( $mod_code, $key_code ) ++ = $keyboardmap{ $keycodetosym{ $keyboard[$i] ++ [$modifier] } } =~ /^(\D+)(\d+)$/; ++ ++ # it is not easy to get around our own alien logic storing modifiers ;-) ++ if ( $modifier < $keyboard_modifier_priority{$mod_code} ) ++ { ++ ++ # YES! current keycode have priority over old one (phew!) ++ $keyboardmap{ $keycodetosym{ $keyboard[$i][$modifier] ++ } } ++ = $keyboard_stringlike_modifiers{$modifier} ++ . ( $i + $min ); ++ } ++ } ++ else { ++ ++ # we don't yet have a mapping... piece of cake! ++ $keyboardmap{ $keycodetosym{ $keyboard[$i][$modifier] } } ++ = $keyboard_stringlike_modifiers{$modifier} ++ . ( $i + $min ); ++ } + } + else { +- logmsg( 2, "Unknown keycode ", $keyboard[$_][0] ) +- if ( $keyboard[$_][0] != 0 ); ++ ++ # we didn't get the code from X11::Keysyms ++ if ( $keyboard[$i][$modifier] != 0 ) { ++ ++ # ignore code=0 ++ logmsg( 2, "Unknown keycode ", $keyboard[$i][$modifier] ); ++ } + } + } +- +- # dont know these two key combs yet... +- #$keyboardmap{ $keycodetosym { $keyboard[$_][4] } } = $_ + $min; +- #$keyboardmap{ $keycodetosym { $keyboard[$_][5] } } = $_ + $min; + } + ++ # dont know these two key combs yet... ++ #$keyboardmap{ $keycodetosym { $keyboard[$_][4] } } = $_ + $min; ++ #$keyboardmap{ $keycodetosym { $keyboard[$_][5] } } = $_ + $min; ++ + #print "$_ => $keyboardmap{$_}\n" foreach(sort(keys(%keyboardmap))); + #print "keysymtocode: $keysymtocode{o}\n"; + #die; +@@ -383,8 +399,9 @@ sub resolve_names(@) { + if ( defined($hostobj) ) { + my @alladdrs = map { inet_ntoa($_) } @{ $hostobj->addr_list }; + if ( $#alladdrs > 0 ) { +- $self->cluster->register_tag($dirty, @alladdrs); +- logmsg( 3, 'Expanded to ', $self->cluster->get_tag($dirty) ); ++ $self->cluster->register_tag( $dirty, @alladdrs ); ++ logmsg( 3, 'Expanded to ', ++ $self->cluster->get_tag($dirty) ); + } + else { + logmsg( 3, 'Only one A record' ); +@@ -407,11 +424,22 @@ sub resolve_names(@) { + # now clean the array up + @servers = grep { $_ !~ m/^$/ } @servers; + ++ if ($self->config->{unique_servers}) { ++ logmsg( 3, 'removing duplicate server names' ); ++ @servers=remove_repeated_servers(@servers); ++ } ++ + logmsg( 3, 'leaving with ', $_ ) foreach (@servers); + logmsg( 2, 'Resolving cluster names: completed' ); + return (@servers); + } + ++sub remove_repeated_servers { ++ my %all=(); ++ @all{@_}=1; ++ return (keys %all); ++} ++ + sub change_main_window_title() { + my ($self) = @_; + my $number = keys(%servers); +@@ -588,7 +616,7 @@ sub open_client_windows(@) { + my $server_object = App::ClusterSSH::Host->parse_host_string($_); + + my $username = $server_object->get_username(); +- $username = $self->config->{user} if ( $self->config->{user} ); ++ $username = $self->config->{user} if ( !$username && $self->config->{user} ); + my $port = $server_object->get_port(); + $port = $self->config->{port} if ( $self->config->{port} ); + my $server = $server_object->get_hostname(); +@@ -751,7 +779,7 @@ sub get_font_size() { + + eval { (%font_info) = $xdisplay->QueryFont($font); } + || die( "Fatal: Unrecognised font used ($terminal_font).\n" +- . "Please amend \$HOME/.csshrc with a valid font (see man page).\n" ++ . "Please amend \$HOME/.clusterssh/config with a valid font (see man page).\n" + ); + + $self->config->{internal_font_width} +@@ -763,7 +791,7 @@ sub get_font_size() { + || !$self->config->{internal_font_height} ) + { + die( "Fatal: Unrecognised font used ($terminal_font).\n" +- . "Please amend \$HOME/.csshrc with a valid font (see man page).\n" ++ . "Please amend \$HOME/.clusterssh/config with a valid font (see man page).\n" + ); + } + +@@ -1134,7 +1162,7 @@ sub add_host_by_name() { + $self->open_client_windows(@names); + } + +- if ( $menus{listbox}->curselection() ) { ++ if ( defined $menus{listbox} && $menus{listbox}->curselection() ) { + my @hosts = $menus{listbox}->get( $menus{listbox}->curselection() ); + logmsg( 2, "host=", join( ' ', @hosts ) ); + $self->open_client_windows( $self->resolve_names(@hosts) ); +@@ -1265,7 +1293,8 @@ sub setup_repeat() { + sub create_windows() { + my ($self) = @_; + logmsg( 2, "create_windows: started" ); +- $windows{main_window} = MainWindow->new( -title => "ClusterSSH" ); ++ $windows{main_window} ++ = MainWindow->new( -title => "ClusterSSH", -class => 'cssh', ); + $windows{main_window}->withdraw; # leave withdrawn until needed + + if ( defined( $self->config->{console_position} ) +@@ -1279,6 +1308,7 @@ sub create_windows() { + -textvariable => \$menus{entrytext}, + -insertborderwidth => 4, + -width => 25, ++ -class => 'cssh', + )->pack( + -fill => "x", + -expand => 1, +@@ -1291,6 +1321,7 @@ sub create_windows() { + -height => $self->config->{history_height}, + -state => 'normal', + -takefocus => 0, ++ -class => 'cssh', + ); + $windows{history}->bindtags(undef); + +@@ -1353,6 +1384,7 @@ sub create_windows() { + -popover => $windows{main_window}, + -overanchor => "c", + -popanchor => "c", ++ -class => 'cssh', + -font => [ + -family => "interface system", + -size => 10, +@@ -1367,6 +1399,7 @@ sub create_windows() { + -overanchor => "c", + -title => "Cssh Documentation", + -buttons => ['Close'], ++ -class => 'cssh', + ); + + my $manpage = `pod2text -l -q=\"\" $0 2>/dev/null`; +@@ -1385,17 +1418,19 @@ sub create_windows() { + -title => "Add Host(s) or Cluster(s)", + -buttons => [ 'Add', 'Cancel' ], + -default_button => 'Add', ++ -class => 'cssh', + ); + + if ( $self->config->{max_addhost_menu_cluster_items} +- && scalar $self->cluster->list_tags() ) ++ && scalar $self->cluster->list_tags() ) + { +- if (scalar +- scalar $self->cluster->list_tags() < $self->config->{max_addhost_menu_cluster_items} ) ++ if (scalar scalar $self->cluster->list_tags() ++ < $self->config->{max_addhost_menu_cluster_items} ) + { + $menus{listbox} = $windows{addhost}->Listbox( + -selectmode => 'extended', + -height => scalar $self->cluster->list_tags(), ++ -class => 'cssh', + )->pack(); + } + else { +@@ -1404,6 +1439,7 @@ sub create_windows() { + -scrollbars => 'e', + -selectmode => 'extended', + -height => $self->config->{max_addhost_menu_cluster_items}, ++ -class => 'cssh', + )->pack(); + } + $menus{listbox}->insert( 'end', sort $self->cluster->list_tags() ); +@@ -1415,6 +1451,7 @@ sub create_windows() { + -width => 20, + -label => 'Host', + -labelPack => [ -side => 'left', ], ++ -class => 'cssh', + )->pack( -side => 'left' ); + logmsg( 2, "create_windows: completed" ); + +@@ -1543,7 +1580,7 @@ sub key_event { + + logmsg( 3, "key=:$key:" ); + if ( $combo =~ /^$key$/ ) { +- logmsg(3, "matched combo"); ++ logmsg( 3, "matched combo" ); + if ( $event eq "KeyRelease" ) { + logmsg( 2, "Received hotkey: $hotkey" ); + send_text_to_all_servers('%s') +@@ -1600,8 +1637,8 @@ sub key_event { + sub create_menubar() { + my ($self) = @_; + logmsg( 2, "create_menubar: started" ); +- $menus{bar} = $windows{main_window}->Menu; +- $windows{main_window}->configure( -menu => $menus{bar} ); ++ $menus{bar} = $windows{main_window}->Menu(); ++ $windows{main_window}->configure( -menu => $menus{bar}, ); + + $menus{file} = $menus{bar}->cascade( + -label => 'File', +@@ -1626,22 +1663,22 @@ sub create_menubar() { + -menuitems => [ + [ "command", + "Retile Windows", +- -command => sub{ $self->retile_hosts }, ++ -command => sub { $self->retile_hosts }, + -accelerator => $self->config->{key_retilehosts}, + ], + + # [ "command", "Capture Terminal", -command => \&capture_terminal, ], + [ "command", + "Toggle active state", +- -command => sub{ $self->toggle_active_state() }, ++ -command => sub { $self->toggle_active_state() }, + ], + [ "command", + "Close inactive sessions", +- -command => sub{ $self->close_inactive_sessions() }, ++ -command => sub { $self->close_inactive_sessions() }, + ], + [ "command", + "Add Host(s) or Cluster(s)", +- -command => sub{ $self->add_host_by_name, }, ++ -command => sub { $self->add_host_by_name, }, + -accelerator => $self->config->{key_addhost}, + ], + '', +@@ -1667,7 +1704,8 @@ sub create_menubar() { + ); + + $windows{main_window}->bind( '<KeyPress>' => [ $self => 'key_event' ], ); +- $windows{main_window}->bind( '<KeyRelease>' => [ $self => 'key_event' ], ); ++ $windows{main_window} ++ ->bind( '<KeyRelease>' => [ $self => 'key_event' ], ); + logmsg( 2, "create_menubar: completed" ); + } + +@@ -1797,12 +1835,15 @@ sub run { + } + + if ( $options{action} ) { +- $self->config->{command} = $options{action} ; ++ $self->config->{command} = $options{action}; + } + ++ $self->config->{unique_servers} = 1 if $options{'unique-servers'}; ++ + $self->config->{auto_quit} = "yes" if $options{autoquit}; + $self->config->{auto_quit} = "no" if $options{'no-autoquit'}; +- $self->config->{auto_close} = $options{autoclose} if $options{'autoclose'}; ++ $self->config->{auto_close} = $options{autoclose} ++ if defined $options{'autoclose'}; + + $self->config->{window_tiling} = "yes" if $options{tile}; + $self->config->{window_tiling} = "no" if $options{'no-tile'}; +@@ -1815,7 +1856,7 @@ sub run { + + $self->config->{terminal_font} = $options{font} if ( $options{font} ); + $self->config->{terminal_args} = $options{'term-args'} +- if ( $options{'term-args'} ); ++ if ( $options{'term-args'} ); + if ( $self->config->{terminal_args} =~ /-class (\w+)/ ) { + $self->config->{terminal_allow_send_events} + = "-xrm '$1.VT100.allowSendEvents:true'"; +@@ -1998,6 +2039,8 @@ the code until this time. + + =item populate_send_menu_entries_from_xml + ++=item remove_repeated_servers ++ + =item resolve_names + + =item retile_hosts +diff --git a/lib/App/ClusterSSH/Base.pm b/lib/App/ClusterSSH/Base.pm +index 95a92ed..e99cca4 100644 +--- a/lib/App/ClusterSSH/Base.pm ++++ b/lib/App/ClusterSSH/Base.pm +@@ -157,7 +157,7 @@ sub set_config { + + =head1 NAME + +-App::ClusterSSH::Base ++App::ClusterSSH::Base - Base object provding utility functions + + =head1 SYNOPSIS + +diff --git a/lib/App/ClusterSSH/Cluster.pm b/lib/App/ClusterSSH/Cluster.pm +index aabbbe8..3724b31 100644 +--- a/lib/App/ClusterSSH/Cluster.pm ++++ b/lib/App/ClusterSSH/Cluster.pm +@@ -26,7 +26,7 @@ sub new { + sub get_clusters { + my ( $self, @files ) = @_; + +- for my $file ( '/etc/clusters', @files ) { ++ for my $file ( '/etc/clusters', $ENV{HOME}.'/.clusterssh/clusters',@files ) { + $self->debug(3, 'Loading in config from: ', $file); + $self->read_cluster_file($file); + } +@@ -118,7 +118,7 @@ sub list_tags { + + =head1 NAME + +-App::ClusterSSH::Cluster ++App::ClusterSSH::Cluster - Object representing cluster configuration + + =head1 SYNOPSIS + +diff --git a/lib/App/ClusterSSH/Config.pm b/lib/App/ClusterSSH/Config.pm +index dbe7c42..0c2fef0 100644 +--- a/lib/App/ClusterSSH/Config.pm ++++ b/lib/App/ClusterSSH/Config.pm +@@ -4,20 +4,25 @@ use strict; + use warnings; + + use version; +-our $VERSION = version->new('0.01'); ++our $VERSION = version->new('0.02'); + + use Carp; + use Try::Tiny; + + use FindBin qw($Script); ++use File::Copy; + + use base qw/ App::ClusterSSH::Base /; + use App::ClusterSSH::Cluster; + + my $clusters; + my %old_clusters; +-my @app_specific = (qw/ command title comms method ssh rsh telnet ccon /); +-my %default_config = ( ++my @app_specific = (qw/ command title comms method /); ++ ++# list of config items to not write out when writing the default config ++my @ignore_default_config = (qw/ user /); ++ ++my %default_config = ( + terminal => "xterm", + terminal_args => "", + terminal_title_opt => "-T", +@@ -54,9 +59,14 @@ my %default_config = ( + terminal_decoration_height => 10, + terminal_decoration_width => 8, + +- rsh_args => "", +- telnet_args => "", +- ssh_args => "", ++ console => 'console', ++ console_args => '', ++ rsh => 'rsh', ++ rsh_args => "", ++ telnet => 'telnet', ++ telnet_args => "", ++ ssh => 'ssh', ++ ssh_args => "", + + extra_cluster_file => "", + +@@ -76,6 +86,9 @@ my %default_config = ( + use_all_a_records => 0, + + send_menu_xml_file => $ENV{HOME} . '/.csshrc_send_menu', ++ ++ # don't set username here as takes precendence over ssh config ++ user => '', + ); + + sub new { +@@ -84,21 +97,19 @@ sub new { + my $self = $class->SUPER::new(%default_config); + + ( my $comms = $Script ) =~ s/^c//; +- $self->{comms} = $comms; ++ ++ $comms = 'telnet' if ( $comms eq 'tel' ); ++ $comms = 'console' if ( $comms eq 'con' ); ++ $comms = 'ssh' if ( $comms eq 'lusterssh' ); + + # list of allowed comms methods +- if ( 'ssh rsh telnet console' !~ m/\B$comms\B/ ) { ++ if ( 'ssh rsh telnet console' !~ m/\b$comms\b/ ) { + $self->{comms} = 'ssh'; + } +- +- if ( $self->{comms} +- && ( !$self->{ $self->{comms} } || !-e $self->{ $self->{comms} } ) ) +- { +- $self->{ $self->{comms} } = $self->find_binary( $self->{comms} ); ++ else { ++ $self->{comms} = $comms; + } + +- $self->{terminal} = $self->find_binary( $self->{terminal} ); +- + $self->{title} = uc($Script); + + $clusters = App::ClusterSSH::Cluster->new(); +@@ -131,13 +142,39 @@ sub validate_args { + App::ClusterSSH::Exception::Config->throw( + unknown_config => \@unknown_config, + error => $self->loc( +- 'Unknown configuration parameters: [_1]', ++ 'Unknown configuration parameters: [_1]' . $/, + join( ',', @unknown_config ) + ) + ) + ); + } + ++ if ( !$self->{comms} ) { ++ croak( ++ App::ClusterSSH::Exception::Config->throw( ++ error => $self->loc( 'Invalid variable: comms' . $/ ), ++ ), ++ ); ++ } ++ ++ if ( !$self->{ $self->{comms} } ) { ++ croak( ++ App::ClusterSSH::Exception::Config->throw( ++ error => $self->loc( ++ 'Invalid variable: [_1]' . $/, ++ $self->{comms} ++ ), ++ ), ++ ); ++ } ++ ++ # # Don't search for the path to the binary - assume it is on the path ++ # # or defined correctly in the config. ++ # if( !-e $self->{ $self->{comms} } ) ++ # { ++ # $self->{ $self->{comms} } = $self->find_binary( $self->{comms} ); ++ # } ++ + return $self; + } + +@@ -150,7 +187,8 @@ sub parse_config_file { + croak( + App::ClusterSSH::Exception::Config->throw( + error => $self->loc( +- 'File [_1] does not exist or cannot be read', $config_file ++ 'File [_1] does not exist or cannot be read' . $/, ++ $config_file + ), + ), + ); +@@ -182,12 +220,13 @@ sub parse_config_file { + } + close(CFG); + +- # grab any c'lusters from the config before validating it ++ # grab any clusters from the config before validating it + if ( $read_config{clusters} ) { + $self->debug( 3, "Picked up clusters defined in $config_file" ); + foreach my $cluster ( sort split / /, $read_config{clusters} ) { + if ( $read_config{$cluster} ) { +- $clusters->register_tag( $cluster, $read_config{$cluster} ); ++ $clusters->register_tag( $cluster, ++ split( / /, $read_config{$cluster} ) ); + $old_clusters{$cluster} = $read_config{$cluster}; + delete( $read_config{$cluster} ); + } +@@ -205,16 +244,6 @@ sub parse_config_file { + sub load_configs { + my ( $self, @configs ) = @_; + +- if ( -e $ENV{HOME} . '/.csshrc' ) { +- warn( +- $self->loc( +- 'NOTICE: [_1] is no longer used - please see documentation and remove', +- $ENV{HOME} . '/.csshrc' +- ), +- $/ +- ); +- } +- + for my $config ( + '/etc/csshrc', + $ENV{HOME} . '/.csshrc', +@@ -248,6 +277,30 @@ sub load_configs { + sub write_user_config_file { + my ($self) = @_; + ++ # attempt to move the old config file to one side ++ if ( -f "$ENV{HOME}/.csshrc" ) { ++ eval { move( "$ENV{HOME}/.csshrc", "$ENV{HOME}/.csshrc.DISABLED" ) }; ++ ++ if ($@) { ++ croak( ++ App::ClusterSSH::Exception::Config->throw( ++ error => $self->loc( ++ 'Unable to move [_1] to [_2]: [_3]' . $/, ++ '$HOME/.csshrc', '$HOME/.csshrc.DISABLED', $@ ++ ), ++ ) ++ ); ++ } ++ else { ++ warn( ++ $self->loc( ++ 'Moved [_1] to [_2]' . $/, '$HOME/.csshrc', ++ '$HOME/.csshrc.DISABLED' ++ ), ++ ); ++ } ++ } ++ + return if ( -f "$ENV{HOME}/.clusterssh/config" ); + + if ( !-d "$ENV{HOME}/.clusterssh" ) { +@@ -255,7 +308,7 @@ sub write_user_config_file { + croak( + App::ClusterSSH::Exception::Config->throw( + error => $self->loc( +- 'Unable to create directory [_1]: [_2]', ++ 'Unable to create directory [_1]: [_2]' . $/, + '$HOME/.clusterssh', $! + ), + ), +@@ -264,34 +317,58 @@ sub write_user_config_file { + } + } + ++ # Debian #673507 - migrate clusters prior to writing ~/.clusterssh/config ++ # in order to update the extra_cluster_file property ++ if (%old_clusters) { ++ if ( open( my $fh, ">", "$ENV{HOME}/.clusterssh/clusters" ) ) { ++ print $fh '# ' ++ . $self->loc('Tag definitions moved from old .csshrc file'), ++ $/; ++ foreach ( sort( keys(%old_clusters) ) ) { ++ print $fh $_, ' ', join( ' ', $old_clusters{$_} ), $/; ++ } ++ close($fh); ++ } ++ else { ++ croak( ++ App::ClusterSSH::Exception::Config->throw( ++ error => $self->loc( ++ 'Unable to write [_1]: [_2]' . $/, ++ '$HOME/.clusterssh/clusters', ++ $! ++ ), ++ ), ++ ); ++ } ++ } ++ + if ( open( CONFIG, ">", "$ENV{HOME}/.clusterssh/config" ) ) { + foreach ( sort( keys(%$self) ) ) { +- print CONFIG "$_=$self->{$_}\n"; ++ my $comment=''; ++ if ( grep /$_/, @ignore_default_config ) { ++ $comment='#'; ++ } ++ print CONFIG ${comment},$_,'=',$self->{$_},$/; + } + close(CONFIG); ++ warn( ++ $self->loc( ++ 'Created new configuration file within [_1]' . $/, ++ '$HOME/.clusterssh/' ++ ) ++ ); + } + else { + croak( + App::ClusterSSH::Exception::Config->throw( + error => $self->loc( +- 'Unable to write default [_1]: [_2]', +- '$HOME/.clusterssh/config', +- $! ++ 'Unable to write default [_1]: [_2]' . $/, ++ '$HOME/.clusterssh/config', $! + ), + ), + ); + } + +- return $self if ( !%old_clusters ); +- +- if ( open( my $fh, ">", "$ENV{HOME}/.clusterssh/clusters" ) ) { +- print $fh '# ' +- . $self->loc('Tag definitions moved from old .csshrc file'), $/; +- foreach ( sort( keys(%old_clusters) ) ) { +- print $fh $_, ' ', join( ' ', $old_clusters{$_} ), $/; +- } +- close($fh); +- } + return $self; + } + +@@ -303,7 +380,7 @@ sub find_binary { + if ( !$binary ) { + croak( + App::ClusterSSH::Exception::Config->throw( +- error => $self->loc('argument not provided'), ++ error => $self->loc('argument not provided') . $/, + ), + ); + } +@@ -355,7 +432,8 @@ sub find_binary { + croak( + App::ClusterSSH::Exception::Config->throw( + error => $self->loc( +- '"[_1]" binary not found - please amend $PATH or the cssh config file', ++ '"[_1]" binary not found - please amend $PATH or the cssh config file' ++ . $/, + $binary + ), + ), +@@ -373,10 +451,14 @@ sub dump { + print( '# Configuration dump produced by "cssh -u"', $/ ); + + foreach my $key ( sort keys %$self ) { ++ my $comment=''; + if ( grep /$key/, @app_specific ) { + next; + } +- print $key, '=', $self->{$key}, $/; ++ if ( grep /$key/, @ignore_default_config ) { ++ $comment='#'; ++ } ++ print $comment, $key, '=', $self->{$key}, $/; + } + + $self->exit if ( !$no_exit ); +@@ -396,7 +478,7 @@ sub dump { + + =head1 NAME + +-ClusterSSH::Config ++ClusterSSH::Config - Object representing application configuration + + =head1 SYNOPSIS + +diff --git a/lib/App/ClusterSSH/Helper.pm b/lib/App/ClusterSSH/Helper.pm +index 07c5898..4289e77 100644 +--- a/lib/App/ClusterSSH/Helper.pm ++++ b/lib/App/ClusterSSH/Helper.pm +@@ -4,7 +4,7 @@ use strict; + use warnings; + + use version; +-our $VERSION = version->new('0.01'); ++our $VERSION = version->new('0.02'); + + use Carp; + use Try::Tiny; +@@ -22,12 +22,12 @@ sub new { + sub script { + my ($self, $config ) = @_; + +- my $comms = $config->{comms}; +- my $comms_args = $config->{$comms.'_args'}; +- my $command = $config->{command}; ++ my $comms = $config->{ $config->{comms} }; ++ my $comms_args = $config->{ $config->{comms} . '_args'}; ++ my $config_command = $config->{command}; + my $autoclose = $config->{auto_close}; + +- my $postcommand = $autoclose ? "echo Press RETURN to continue; read IGNORE" : "sleep $autoclose"; ++ my $postcommand = $autoclose ? "echo Sleeping for $autoclose seconds; sleep $autoclose" : "echo Press RETURN to continue; read IGNORE"; # : "sleep $autoclose"; + + # # P = pipe file + # # s = server +@@ -119,7 +119,10 @@ sub script { + \$command .= "\$svr"; + } + } +- \$command .= " \\\"$command\\\" ; $postcommand"; ++ if("$config_command") { ++ \$command .= " \\\"$config_command\\\""; ++ } ++ \$command .= " ; $postcommand"; + warn("Running:\$command\\n"); # for debug purposes + exec(\$command); + HERE +@@ -145,7 +148,7 @@ sub script { + + =head1 NAME + +-ClusterSSH::Helper ++ClusterSSH::Helper - Object representing helper script + + =head1 SYNOPSIS + +diff --git a/lib/App/ClusterSSH/Host.pm b/lib/App/ClusterSSH/Host.pm +index c9c00be..7d352e5 100644 +--- a/lib/App/ClusterSSH/Host.pm ++++ b/lib/App/ClusterSSH/Host.pm +@@ -301,7 +301,7 @@ use overload ( + + =head1 NAME + +-ClusterSSH::Host ++ClusterSSH::Host - Object representing a host. + + =head1 SYNOPSIS + +diff --git a/t/15config.t b/t/15config.t +index 81d8f70..cce91a6 100644 +--- a/t/15config.t ++++ b/t/15config.t +@@ -8,6 +8,7 @@ use Test::More; + use Test::Trap; + use File::Which qw(which); + use File::Temp qw(tempdir); ++use Test::Differences; + + use Readonly; + +@@ -21,7 +22,7 @@ $config = App::ClusterSSH::Config->new(); + isa_ok( $config, 'App::ClusterSSH::Config' ); + + Readonly::Hash my %default_config => { +- terminal => "/usr/bin/xterm", ++ terminal => "xterm", + terminal_args => "", + terminal_title_opt => "-T", + terminal_colorize => 1, +@@ -59,9 +60,14 @@ Readonly::Hash my %default_config => { + + ssh => '/usr/bin/ssh', + +- rsh_args => "", +- telnet_args => "", +- ssh_args => "", ++ console => 'console', ++ console_args => '', ++ rsh => 'rsh', ++ rsh_args => "", ++ telnet => 'telnet', ++ telnet_args => "", ++ ssh => 'ssh', ++ ssh_args => "", + + extra_cluster_file => "", + +@@ -72,8 +78,8 @@ Readonly::Hash my %default_config => { + history_height => 10, + + command => q{}, +- title => q{15CONFIG.T}, +- comms => q{ssh}, ++ title => q{15CONFIG.T}, ++ comms => q{ssh}, + max_host_menu_items => 30, + + max_addhost_menu_cluster_items => 6, +@@ -88,6 +94,7 @@ Readonly::Hash my %default_config => { + debug => 0, + lang => 'en', + ++ user => '', + }; + my %expected = %default_config; + is_deeply( $config, \%expected, 'default config is correct' ); +@@ -101,7 +108,7 @@ trap { + }; + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + is( $trap->die, +- 'Unknown configuration parameters: doesnt_exist,whoops', ++ 'Unknown configuration parameters: doesnt_exist,whoops' . $/, + 'got correct error message' + ); + is_deeply( +@@ -134,7 +141,7 @@ trap { + }; + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + is( $trap->die, +- "File $file does not exist or cannot be read", ++ "File $file does not exist or cannot be read" . $/, + 'got correct error message' + ); + +@@ -166,7 +173,7 @@ trap { + is( $trap->leaveby, 'die', 'died ok' ); + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + is( $trap->die, +- 'Unknown configuration parameters: missing,rubbish', ++ 'Unknown configuration parameters: missing,rubbish' . $/, + 'die message correct' + ); + isa_ok( $config, "App::ClusterSSH::Config" ); +@@ -197,7 +204,7 @@ trap { + is( $trap->leaveby, 'die', 'died ok' ); + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + isa_ok( $config, "App::ClusterSSH::Config" ); +-is( $trap->die, 'argument not provided', 'die message correct' ); ++is( $trap->die, 'argument not provided' . $/, 'die message correct' ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, q{}, 'Expecting no STDERR' ); +@@ -210,7 +217,8 @@ is( $trap->leaveby, 'die', 'died ok' ); + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->die, +- '"missing" binary not found - please amend $PATH or the cssh config file', ++ '"missing" binary not found - please amend $PATH or the cssh config file' ++ . $/, + 'die message correct' + ); + isa_ok( $config, "App::ClusterSSH::Config" ); +@@ -241,11 +249,11 @@ is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, q{}, 'Expecting no STDERR' ); + is_deeply( $config, \%expected, 'amended config is correct' ); + is( $path, which('ls'), 'Found correct path to "ls"' ); +-is( $path, $newpath, 'No change made from find_binary'); ++is( $path, $newpath, 'No change made from find_binary' ); + + # give false path to force another search + trap { +- $newpath = $config->find_binary('/does/not/exist/'.$path); ++ $newpath = $config->find_binary( '/does/not/exist/' . $path ); + }; + is( $trap->leaveby, 'return', 'returned ok' ); + isa_ok( $config, "App::ClusterSSH::Config" ); +@@ -254,7 +262,7 @@ is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, q{}, 'Expecting no STDERR' ); + is_deeply( $config, \%expected, 'amended config is correct' ); + is( $path, which('ls'), 'Found correct path to "ls"' ); +-is( $path, $newpath, 'No change made from find_binary'); ++is( $path, $newpath, 'No change made from find_binary' ); + + note('Checks on loading configs'); + note('empty dir'); +@@ -268,7 +276,10 @@ isa_ok( $config, "App::ClusterSSH::Config" ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->die, undef, 'die message correct' ); + is( $trap->stdout, q{}, 'Expecting no STDOUT' ); +-is( $trap->stderr, q{}, 'Expecting no STDERR' ); ++is( $trap->stderr, ++ 'Created new configuration file within $HOME/.clusterssh/' . $/, ++ 'Got correct STDERR output for .csshrc' ++); + + #note(qx/ls -laR $ENV{HOME}/); + ok( -d $ENV{HOME} . '/.clusterssh', '.clusterssh dir exists' ); +@@ -292,9 +303,9 @@ isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->die, undef, 'die message correct' ); + is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, +- 'NOTICE: ' +- . $ENV{HOME} +- . '/.csshrc is no longer used - please see documentation and remove' ++ 'Moved $HOME/.csshrc to $HOME/.csshrc.DISABLED' ++ . $/ ++ . 'Created new configuration file within $HOME/.clusterssh/' + . $/, + 'Got correct STDERR output for .csshrc' + ); +@@ -303,6 +314,12 @@ ok( -f $ENV{HOME} . '/.clusterssh/config', '.clusterssh config file exists' ); + is_deeply( $config, \%expected, 'amended config is correct' ); + + note('.csshrc warning and .clusterssh dir plus config'); ++ ++# need to recreate .csshrc as it was just moved ++open( $csshrc, '>', $ENV{HOME} . '/.csshrc' ); ++print $csshrc 'auto_quit = no', $/; ++close($csshrc); ++$expected{auto_quit} = 'no'; + open( $csshrc, '>', $ENV{HOME} . '/.clusterssh/config' ); + print $csshrc 'window_tiling = no', $/; + close($csshrc); +@@ -317,10 +334,7 @@ isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->die, undef, 'die message correct' ); + is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, +- 'NOTICE: ' +- . $ENV{HOME} +- . '/.csshrc is no longer used - please see documentation and remove' +- . $/, ++ 'Moved $HOME/.csshrc to $HOME/.csshrc.DISABLED' . $/, + 'Got correct STDERR output for .csshrc' + ); + ok( -d $ENV{HOME} . '/.clusterssh', '.clusterssh dir exists' ); +@@ -395,7 +409,7 @@ is( $trap->leaveby, 'die', 'died ok' ); + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->die, +- 'Unable to create directory $HOME/.clusterssh: File exists', ++ 'Unable to create directory $HOME/.clusterssh: File exists' . $/, + 'die message correct' + ); + isa_ok( $config, "App::ClusterSSH::Config" ); +@@ -414,7 +428,7 @@ is( $trap->leaveby, 'die', 'died ok' ); + isa_ok( $trap->die, 'App::ClusterSSH::Exception::Config' ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->die, +- 'Unable to write default $HOME/.clusterssh/config: Is a directory', ++ 'Unable to write default $HOME/.clusterssh/config: Is a directory' . $/, + 'die message correct' + ); + isa_ok( $config, "App::ClusterSSH::Config" ); +@@ -434,7 +448,7 @@ is( $trap->leaveby, 'return', 'died ok' ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, +- q{Unable to create directory $HOME/.clusterssh: File exists} . $/, ++ q{Unable to create directory $HOME/.clusterssh: File exists} . $/ . $/, + 'Expecting no STDERR' + ); + +@@ -451,19 +465,23 @@ isa_ok( $config, "App::ClusterSSH::Config" ); + isa_ok( $config, "App::ClusterSSH::Config" ); + is( $trap->stdout, q{}, 'Expecting no STDOUT' ); + is( $trap->stderr, +- q{Unable to write default $HOME/.clusterssh/config: Is a directory} . $/, ++ q{Unable to write default $HOME/.clusterssh/config: Is a directory} ++ . $/ ++ . $/, + 'Expecting no STDERR' + ); + + note('Checking dump'); +-$config = App::ClusterSSH::Config->new(); ++$config = App::ClusterSSH::Config->new( ++ send_menu_xml_file => $ENV{HOME} . '/.csshrc_send_menu' ); + trap { + $config->dump(); + }; +-my $expected = <<'EOF'; +-# Configuration dump produced by "cssh -u" ++my $expected = qq{# Configuration dump produced by "cssh -u" + auto_close=5 + auto_quit=yes ++console=console ++console_args= + console_position= + debug=0 + extra_cluster_file= +@@ -481,16 +499,19 @@ max_host_menu_items=30 + menu_host_autotearoff=0 + menu_send_autotearoff=0 + mouse_paste=Button-2 ++rsh=rsh + rsh_args= + screen_reserve_bottom=60 + screen_reserve_left=0 + screen_reserve_right=0 + screen_reserve_top=0 +-send_menu_xml_file=/home/dferguson/.csshrc_send_menu ++send_menu_xml_file=} . $ENV{HOME} . qq{/.csshrc_send_menu + show_history=0 ++ssh=ssh + ssh_args= ++telnet=telnet + telnet_args= +-terminal=/usr/bin/xterm ++terminal=xterm + terminal_allow_send_events=-xrm '*.VT100.allowSendEvents:true' + terminal_args= + terminal_bg_style=dark +@@ -507,12 +528,14 @@ terminal_title_opt=-T + unmap_on_redraw=no + use_all_a_records=0 + use_hotkeys=yes ++#user= + window_tiling=yes + window_tiling_direction=right +-EOF ++}; ++ + isa_ok( $config, "App::ClusterSSH::Config" ); +-is( $trap->die, undef, 'die message correct' ); +-is( $trap->stdout, $expected, 'Expecting no STDOUT' ); +-is( $trap->stderr, q{}, 'Expecting no STDERR' ); ++is( $trap->die, undef, 'die message correct' ); ++eq_or_diff( $trap->stdout, $expected, 'Expecting no STDOUT' ); ++is( $trap->stderr, q{}, 'Expecting no STDERR' ); + + done_testing(); +diff --git a/t/30cluster.t b/t/30cluster.t +index 487bbfc..1beadec 100644 +--- a/t/30cluster.t ++++ b/t/30cluster.t +@@ -8,6 +8,7 @@ use Test::More; + use Test::Trap; + use File::Which qw(which); + use File::Temp qw(tempdir); ++use English '-no_match_vars'; + + use Readonly; + +@@ -27,24 +28,33 @@ $cluster1->register_tag( 'people', @expected ); + + my @got = $cluster2->get_tag('people'); + +-is_deeply( \@got, \@expected, +- 'Shared cluster object' ); ++is_deeply( \@got, \@expected, 'Shared cluster object' ); + + # should pass without issue + trap { + $cluster1->read_cluster_file( $Bin . '/30cluster.doesnt exist' ); + }; +-is( ! $trap, '', 'coped with missing file ok' ); ++is( !$trap, '', 'coped with missing file ok' ); + isa_ok( $cluster1, 'App::ClusterSSH::Cluster' ); + +-my $no_read=$Bin . '/30cluster.cannot_read'; +-chmod 0000, $no_read; +-trap { +- $cluster1->read_cluster_file( $no_read ); +-}; +-chmod 0644, $no_read; +-isa_ok( $trap->die, 'App::ClusterSSH::Exception::Cluster' ); +-is( $trap->die, "Unable to read file $no_read: Permission denied", 'Error on reading an existing file ok'); ++# no point running this test as root since root cannot be blocked ++# from accessing the file ++if ( $EUID != 0 ) { ++ my $no_read = $Bin . '/30cluster.cannot_read'; ++ chmod 0000, $no_read; ++ trap { ++ $cluster1->read_cluster_file($no_read); ++ }; ++ chmod 0644, $no_read; ++ isa_ok( $trap->die, 'App::ClusterSSH::Exception::Cluster' ); ++ is( $trap->die, ++ "Unable to read file $no_read: Permission denied", ++ 'Error on reading an existing file ok' ++ ); ++} ++else { ++ pass('Cannot test for lack of read access when run as root'); ++} + + @expected = ('host1'); + $cluster1->read_cluster_file( $Bin . '/30cluster.file1' ); +@@ -53,18 +63,15 @@ is_deeply( \@got, \@expected, 'read simple file OK' ); + + @expected = ('host1'); + $cluster1->read_cluster_file( $Bin . '/30cluster.file2' ); +-@got=$cluster1->get_tag('tag1'); +-is_deeply( \@got, +- \@expected, 'read more complex file OK' ); ++@got = $cluster1->get_tag('tag1'); ++is_deeply( \@got, \@expected, 'read more complex file OK' ); + + @expected = ('host2'); +-@got=$cluster1->get_tag('tag2'); +-is_deeply( \@got, +- \@expected, 'read more complex file OK' ); ++@got = $cluster1->get_tag('tag2'); ++is_deeply( \@got, \@expected, 'read more complex file OK' ); + + @expected = ( 'host3', 'host4' ); +-@got=$cluster1->get_tag('tag3'); +-is_deeply( \@got, +- \@expected, 'read more complex file OK' ); ++@got = $cluster1->get_tag('tag3'); ++is_deeply( \@got, \@expected, 'read more complex file OK' ); + + done_testing(); diff --git a/network/ClusterSSH/slack-desc b/network/ClusterSSH/slack-desc new file mode 100644 index 000000000000..ea69925bbfff --- /dev/null +++ b/network/ClusterSSH/slack-desc @@ -0,0 +1,19 @@ +# HOW TO EDIT THIS FILE: +# The "handy ruler" below makes it easier to edit a package description. +# Line up the first '|' above the ':' following the base package name, and +# the '|' on the right side marks the last column you can put a character in. +# You must make exactly 11 lines for the formatting to be correct. It's also +# customary to leave one space after the ':' except on otherwise blank lines. + + |-----handy-ruler------------------------------------------------------| +ClusterSSH: ClusterSSH (running multiple ssh clients at the same time in paralell) +ClusterSSH: +ClusterSSH: ClusterSSH is a tool for making the same change on multiple servers +ClusterSSH: at the same time. The 'cssh' command opens an administration console +ClusterSSH: and an xterm to all specified hosts. Any text typed into the +ClusterSSH: administration console is replicated to all windows. All windows may +ClusterSSH: also be typed into directly. +ClusterSSH: +ClusterSSH: Homepage: http://clusterssh.sourceforge.net +ClusterSSH: +ClusterSSH: |