# HG changeset patch # User Christian Arnold # Date 1280316306 -7200 # Node ID e7973168471df17b7d0acc226d18e11d48a551c5 # Parent 482b827bb2cd0bd468e3e90680be3628cb32760d * 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 diff -r 482b827bb2cd -r e7973168471d .hgignore --- /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[./] diff -r 482b827bb2cd -r e7973168471d Makefile --- 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 $< diff -r 482b827bb2cd -r e7973168471d config.ex --- 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 diff -r 482b827bb2cd -r e7973168471d debian/changelog --- 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 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 Wed, 21 Jul 2010 11:03:43 +0200 + send-config (1.1-1) stable; urgency=low * build debian stable packet diff -r 482b827bb2cd -r e7973168471d debian/control --- 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 diff -r 482b827bb2cd -r e7973168471d debian/copyright --- 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 on Thu, 28 Apr 2005 16:48:12 +0200. -It was downloaded from - Copyright Holder: Christian Arnold arnold@schlittermann.de License: diff -r 482b827bb2cd -r e7973168471d debian/dirs --- 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 diff -r 482b827bb2cd -r e7973168471d debian/rules --- 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 diff -r 482b827bb2cd -r e7973168471d filter.rules.ex --- /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* diff -r 482b827bb2cd -r e7973168471d send-config.pl --- 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 () { - 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 () { - 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, "+) { + 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 - copies files and directories to a remote machine + +=head1 SYNOPSIS + +B [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 copies files and directories to a remote machine. It uses L for data transfer +and uses the same authentication and provides the same security as L. + +=over + +=item B + +remote machine + +=back + +=head1 OPTIONS + +=over 7 + +=item B<-r, --rules-file=FILE> + +read exclude/include patterns from FILE (default: F) + +=item B<-c, --config=FILE> + +configuration file for send-config (default: F) + +=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 + +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 + +I is the remote login user for ssh and rsync process. I is the absolute destination path on the remote machine where the files will be stored. I 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 + +F + +=head1 AUTHOR + +Christian Arnold + +=head1 COPYRIGHT + +Copyright (C) 2010 Schlittermann - internet & unix support. License GPLv3+: GNU GPL version 3 or later + +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, L + +=cut +