#!/usr/bin/ruby
#--
##  Copyright (C) 2008 Red Hat Inc.
##
##  This library is free software; you can redistribute it and/or
##  modify it under the terms of the GNU Lesser General Public
##  License as published by the Free Software Foundation; either
##  version 2.1 of the License, or (at your option) any later version.
##
##  This library is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
##  Lesser General Public License for more details.
##
##  You should have received a copy of the GNU Lesser General Public
##  License along with this library; if not, write to the Free Software
##  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
##
## Author: Joey Boggs <jboggs@redhat.com>
##--
## oVirt Installation Script

require 'socket'
require 'fileutils'
require 'erb'
require 'rubygems'
require 'highline'

#
# Input output controls
#

@cli = HighLine.new()

# prompt a user for a non-blank answer
def prompt_for_answer(prompt, options={})
    default = options[:default]
    expression = options[:regex]
    password = options[:password]
    prompt = "\n#{prompt} "

    answer = @cli.ask(prompt, String) do |q|
      q.default                  = default if default
      q.validate                 = expression if expression
      q.echo                     = "*" if password
      q.responses[:not_valid]    = 'Please enter a valid value.'
      q.responses[:ask_on_error] = :question
    end

    return answer
end

# prompt a user for a password, with confirmation
def prompt_for_password(prompt, confirm)
  loop do
    pass = prompt_for_answer(prompt, :password => true)
    conf = prompt_for_answer(confirm, :password => true)
    if pass == conf
      return pass
    end
    @cli.say("Passwords do not match!")
  end
end

def prompt_for_interface(prompt, interfaces, options={})
    loop do
    nic = prompt_for_answer(prompt,options)
    if interfaces.has_key?(nic)
        return nic
    end
    @cli.say("Please pick an active device from the list")
    end
end

# Allow a user to enter a Yes/No
# And repeat the prompt until they do
def prompt_yes_no(prompt, options={})
    default = options[:default]
    prompt = "\n#{prompt} "

    answer = @cli.ask(prompt, lambda { |yn| yn.downcase[0] == ?y}) do |q|
      q.default                  = default
      q.validate                 = /\Ay(?:es)?|no?\Z/i
      q.responses[:not_valid]    = 'Please enter "yes" or "no".'
      q.responses[:ask_on_error] = :question
      q.whitespace               = :chomp
    end

    return answer ? "y" : "n"
end

#
# The real script begins here
#

# These regular expressions will be used to
# validate the user input
IP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
THREE_OCTETS = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){2}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
FQDN = /(?=^.{1,254}$)(^(?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})$)/
IP_OR_FQDN = /(?=^.{1,254}$)(^((?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$)/
OCTET = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/

# Print a friendly welcome message
welcome =  "This installer will configure the ovirt installation based on a series\n\
of questions. When complete, you will be asked to install oVirt or\n\
do the installation manually. Would you like to continue?"

# verify hostname is not on the loopback line in /etc/hosts to prevent kerberos problems
hostname = `hostname`
hostsfile = File.new("/etc/hosts", "r")
while (line = hostsfile.gets)
     if line =~ /127.0.0.1/ && line.include?(hostname.chomp)
         if hostname !~ /localhost.localdomain/
         @cli.say("\nHostname must not be on the loopback 127.0.0.1 line in /etc/hosts")
         @cli.say("#{line}")
         exit(0)
         end
     end
end
hostsfile.close

if (prompt_yes_no(welcome, :default => "y") == "n")
    exit(0)
end

modssl_check = `rpm -q mod_ssl`
if modssl_check !~ /(not installed)/
    puts "\nmod_ssl package must be removed prior to installing, to remove run: rpm -e mod_ssl\n\n"
    exit
end

if File.exist?("/usr/sbin/selinuxenabled")
    sestatus = system "/usr/sbin/selinuxenabled"
    if sestatus == true
        selinux_permissive = prompt_yes_no("SELinux enforcing, would you like to set it permissive?", :default => "y")
        if selinux_permissive == "n"
            @cli.say("SELinux must be permissive or disabled prior to running the installer again")
            exit(0)
        else
            @cli.say("Setting SELinux permissive")
            system "/usr/sbin/lokkit --selinux=permissive"
            system "/usr/sbin/setenforce 0"
        end
    end
end

# Networking Configuration
dev_ct = 0
net_devs = `hal-find-by-capability --capability net`
net_devs.each_line do |dev|
    dev_ct = dev_ct + 1
end

if dev_ct == 0
    @cli.say("Unable to install without a network interface")
    exit
else
    puts ""
    @cli.say("Below are the active networking devices, if there are any missing devices\n\n")
    @cli.say("ensure they are active and have an ip address before running the installer")
    @cli.say("mac address           interface        ip address")
    interfaces = {}
    net_devs.each_line do |dev|
        dev = dev.chomp
        interface = `hal-get-property --udi #{dev} --key net.interface`
        mac = `hal-get-property --udi #{dev} --key net.address`
        ip = `ifconfig #{interface}`
        ipaddr = ip.scan(/\s*inet addr:([\d.]+)/)
        interfaces[interface.chop] = ipaddr.to_s if interface.chop != "lo"
        puts (mac.chop + "   :   " + interface.chop   +  "    :    " + ipaddr.to_s) if interface.chop != "lo" and ipaddr.to_s != ""
    end
end

guest_httpd_dev = prompt_for_interface("Enter the interface for the Guest network:", interfaces, :default => "eth0")
admin_dev = prompt_for_interface("Enter the interface for the Admin network (this may be the same as the Guest network interface):", interfaces, :default => "eth0")

#FIXME: correctly configure separate networks.
#For now, define admin and guest networks to be the same
guest_dev = admin_dev
#sep_networks = (guest_dev == admin_dev) ? "n" : "y"

ovirt_host = prompt_for_answer("Enter the hostname of the oVirt management server (example: management.example.com):", :regex => IP_OR_FQDN)
ipa_host = ovirt_host

# DNS Configuration
@cli.say( "\nThe following DNS servers were found:")
File.open('/etc/resolv.conf').each_line{ |line|
  line = line.chomp
    puts line if line =~ /nameserver/
}
@cli.say("<%= color('\nIf your above dns servers contain the A records for your management server select \"y\"
otherwise select \"n\" and a dns server will be configured during the install', RED) %>")
dns_servers = prompt_yes_no("Use this systems's dns servers?")

guest_httpd_ipaddr = interfaces[guest_httpd_dev]
guest_ipaddr = interfaces[guest_dev]
admin_ipaddr = interfaces[admin_dev]

if dns_servers == "y"
    guest_ipaddr_lookup = Socket.getaddrinfo(guest_ipaddr.to_s,nil)
    guest_hostname = guest_ipaddr_lookup[1][2]
    if guest_hostname.to_s != ipa_host.to_s
        @cli.say("Reverse dns lookup for #{guest_ipaddr} failed, exiting")
        exit(0)
    end

    ipa_host_lookup = Socket.getaddrinfo(ipa_host,nil)
    ipa_hostip = ipa_host_lookup[1][3]
    if ipa_hostip.to_s != guest_ipaddr.to_s
         @cli.say("Forward dns lookup for #{ipa_host} failed, exiting")
         exit(0)
    end
end

# DHCP Configuration
dhcp_setup = prompt_yes_no("Does your Admin network already have dhcp?")
if dhcp_setup == "n"
    dnsdomainname = `/bin/dnsdomainname`
    default_gw = `route -n | grep 'UG'|awk {'print $2'}`
    dhcp_interface = admin_dev
    dhcp_network = prompt_for_answer("Enter the first 3 octets of the dhcp network you wish to use (example: 192.168.50):", :regex => THREE_OCTETS)
    dhcp_start = prompt_for_answer("Enter the dhcp pool start address (example: 3):", :regex => OCTET)
    dhcp_stop = prompt_for_answer("Enter the dhcp pool end addess (example: 100):", :regex => OCTET)
    dhcp_domain = prompt_for_answer("Enter the dhcp domain you wish to use (example: example.com):", :default => dnsdomainname.chomp, :regex => IP_OR_FQDN)
    admin_dns_server = interfaces[admin_dev]
    admin_network_gateway = prompt_for_answer("Enter the network gateway for your Admin network (example: 192.168.50.254):", :default => default_gw.chomp, :regex => IP_OR_FQDN)
    tftp_setup = prompt_yes_no("Provide pxe/tftp capability?")
end

# Cobbler Configuration
cobbler_setup = prompt_yes_no("Do you have a cobbler server already that you wish to use?")

if cobbler_setup == "y"
    cobbler_hostname = prompt_for_answer("Enter the hostname of your cobbler server:", :regex => IP_OR_FQDN)
elsif cobbler_setup == "n"
    cobbler_hostname = "localhost"
    @cli.say("\nWe will setup a cobbler instance, please provide the following information")
end

cobbler_user_name= prompt_for_answer("Enter your cobbler username:")
cobbler_user_password = prompt_for_password("Enter your cobbler user password:", "Confirm your cobbler user password:")

# Postgres Configuration
db_username = "ovirt"
db_password = prompt_for_password("Enter a password for the ovirt postgres account:", "Confirm your ovirt postgres password")
# FreeIPA Configuration
realm_name = prompt_for_answer("Enter your realm name (example: example.com):", :regex => FQDN)

@cli.say("NOTE: The following password will also be your ovirtadmin password for the web management login")
freeipa_password = prompt_for_password("Enter an administrator password for FreeIPA:", "Confirm your FreeIPA admin password:")
ldap_dn = ""
ldap_dn_temp = realm_name.split(".")
ldap_dn_temp.each do |i|
    ldap_dn += "dc=#{i},"
end
ldap_dn = ldap_dn.chop


#
# Use ERB to spit out the puppet file whcih is used by ace.
#

# Create the template
template = <<END_OF_TEMPLATE
# Configurations script generated by ovirt-installer
# at <%= Time.now().to_s() %>
#

import 'ovirt'
import 'firewall'
firewall::setup{'setup':
    status => 'enabled'
}

firewall_rule{"ssh": destination_port => "22"}

#DNS Configuration
$guest_httpd_ipaddr = '<%= guest_httpd_ipaddr %>'
$guest_ipaddr = '<%= guest_ipaddr %>'
$admin_ipaddr = '<%= admin_ipaddr %>'
$ovirt_host = '<%= ovirt_host %>'
$ipa_host = '<%= ipa_host %>'

<% if dns_servers == "n" %>
dns::bundled{setup:
<% else %>
dns::remote{setup:
<% end %>
    guest_ipaddr=> $guest_ipaddr,
    admin_ipaddr=> $admin_ipaddr,
    guest_dev => '<%= guest_dev %>',
    admin_dev => '<%= admin_dev %>'
}

# DHCP Configuration
<% if dhcp_setup == "n" %>
$dhcp_interface = '<%= dhcp_interface %>'
$dhcp_network = '<%= dhcp_network %>'
$dhcp_start = '<%= dhcp_start %>'
$dhcp_stop = '<%= dhcp_stop %>'
$dhcp_domain = '<%= dhcp_domain %>'
$ntp_server = '<%= guest_ipaddr %>'
$admin_network_gateway = '<%= admin_network_gateway %>'
$admin_dns_server = '<%= admin_dns_server %>'
<% if tftp_setup == "y" %>
include tftp::bundled
<% end %>
<% end %>


# Cobbler configuration
$cobbler_hostname = '<%= cobbler_hostname %>'
$cobbler_user_name = '<%= cobbler_user_name %>'
$cobbler_user_password = '<%= cobbler_user_password %>'

# Postgres Configuration
$db_username = '<%= db_username %>'
$db_password = '<%= db_password %>'

# FreeIPA configuration
$realm_name = '<%= realm_name %>'
$freeipa_password = '<%= freeipa_password %>'
$short_ldap_dn = '<%= ldap_dn %>'
$ldap_dn = 'cn=ipaConfig,cn=etc,<%= ldap_dn %>'

<% if cobbler_setup == "n" %>
include cobbler::bundled
<% else %>
include cobbler::remote
<% end %>
<% if dhcp_setup == "n" %>
include dhcp::bundled
firewall_rule{"nat-forward": chain => "FORWARD", in_interface => "<%= admin_dev %>", out_interface => "<%= guest_dev %>", protocol => ""}
firewall_rule{"nat-postrouting": table => "nat", chain => "POSTROUTING", out_interface => "<%= guest_dev %>", protocol => "", action => "MASQUERADE"}
<% end %>
include postgres::bundled
include freeipa::bundled
include ovirt::setup
END_OF_TEMPLATE

# Generate the file and output it.
FileUtils.mkdir_p("/usr/share/ace/appliances/ovirt")
config_file = File.new("/usr/share/ace/appliances/ovirt/ovirt.pp", "w", 0600)
config_file.write(ERB.new(template, 0, "%>").result)
config_file.close()


# Give a friendly reminder about what to do next
puts "\nTo start the installation run:    ace install ovirt"
