blob: d333f4a0ef8200d41787740a020e384cdd501147 [file] [log] [blame]
#!/bin/bash
#\
exec expect "$0" -- ${1+"$@"}
# This file contains utilities for working with Serial over Lan (SOL).
# Example use case:
# sol_utils.tcl --os_host=ip --os_password=password --os_username=username
# --openbmc_host=ip --openbmc_password=password --openbmc_username=username
# --proc_name=boot_to_petitboot
source [exec bash -c "which source.tcl"]
my_source \
[list print.tcl opt.tcl valid.tcl call_stack.tcl tools.exp cmd.tcl host.tcl]
longoptions openbmc_host: openbmc_username:=root openbmc_password:=0penBmc\
os_host: os_username:=root os_password: proc_name: ftp_username: \
ftp_password: os_repo_url: autoboot_setting: test_mode:=0 quiet:=0 debug:=0
pos_parms
set valid_proc_name [list os_login boot_to_petitboot go_to_petitboot_shell \
install_os time_settings software_selection root_password set_autoboot]
# Create help dictionary for call to gen_print_help.
set help_dict [dict create\
${program_name} [list "${program_name} is an SOL utilities program that\
will run the user's choice of utilities. See the \"proc_name\" parm below\
for details."]\
openbmc_host [list "The OpenBMC host name or IP address." "host"]\
openbmc_username [list "The OpenBMC username." "username"]\
openbmc_password [list "The OpenBMC password." "password"]\
os_host [list "The OS host name or IP address." "host"]\
os_username [list "The OS username." "username"]\
os_password [list "The OS password." "password"]\
proc_name [list "The proc_name you'd like to run. Valid values are as\
follows: [regsub -all {\s+} $valid_proc_name {, }]."]\
autoboot_setting [list "The desired state of autoboot." "true/flase"]\
]
# Setup state dictionary.
set state [dict create\
ssh_logged_in 0\
os_login_prompt 0\
os_logged_in 0\
petitboot_screen 0\
petitboot_shell_prompt 0\
]
proc help {} {
gen_print_help
}
proc exit_proc { {ret_code 0} } {
# Execute whenever the program ends normally or with the signals that we
# catch (i.e. TERM, INT).
dprintn ; dprint_executing
dprint_var ret_code
set cmd_buf os_logoff
qprintn ; qprint_issuing
eval ${cmd_buf}
set cmd_buf sol_logoff
qprintn ; qprint_issuing
eval ${cmd_buf}
qprint_pgm_footer
exit $ret_code
}
proc validate_parms {} {
trap { exit_proc } [list SIGTERM SIGINT]
valid_value openbmc_host
valid_value openbmc_username
valid_value openbmc_password
valid_value os_host
valid_value os_username
valid_password os_password
global valid_proc_name
global proc_name proc_names
set proc_names [split $proc_name " "]
if { [lsearch -exact $proc_names "install_os"] != -1 } {
valid_value ftp_username
valid_password ftp_password
valid_value os_repo_url
}
if { [lsearch -exact $proc_names "set_autoboot"] != -1 } {
valid_value autoboot_setting {} [list "true" "false"]
}
}
proc sol_login {} {
# Login to the SOL console.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global openbmc_host openbmc_username openbmc_password
global cr_lf_regex
global ssh_password_prompt
set cmd_buf "spawn -nottycopy ssh -p 2200 $openbmc_username@$openbmc_host"
qprint_issuing
eval $cmd_buf
append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
append bad_host_regex " Name or service not known"
set expect_result [expect_wrap\
[list $bad_host_regex $ssh_password_prompt]\
"an SOL password prompt" 5]
if { $expect_result == 0 } {
puts stderr ""
print_error "Invalid openbmc_host value.\n"
exit_proc 1
}
send_wrap "${openbmc_password}"
append bad_user_pw_regex "Permission denied, please try again\."
append bad_user_pw_regex "${cr_lf_regex}${ssh_password_prompt}"
set expect_result [expect_wrap\
[list $bad_user_pw_regex "sh: xauth: command not found"]\
"an SOL prompt" 10]
switch $expect_result {
0 {
puts stderr "" ; print_error "Invalid OpenBmc username or password.\n"
exit_proc 1
}
1 {
# Currently, this string always appears but that is not necessarily
# guaranteed.
dict set state ssh_logged_in 1
}
}
if { [dict get $state ssh_logged_in] } {
qprintn ; qprint_timen "Logged into SOL."
dprintn ; dprint_dict state
return
}
# If we didn't get a hit on the "sh: xauth: command not found", then we just
# need to see a linefeed.
set expect_result [expect_wrap [list ${cr_lf_regex}] "an SOL prompt" 5]
dict set state ssh_logged_in 1
qprintn ; qprint_timen "Logged into SOL."
dprintn ; dprint_dict state
}
proc sol_logoff {} {
# Logoff from the SOL console.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global openbmc_host
if { ! [dict get $state ssh_logged_in] } {
qprintn ; qprint_timen "No SOL logoff required."
return
}
send_wrap "~."
set expect_result [expect_wrap\
[list "Connection to $openbmc_host closed"]\
"a connection closed message" 5]
dict set state ssh_logged_in 0
qprintn ; qprint_timen "Logged off SOL."
dprintn ; dprint_dict state
}
proc get_post_ssh_login_state {} {
# Get the initial state following sol_login.
# The following state global dictionary variable is set by this procedure.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global os_login_prompt_regex
global os_prompt_regex
global petitboot_screen_regex
global petitboot_shell_prompt_regex
global installer_screen_regex
if { ! [dict get $state ssh_logged_in] } {
puts stderr ""
append message "Programmer error - [get_stack_proc_name] must only be"
append message " called after sol_login has been called."
print_error_report $message
exit_proc 1
}
# The first thing one must do after signing into ssh -p 2200 is hit enter to
# see where things stand.
send_wrap ""
set expect_result [expect_wrap\
[list $os_login_prompt_regex $os_prompt_regex $petitboot_screen_regex \
$petitboot_shell_prompt_regex $installer_screen_regex] \
"any indication of status" 5 0]
switch $expect_result {
0 {
dict set state os_login_prompt 1
}
1 {
dict set state os_logged_in 1
}
2 {
dict set state petitboot_screen 1
}
3 {
dict set state petitboot_shell_prompt 1
}
}
dprintn ; dprint_dict state
}
proc os_login {} {
# Login to the OS.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global openbmc_host os_username os_password
global os_password_prompt
global os_prompt_regex
if { [dict get $state os_logged_in] } {
printn ; print_timen "We are already logged in to the OS."
return
}
send_wrap "${os_username}"
append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
append bad_host_regex " Name or service not known"
set expect_result [expect_wrap\
[list $os_password_prompt]\
"an OS password prompt" 5]
send_wrap "${os_password}"
set expect_result [expect_wrap\
[list "Login incorrect" "$os_prompt_regex"]\
"an OS prompt" 10]
switch $expect_result {
0 {
puts stderr "" ; print_error "Invalid OS username or password.\n"
exit_proc 1
}
}
dict set state os_logged_in 1
dict set state os_login_prompt 0
qprintn ; qprint_timen "Logged into OS."
dprintn ; dprint_dict state
}
proc os_logoff {} {
# Logoff from the SOL console.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global os_login_prompt_regex
if { ! [dict get $state os_logged_in] } {
qprintn ; qprint_timen "No OS logoff required."
return
}
send_wrap "exit"
set expect_result [expect_wrap\
[list $os_login_prompt_regex]\
"an OS prompt" 5]
dict set state os_logged_in 0
qprintn ; qprint_timen "Logged off OS."
dprintn ; dprint_dict state
}
proc shell_command {command_string {prompt_regex} { quiet {} } \
{ test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} {
# Execute the command_string on the shell command line and return a list
# consisting of 1) the return code of the command 2) the stdout/
# stderr.
# It is the caller's responsibility to make sure we are logged into the OS.
# Description of argument(s):
# command_string The command string which is to be run on the shell (e.g.
# "hostname" or "grep this that").
# prompt_regex The regular expression of the shell the command string is
# to run on. (e.g "os_prompt_regex").
# quiet Indicates whether this procedure should run the
# print_issuing() procedure which prints "Issuing:
# <cmd string>" to stdout. The default value is 0.
# test_mode If test_mode is set, this procedure will not actually run
# the command. If print_output is set, it will print
# "(test_mode) Issuing: <cmd string>" to stdout. The default
# value is 0.
# show_err If show_err is set, this procedure will print a
# standardized error report if the shell command returns non-
# zero. The default value is 1.
# ignore_err If ignore_err is set, this procedure will not fail if the
# shell command fails. However, if ignore_err is not set,
# this procedure will exit 1 if the shell command fails. The
# default value is 1.
# trim_cr_lf Trim any trailing carriage return or line feed from the
# result.
# Set defaults (this section allows users to pass blank values for certain
# args)
set_var_default quiet [get_stack_var quiet 0 2]
set_var_default test_mode 0
set_var_default show_err 1
set_var_default ignore_err 0
set_var_default acceptable_shell_rcs 0
global spawn_id
global expect_out
qprintn ; qprint_issuing ${command_string} ${test_mode}
if { $test_mode } {
return [list 0 ""]
}
send_wrap "${command_string}"
set expect_result [expect_wrap\
[list "-ex $command_string"]\
"the echoed command" 5]
set expect_result [expect_wrap\
[list {[\n\r]{1,2}}]\
"one or two line feeds" 5]
# Note the non-greedy specification in the regex below (the "?").
set expect_result [expect_wrap\
[list "(.*?)$prompt_regex"]\
"command output plus prompt" -1]
# The command's stdout/stderr should be captured as match #1.
set out_buf $expect_out(1,string)
if { $trim_cr_lf } {
set out_buf [ string trimright $out_buf "\r\n" ]
}
# Get rc via recursive call to this function.
set rc 0
set proc_name [get_stack_proc_name]
set calling_proc_name [get_stack_proc_name -2]
if { $calling_proc_name != $proc_name } {
set sub_result [shell_command {echo ${?}} $prompt_regex 1]
dprintn ; dprint_list sub_result
set rc [lindex $sub_result 1]
}
if { $rc != 0 } {
if { $show_err } {
puts stderr "" ; print_error_report "The prior shell command failed.\n"
}
if { ! $ignore_err } {
if { [info procs "exit_proc"] != "" } {
exit_proc 1
}
}
}
return [list $rc $out_buf]
}
proc boot_to_petitboot {} {
# Boot the machine until the petitboot screen is reached.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global os_prompt_regex
global petitboot_screen_regex
global autoboot_setting
if { [dict get $state petitboot_screen] } {
qprintn ; qprint_timen "Already at petiboot."
return
}
if { [dict get $state petitboot_shell_prompt] } {
qprintn ; qprint_timen "Now at the shell prompt. Going to petitboot."
send_wrap "exit"
set expect_result [expect_wrap [list $petitboot_screen_regex]\
"the petitboot screen" 900]
dict set state petitboot_shell_prompt 0
dict set state petitboot_screen 1
return
}
if { [dict get $state os_login_prompt] } {
set cmd_buf os_login
qprintn ; qprint_issuing
eval ${cmd_buf}
}
# Turn off autoboot.
set_autoboot "false"
# Reboot and wait for petitboot.
send_wrap "reboot"
# Once we've started a reboot, we are no longer logged into OS.
dict set state os_logged_in 0
dict set state os_login_prompt 0
set expect_result [expect_wrap\
[list $petitboot_screen_regex]\
"the petitboot screen" 900]
set expect_result [expect_wrap\
[list "Exit to shell"]\
"the 'Exit to shell' screen" 10]
dict set state petitboot_screen 1
qprintn ; qprint_timen "Arrived at petitboot screen."
dprintn ; dprint_dict state
}
proc go_to_petitboot_shell {} {
# Go to petitboot shell.
global spawn_id
global state
global expect_out
global petitboot_shell_prompt_regex
if { [dict get $state petitboot_shell_prompt] } {
qprintn ; qprint_timen "Already at the shell prompt."
return
}
eval boot_to_petitboot
send_wrap "x"
set expect_result [expect_wrap [list $petitboot_shell_prompt_regex]\
"the shell prompt" 10]
dict set state petitboot_screen 0
dict set state petitboot_shell_prompt 1
qprintn ; qprint_timen "Arrived at the shell prompt."
qprintn ; qprint_timen state
}
proc set_autoboot { { autoboot_setting {}} } {
# Set the state of autoboot.
# Defaults to the value of the global autoboot_setting.
# This will work regardless of whether the OS is logged in or at petitboot.
# Description of argument(s):
# autoboot_setting The desired state of autoboot (true/flase).
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global os_prompt_regex
global petitboot_shell_prompt_regex
set_var_default autoboot_setting [get_stack_var autoboot_setting 0 2]
set prompt_regex $petitboot_shell_prompt_regex
if { [dict get $state os_login_prompt] } {
set cmd_buf os_login
qprintn ; qprint_issuing
eval ${cmd_buf}
set prompt_regex $os_prompt_regex
}
if { [dict get $state petitboot_screen ] } {
set cmd_buf go_to_petitboot_shell
qprintn ; qprint_issuing
eval ${cmd_buf}
}
if { [dict get $state os_logged_in ] } {
set prompt_regex $os_prompt_regex
}
set cmd_result [shell_command\
"nvram --update-config auto-boot?=$autoboot_setting" $prompt_regex]
set cmd_result [shell_command "nvram --print-config" $prompt_regex]
}
proc installation_destination {} {
# Set the software installation destination.
# Expectation is that we are starting at the "Installation" options screen.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global installer_screen_regex
qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
qprintn ; qprint_timen "Setting Installation Destination."
# Option 5). Installation Destination
qprintn ; qprint_timen "Selecting \"Installation Destination\" option."
send_wrap "5"
expect_wrap [list "Installation Destination"] "installation destination\
menu" 30
qprintn ; qprint_timen "Selecting \"Select all\" option."
send_wrap "3"
expect_wrap [list "Select all"] "selected all disks" 10
qprintn ; qprint_timen "Selecting \"continue\" option."
send_wrap "c"
expect_wrap [list "Autopartitioning"] "autopartitioning options" 10
qprintn ; qprint_timen "\
Selecting \"Replace Existing Linux system(s)\" option."
send_wrap "1"
expect_wrap [list "Replace"] "selected stanard partition" 10
qprintn ; qprint_timen "Selecting \"continue\" option."
send_wrap "c"
expect_wrap [list "Partition Scheme"] "partition scheme options" 10
qprintn ; qprint_timen "Selecting \"LVM\" option."
send_wrap "3"
expect_wrap [list "LVM"] "lvm option" 10
qprintn ; qprint_timen "Selecting \"continue\" option."
send_wrap "c"
expect_wrap [list $installer_screen_regex] "installation options screen" 10
}
proc time_settings {} {
# Set the time/zone via the petitboot shell prompt "Time settings" menu.
# Expectation is that we are starting at the "Installation" options screen.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global installer_screen_regex
# Option 2). Timezone.
qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
qprintn ; qprint_timen "Setting time."
qprintn ; qprint_timen "Selecting \"Time settings\"."
send_wrap "2"
expect_wrap [list "Set timezone" "Time settings"] "Time settings menu" 30
qprintn ; qprint_timen "Selecting \"Change timezone\"."
send_wrap "1"
expect_wrap [list "Available regions"] "available regions menu" 10
qprintn ; qprint_timen "Selecting \"US\"."
send_wrap "11"
expect_wrap [list "region US"] "select region in US menu" 10
qprintn ; qprint_timen "Selecting \"Central\"."
send_wrap "3"
expect_wrap [list $installer_screen_regex] "installation options screen" 10
}
proc software_selection {} {
# Set the base environment via the petitboot shell prompt
# "Software Selection" menu.
# Expectation is that we are starting at the "Installation" options
# screen.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global installer_screen_regex
qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
qprintn ; qprint_timen "Software selection."
# Option 4). Software selection.
set expect_result 0
while { $expect_result != 1 } {
qprintn ; qprint_timen "Selecting \"Software selection\"."
send_wrap "4"
set expect_result [expect_wrap\
[list "Installation source needs to be set up first." \
"Base environment"] "base environment menu" 10 0]
switch $expect_result {
0 {
qprintn ; qprint_timen "Selecting \"continue\"."
send_wrap "c"
expect_wrap [list $installer_screen_regex] \
"installation options screen" 15
}
1 {
break
}
}
}
qprintn ; qprint_timen "Selecting \"Infrastructure Server\"."
send_wrap "2"
expect_wrap [list "Infrastructure"] "selected infrastructure" 15
qprintn ; qprint_timen "Selecting \"continue\"."
send_wrap "c"
expect_wrap [list $installer_screen_regex] "installation options screen" 15
}
proc root_password {} {
# Set the os root password via the petitboot shell prompt "Root password"
# option.
# Expectation is that we are starting at the "Installation" options screen.
dprintn ; dprint_executing
global spawn_id
global expect_out
global state
global os_password
global installer_screen_regex
qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
qprintn ; qprint_timen "Setting root password."
# Option 8). Root password.
qprintn ; qprint_timen "Selecting \"Root password\"."
send_wrap "8"
expect_wrap [list "Password:"] "root password prompt" 30
qprintn ; qprint_timen "Entering root password."
send_wrap "$os_password"
expect_wrap [list "confirm"] "comfirm root password prompt" 15
qprintn ; qprint_timen "Re-entering root password."
send_wrap "$os_password"
set expect_result [expect_wrap\
[list $installer_screen_regex "The password you have provided is weak"] \
"root password accepted" 10 0]
switch $expect_result {
0 {
break
}
1 {
qprintn ; qprint_timen "Confirming weak password."
send_wrap "yes"
}
}
expect_wrap [list $installer_screen_regex] "installation options screen" 10
}
proc install_os {} {
# Install an os on the machine.
global spawn_id
global expect_out
global petitboot_shell_prompt_regex
global installer_screen_regex
global ftp_username ftp_password os_repo_url
global os_host os_username os_password
lassign [get_host_name_ip $os_host 0] os_hostname short_host_name ip_address
set netmask [get_host_netmask $os_host $os_username $os_password {} 0]
set gateway [get_host_gateway $os_host $os_username $os_password 0]
set mac_address \
[get_host_mac_address $os_host $os_username $os_password {} 0]
set name_servers [get_host_name_servers $os_host $os_username $os_password 0]
set dns [lindex $name_servers 0]
set domain [get_host_domain $os_host $os_username $os_password 0]
# Go to shell and download files for installation
eval go_to_petitboot_shell
after 10000
set vmlinuz_url \
"ftp://$ftp_username:$ftp_password@$os_repo_url/ppc/ppc64/vmlinuz"
set initrd_url \
"ftp://$ftp_username:$ftp_password@$os_repo_url/ppc/ppc64/initrd.img"
send_wrap "wget -c $vmlinuz_url"
expect_wrap [list "vmlinuz *100%"] "wget vmlinuz file success" 30
send_wrap "wget -c $initrd_url"
expect_wrap [list "initrd.img *100%"] "wget initrd file success" 30
# Setup parms and run kexec.
set colon "::"
set squashfs_url \
"ftp://$ftp_username:$ftp_password@$os_repo_url/LiveOS/squashfs.img"
set kexec_args "kexec -l vmlinuz --initrd initrd.img\
--append='root=live:$squashfs_url \
repo=ftp://$ftp_username:$ftp_password@$os_repo_url rd.dm=0 rd.md=0\
nodmraid console=hvc0 ifname=net0:$mac_address\
ip=$os_host$colon$gateway:$netmask:$os_hostname:net0:none nameserver=$dns\
inst.text'"
send_wrap "$kexec_args"
dprintn ; dprint_vars expect_out
set expect_result [expect_wrap [list $petitboot_shell_prompt_regex]\
"the shell prompt" 10]
# Turn on autoboot.
set_autoboot "true"
send_wrap "kexec -e"
# Begin installation process, go to settings screen.
set expect_result [expect_wrap [list "Starting installer"]\
"starting installer log" 900]
set expect_result [expect_wrap [list "Use text mode"]\
"install mode selection prompt" 120]
send_wrap "2"
expect_wrap [list $installer_screen_regex] "installation options screen" 15
installation_destination
time_settings
software_selection
root_password
# Now begin installation process.
set expect_result [expect_wrap\
[list $os_repo_url "Processing..."] \
"installation source processing" 10 0]
switch $expect_result {
0 {
break
}
1 {
expect_wrap [list $os_repo_url] "source processing complete" 240
}
}
send_wrap "b"
set expect_result [expect_wrap \
[list "Installation complete* Press return to quit"] \
"os installation complete message" 2000]
send_wrap ""; # Reboots to petitboot.
return
}
# Main
gen_get_options $argv
validate_parms
qprint_pgm_header
# Global variables for current prompts of the SOL console.
set ssh_password_prompt ".* password: "
set os_login_prompt_regex "login: "
set os_password_prompt "Password: "
set petitboot_screen_regex "Petitboot"
set cr_lf_regex "\[\n\r\]"
set os_prompt_regex "(\\\[${os_username}@\[^ \]+ ~\\\]# )"
set petitboot_shell_prompt_regex "/ #"
set installer_screen_regex \
{Installation[\r\n].*Please make your choice from above.* to refresh\]: }
dprintn ; dprint_dict state
set cmd_buf sol_login
qprint_issuing
eval ${cmd_buf}
set cmd_buf get_post_ssh_login_state
qprintn ; qprint_issuing
eval ${cmd_buf}
foreach proc_name $proc_names {
set cmd_buf ${proc_name}
qprintn ; qprint_issuing
eval ${cmd_buf}
}
exit_proc