#!/usr/bin/perl # prepares TFTP directory, *simple* DHCP, and *simple* DNS for server install. # stdin (or file argument) needs to have space-delimited file: # hostname MAC IP TFTP-server-IP pxeconfig-profile-name # use as "perl autoconfig.pl --help" use strict; use warnings; use Getopt::Long; use File::Copy qw(mv cp); use File::Path qw(mkpath); $ENV{PATH}="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"; our $DNS_DIR = "/var/named"; our $DNS_SERVER = "dns.example.com"; our $DNS_SERVER_IP = "10.1.1.1"; our $TFTP_ROOT = "/tftpboot"; our $MASTER_INSTALL_DIR = "$TFTP_ROOT/install"; our @INSTALL = (); our %subnets; our $COUNT = 1; our $NAMED_CONF = "/etc/named.conf"; our $DHCPD_CONF = "/etc/dhcpd.conf"; our $CFENGINE_KEYS = "/var/cfengine/ppkeys"; GetOptions( "dns-dir=s" => \$DNS_DIR, "dns-server=s" => \$DNS_SERVER, "install=s" => \@INSTALL, "usage" => \&usage, "help" => \&usage, ) or exit 1; our %INSTALL = map { $_, 1 } @INSTALL; # MAIN initialize_dhcp(); initialize_dns(); while( my $machine = read_machine() ) { add_hashinfo( $machine ); do_dhcp_config( $machine); do_dns_config( $machine); do_install( $machine ) if install_p( $machine ); } finalize_dhcp(); finalize_dns(); # END MAIN sub soa { my $zone = shift; <<"_SOA_"; @ IN SOA $DNS_SERVER root.example.com ( 1 3600 1800 604800 86400 ) @ NS $DNS_SERVER. _SOA_ } sub install_p { my $machine = shift; if ( $INSTALL{ $machine->{name} } or $INSTALL{ $machine->{short_name} } or $INSTALL{ $machine->{ip} } or $INSTALL{ $machine->{mac} } ) { return 1; } else { return 0; } } sub ip_to_hex { return join '', map { uc sprintf("%02x", $_ ) } split /\./, shift; } sub do_install { my $machine = shift; my $ip = $machine->{ip}; my $hex_ip = ip_to_hex( $ip ) or die "Couldn't calculate hex IP from '$ip'\n"; print "installing $ip ($hex_ip)\n"; my $in_file = "$MASTER_INSTALL_DIR/$machine->{pxe}"; die "PXE type '$machine->{pxe}' does not have a corresponding file '$in_file'!\n" unless -e $in_file; my $out_file = "$TFTP_ROOT/pxelinux.cfg/$hex_ip"; cp ( $in_file, $out_file ) or die "Couldn't copy: $!\n"; chmod 0666, $out_file; my $cfengine_key = "$CFENGINE_KEYS/root-$ip.pub"; unlink $cfengine_key if ( -e $cfengine_key); unless ( -s $in_file == -s $out_file ) { die "Copy from '$in_file' to '$out_file' seems to have failed\n"; } } sub read_machine { while (<>) { s/#.*//; s/^\s+//; s/\s+$//; next unless $_; my @machine = split /\s+/; return { name => $machine[0], mac => $machine[1], ip => $machine[2], dhcp => $machine[3], pxe => $machine[4], disk => $machine[5], }; } return; } sub add_hashinfo { my $machine = shift; ( my $short_host = $machine->{name} ) =~ s|\..*||; $machine->{short_name} = $short_host; } # DHCP sub initialize_dhcp { open *main::DHCP, "> $DHCPD_CONF" or die "Can't open '$DHCPD_CONF': $!\n"; print main::DHCP "ddns-update-style none;\n\n"; } sub do_dhcp_config { my $machine = shift; (my $subnet = $machine->{ip} ) =~ s/\.\d+$//; unless ( $subnets{$subnet}++ ) { print main::DHCP "subnet $subnet.0 netmask 255.255.255.0 {}\n\n"; } ( my $router = $machine->{ip} ) =~ s/\d+$/254/; print main::DHCP << "_END_MACHINE_"; host $COUNT { option routers $router; option host-name "$machine->{name}"; option domain-name-servers $DNS_SERVER_IP; hardware ethernet $machine->{mac}; fixed-address $machine->{ip}; filename "/pxelinux.0"; next-server $machine->{dhcp}; } _END_MACHINE_ $COUNT++ } sub finalize_dhcp { system("service dhcpd restart"); } # DNS sub initialize_dns { opendir D, $DNS_DIR or die "Can't open '$DNS_DIR': $!\n"; while( my $f = readdir D ) { my $path = "$DNS_DIR/$f"; next unless -f $path; unlink $path; } } sub write_to_zone { my ($zone, $data) = @_; open F, ">> $DNS_DIR/$zone" or die "Can't open '$DNS_DIR/$zone': $!\n"; print F $data; } sub do_dns_config { my $machine = shift; my @rev_ip = reverse split /\./, $machine->{ip}; my $last_octet = shift @rev_ip; my $zone = join '.', @rev_ip, qw(in-addr arpa); write_to_zone( $zone, "$last_octet IN PTR $machine->{name}.\n" ); my @rev_name = split /\./, $machine->{name}; my $last_name = shift @rev_name; $zone = join '.', @rev_name; write_to_zone( $zone, "$last_name IN A $machine->{ip}\n" ); } sub finalize_dns { opendir D, $DNS_DIR or die "Can't open '$DNS_DIR': $!\n"; my @zones; while( my $zone = readdir D ) { my $path = "$DNS_DIR/$zone"; next unless -f $path; push @zones, $zone; my $tmp_path = $path . ".tmp"; mv ($path, $tmp_path); open F, "> $path" or die "Can't open '$path': $!\n"; print F soa( $zone ); open TMP, "$tmp_path" or die "Can't open '$tmp_path': $!\n"; print F $_ while (); close F; close TMP; unlink $tmp_path; } open CONF, "> $NAMED_CONF" or die "Can't open '$NAMED_CONF': $!\n"; print CONF qq{options { directory "$DNS_DIR"; };\n\n}; foreach my $zone ( @zones ) { print CONF <<"_ZONE_"; zone "$zone" in { type master; file "$zone"; }; _ZONE_ } system("service named restart"); } sub usage { die <<"_USAGE_"; usage: $0 [ --dns-dir=/path/to/dns ] [ --dns-server=x.y.z.example.com ] [ --install=server.example.com [ --install=server2.example.com ... ] ] hosts.conf | --help | --usage _USAGE_ }