#!/usr/bin/env ruby # description: ManageIQ appliance console # require 'bundler' Bundler.setup require 'manageiq-appliance_console' require 'pathname' gem_root = Pathname.new(__dir__).join("..") require 'fileutils' require 'highline/import' require 'highline/system_extensions' require 'rubygems' require 'bcrypt' require 'linux_admin' require 'util/postgres_admin' require 'awesome_spawn' include HighLine::SystemExtensions require 'i18n' locales_dir = ENV['CONTAINER'] ? "container" : "appliance" locales_paths = [ gem_root.join("locales", locales_dir, "*.yml"), File.expand_path(File.join("productization/appliance_console/locales", locales_dir, "*.yml"), ManageIQ::ApplianceConsole::RAILS_ROOT) ] locales_paths.each { |p| I18n.load_path += Dir[p].sort } I18n.enforce_available_locales = true I18n.backend.load_translations SCAP_RULES_DIR = File.expand_path("productization/appliance_console/config", ManageIQ::ApplianceConsole::RAILS_ROOT) $terminal.wrap_at = 80 $terminal.page_at = 35 def summary_entry(field, value) dfield = "#{field}:" "#{dfield.ljust(24)} #{value}" end [:INT, :TERM, :ABRT, :TSTP].each { |s| trap(s) { raise MiqSignalError } } VERSION_FILE = ManageIQ::ApplianceConsole::RAILS_ROOT.join("VERSION") LOGFILE = ManageIQ::ApplianceConsole::RAILS_ROOT.join("log", "appliance_console.log") DB_RESTORE_FILE = "/tmp/evm_db.backup".freeze AS_OPTIONS = I18n.t("advanced_settings.menu_order").collect do |item| I18n.t("advanced_settings.#{item}") end require 'util/miq-password' MiqPassword.key_root = ManageIQ::ApplianceConsole::RAILS_ROOT.join("certs").to_s # Load appliance_console libraries include ManageIQ::ApplianceConsole::Prompts # Restore database choices RESTORE_LOCAL = "Local file".freeze RESTORE_NFS = "Network File System (NFS)".freeze RESTORE_SMB = "Samba (SMB)".freeze RESTORE_OPTIONS = [RESTORE_LOCAL, RESTORE_NFS, RESTORE_SMB, ManageIQ::ApplianceConsole::CANCEL].freeze # Restart choices RE_RESTART = "Restart".freeze RE_DELLOGS = "Restart and Clean Logs".freeze RE_OPTIONS = [RE_RESTART, RE_DELLOGS, ManageIQ::ApplianceConsole::CANCEL].freeze NETWORK_INTERFACE = "eth0".freeze CLOUD_INIT_NETWORK_CONFIG_FILE = "/etc/cloud/cloud.cfg.d/99_miq_disable_network_config.cfg".freeze CLOUD_INIT_DISABLE_NETWORK_CONFIG = "network: {config: disabled}\n".freeze module ManageIQ module ApplianceConsole eth0 = LinuxAdmin::NetworkInterface.new(NETWORK_INTERFACE) # Because it takes a few seconds, get the region once in the outside loop region = ManageIQ::ApplianceConsole::DatabaseConfiguration.region # Calling stty to provide the equivalent line settings when the console is run via an ssh session or # over the virtual machine console. system("stty -echoprt ixany iexten echoe echok") loop do begin dns = LinuxAdmin::Dns.new eth0.reload eth0.parse_conf if eth0.respond_to?(:parse_conf) host = LinuxAdmin::Hosts.new.hostname ip = eth0.address mac = eth0.mac_address mask = eth0.netmask gw = eth0.gateway dns1, dns2 = dns.nameservers order = dns.search_order.join(' ') timezone = LinuxAdmin::TimeDate.system_timezone version = File.read(VERSION_FILE).chomp if File.exist?(VERSION_FILE) dbhost = ManageIQ::ApplianceConsole::DatabaseConfiguration.database_host database = ManageIQ::ApplianceConsole::DatabaseConfiguration.database_name evm_running = LinuxAdmin::Service.new("evmserverd").running? summary_attributes = [ summary_entry("Hostname", host), summary_entry("IPv4 Address", "#{ip}/#{mask}"), summary_entry("IPv4 Gateway", gw), summary_entry("IPv6 Address", eth0.address6 ? "#{eth0.address6}/#{eth0.prefix6}" : ''), summary_entry("IPV6 Gateway", eth0.gateway6), summary_entry("Primary DNS", dns1), summary_entry("Secondary DNS", dns2), summary_entry("Search Order", order), summary_entry("MAC Address", mac), summary_entry("Timezone", timezone), summary_entry("Local Database Server", PostgresAdmin.local_server_status), summary_entry("#{I18n.t("product.name")} Server", evm_running ? "running" : "not running"), summary_entry("#{I18n.t("product.name")} Database", dbhost || "not configured"), summary_entry("Database/Region", database ? "#{database} / #{region.to_i}" : "not configured"), summary_entry("External Auth", ExternalHttpdAuthentication.config_status), summary_entry("#{I18n.t("product.name")} Version", version), ] clear_screen say(<<-EOL) Welcome to the #{I18n.t("product.name")} Virtual Appliance. To modify the configuration, use a web browser to access the management page. #{$terminal.list(summary_attributes)} EOL press_any_key clear_screen selection = ask_with_menu("Advanced Setting", AS_OPTIONS, nil, true) case selection when I18n.t('advanced_settings.networking') options = { 'Set DHCP Network Configuration' => 'dhcp', 'Set IPv4 Static Network Configuration' => 'static', 'Set IPv6 Static Network Configuration' => 'static6', 'Test Network Configuration' => 'testnet', 'Set Hostname' => 'hostname' } case ask_with_menu('Network Configuration', options) when 'dhcp' say("DHCP Network Configuration\n\n") ipv4 = agree('Enable DHCP for IPv4 network configuration? (Y/N): ') ipv6 = agree('Enable DHCP for IPv6 network configuration? (Y/N): ') if ipv4 || ipv6 say("\nApplying DHCP network configuration...") resolv = LinuxAdmin::Dns.new resolv.search_order = [] resolv.nameservers = [] resolv.save eth0.enable_dhcp if ipv4 eth0.enable_dhcp6 if ipv6 eth0.save File.write(CLOUD_INIT_NETWORK_CONFIG_FILE, CLOUD_INIT_DISABLE_NETWORK_CONFIG) say("\nAfter completing the appliance configuration, please restart #{I18n.t("product.name")} server processes.") press_any_key end when 'static' say("Static Network Configuration\n\n") say("Enter the new static network configuration settings.\n\n") new_ip = ask_for_ipv4("IP Address", ip) new_mask = ask_for_ipv4("Netmask", mask) new_gw = ask_for_ipv4("Gateway", gw) new_dns1 = ask_for_ip("Primary DNS", dns1) new_dns2 = ask_for_ip_or_none("Secondary DNS (Enter 'none' for no value)", dns2) new_search_order = ask_for_many("domain", "Domain search order", order) clear_screen say(<<-EOL) Static Network Configuration IP Address: #{new_ip} Netmask: #{new_mask} Gateway: #{new_gw} Primary DNS: #{new_dns1} Secondary DNS: #{new_dns2} Search Order: #{new_search_order.join(" ")} EOL if agree("Apply static network configuration? (Y/N)") say("\nApplying static network configuration...") resolv = LinuxAdmin::Dns.new resolv.search_order = [] resolv.nameservers = [] resolv.save begin network_configured = eth0.apply_static(new_ip, new_mask, new_gw, [new_dns1, new_dns2], new_search_order) rescue ArgumentError => e say("\nNetwork configuration failed: #{e.message}") press_any_key next end unless network_configured say("\nNetwork interface failed to start using the values supplied.") press_any_key next end File.write(CLOUD_INIT_NETWORK_CONFIG_FILE, CLOUD_INIT_DISABLE_NETWORK_CONFIG) say("\nAfter completing the appliance configuration, please restart #{I18n.t("product.name")} server processes.") press_any_key end when 'static6' say("IPv6: Static Network Configuration\n\n") say("Enter the new static network configuration settings.\n\n") new_ip = ask_for_ipv6('IP Address', eth0.address6) new_prefix = ask_for_integer('IPv6 prefix length', 1..127, eth0.prefix6 || 64) new_gw = ask_for_ipv6('Default gateway', eth0.gateway6) new_dns1 = ask_for_ip('Primary DNS', dns1) new_dns2 = ask_for_ip_or_none("Secondary DNS (Enter 'none' for no value)", dns2) new_search_order = ask_for_many('domain', 'Domain search order', order) clear_screen say(<<-EOL) Static Network Configuration IP Address: #{new_ip}/#{new_prefix} Gateway: #{new_gw} Primary DNS: #{new_dns1} Secondary DNS: #{new_dns2} Search Order: #{new_search_order.join(" ")} EOL if agree('Apply static network configuration? (Y/N)') say("\nApplying static network configuration...") resolv = LinuxAdmin::Dns.new resolv.search_order = [] resolv.nameservers = [] resolv.save begin network_configured = eth0.apply_static6(new_ip, new_prefix, new_gw, [new_dns1, new_dns2], new_search_order) rescue ArgumentError => e say("\nNetwork configuration failed: #{e.message}") press_any_key next end unless network_configured say("\nNetwork interface failed to start using the values supplied.") press_any_key next end File.write(CLOUD_INIT_NETWORK_CONFIG_FILE, CLOUD_INIT_DISABLE_NETWORK_CONFIG) say("\nAfter completing the appliance configuration, please restart #{I18n.t("product.name")} server processes.") press_any_key end when 'testnet' ManageIQ::ApplianceConsole::Utilities.test_network when 'hostname' say("Hostname Configuration\n\n") new_host = just_ask("new hostname", host) if new_host != host say("Applying new hostname...") system_hosts = LinuxAdmin::Hosts.new system_hosts.parsed_file.each { |line| line[:hosts].to_a.delete(host) } unless host =~ /^localhost.*/ system_hosts.hostname = new_host system_hosts.set_loopback_hostname(new_host) system_hosts.save press_any_key end end when I18n.t("advanced_settings.timezone") say("#{selection}\n\n") timezone_config = ManageIQ::ApplianceConsole::TimezoneConfiguration.new(timezone) if timezone_config.ask_questions && timezone_config.activate say("Timezone configured") press_any_key else say("Timezone not configured") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.datetime") say("#{selection}\n\n") date_time_config = ManageIQ::ApplianceConsole::DateTimeConfiguration.new if date_time_config.ask_questions && date_time_config.activate say("Date and time configured") press_any_key else say("Date and time not configured") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.httpdauth") say("#{selection}\n\n") httpd_auth = ExternalHttpdAuthentication.new(host) if httpd_auth.ask_questions && httpd_auth.activate httpd_auth.post_activation say("\nExternal Authentication configured successfully.\n") press_any_key else say("\nExternal Authentication configuration failed!\n") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.extauth_opts") say("#{selection}\n\n") extauth_options = ExternalAuthOptions.new if extauth_options.ask_questions && extauth_options.any_updates? extauth_options.update_configuration say("\nExternal Authentication Options updated successfully.\n") else say("\nExternal Authentication Options not updated.\n") end press_any_key when I18n.t("advanced_settings.ca") say("#{selection}\n\n") begin ca = CertificateAuthority.new(:hostname => host) if ca.ask_questions && ca.activate say "\ncertificate result: #{ca.status_string}" unless ca.complete? say "After the certificates are retrieved, rerun to update service configuration files" end press_any_key else say("\nCertificates not fetched.\n") press_any_key end rescue AwesomeSpawn::CommandResultError => e say e.result.output say e.result.error say "" press_any_key end when I18n.t("advanced_settings.evmstop") say("#{selection}\n\n") service = LinuxAdmin::Service.new("evmserverd") if service.running? if ask_yn? "\nNote: It may take up to a few minutes for all #{I18n.t("product.name")} server processes to exit gracefully. Stop #{I18n.t("product.name")}" say("\nStopping #{I18n.t("product.name")} Server...") logger.info("EVM server stop initiated by appliance console.") service.stop end else say("\n#{I18n.t("product.name")} Server is not running...") end press_any_key when I18n.t("advanced_settings.evmstart") say("#{selection}\n\n") if ask_yn?("\nStart #{I18n.t("product.name")}") say("\nStarting #{I18n.t("product.name")} Server...") logger.info("EVM server start initiated by appliance console.") begin LinuxAdmin::Service.new("evmserverd").start rescue AwesomeSpawn::CommandResultError => e say e.result.output say e.result.error say "" end press_any_key end when I18n.t("advanced_settings.dbrestore") say("#{selection}\n\n") task_params = [] uri = nil # TODO: merge into 1 prompt case ask_with_menu("Restore Database File", RESTORE_OPTIONS, RESTORE_LOCAL, nil) when RESTORE_LOCAL validate = ->(a) { File.exist?(a) } uri = just_ask("location of the local restore file", DB_RESTORE_FILE, validate, "file that exists") task = "evm:db:restore:local" task_params = ["--", {:local_file => uri}] when RESTORE_NFS uri = ask_for_uri("location of the remote backup file\nExample: #{sample_url('nfs')})", "nfs") task = "evm:db:restore:remote" task_params = ["--", {:uri => uri}] when RESTORE_SMB uri = ask_for_uri("location of the remote backup file\nExample: #{sample_url('smb')}", "smb") user = just_ask("username with access to this file.\nExample: 'mydomain.com/user'") pass = ask_for_password("password for #{user}") task = "evm:db:restore:remote" task_params = ["--", {:uri => uri, :uri_username => user, :uri_password => pass}] when ManageIQ::ApplianceConsole::CANCEL raise MiqSignalError end clear_screen say("#{selection}\n\n") delete_agreed = false if selection == RESTORE_LOCAL say "The local database restore file is located at: '#{uri}'.\n" delete_agreed = agree("Should this file be deleted after completing the restore? (Y/N): ") end say "\nNote: A database restore cannot be undone. The restore will use the file: #{uri}.\n" if agree("Are you sure you would like to restore the database? (Y/N): ") say("\nRestoring the database...") rake_success = ManageIQ::ApplianceConsole::Utilities.rake(task, task_params) if rake_success && delete_agreed say("\nRemoving the database restore file #{DB_RESTORE_FILE}...") File.delete(DB_RESTORE_FILE) elsif !rake_success say("\nDatabase restore failed. Check the logs for more information") end end press_any_key when I18n.t("advanced_settings.key_gen") say("#{selection}\n\n") key_config = ManageIQ::ApplianceConsole::KeyConfiguration.new if key_config.ask_question_loop say("\nEncryption key now configured.") press_any_key else say("\nEncryption key not configured.") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.db_config") say("#{selection}\n\n") key_config = ManageIQ::ApplianceConsole::KeyConfiguration.new unless key_config.key_exist? say "No encryption key found.\n" say "For migrations, copy encryption key from a hardened appliance." say "For worker and multi-region setups, copy key from another appliance.\n" say "If this is your first appliance, just generate one now.\n\n" if key_config.ask_question_loop say("\nEncryption key now configured.\n\n") else say("\nEncryption key not configured.") press_any_key raise MiqSignalError end end options = { "Create Internal Database" => "create_internal", "Create Region in External Database" => "create_external", "Join Region in External Database" => "join_external", "Reset Configured Database" => "reset_region" } action = ask_with_menu("Database Operation", options) database_configuration = case action when "create_internal" ManageIQ::ApplianceConsole::InternalDatabaseConfiguration.new when /_external/ ManageIQ::ApplianceConsole::ExternalDatabaseConfiguration.new(:action => action.split("_").first.to_sym) else ManageIQ::ApplianceConsole::DatabaseConfiguration.new end case action when "reset_region" if database_configuration.reset_region say("Database reset successfully") say("Start the server processes via '#{I18n.t("advanced_settings.evmstart")}'.") else say("Failed to reset database") end when "create_internal", /_external/ database_configuration.run_interactive end # Get the region again because it may have changed region = ManageIQ::ApplianceConsole::DatabaseConfiguration.region press_any_key when I18n.t("advanced_settings.db_replication") say("#{selection}\n\n") options = { "Configure Server as Primary" => "primary", "Configure Server as Standby" => "standby" } action = ask_with_menu("Database replication Operation", options) case action when "primary" db_replication = ManageIQ::ApplianceConsole::DatabaseReplicationPrimary.new logger.info("Configuring Server as Primary") when "standby" db_replication = ManageIQ::ApplianceConsole::DatabaseReplicationStandby.new logger.info("Configuring Server as Standby") end if db_replication.ask_questions && db_replication.activate say("Database Replication configured") logger.info("Database Replication configured") press_any_key else say("Database Replication not configured") logger.info("Database Replication not configured") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.failover_monitor") say("#{selection}\n\n") options = { "Start Database Failover Monitor" => "start", "Stop Database Failover Monitor" => "stop" } action = ask_with_menu("Failover Monitor Configuration", options) failover_service = LinuxAdmin::Service.new("evm-failover-monitor") begin case action when "start" logger.info("Starting and enabling evm-failover-monitor service") failover_service.enable.start when "stop" logger.info("Stopping and disabling evm-failover-monitor service") failover_service.disable.stop end rescue AwesomeSpawn::CommandResultError => e say("Failed to configure failover monitor") logger.error("Failed to configure evm-failover-monitor service") say(e.result.output) say(e.result.error) say("") press_any_key raise MiqSignalError end say("Failover Monitor Service configured successfully") press_any_key when I18n.t("advanced_settings.log_config") say("#{selection}\n\n") log_config = ManageIQ::ApplianceConsole::LogfileConfiguration.new if log_config.ask_questions && log_config.activate say("Log file configuration updated.") say("The appliance may take a few minutes to fully restart.") press_any_key else say("Log file configuration unchanged") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.tmp_config") say("#{selection}\n\n") tmp_config = ManageIQ::ApplianceConsole::TempStorageConfiguration.new if tmp_config.ask_questions && tmp_config.activate say("Temp storage disk configured") press_any_key else say("Temp storage disk not configured") press_any_key raise MiqSignalError end when I18n.t("advanced_settings.restart") case ask_with_menu("Restart Option", RE_OPTIONS, nil, false) when ManageIQ::ApplianceConsole::CANCEL # don't do anything when RE_RESTART if are_you_sure?("restart the appliance now") logger.info("Appliance restart initiated by appliance console.") LinuxAdmin::Service.new("evmserverd").stop LinuxAdmin::System.reboot! end when RE_DELLOGS if are_you_sure?("restart the appliance now") logger.info("Appliance restart with clean logs initiated by appliance console.") LinuxAdmin::Service.new("evmserverd").stop LinuxAdmin::Service.new("miqtop").stop LinuxAdmin::Service.new("miqvmstat").stop LinuxAdmin::Service.new("httpd").stop FileUtils.rm_rf(Dir.glob("/var/www/miq/vmdb/log/*.log*")) FileUtils.rm_rf(Dir.glob("/var/www/miq/vmdb/log/apache/*.log*")) logger.info("Logs cleaned and appliance rebooted by appliance console.") LinuxAdmin::System.reboot! end end when I18n.t("advanced_settings.shutdown") say("#{selection}\n\n") if are_you_sure?("shut down the appliance now") say("\nShutting down appliance... This process may take a few minutes.\n\n") logger.info("Appliance shutdown initiated by appliance console") LinuxAdmin::Service.new("evmserverd").stop LinuxAdmin::System.shutdown! end when I18n.t("advanced_settings.scap") say("#{selection}\n\n") ManageIQ::ApplianceConsole::Scap.new(SCAP_RULES_DIR).lockdown press_any_key when I18n.t("advanced_settings.summary") # Do nothing when I18n.t("advanced_settings.quit") break end rescue MiqSignalError # If a signal is caught anywhere in the inner (after login) loop, go back to the summary screen next end end end end