* 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
--- /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
+