* only on ssh connect for file transfer to remote machine
authorChristian Arnold <arnold@schlittermann.de>
Wed, 28 Jul 2010 13:25:06 +0200
changeset 6 e7973168471d
parent 5 482b827bb2cd
child 7 19ba8f130480
* only on ssh connect for file transfer to remote machine * add filter.rules file for include/exclude rules * use separate config file * add documentation * this commit is the current debian package version 2.0
.hgignore
Makefile
config.ex
debian/changelog
debian/control
debian/copyright
debian/dirs
debian/rules
filter.rules.ex
send-config.pl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed Jul 28 13:25:06 2010 +0200
@@ -0,0 +1,9 @@
+syntax: glob
+*.swp
+debian/files
+send-config
+send-config.1.gz
+
+syntax: regexp
+(build|configure)-stamp$
+debian/send-config[./]
--- a/Makefile	Wed Jul 28 12:34:17 2010 +0200
+++ b/Makefile	Wed Jul 28 13:25:06 2010 +0200
@@ -1,29 +1,38 @@
-# $Id$
-# $URL$
+# $Id: Makefile 839 2005-05-03 08:30:20Z arnold $
+# $URL: https://svn.schlittermann.de/is/send-config/trunk/Makefile $
 
 PERL = /usr/bin/perl
 
 prefix = /usr
-sbindir = ${prefix}/sbin
+bindir = ${prefix}/bin
 confdir = /etc/send-config
+mandir = /usr/share/man/man1
 
 DESTDIR =
 
-sbin_SCRIPTS = send-config
+SCRIPT = send-config
+DOC = $(SCRIPT:=.1.gz)
 
 
 
-.PHONY:	all clean install
+.PHONY:	all clean install doc
 
-all:	$(sbin_SCRIPTS)
+all:	$(SCRIPT) $(DOC)
 
 install: all
 	install -d -m 0755 $(DESTDIR)$(confdir)
-	install -m 0755 $(sbin_SCRIPTS) $(DESTDIR)$(sbindir)/
+	install -m 0755 $(SCRIPT) $(DESTDIR)$(bindir)/
 	test -f $(DESTDIR)$(confdir)/config \
 	  || install -m 644 config.ex $(DESTDIR)$(confdir)/config
+	test -f $(DESTDIR)$(confdir)/filter.rules \
+		|| install -m 644 filter.rules.ex $(DESTDIR)$(confdir)/filter.rules
+	install -m 644 $(SCRIPT).1.gz $(DESTDIR)$(mandir)/$(SCRIPT).1.gz 
+
 clean:
-	-rm -f $(sbin_SCRIPTS) core
+	-rm -f $(SCRIPT) $(DOC) core
+
+%.1.gz:	%
+	@pod2man --utf8 $< | gzip >$@
 
 %:	%.pl
 	@$(PERL) -c $<
--- a/config.ex	Wed Jul 28 12:34:17 2010 +0200
+++ b/config.ex	Wed Jul 28 13:25:06 2010 +0200
@@ -1,13 +1,3 @@
-!*~
-!*kernel-source*
-!/usr/local/uvscan
-!/usr/local/src
-!/etc/samba/codepages
-/etc
-/usr/local
-/var/cache/debconf
-/var/lib/dpkg/status
-/boot/config*
-/usr/src/*/.config
-/usr/src/*config*/
-/root/LOG
+#username = root
+#path = /root/Configs/Hosts/
+#stamp = /var/tmp/get-config.stamp
--- a/debian/changelog	Wed Jul 28 12:34:17 2010 +0200
+++ b/debian/changelog	Wed Jul 28 13:25:06 2010 +0200
@@ -1,3 +1,18 @@
+send-config (2.0) stable; urgency=low
+
+  * only on ssh connect for file transfer to remote machine
+  * change the rule.file syntax
+  * use separate config file
+  * add documentation
+
+ -- Christian Arnold <arnold@schlittermann.de>  Tue, 27 Jul 2010 14:50:15 +0200
+
+send-config (1.2) stable; urgency=low
+
+  * add file globbing for (*) entries in config file
+
+ -- Christian Arnold <arnold@schlittermann.de>  Wed, 21 Jul 2010 11:03:43 +0200
+
 send-config (1.1-1) stable; urgency=low
 
   * build debian stable packet
--- a/debian/control	Wed Jul 28 12:34:17 2010 +0200
+++ b/debian/control	Wed Jul 28 13:25:06 2010 +0200
@@ -7,5 +7,5 @@
 
 Package: send-config
 Architecture: all
-Depends: rsync, perl, libnet-ssh-perl, libsys-hostname-long-perl
+Depends: rsync, perl, libconfig-tiny-perl, libnet-ssh-perl, libsys-hostname-long-perl
 Description: transfers selected files to an remote host
--- a/debian/copyright	Wed Jul 28 12:34:17 2010 +0200
+++ b/debian/copyright	Wed Jul 28 13:25:06 2010 +0200
@@ -1,8 +1,6 @@
 This package was debianized by Christian Arnold <arnold@schlittermann.de> on
 Thu, 28 Apr 2005 16:48:12 +0200.
 
-It was downloaded from <fill in ftp site>
-
 Copyright Holder: Christian Arnold arnold@schlittermann.de
 
 License:
--- a/debian/dirs	Wed Jul 28 12:34:17 2010 +0200
+++ b/debian/dirs	Wed Jul 28 13:25:06 2010 +0200
@@ -1,2 +1,2 @@
 usr/bin
-usr/sbin
+usr/share/man/man1
--- a/debian/rules	Wed Jul 28 12:34:17 2010 +0200
+++ b/debian/rules	Wed Jul 28 13:25:06 2010 +0200
@@ -8,7 +8,7 @@
 
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
-export DH_COMPAT=3
+export DH_COMPAT=4
 p = send-config
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filter.rules.ex	Wed Jul 28 13:25:06 2010 +0200
@@ -0,0 +1,20 @@
+- /usr/src
+- /etc/samba/codepages
+- /usr/local/uvscan
+- /usr/local/src
+- /root/local-packages
+
++ /etc
++ /var/cache/debconf
++ /var/lib/dpkg/status
++ /boot/config*
++ /boot/grub/menu.lst
++ /root/.bash*
++ /root/.profile
++ /root/LOG*
++ /root/.ssh
+#+ /proc/config.gz
++ /usr/local
++ /usr/lib/AntiVir/*.[Kk][Ee][Yy]
++ /usr/src/*/*.config
++ /usr/src/*config*
--- a/send-config.pl	Wed Jul 28 12:34:17 2010 +0200
+++ b/send-config.pl	Wed Jul 28 13:25:06 2010 +0200
@@ -1,91 +1,337 @@
-#! /usr/bin/perl -w
-# $Id$
-# $URL$
-
-my $USAGE = <<'_';
-Usage: $ME [options] host
-       host		destination host
-       -v --[no]verbose	be verbose [$opt_verbose]
-       -k --keepgoing	don't stop on errors [$opt_keepgoing]
-_
+#!/usr/bin/perl -w
 
 use strict;
 use File::Basename;
+use Pod::Usage;
 use Getopt::Long;
+use Config::Tiny;
 use Net::SSH qw(sshopen2);
 use Sys::Hostname::Long;
 use Socket;
+use IO::File;
 
 my $ME = basename $0;
-my $CONFIG = "/etc/send-config/config";
 
-my $opt_verbose = 0;
-my $opt_keepgoing = 0;
+my $opt_rules_file = '/etc/send-config/filter.rules';
+my $opt_config     = '/etc/send-config/config';
+my $opt_dry_run    = 0;
+my $opt_keepgoing  = 0;
+my $opt_verbose    = 0;
+my $opt_debug      = 0;
+
+sub debug;
+sub readConfig($);
+sub rsync;
+sub readFilter($);
 
 MAIN: {
-    GetOptions(
-	"verbose!"  => \$opt_verbose,
-	"keepgoing" => \$opt_keepgoing,
-    ) or die eval "\"$USAGE\"";
-
-    unless (scalar(@ARGV) == 1) { die eval "\"$USAGE\""; }
-    
-    (my $host = $ARGV[0]) =~ s/.*\/([a-z0-9.-]+).*/$1/;
-    my $ip = scalar gethostbyname($host);
-    
-    $ip or do {
-	warn "can't resolve $host\n";
-	next;
-    };
-    my $addr = inet_ntoa($ip);
-    my $hostname_long = hostname_long();
-
-    my $user = "root";
-    my $path = "/root/Configs/Hosts/$hostname_long";
-    my $ssh_cmd = "mkdir -m 0700 -p $path";
-    
-    sshopen2("$user\@$host", *READER, *WRITER, "$ssh_cmd") || die "ssh: $!";
-    close(READER); close(WRITER);
-
-    open(CONF, $CONFIG) or die "$ME: Can't open $CONFIG: $!\n";
 
-    my @cmd = (
-	qw(rsync --rsh), "ssh -x",
-	qw(--compress --numeric-ids
-	   --delete --delete-excluded
-	   --archive --relative)
-    );
-    push @cmd, "--verbose" if $opt_verbose;
-    
-    while (<CONF>) {
-	chomp;
-	 /^!(.*)\s*/ or next;
-	 push @cmd, "--exclude", $1;
-    }
-    seek CONF, 0, 0 or die "$ME: Can't seek $CONFIG: $!\n";
+    GetOptions(
+        "r|rules-file=s" => \$opt_rules_file,
+        "c|config=s"     => \$opt_config,
+        "k|keepgoing!"   => \$opt_keepgoing,
+        "n|dry-run!"     => \$opt_dry_run,
+        "v|verbose!"     => \$opt_verbose,
+        "d|debug!"       => \$opt_debug,
+        "h|help"         => sub { pod2usage( -verbose => 0, -exitval => 0 ) }
+    ) or pod2usage();
 
-    while (<CONF>) {
-	chomp;
-	/^\// or next;
-	my $status = "";
-	my $src = "$_";
-	my $dst = "$user\@$host:$path";
-	print "* $src -> $dst$_\n";
-	system @cmd, $src, $dst;
-	if ($?) {
-	    $status = "ERR " . ($? >> 8);
-	    $status .= " SIGNAL " . ($? & 127);
-	} else { $status = "OK"; };
-
-	if ($status ne "OK") {
-	    warn "$ME: ???. system command ended with $status" unless $opt_keepgoing;
-	}
+    unless ( scalar(@ARGV) == 1 ) {
+        pod2usage( -verbose => 0, -exitval => 0 );
     }
 
-    my $STAMP = "/var/tmp/get-config.stamp";
-    open(TOUCH, ">$STAMP") or die "$ME: Can't open >>$STAMP: $!\n";
-    close(TOUCH);
+    my $dest    = $ARGV[0];
+    my $dest_ip = scalar gethostbyname($dest);
+
+    ($dest_ip) or die "$ME: Can't resolve $dest\n";
+
+    my $source_host = hostname_long();
+
+    debug(  "PROG destination host: [" 
+          . $dest . "]" 
+          . " ip: ["
+          . inet_ntoa($dest_ip)
+          . "]" )
+      if $opt_debug;
+    debug( "PROG source host: [" . $source_host . "]" ) if $opt_debug;
+
+    my ( $username, $path, $stamp ) = readConfig($opt_config);
+
+    # adding trailing / to destination directory
+    $path .= "/" unless ( $path =~ /\/$/ );
+    my $dest_path = $path . $source_host;
+
+    # create remote destination directory
+    my $command = "mkdir -m 0700 -p $dest_path";
+
+    unless ($opt_dry_run) {
+        sshopen2( "$username\@$dest", *READER, *WRITER, "$command" )
+          || die "ssh: $!";
+    }
+    debug("PROG ssh command: ssh $username\@$dest $command") if $opt_debug;
+    close(READER);
+    close(WRITER);
+
+    rsync( $username, $dest, $dest_path, $stamp );
+
+}
+
+sub debug {
+    print map( "DEBUG: $_\n", @_ );
+}
+
+sub readConfig($) {
+
+    my $file = shift;
+
+    my $config = Config::Tiny->read($file) or die "$ME: Can't open $file: $!\n";
+
+    my $username = $config->{_}->{username} ? $config->{_}->{username} : 'root';
+    my $path =
+      $config->{_}->{path} ? $config->{_}->{path} : '/root/Configs/Hosts/';
+    my $stamp =
+        $config->{_}->{stamp}
+      ? $config->{_}->{stamp}
+      : '/var/tmp/get-config.stamp';
+
+    debug(
+        "CONF remote username: $username",
+        "CONF destination path: $path",
+        "CONF stamp path: $stamp"
+    ) if $opt_debug;
+
+    return ( $username, $path, $stamp );
 
 }
 
-# vim:sts=4 sw=4 aw ai sm:
+sub rsync() {
+
+    my ( $username, $dest, $dest_path, $stamp ) = @_;
+    my @status = ();
+
+    my @cmd = (
+        qw(rsync --rsh), "ssh -x",
+        qw(--compress --numeric-ids
+          --delete --delete-excluded
+          --archive --relative)
+    );
+
+    push @cmd, "--verbose" if $opt_verbose;
+    push @cmd, "--dry-run" if $opt_dry_run;
+
+    my @filter = readFilter($opt_rules_file);
+
+    my @src = map { /^\+\s*(.*)/ ? ( glob($1) ) : () } @filter;
+
+    if (
+        my @excludes =
+        map { /^-\s*(.*)/ ? ( "--exclude" => glob($1) ) : () } @filter
+      )
+    {
+        push @cmd, @excludes;
+    }
+
+    push @cmd, @src, "$username\@$dest:$dest_path";
+
+    debug( "PROG rsync command: " . join( " ", @cmd ) ) if $opt_debug;
+
+    open( TMP, "+>/tmp/$ME.$$" )
+      or die "$ME: Can't open /tmp/$ME.$$";
+
+    my $pid = fork();
+    die "Can't fork" if not defined $pid;
+
+    if ( $pid == 0 ) {
+        open( STDERR, ">&TMP" ) or die "$!";
+        open( STDOUT, ">/dev/null" ) unless $opt_verbose;
+        exec @cmd;
+    }
+    else {
+        waitpid $pid, 0;
+    }
+
+    close(TMP);
+
+    if ($?) {
+
+        open( TMP, "+</tmp/$ME.$$" )
+          or die "$ME: Can't open /tmp/$ME.$$";
+        unlink "/tmp/$ME.$$";
+
+        while (<TMP>) {
+            if (/(.*)(?<=failed:)\s+(.*)\s*\(\d+\)/) {
+                push @status, "[WARNING] $1 $2"
+                  unless ( $opt_keepgoing and !$opt_dry_run );
+            }
+        }
+        close(TMP);
+    }
+
+    if (@status) {
+        unless ($opt_keepgoing) {
+            warn join( "\n", @status ), "\n";
+            warn "[WARNING] $stamp not touched!\n";
+            warn "\nUse option --keepgoing, to ignore this warning.\n";
+        }
+    }
+    else {
+        open( STAMP, ">$stamp" )
+          or die "$ME: Can't open: $stamp: $!\n"
+          unless $opt_dry_run;
+        close(STAMP) unless $opt_dry_run;
+        warn "[OK] touched $stamp.\n" unless $opt_dry_run;
+    }
+
+}
+
+sub readFilter($) {
+
+    my $file = shift;
+    my @filter;
+
+    my $cf = new IO::File $file or die "$ME: Can't open $file: $!\n";
+
+    while (<$cf>) {
+        next if /^(;#\s*$)/;
+        push @filter, $_;
+    }
+
+    return @filter;
+
+}
+
+__END__
+
+=pod
+
+=head1 NAME
+
+B<send-config> - copies files and directories to a remote machine
+
+=head1 SYNOPSIS
+
+B<send-config> [OPTION...] DEST
+
+=over
+
+=item B<-r, --rules-file=FILE>
+
+=item B<-c, --config=FILE>
+
+=item B<-n, --dry-run>
+
+=item B<-k, --keepgoing>
+
+=item B<-v, --verbose>
+
+=item B<-d, --debug>
+
+=item B<-h, --help>
+
+=back
+
+=head1 DESCRIPTION
+
+B<send-config> copies files and directories to a remote machine. It uses L<rsync(1)> for data transfer
+and uses the same authentication and provides the same security as L<ssh(1)>.
+
+=over
+
+=item B<DEST>
+
+remote machine
+
+=back
+
+=head1 OPTIONS
+
+=over 7
+
+=item B<-r, --rules-file=FILE>
+
+read exclude/include patterns from FILE (default: F</etc/send-config/filter.rules>)
+
+=item B<-c, --config=FILE>
+
+configuration file for send-config (default: F</etc/send-config/config>)
+
+=item B<-n, --dry-run>
+
+perform a trial run with no changes made
+
+=item B<-k, --keepgoing>
+
+don't stop on warnings
+
+=item B<-v, --verbose>
+
+explain what is being done
+
+=item B<-d, --debug>
+
+print debug informations
+
+=item B<-h, --help>
+
+display short help and exit
+
+=back
+
+=head1 EXAMPLES
+
+F</etc/send-config/filter.rules>
+
+Lines started with an # or ; are comments. New lines will be ignored. Lines with prefix + or - will be (+ included) or (- excluded) by the rsync process.
+
+ - *~
+ - /usr/src
+ - /etc/samba/codepages
+ - /usr/local/uvscan
+ - /usr/local/src
+ - /root/local-packages
+
+ + /etc
+ + /var/cache/debconf
+ + /var/lib/dpkg/status
+ + /boot/config*
+ + /boot/grub/menu.lst
+ + /root/.bash*
+ + /root/.profile
+ + /root/LOG*
+ + /root/.ssh
+ + /proc/config.gz
+ + /usr/local
+ + /usr/lib/AntiVir/*.[Kk][Ee][Yy]
+ + /usr/src/*/*.config
+ + /usr/src/*config*
+
+F</etc/send-config/config>
+
+I<username> is the remote login user for ssh and rsync process. I<path> is the absolute destination path on the remote machine where the files will be stored. I<stamp> is the absolute path on the local machine to create or touch an stamp file, you can use it e.g. for nagios (check_file_age).
+
+ username = root
+ path = /root/Configs/Hosts/
+ stamp = /var/tmp/get-config.stamp
+
+=head1 FILES
+
+F</etc/send-config/config>
+
+F</etc/send-config/filter.rules>
+
+=head1 AUTHOR
+
+Christian Arnold <arnold@schlittermann.de>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2010 Schlittermann - internet & unix support. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+
+This is free software: you are free to change and redistribute it.  There is NO WARRANTY, to the extent permitted by law.
+
+=head1 SEE ALSO
+
+L<ssh(1)>, L<rsync(1)>
+
+=cut
+