#!/usr/bin/perl

use strict;
use warnings;

use REST::Client;
use File::Copy "mv";
use File::Slurp;
use JSON::XS;
use YAML::Syck;
use Log::Log4perl qw(:easy);
use Capture::Tiny ':all';
use Sys::Hostname;
use Fcntl  qw(:flock);
use Socket qw(inet_ntoa);
use File::Basename;
use File::Path 'mkpath';
use Net::DNS;
use Data::Dumper;

=item /root/initial/conf/hal_local_data.yaml

For test servers that are not in HAL. A local file with a minimum required set of values can be created

---
hostname: <hostname>
silo_type: internal
region_id: 3
meta:
  prov_stage: initial
  test_server: 1
  hal_local: 1
=cut

$| = 1;

my $log_file            = '/root/initial/initial.log';
my $brand_conf          = '/root/initial/conf/TEMPLATE_brands.yaml';
my $regions_conf        = '/root/initial/conf/regions.yaml';
my $oci_region_map_conf = '/root/initial/conf/oci_region_map.yaml';
my $billing_setup       = 'undef';
my $hal_local_conf      = '/root/initial/conf/hal_local_data.yaml';
my $prov_api_host       = 'provision.hostgator.com';
my $migration_api_host  = 'migration-api.pssh.prod.us-ashburn-1.oci.eigbox.com';
my $initial_task        = 'initial';
my $initial_status      = 'started';
my $migration_notify    = 0;

if ( $ENV{'prov_env'} ) {
    if ( $ENV{'prov_env'} ne 'prod' ) {
        if ( $ENV{'env_override'} ) {
            $prov_api_host = "provision.$ENV{'env_override'}.$ENV{'brand_group'}.hostgator.com";
        }
        else {
            $prov_api_host = "provision.beta.$ENV{'brand_group'}.hostgator.com";
        }
        $migration_api_host = 'migration-api.pssh.preprod.us-ashburn-1.oci.eigbox.com';
    }
    elsif ( $ENV{'brand_group'} ne 'bh' && $ENV{'brand_group'} ne 'la' ) {
        $prov_api_host = "provision.$ENV{'brand_group'}.hostgator.com";
    }
}

Log::Log4perl->easy_init(
    {
        level  => $DEBUG,
        file   => ">>$log_file",
        layout => '%d - %p %L-%M: %m%n'
    }
);

my $logger = get_logger("");

open my $initial_lock, '<', $0 or die_with_notify($!);
flock $initial_lock, LOCK_EX | LOCK_NB or exit;

my $host_groups     = eval { LoadFile($brand_conf) }   or die_with_notify( $! . " $brand_conf" );
my $regions         = eval { LoadFile($regions_conf) } or die_with_notify( $! . " $regions_conf" );
my $hal_server_info = hal_info( { method => 'hal_server_info' } );
my $cloud_info      = cloud_info() if ( $ENV{'cloud_name'} );

if ( $cloud_info && $cloud_info->{metadata} && exists $cloud_info->{metadata}->{mysql_57} ) {
    $ENV{'MYSQL_57'} = 1;
}

if ( $cloud_info && $cloud_info->{metadata} && exists $cloud_info->{metadata}->{purpose} && $cloud_info->{metadata}->{purpose} eq 'migration' ) {
    $ENV{'MIGRATION'} = 1;
    $migration_notify = 1;
    migration_monitor_notify();
}

if ( ( $ENV{'prov_env'} ) && ( $ENV{'prov_env'} ne 'prod' ) ) {
    $hal_server_info->{meta}->{test_server} = 1;
}

unless ( $hal_server_info->{meta}->{prov_stage} && $hal_server_info->{meta}->{prov_stage} eq 'initial' ) {
    die_with_notify("Cannot Initial, prov_stage meta field not set to 'initial' in HAL");
}

if ( $hal_server_info->{silo_type} eq 'openstack' ) {
    until ( $hal_server_info->{status} eq 'available' or $hal_server_info->{status} eq 'active' ) {
        $logger->info("Server still in postprov. Current status is $hal_server_info->{status}");
        sleep 60;
        $hal_server_info = hal_info( { method => 'hal_server_info' } );
    }
}

$ENV{BRAND}     = $host_groups->{default}->{division};
$ENV{TIME_ZONE} = 'America/Chicago';
my $ident;
if ( $hal_server_info->{meta}->{hal_local} ) {
    my $oci_region = lc($cloud_info->{regionInfo}->{regionKey});
    my $oci_map    = eval { LoadFile($oci_region_map_conf) } or die_with_notify( $! . " $oci_region_map_conf" );
    $ident = $oci_map->{$oci_region};
    die_with_notify("Region $oci_region not found in map") unless $ident;
    $logger->info("HAL Local: Mapped OCI region '$oci_region' to ident '$ident'");
}
else {
    my $hal_region_info = hal_info( { method => 'hal_region_list', region_id => $hal_server_info->{region_id} } );
    if ( !$hal_region_info->{rows}->[0]->{ident} ) {
        die_with_notify("Could not find ident for region_id $hal_server_info->{region_id}");
    }
    $ident = $hal_region_info->{rows}->[0]->{ident};
    $logger->info("Found ident '$ident' for region_id $hal_server_info->{region_id}");
}
$ENV{LOCATION} = $regions->{$ident} || 'atl1_qts';

( -x '/usr/local/cpanel/cpsrvd' ) or die_with_notify("Cannot Initial, cPanel appears to be missing");

sh_command( '/usr/local/cpanel/cpkeyclt', 'die' );

if ( !-s '/root/.accesshash' ) {
    system('/usr/sbin/whmapi1 accesshash user=root generate=1');
    if ( -x '/usr/local/cpanel/scripts/convert_accesshash_to_token' ) {
        system('/usr/local/cpanel/scripts/convert_accesshash_to_token');
    }
}
elsif ( !-s '/var/cpanel/authn/api_tokens_v2/whostmgr/root.json' ) {
    system('/usr/local/cpanel/scripts/convert_accesshash_to_token');
}

unless ( -d "/root/initial/tasks" ) {
    mkdir "/root/initial/tasks";
}

my ( $host, $domain ) = split( '\.', $hal_server_info->{hostname}, 2 ) or die_with_notify($!);
my ( $class, $serv_num, $brand_prefix ) = $host =~ /(^[a-z-]+)(\d+)?(?:-(\w+))?/;
$class = "$class-$brand_prefix" if ($brand_prefix);
my ( $homesize, $number_of_homes ) = get_home_info();
$host_groups->{$domain} = $host_groups->{ $hal_server_info->{meta}->{config_group} } if ( $hal_server_info->{meta}->{config_group} );
my $primary_ip  = $hal_server_info->{primary_ip}         ? $hal_server_info->{primary_ip}         : read_file( '/var/cpanel/mainip', err_mode => 'carp' ) or die_with_notify($!);
my $access_vip  = $hal_server_info->{meta}->{access_vip} ? $hal_server_info->{meta}->{access_vip} : $hal_server_info->{primary_ip};
my $server_name = $hal_server_info->{hostname};

# Check that we have things in conf
die_with_notify("Cannot find \"$host_groups->{default}\" in \"$brand_conf\".")                                     if ( !exists( $host_groups->{default} ) );
die_with_notify("Cannot find \"$host_groups->{default}/$host_groups->{default}->{division}\" in \"$brand_conf\".") if ( !exists( $host_groups->{default}->{division} ) );
die_with_notify("Cannot find \"$host_groups->{$domain}\" in \"$brand_conf\".")                                     if ( !exists( $host_groups->{$domain} ) );
die_with_notify("Cannot find \"$domain/default\" in $brand_conf")                                                  if ( !exists( $host_groups->{$domain}->{default} ) );
for my $thing (qw(registrar dns_provider brand ns1_mod ns2_mod)) {
    die_with_notify("Cannot find \"$domain/default/$thing\" in \"$brand_conf\".") if ( !exists( $host_groups->{$domain}->{default}->{$thing} ) );
}
if ( !exists( $host_groups->{$domain}->{$class} ) ) {
    if ( $brand_prefix && exists( $host_groups->{$domain}->{"_wildcard_${brand_prefix}_"} ) ) {
        $class = "_wildcard_${brand_prefix}_";
    }
    elsif ( exists( $host_groups->{$domain}->{_wildcard_} ) ) {
        $class = '_wildcard_';
    }
    else {
        die_with_notify("Cannot find \"$class\" or \"_wildcard_\" in \"$brand_conf\".");
    }
}
for my $thing (qw(product henson_env henson_hg init_script ns zabbix)) {
    die_with_notify("Cannot find \"$domain/$class/$thing\" in \"$brand_conf\".") if ( !exists( $host_groups->{$domain}->{$class}->{$thing} ) );
}

my $initial_script = $host_groups->{$domain}->{$class}->{init_script};
## using eval here until better method can be added to calculate the different number of possible NS conventions each brand uses
my $dns_domain = $host_groups->{$domain}->{$class}->{dns_domain} // $host_groups->{$domain}->{default}->{dns_domain} // $domain;
my $num_length = length($serv_num);
my ( $ns1_num, $ns2_num );
if ( $host_groups->{$domain}->{default}->{dns_local} ) {
    $ns1_num = sprintf( "%0${num_length}d", eval( $host_groups->{$domain}{default}{ns1_mod} ) // die_with_notify("ns1_mod eval returned undef") );
    $ns2_num = sprintf( "%0${num_length}d", eval( $host_groups->{$domain}{default}{ns2_mod} ) // die_with_notify("ns2_mod eval returned undef") );
}
else {
    $ns1_num = eval( $host_groups->{$domain}{default}{ns1_mod} ) // die_with_notify("ns1_mod eval returned undef");
    $ns2_num = eval( $host_groups->{$domain}{default}{ns2_mod} ) // die_with_notify("ns2_mod eval returned undef");
}
my $ns1 = "$host_groups->{$domain}{$class}{ns}${ns1_num}.$dns_domain";
my $ns2 = "$host_groups->{$domain}{$class}{ns}${ns2_num}.$dns_domain";
my ( @secondary_ips, @private_ips );

cloud_ips() if ( $ENV{'cloud_name'} );

if ( ( $hal_server_info->{meta}->{test_server} ) && ( !$hal_server_info->{other_ips} ) && ( !$hal_server_info->{shared_ips} ) && !$ENV{'MIGRATION'} ) {
    @secondary_ips = ( $primary_ip, $primary_ip );
}
else {
    @secondary_ips = eval { @{ $hal_server_info->{shared_ips} ? $hal_server_info->{shared_ips} : $hal_server_info->{other_ips} } };
    if ( ($@) || ( !@secondary_ips && ( ( !$hal_server_info->{meta}->{test_server} ) || $ENV{'MIGRATION'} ) ) ) {
        die_with_notify('missing secondary IPs!!!');
    }
}

if ( $host_groups->{$domain}->{default}->{billing_setup} ) {
    $billing_setup = 'billing_setup';
}
if ( $host_groups->{$domain}->{default}->{time_zone} ) {
    $ENV{TIME_ZONE} = $host_groups->{$domain}->{default}->{time_zone};
}
$ENV{PRODUCT} = $host_groups->{$domain}->{$class}->{product};

my $hg_api_methods = {
    spf_record => {
        method       => 'hg_ensure_spf_record',
        hostname     => $server_name,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand},
    },
    a_record_hostname => {
        method       => 'hg_ensure_a_record',
        ip           => $primary_ip,
        hostname     => $server_name,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand},
    },
    a_record_ns1 => {
        method       => 'hg_ensure_a_record',
        ip           => $secondary_ips[0],
        hostname     => $ns1,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand}
    },
    a_record_ns2 => {
        method       => 'hg_ensure_a_record',
        ip           => $secondary_ips[1],
        hostname     => $ns2,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand}
    },
    a_record_cpanel_proxy => {
        method       => 'hg_ensure_a_record',
        ip           => $primary_ip,
        hostname     => 'cpanel-' . $server_name,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand}
    },
    a_record_webmail_proxy => {
        method       => 'hg_ensure_a_record',
        ip           => $primary_ip,
        hostname     => 'webmail-' . $server_name,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand}
    },
    a_record_webmail_cpanel_proxy => {
        method       => 'hg_ensure_a_record',
        ip           => $primary_ip,
        hostname     => 'webmail.cpanel-' . $server_name,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand}
    },
    a_record_access_vip => {
        method       => 'hg_ensure_a_record',
        ip           => $access_vip,
        hostname     => 'access.' . $server_name,
        dns_provider => $host_groups->{$domain}->{default}->{dns_provider},
        brand        => $host_groups->{$domain}->{default}->{brand}
    },
    ptr_hostname => {
        method   => 'hg_ensure_ptr_record',
        ip       => $primary_ip,
        hostname => $server_name
    },
    ptr_ns1 => {
        method   => 'hg_ensure_ptr_record',
        ip       => $secondary_ips[0],
        hostname => $ns1
    },
    ptr_ns2 => {
        method   => 'hg_ensure_ptr_record',
        ip       => $secondary_ips[1],
        hostname => $ns2
    },
    register_ns1 => {
        method    => 'hg_ensure_ns',
        ip        => $secondary_ips[0],
        hostname  => $ns1,
        registrar => $host_groups->{$domain}->{default}->{registrar},
        brand     => $host_groups->{$domain}->{default}->{brand}
    },
    register_ns2 => {
        method    => 'hg_ensure_ns',
        ip        => $secondary_ips[1],
        hostname  => $ns2,
        registrar => $host_groups->{$domain}->{default}->{registrar},
        brand     => $host_groups->{$domain}->{default}->{brand}
    },
    cm_allow_relay_cmgw10 => {
        method     => 'hg_cm_allow_relay',
        ip         => $primary_ip,
        cm_cluster => 'cmgw10'
    },
    cm_allow_relay_atl3asocm01 => {
        method     => 'hg_cm_allow_relay',
        ip         => $primary_ip,
        cm_cluster => 'atl3asocm01'
    },
    cm_allow_relay_atl1wswcm01 => {
        method     => 'hg_cm_allow_relay',
        ip         => $primary_ip,
        cm_cluster => 'atl1wswcm01'
    },
    foreman_hostgroup_set => {
        method    => 'henson_update_server',
        hostname  => $server_name,
        hostgroup => $host_groups->{$domain}->{$class}->{'henson_hg'}
    },
    foreman_dc_set => {
        method    => 'henson_ensure_param',
        hostname  => $server_name,
        parameter => 'datacenter'
    },
    foreman_dev_branch_set => {
        method    => 'henson_ensure_param',
        hostname  => $server_name,
        parameter => 'bhdev_branch'
    },
    foreman_temp_url_set => {
        method    => 'henson_ensure_param',
        hostname  => $server_name,
        parameter => 'temp_url'
    },
    zabbix_add_server => {
        method        => 'zabbix_add_server',
        ip            => $primary_ip,
        hostname      => $server_name,
        zabbix_group  => $host_groups->{$domain}->{$class}->{zabbix},
        brand         => $host_groups->{$domain}->{default}->{brand},
        zabbix_server => $host_groups->{default}->{zabbix_server}
          || 'hou_c1'

    },
    site247_add_server => {
        method   => 'site247_add_server',
        hostname => $server_name
    },
    softac_license_ip => {
        method => 'softac_license_ip',
        ip     => $primary_ip,
    },
    hal_watermark_set => {
        method     => 'hal_update_meta',
        server_id  => $hal_server_info->{id},
        meta       => 'watermark',
        meta_value => $host_groups->{$domain}->{$class}->{watermark}
    },
    hal_prov_stage_update => {
        method     => 'hal_update_meta',
        server_id  => $hal_server_info->{id},
        meta       => 'prov_stage',
        meta_value => $billing_setup
    },
    hal_set_active => {
        method    => 'hal_set_active',
        server_id => $hal_server_info->{id}
    },
    hal_server_access_update => {
        method    => 'hal_server_access_update',
        server_id => $hal_server_info->{id}
    },
    server_password_get => {
        method   => 'hg_server_pass',
        hostname => $server_name
    },
    get_private_ip => {
        method   => 'hal_get_private_ip',
        hostname => $server_name
    },
    get_network_info => {
        method => 'ipam_get_network_info',
    },
    backupmaster_server_add => {
        method   => 'backupmaster_server_add',
        ip       => $primary_ip,
        hostname => $server_name,
        brand    => $host_groups->{$domain}->{default}->{brand},
        dc       => $ENV{LOCATION},
    },
    backupmaster_partitions_add => {
        method          => 'backupmaster_partitions_add',
        hostname        => $server_name,
        number_of_homes => $number_of_homes,
        homesize        => $homesize,
    },
};

my $whm_api_methods = {
    hostname_set      => [ 'sethostname', "hostname $server_name" ],
    zone_add_hostname => [ 'adddns',      "domain $server_name ip $primary_ip" ],
    zone_add_ns1      => [ 'adddns',      "domain $ns1 ip $secondary_ips[0]" ],
    zone_add_ns2      => [ 'adddns',      "domain $ns2 ip $secondary_ips[1]" ]
};

run_tasks();

sub run_tasks {
    if ( $server_name ne hostname ) {
        whm_exec('hostname_set');
    }
    $hal_server_info->{meta}->{hal_local} || set_password();
    ( ( !@private_ips ) && ( !$hal_server_info->{other_ips} ) ) || sec_ips();
    set_dnsadmin() if ( $dns_domain && !$host_groups->{$domain}->{default}->{skip_dnsadmin} && !$host_groups->{default}->{skip_dnsadmin} );
    wildcard_dns();
    if ( $host_groups->{$domain}->{default}->{dns_zone} ) {
        $hg_api_methods->{a_record_hostname}->{dns_zone} = $host_groups->{$domain}->{default}->{dns_zone};
        $hg_api_methods->{spf_record}->{dns_zone}        = $host_groups->{$domain}->{default}->{dns_zone};
    }
    hg_api('a_record_hostname');
    if ( $host_groups->{$domain}->{default}->{spf_record} ) {
        $hg_api_methods->{spf_record}->{spf_data} = $host_groups->{$domain}->{default}->{spf_record};
        hg_api('spf_record');
    }
    if ( !$host_groups->{default}->{ptr_skip} ) {
        $hal_server_info->{meta}->{test_server} || hg_api('ptr_hostname');
    }
    if ( $host_groups->{$domain}->{default}->{dns_local} ) {
        if ( !$hal_server_info->{meta}->{test_server} ) {
            hg_api('a_record_ns1');
            hg_api('ptr_ns1');
            hg_api('register_ns1');
            whm_exec('zone_add_ns1');
            hg_api('a_record_ns2');
            hg_api('ptr_ns2');
            hg_api('register_ns2');
            whm_exec('zone_add_ns2');
        }
    }
    if ( $host_groups->{$domain}->{$class}->{temp_url} ) {
        $hg_api_methods->{a_record_temp_url} = {
            method       => 'hg_ensure_a_record',
            ip           => $primary_ip,
            hostname     => $host . '.' . $host_groups->{$domain}->{$class}->{temp_url},
            dns_provider => $host_groups->{default}->{temp_url_dns_provider} ? $host_groups->{default}->{temp_url_dns_provider} : 'cloudflare',
            brand        => $host_groups->{default}->{temp_url_dns_brand}    ? $host_groups->{default}->{temp_url_dns_brand}    : $host_groups->{$domain}->{default}->{brand}
        };
    }
    if ( $host_groups->{default}->{proxy_domains} ) {
        hg_api('a_record_cpanel_proxy');
        hg_api('a_record_webmail_proxy');
        if ( $host_groups->{$domain}->{default}->{dns_provider} eq 'ns1' ) {
            hg_api('a_record_webmail_cpanel_proxy');
        }
    }
    if ( $host_groups->{$domain}->{default}->{access_domain} ) {
        hg_api('a_record_access_vip');
    }
    sh_command( qq{sed -i "s#NS .*#NS $ns1#" /etc/wwwacct.conf},                              'die' );
    sh_command( qq{sed -i "s#NS2 .*#NS2 $ns2#" /etc/wwwacct.conf},                            'die' );
    sh_command( qq{sed -i "s#^TTL .*#TTL 3600#" /etc/wwwacct.conf},                           'die' );
    sh_command( qq|sed -i "s#^ADDR .*#ADDR \$(cat /var/cpanel/mainip)#" /etc/wwwacct.conf|,   'die' );
    sh_command( qq|$initial_script stage1 '$host_groups->{$domain}->{$class}->{henson_env}'|, 'die' );
    hg_api('foreman_hostgroup_set');
    sleep(5);

    if ( $host_groups->{$domain}->{$class}->{temp_url} ) {
        hg_api('a_record_temp_url');
        if ( $host_groups->{$domain}->{$class}->{temp_url_override} ) {
            set_foreman_temp_url( $host_groups->{$domain}->{$class}->{temp_url} );
        }
    }

    unless ( $hal_server_info->{meta}->{test_server} ) {
        hg_api('backupmaster_server_add');
        hg_api('backupmaster_partitions_add');
    }
    if ( $host_groups->{$domain}->{default}->{dev_branch} ) {
        if ( $hal_server_info->{code_level} ) {
            set_foreman_dev_branch(
                $hal_server_info->{code_level} eq 'dev'
                ? 'alpha'
                : $hal_server_info->{code_level}
            );
        }
    }
    sh_command( qq|$initial_script stage2|, 'die' );
    if ( !$host_groups->{default}->{softac_license_skip} ) {
        $hal_server_info->{meta}->{hal_local} || hg_api('softac_license_ip');
    }
    sh_command( qq|$initial_script stage3|, 'die' );
    chomp( my $virtual = capture_stdout { system('facter is_virtual') } );

    if ( $virtual eq 'false' ) {
        private_interface();
        sh_command( qq{sed -i "s#ETHDEV.*#ETHDEV eth1#" /etc/wwwacct.conf}, 'die' );
    }
    unless ( $hal_server_info->{meta}->{test_server} ) {
        if ( !$ENV{'cloud_name'} ) {
            hg_api('hal_server_access_update');
        }
        sh_command( qq{/opt/eig_linux/bin/genbackupkey},               'die' );
        sh_command( qq{/opt/eig_linux/bin/updatebackupinfo},           'die' );
        sh_command( qq{/opt/eig_linux/bin/backuphelper setup nomount}, 'die' );
    }
    if ( $host_groups->{$domain}->{$class}->{watermark} ) {
        hg_api('hal_watermark_set');
    }
    if ( $host_groups->{$domain}->{default}->{sso_setup} ) {
        sso_setup();
    }
    $hal_server_info->{meta}->{test_server} || whm_exec('zone_add_hostname');
    sh_command( qq|$initial_script stage4|, 'die' );
    if ( !$ENV{'cloud_name'} ) {
        hg_api('hal_set_active');
    }
    hg_api('hal_prov_stage_update');
    sh_command( qq|$initial_script stage5|, 'die' );
    $initial_task = 'initial_post_reboot';
    if ($migration_notify) {
        migration_build_notify_cron();
        migration_monitor_notify();
    }
}

sub migration_build_notify_cron {
    my $cron_file = '/etc/cron.d/migration_reboot_notify';
    my $post_data = {
        stage    => 'build',
        status   => 'completed',
        hostname => $cloud_info->{hostname}
    };

    my $json_payload = encode_json($post_data) =~ s/"/\\"/gr;
    my $curl_cmd     = qq{curl -s -H "Content-Type: application/json" -d "$json_payload" https://$migration_api_host/api/update};
    my $cron_content = qq{\@reboot root sh -c 'sleep 30; $curl_cmd 2>&1 | logger -t migration_build_notify; rm -f $cron_file'\n};

    write_conf( $cron_content, $cron_file );
}

sub cloud_info {
    my $client = REST::Client->new();
    $client->setHost("http://169.254.169.254");
    my $url = 'opc/v2/instance';
    $client->GET( $url, { Authorization => 'Bearer Oracle' } );
    my $response = decode_json( $client->responseContent() );
    return $response;
}

sub cloud_ips {
    my $metadata_ips = $cloud_info->{metadata}->{ips};
    my @ips;

    if ( ref($metadata_ips) eq 'HASH' ) {
        foreach my $vnic ( keys %{$metadata_ips} ) {
            my $val = $metadata_ips->{$vnic};
            if ( ref($val) eq 'ARRAY' ) {
                push @ips, @{$val};
            }
            elsif ( !ref($val) && $val ) {
                my @pairs = split( /,/, $val );
                my $count = 0;
                foreach my $pair (@pairs) {
                    my ( $public, $private ) = split( /:/, $pair );
                    my $purpose = ( ( $vnic eq '1' || $vnic eq 'vnic1' ) && $count == 0 ) ? 'primary' : 'shared';
                    push @ips, { e => $public, i => $private, u => $purpose };
                    $count++;
                }
            }
        }
    }
    elsif ( ref($metadata_ips) eq 'ARRAY' ) {
        @ips = @{$metadata_ips};
    }

    foreach my $ip (@ips) {
        my $purpose = $ip->{u} // $ip->{purpose};
        my $private = $ip->{i} // $ip->{private};
        my $public  = $ip->{e} // $ip->{public};

        if ( $purpose eq 'shared' || $purpose eq 'secondary' ) {
            push( @private_ips, $private );
            if ( $hal_server_info->{meta}->{hal_local} ) {
                push( @{ $hal_server_info->{other_ips} }, $public );
            }
        }
    }

    if ( $cloud_info->{metadata}->{purpose} eq 'migration' ) {
        $ENV{CPNAT_DISABLE} = 1;
        migration_ip_map($cloud_info);
    }
}

sub hg_api {
    my ( $task, $quiet ) = @_;
    my $task_done = task_lock_check($task);
    if ( $hal_server_info->{meta}->{hal_local} || ( $task_done && $task ne 'hal_server_info' ) ) {
        return 1;
    }
    my $client = REST::Client->new();
    $client->setHost("https://$prov_api_host");
    my $url = 'initial.pl';
    my $uri = $client->buildQuery( $hg_api_methods->{$task} );
    $client->GET( $url . $uri );
    my $response = $client->responseContent();
    eval { $response = decode_json($response) };
    $logger->info( encode_json( $hg_api_methods->{$task} ) );

    if ( $client->responseCode() != 200 || !( ref($response) eq "HASH" && $response->{status} ) ) {
        die_with_notify( $client->responseContent() );
    }
    elsif ( $response->{status} ne 'success' ) {
        die_with_notify( $client->responseContent() );
    }
    else {
        if ($quiet) {
            $logger->info("$hg_api_methods->{$task}->{method} => success");
        }
        else {
            $logger->info( $client->responseContent() );
        }
    }
    task_lock_set($task);
    return $response->{data};
}

sub hal_info {
    my ( $query, $quiet ) = @_;
    if ( -f $hal_local_conf ) {
        my $hal_local_data = eval { LoadFile($hal_local_conf) } or die_with_notify( $! . " $hal_local_conf" );
        return $hal_local_data;
    }
    my $client = REST::Client->new();
    $client->setHost("https://$prov_api_host");
    my $url = 'initial.pl';
    my $uri = $client->buildQuery($query);
    $client->GET( $url . $uri );
    my $response = $client->responseContent();
    eval { $response = decode_json($response) };
    $logger->info( encode_json($query) );

    if ( $client->responseCode() != 200 || !( ref($response) eq "HASH" && $response->{status} ) ) {
        die_with_notify( $client->responseContent() );
    }
    elsif ( $response->{status} ne 'success' ) {
        die_with_notify( $client->responseContent() );
    }
    else {
        if ($quiet) {
            $logger->info("$query->{method} => success");
        }
        else {
            $logger->info( $client->responseContent() );
        }
    }
    return $response->{data};
}

sub sec_ips {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ( !$task_done ) {
        my $etc_ips = join( "\n", ( map { "$_:255.255.255.255:$_" } @private_ips ? @private_ips : @secondary_ips ), '' );
        write_conf( $etc_ips, '/etc/ips' );
        task_lock_set($task);
    }
}

sub set_dc_param {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ( !$task_done ) {
        my ( $region_id, $dc ) = shift;
        if ( $region_id == 1 ) {
            $dc = 'provo';
        }
        elsif ( $region_id == 3 ) {
            $dc = 'c1';
        }
        elsif ( $region_id == 9 ) {
            $dc = 'br';
        }
        $hg_api_methods->{foreman_dc_set}->{value} = $dc;
        hg_api('foreman_dc_set');
        task_lock_set($task);
    }
}

sub set_foreman_dev_branch {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ( !$task_done ) {
        my $dev_branch = shift;
        $hg_api_methods->{foreman_dev_branch_set}->{value} = $dev_branch;
        hg_api('foreman_dev_branch_set');
        task_lock_set($task);
    }
}

sub set_foreman_temp_url {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ( !$task_done ) {
        my $temp_url = shift;
        $hg_api_methods->{foreman_temp_url_set}->{value} = $temp_url;
        hg_api('foreman_temp_url_set');
        task_lock_set($task);
    }
}

sub get_home_info {
    my @df              = qx(df -h -BG /home* -t ext4 --output=size | sed 1d);
    my $number_of_homes = scalar @df;
    my $homesize        = $df[0] =~ m/(\d+)G/ ? sprintf( "%.2f", $1 / 1024 ) : 1.5;
    return $homesize, $number_of_homes;
}

sub set_password {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ( !$task_done ) {
        my $crypted_sha = hg_api( 'server_password_get', 1 );
        if ( !$crypted_sha ) {
            die_with_notify("$task, didn't get password hash");
        }
        sh_command( qq|usermod -p '$crypted_sha' root|, 'die' );
        task_lock_set($task);
    }
}

sub sh_command {
    my ( $command, $error ) = @_;
    system("$command >> $log_file");
    if ( $? && $error eq 'die' ) {
        die_with_notify( "$command ", $? >> 8 );
    }
}

sub whm_exec {
    my ($task) = @_;
    my $task_done = task_lock_check($task);
    if ($task_done) {
        return 0;
    }
    my $module = @{ $whm_api_methods->{$task} }[0];
    my $args   = @{ $whm_api_methods->{$task} }[1];
    my $resp   = qx{whmapi1 --output=json $module $args};
    validate_resp( decode_json($resp) );
    task_lock_set($task);
}

sub set_dnsadmin {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ($task_done) {
        return 1;
    }
    my $dnsadmin_conf = qq|dnsadmin_host=my.$dns_domain\n|;
    write_conf( $dnsadmin_conf, '/var/cpanel/eig_custom_event.conf' );
    task_lock_set($task);
}

sub private_interface {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ($task_done) {
        return 1;
    }
    my $response   = hg_api('get_private_ip');
    my $private_ip = $response->{ip};
    $hg_api_methods->{get_network_info}->{ip} = $private_ip;
    $response = hg_api('get_network_info');
    my $net_info   = $response->{networkinfo};
    my $ifcfg_eth0 = qq|DEVICE=eth0
BOOTPROTO=static
NM_CONTROLLED=yes
ONBOOT=yes
TYPE=Ethernet
IPV6INIT=no
NAME="System eth0"
IPADDR=$private_ip
PREFIX=$net_info->{prefix}
|;
    write_conf( $ifcfg_eth0, '/etc/sysconfig/network-scripts/ifcfg-eth0' );

    my $route_eth0 = qq|10.0.0.0/8 via $net_info->{gw} dev eth0
|;
    write_conf( $route_eth0, '/etc/sysconfig/network-scripts/route-eth0' );
    task_lock_set($task);
}

sub write_conf {
    my ( $data, $conf_file ) = @_;
    $logger->info("$data $conf_file");
    my $dir = dirname($conf_file);
    if ( $dir && !-d $dir ) {
        mkpath($dir) or die_with_notify("Failed to create directory $dir: $!");
    }
    write_file( $conf_file, { err_mode => 'carp' }, $data ) or die_with_notify($!);
}

sub validate_resp {
    my $resp = shift;
    if ( $resp->{metadata}->{result} == 1 ) {
        $logger->info( $resp->{metadata}->{reason} );
    }
    else {
        die_with_notify( $resp->{metadata}->{reason} );
    }
}

sub wildcard_dns {
    my $task      = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done = task_lock_check($task);
    if ( !$task_done ) {
        my $named_file = '/etc/named.conf';
        my $wildcard   = qq|
zone "." IN {
    type master;
    file "/var/named/catchall.db";
};|;
        my @named = read_file( $named_file, err_mode => 'carp' ) or die_with_notify($!);
        chomp(@named);
        if ( !grep { /catchall\.db/ } @named ) {
            foreach my $conf_line (@named) {
                if ( $conf_line =~ s/.*BEGIN external zone entries.*/$conf_line\n$wildcard/ ) {
                }
            }
            write_conf( join( "\n", @named, '' ), $named_file );
        }
        task_lock_set($task);
    }
}

sub migration_ip_map {
    my ($cloud_info) = @_;
    my $task         = ( split( '::', ( ( caller(0) )[3] ) ) )[1];
    my $task_done    = task_lock_check($task);

    if ( !$task_done ) {
        my $metadata_ips = $cloud_info->{metadata}->{ips};
        my @ips;

        if ( ref($metadata_ips) eq 'HASH' ) {
            foreach my $vnic ( keys %{$metadata_ips} ) {
                my $val = $metadata_ips->{$vnic};
                if ( ref($val) eq 'ARRAY' ) {
                    push @ips, @{$val};
                }
                elsif ( !ref($val) && $val ) {
                    my @pairs = split( /,/, $val );
                    my $count = 0;
                    foreach my $pair (@pairs) {
                        my ( $public, $private ) = split( /:/, $pair );
                        my $purpose = ( ( $vnic eq '1' || $vnic eq 'vnic1' ) && $count == 0 ) ? 'primary' : 'shared';
                        push @ips, { e => $public, i => $private, u => $purpose };
                        $count++;
                    }
                }
            }
        }
        elsif ( ref($metadata_ips) eq 'ARRAY' ) {
            @ips = @{$metadata_ips};
        }

        die_with_notify("Missing instance IP info in metadata") unless @ips;

        my @ip_map_lines;
        foreach my $ip_entry (@ips) {
            my $purpose = $ip_entry->{u} // $ip_entry->{purpose};
            my $private = $ip_entry->{i} // $ip_entry->{private};
            my $public;

            if ( $purpose eq 'primary' ) {
                $public = resolve_hostname_to_ip( $cloud_info->{hostname} );
                if ( !defined $public ) {
                    die_with_notify("Failed to get public IP for primary interface from hostname resolution.");
                }
            }
            else {
                $public = $ip_entry->{e} // $ip_entry->{public};
            }

            if ( defined $private && defined $public && $private eq $public ) {
                die_with_notify("Invalid IP mapping: private and public IP cannot be the same ($private)");
            }

            push @ip_map_lines, "$private $public";
        }
        my $ip_map_content = join( "\n", @ip_map_lines, '' );
        write_conf( $ip_map_content, '/root/.server_migration/nat_ip_map' );
        task_lock_set($task);
    }
}

sub resolve_hostname_to_ip {
    my ($hostname) = @_;

    my $nameserver = '169.254.169.254';
    my $res        = Net::DNS::Resolver->new(
        nameservers => [$nameserver],
        recurse     => 1,
        udp_timeout => 5,
        tcp_timeout => 5,
    );
    my $query = $res->search($hostname);

    if ($query) {
        foreach my $rr ( $query->answer ) {
            next unless $rr->type eq "A";    # only IPv4
            return $rr->address;             # return first IPv4
        }
        $logger->warn("Failed to resolve IP for hostname: $hostname using $nameserver");
        return undef;
    }
    else {
        $logger->warn( "Failed to resolve IP for hostname: $hostname using $nameserver " . $res->errorstring );
        return undef;
    }
}

sub migration_monitor_notify {
    if ( !$migration_api_host ) {
        $logger->warn("migration_api_host hostname not set, skipping update.");
        return 1;
    }

    my $post_data->{stage}    //= 'build';
    $post_data->{task}        //= $initial_task;
    $post_data->{status}      //= $initial_status;
    $post_data->{hostname}    //= $cloud_info->{hostname};
    $post_data->{destination} //= $cloud_info->{definedTags}->{dino}->{public_ip};
    $post_data->{vcn_name}    //= $cloud_info->{metadata}->{vcn_name};

    my $json_payload = eval { encode_json($post_data) };

    if ($@) {
        $logger->error("Failed to encode JSON payload for migration_api_host: $@");
        return 0;
    }

    my $params_log = join ', ', map { "$_ => '$post_data->{$_}'" } sort keys %{$post_data};

    my $client = REST::Client->new();
    $client->setHost("https://$migration_api_host");
    $client->addHeader( 'Content-Type', 'application/json' );
    $client->POST( '/api/update', $json_payload );

    if ( $client->responseCode() == 200 ) {
    }
    else {
        my $error_message = "Migration monitor update failed with params: {" . $params_log . "}: " . $client->responseCode() . " - " . $client->responseContent();
        $logger->logdie($error_message);
    }
}

sub task_lock_check {
    my ($task) = @_;
    my $task_lock = "/root/initial/tasks/$task.done";
    if ( -f $task_lock ) {
        return 1;
    }
    else {
        return 0;
    }
}

sub task_lock_set {
    my ($task) = @_;
    my $task_lock = "/root/initial/tasks/$task.done";
    my $task_fh;
    open( $task_fh, '>', $task_lock )
      or die_with_notify("Unable to open file $task_lock : $!");
}

sub die_with_notify {
    my ($msg) = @_;
    $initial_status = 'failed';
    migration_monitor_notify() if $migration_notify;
    $logger->logdie($msg);
}
