1#!/bin/bash
2#\
3exec expect "$0" -- ${1+"$@"}
4
5# This file contains utilities for working with Serial over Lan (SOL).
6
7# Example use case:
8# sol_utils.tcl --os_host=ip --os_password=password --os_username=username
9# --openbmc_host=ip --openbmc_password=password --openbmc_username=username
10# --proc_name=boot_to_petitboot
11
12source [exec bash -c "which source.tcl"]
13my_source \
14[list print.tcl opt.tcl valid.tcl call_stack.tcl tools.exp cmd.tcl host.tcl]
15
16longoptions openbmc_host: openbmc_username:=root openbmc_password:=0penBmc\
17  os_host: os_username:=root os_password: proc_name: ftp_username: \
18  ftp_password: os_repo_url: autoboot_setting: test_mode:=0 quiet:=0 debug:=0
19pos_parms
20
21set valid_proc_name [list os_login boot_to_petitboot go_to_petitboot_shell \
22  install_os time_settings software_selection root_password set_autoboot]
23
24# Create help dictionary for call to gen_print_help.
25set help_dict [dict create\
26  ${program_name} [list "${program_name} is an SOL utilities program that\
27    will run the user's choice of utilities.  See the \"proc_name\" parm below\
28    for details."]\
29  openbmc_host [list "The OpenBMC host name or IP address." "host"]\
30  openbmc_username [list "The OpenBMC username." "username"]\
31  openbmc_password [list "The OpenBMC password." "password"]\
32  os_host [list "The OS host name or IP address." "host"]\
33  os_username [list "The OS username." "username"]\
34  os_password [list "The OS password." "password"]\
35  proc_name [list "The proc_name you'd like to run.  Valid values are as\
36    follows: [regsub -all {\s+} $valid_proc_name {, }]."]\
37  autoboot_setting [list "The desired state of autoboot." "true/flase"]\
38]
39
40
41# Setup state dictionary.
42set state [dict create\
43  ssh_logged_in 0\
44  os_login_prompt 0\
45  os_logged_in 0\
46  petitboot_screen 0\
47  petitboot_shell_prompt 0\
48]
49
50
51proc help {} {
52
53  gen_print_help
54
55}
56
57
58proc exit_proc { {ret_code 0} } {
59
60  # Execute whenever the program ends normally or with the signals that we
61  # catch (i.e. TERM, INT).
62
63  dprintn ; dprint_executing
64  dprint_var ret_code
65
66  set cmd_buf os_logoff
67  qprintn ; qprint_issuing
68  eval ${cmd_buf}
69
70  set cmd_buf sol_logoff
71  qprintn ; qprint_issuing
72  eval ${cmd_buf}
73
74  qprint_pgm_footer
75
76  exit $ret_code
77
78}
79
80
81proc validate_parms {} {
82
83  trap { exit_proc } [list SIGTERM SIGINT]
84
85  valid_value openbmc_host
86  valid_value openbmc_username
87  valid_value openbmc_password
88  valid_value os_host
89  valid_value os_username
90  valid_password os_password
91  global valid_proc_name
92  global proc_name proc_names
93  set proc_names [split $proc_name " "]
94  if { [lsearch -exact $proc_names "install_os"] != -1 } {
95    valid_value ftp_username
96    valid_password ftp_password
97    valid_value os_repo_url
98  }
99  if { [lsearch -exact $proc_names "set_autoboot"] != -1 } {
100    valid_value autoboot_setting {} [list "true" "false"]
101  }
102
103}
104
105
106proc sol_login {} {
107
108  # Login to the SOL console.
109
110  dprintn ; dprint_executing
111
112  global spawn_id
113  global expect_out
114  global state
115  global openbmc_host openbmc_username openbmc_password
116  global cr_lf_regex
117  global ssh_password_prompt
118
119  set cmd_buf "spawn -nottycopy ssh -p 2200 $openbmc_username@$openbmc_host"
120  qprint_issuing
121  eval $cmd_buf
122
123  append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
124  append bad_host_regex " Name or service not known"
125  set expect_result [expect_wrap\
126    [list $bad_host_regex $ssh_password_prompt]\
127    "an SOL password prompt" 5]
128
129  if { $expect_result == 0 } {
130    puts stderr ""
131    print_error "Invalid openbmc_host value.\n"
132    exit_proc 1
133  }
134
135  send_wrap "${openbmc_password}"
136
137  append bad_user_pw_regex "Permission denied, please try again\."
138  append bad_user_pw_regex "${cr_lf_regex}${ssh_password_prompt}"
139  set expect_result [expect_wrap\
140    [list $bad_user_pw_regex "sh: xauth: command not found"]\
141    "an SOL prompt" 10]
142
143  switch $expect_result {
144    0 {
145      puts stderr "" ; print_error "Invalid OpenBmc username or password.\n"
146      exit_proc 1
147    }
148    1 {
149      # Currently, this string always appears but that is not necessarily
150      # guaranteed.
151      dict set state ssh_logged_in 1
152    }
153  }
154
155  if { [dict get $state ssh_logged_in] } {
156    qprintn ; qprint_timen "Logged into SOL."
157    dprintn ; dprint_dict state
158    return
159  }
160
161  # If we didn't get a hit on the "sh: xauth: command not found", then we just
162  # need to see a linefeed.
163  set expect_result [expect_wrap [list ${cr_lf_regex}] "an SOL prompt" 5]
164
165  dict set state ssh_logged_in 1
166  qprintn ; qprint_timen "Logged into SOL."
167  dprintn ; dprint_dict state
168
169}
170
171
172proc sol_logoff {} {
173
174  # Logoff from the SOL console.
175
176  dprintn ; dprint_executing
177
178  global spawn_id
179  global expect_out
180  global state
181  global openbmc_host
182
183  if { ! [dict get $state ssh_logged_in] } {
184    qprintn ; qprint_timen "No SOL logoff required."
185    return
186  }
187
188  send_wrap "~."
189
190  set expect_result [expect_wrap\
191    [list "Connection to $openbmc_host closed"]\
192    "a connection closed message" 5]
193
194  dict set state ssh_logged_in 0
195  qprintn ; qprint_timen "Logged off SOL."
196  dprintn ; dprint_dict state
197
198}
199
200
201proc get_post_ssh_login_state {} {
202
203  # Get the initial state following sol_login.
204
205  # The following state global dictionary variable is set by this procedure.
206
207  dprintn ; dprint_executing
208
209  global spawn_id
210  global expect_out
211  global state
212  global os_login_prompt_regex
213  global os_prompt_regex
214  global petitboot_screen_regex
215  global petitboot_shell_prompt_regex
216  global installer_screen_regex
217
218  if { ! [dict get $state ssh_logged_in] } {
219    puts stderr ""
220    append message "Programmer error - [get_stack_proc_name] must only be"
221    append message " called after sol_login has been called."
222    print_error_report $message
223    exit_proc 1
224  }
225
226  # The first thing one must do after signing into ssh -p 2200 is hit enter to
227  # see where things stand.
228  send_wrap ""
229  set expect_result [expect_wrap\
230  [list $os_login_prompt_regex $os_prompt_regex $petitboot_screen_regex \
231  $petitboot_shell_prompt_regex $installer_screen_regex] \
232  "any indication of status" 5 0]
233
234  switch $expect_result {
235    0 {
236      dict set state os_login_prompt 1
237    }
238    1 {
239      dict set state os_logged_in 1
240    }
241    2 {
242      dict set state petitboot_screen 1
243    }
244    3 {
245      dict set state petitboot_shell_prompt 1
246    }
247  }
248
249  dprintn ; dprint_dict state
250
251}
252
253
254proc os_login {} {
255
256  # Login to the OS.
257
258  dprintn ; dprint_executing
259
260  global spawn_id
261  global expect_out
262  global state
263  global openbmc_host os_username os_password
264  global os_password_prompt
265  global os_prompt_regex
266
267  if { [dict get $state os_logged_in] } {
268    printn ; print_timen "We are already logged in to the OS."
269    return
270  }
271
272  send_wrap "${os_username}"
273
274  append bad_host_regex "ssh: Could not resolve hostname ${openbmc_host}:"
275  append bad_host_regex " Name or service not known"
276  set expect_result [expect_wrap\
277    [list $os_password_prompt]\
278    "an OS password prompt" 5]
279
280  send_wrap "${os_password}"
281  set expect_result [expect_wrap\
282    [list "Login incorrect" "$os_prompt_regex"]\
283    "an OS prompt" 10]
284  switch $expect_result {
285    0 {
286      puts stderr "" ; print_error "Invalid OS username or password.\n"
287      exit_proc 1
288    }
289  }
290
291  dict set state os_logged_in 1
292  dict set state os_login_prompt 0
293  qprintn ; qprint_timen "Logged into OS."
294  dprintn ; dprint_dict state
295
296}
297
298
299proc os_logoff {} {
300
301  # Logoff from the SOL console.
302
303  dprintn ; dprint_executing
304
305  global spawn_id
306  global expect_out
307  global state
308  global os_login_prompt_regex
309
310  if { ! [dict get $state os_logged_in] } {
311    qprintn ; qprint_timen "No OS logoff required."
312    return
313  }
314
315  send_wrap "exit"
316  set expect_result [expect_wrap\
317    [list $os_login_prompt_regex]\
318    "an OS prompt" 5]
319
320  dict set state os_logged_in 0
321  qprintn ; qprint_timen "Logged off OS."
322  dprintn ; dprint_dict state
323
324}
325
326
327proc boot_to_petitboot {} {
328
329  # Boot the machine until the petitboot screen is reached.
330
331  dprintn ; dprint_executing
332
333  global spawn_id
334  global expect_out
335  global state
336  global os_prompt_regex
337  global petitboot_screen_regex
338  global autoboot_setting
339
340  if { [dict get $state petitboot_screen] } {
341    qprintn ; qprint_timen "Already at petiboot."
342    return
343  }
344
345  if { [dict get $state petitboot_shell_prompt] } {
346    qprintn ; qprint_timen "Now at the shell prompt. Going to petitboot."
347    send_wrap "exit"
348    set expect_result [expect_wrap [list $petitboot_screen_regex]\
349    "the petitboot screen" 900]
350    dict set state petitboot_shell_prompt 0
351    dict set state petitboot_screen 1
352    return
353  }
354
355  if { [dict get $state os_login_prompt] } {
356    set cmd_buf os_login
357    qprintn ; qprint_issuing
358    eval ${cmd_buf}
359  }
360
361  # Turn off autoboot.
362  set_autoboot "false"
363
364  # Reboot and wait for petitboot.
365  send_wrap "reboot"
366
367  # Once we've started a reboot, we are no longer logged into OS.
368  dict set state os_logged_in 0
369  dict set state os_login_prompt 0
370
371  set expect_result [expect_wrap\
372    [list $petitboot_screen_regex]\
373    "the petitboot screen" 900]
374  set expect_result [expect_wrap\
375    [list "Exit to shell"]\
376    "the 'Exit to shell' screen" 10]
377  dict set state petitboot_screen 1
378
379  qprintn ; qprint_timen "Arrived at petitboot screen."
380  dprintn ; dprint_dict state
381
382}
383
384
385proc go_to_petitboot_shell {} {
386
387  # Go to petitboot shell.
388  global spawn_id
389  global state
390  global expect_out
391  global petitboot_shell_prompt_regex
392
393  if { [dict get $state petitboot_shell_prompt] } {
394    qprintn ; qprint_timen "Already at the shell prompt."
395    return
396  }
397
398  eval boot_to_petitboot
399  send_wrap "x"
400  set expect_result [expect_wrap [list $petitboot_shell_prompt_regex]\
401    "the shell prompt" 10]
402  dict set state petitboot_screen 0
403  dict set state petitboot_shell_prompt 1
404  qprintn ; qprint_timen "Arrived at the shell prompt."
405  qprintn ; qprint_timen state
406
407}
408
409
410proc set_autoboot { { autoboot_setting {}} } {
411
412  # Set the state of autoboot.
413  # Defaults to the value of the global autoboot_setting.
414
415  # This will work regardless of whether the OS is logged in or at petitboot.
416
417  # Description of argument(s):
418  # autoboot_setting  The desired state of autoboot (true/flase).
419
420  dprintn ; dprint_executing
421  global spawn_id
422  global expect_out
423  global state
424  global os_prompt_regex
425  global petitboot_shell_prompt_regex
426
427  set_var_default autoboot_setting [get_stack_var autoboot_setting 0 2]
428  set prompt_regex $petitboot_shell_prompt_regex
429
430  if { [dict get $state os_login_prompt] } {
431    set cmd_buf os_login
432    qprintn ; qprint_issuing
433    eval ${cmd_buf}
434    set prompt_regex $os_prompt_regex
435  }
436
437  if { [dict get $state petitboot_screen ] } {
438    set cmd_buf go_to_petitboot_shell
439    qprintn ; qprint_issuing
440    eval ${cmd_buf}
441  }
442
443  if { [dict get $state os_logged_in ] } {
444    set prompt_regex $os_prompt_regex
445  }
446
447
448  set cmd_result [shell_command\
449    "nvram --update-config auto-boot?=$autoboot_setting" $prompt_regex]
450  set cmd_result [shell_command "nvram --print-config" $prompt_regex]
451
452}
453
454
455proc installation_destination {} {
456
457  # Set the software installation destination.
458
459  # Expectation is that we are starting at the "Installation" options screen.
460
461  dprintn ; dprint_executing
462
463  global spawn_id
464  global expect_out
465  global state
466  global installer_screen_regex
467
468  qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
469
470  qprintn ; qprint_timen "Setting Installation Destination."
471  # Option 5). Installation Destination
472  qprintn ; qprint_timen "Selecting \"Installation Destination\" option."
473  send_wrap "5"
474  expect_wrap [list "Installation Destination"] "installation destination\
475  menu" 30
476
477  qprintn ; qprint_timen "Selecting \"Select all\" option."
478  send_wrap "3"
479  expect_wrap [list "Select all"] "selected all disks" 10
480
481  qprintn ; qprint_timen "Selecting \"continue\" option."
482  send_wrap "c"
483  expect_wrap [list "Autopartitioning"] "autopartitioning options" 10
484
485  qprintn ; qprint_timen "\
486  Selecting \"Replace Existing Linux system(s)\" option."
487  send_wrap "1"
488  expect_wrap [list "Replace"] "selected stanard partition" 10
489
490  qprintn ; qprint_timen "Selecting \"continue\" option."
491  send_wrap "c"
492  expect_wrap [list "Partition Scheme"] "partition scheme options" 10
493
494  qprintn ; qprint_timen "Selecting \"LVM\" option."
495  send_wrap "3"
496  expect_wrap [list "LVM"] "lvm option" 10
497
498  qprintn ; qprint_timen "Selecting \"continue\" option."
499  send_wrap "c"
500  expect_wrap [list $installer_screen_regex] "installation options screen" 10
501
502}
503
504
505proc time_settings {} {
506
507  # Set the time/zone via the petitboot shell prompt "Time settings" menu.
508
509  # Expectation is that we are starting at the "Installation" options screen.
510
511  dprintn ; dprint_executing
512
513  global spawn_id
514  global expect_out
515  global state
516  global installer_screen_regex
517
518  # Option 2). Timezone.
519
520  qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
521  qprintn ; qprint_timen "Setting time."
522
523  qprintn ; qprint_timen "Selecting \"Time settings\"."
524  send_wrap "2"
525  expect_wrap [list "Set timezone" "Time settings"] "Time settings menu" 30
526
527  qprintn ; qprint_timen "Selecting \"Change timezone\"."
528  send_wrap "1"
529  expect_wrap [list "Available regions"] "available regions menu" 10
530
531  qprintn ; qprint_timen "Selecting \"US\"."
532  send_wrap "11"
533  expect_wrap [list "region US"] "select region in US menu" 10
534
535  qprintn ; qprint_timen "Selecting \"Central\"."
536  send_wrap "3"
537  expect_wrap [list $installer_screen_regex] "installation options screen" 10
538
539}
540
541
542proc software_selection {} {
543
544  # Set the base environment via the petitboot shell prompt
545  # "Software Selection" menu.
546
547  # Expectation is that we are starting at the "Installation" options
548  # screen.
549
550  dprintn ; dprint_executing
551
552  global spawn_id
553  global expect_out
554  global state
555  global installer_screen_regex
556
557  qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
558  qprintn ; qprint_timen "Software selection."
559  # Option 4). Software selection.
560  set expect_result 0
561  while { $expect_result != 1 } {
562    qprintn ; qprint_timen "Selecting \"Software selection\"."
563    send_wrap "4"
564    set expect_result [expect_wrap\
565      [list "Installation source needs to be set up first." \
566      "Base environment"] "base environment menu" 10 0]
567
568    switch $expect_result {
569      0 {
570        qprintn ; qprint_timen "Selecting \"continue\"."
571        send_wrap "c"
572        expect_wrap [list $installer_screen_regex] \
573        "installation options screen" 15
574      }
575      1 {
576        break
577      }
578    }
579  }
580
581  qprintn ; qprint_timen "Selecting \"Infrastructure Server\"."
582  send_wrap "2"
583  expect_wrap [list "Infrastructure"] "selected infrastructure" 15
584
585  qprintn ; qprint_timen "Selecting \"continue\"."
586  send_wrap "c"
587  expect_wrap [list $installer_screen_regex] "installation options screen" 15
588
589}
590
591
592proc root_password {} {
593
594  # Set the os root password via the petitboot shell prompt "Root password"
595  # option.
596
597  # Expectation is that we are starting at the "Installation" options screen.
598
599  dprintn ; dprint_executing
600
601  global spawn_id
602  global expect_out
603  global state
604  global os_password
605  global installer_screen_regex
606
607  qprintn ; qprint_timen "Presumed to be at \"Installation\" screen."
608  qprintn ; qprint_timen "Setting root password."
609
610  # Option 8). Root password.
611  qprintn ; qprint_timen "Selecting \"Root password\"."
612  send_wrap "8"
613  expect_wrap [list "Password:"] "root password prompt" 30
614
615  qprintn ; qprint_timen "Entering root password."
616  send_wrap "$os_password"
617  expect_wrap [list "confirm"] "comfirm root password prompt" 15
618
619  qprintn ; qprint_timen "Re-entering root password."
620  send_wrap "$os_password"
621  set expect_result [expect_wrap\
622    [list $installer_screen_regex "The password you have provided is weak"] \
623    "root password accepted" 10 0]
624  switch $expect_result {
625    0 {
626      break
627    }
628    1 {
629    qprintn ; qprint_timen "Confirming weak password."
630      send_wrap "yes"
631    }
632  }
633  expect_wrap [list $installer_screen_regex] "installation options screen" 10
634
635}
636
637
638proc install_os {} {
639
640  # Install an os on the machine.
641  global spawn_id
642  global expect_out
643  global petitboot_shell_prompt_regex
644  global installer_screen_regex
645  global ftp_username ftp_password os_repo_url
646  global os_host os_username os_password
647
648  lassign [get_host_name_ip $os_host 0] os_hostname short_host_name ip_address
649  set netmask [get_host_netmask $os_host $os_username $os_password {} 0]
650  set gateway [get_host_gateway $os_host $os_username $os_password 0]
651  set mac_address \
652    [get_host_mac_address $os_host $os_username $os_password {} 0]
653  set name_servers [get_host_name_servers $os_host $os_username $os_password 0]
654  set dns [lindex $name_servers 0]
655  set domain [get_host_domain $os_host $os_username $os_password 0]
656
657  # Go to shell and download files for installation
658  eval go_to_petitboot_shell
659  after 10000
660  set vmlinuz_url \
661    "ftp://$ftp_username:$ftp_password@$os_repo_url/ppc/ppc64/vmlinuz"
662  set initrd_url \
663    "ftp://$ftp_username:$ftp_password@$os_repo_url/ppc/ppc64/initrd.img"
664  send_wrap "wget -c $vmlinuz_url"
665  expect_wrap [list "vmlinuz *100%"] "wget vmlinuz file success" 30
666  send_wrap "wget -c $initrd_url"
667  expect_wrap [list "initrd.img *100%"] "wget initrd file success" 30
668
669  # Setup parms and run kexec.
670  set colon "::"
671  set squashfs_url \
672    "ftp://$ftp_username:$ftp_password@$os_repo_url/LiveOS/squashfs.img"
673  set kexec_args "kexec -l vmlinuz --initrd initrd.img\
674    --append='root=live:$squashfs_url \
675    repo=ftp://$ftp_username:$ftp_password@$os_repo_url rd.dm=0 rd.md=0\
676    nodmraid console=hvc0 ifname=net0:$mac_address\
677    ip=$os_host$colon$gateway:$netmask:$os_hostname:net0:none nameserver=$dns\
678    inst.text'"
679  send_wrap "$kexec_args"
680  dprintn ; dprint_vars expect_out
681  set expect_result [expect_wrap [list $petitboot_shell_prompt_regex]\
682    "the shell prompt" 10]
683
684  # Turn on autoboot.
685  set_autoboot "true"
686
687  send_wrap "kexec -e"
688
689  # Begin installation process, go to settings screen.
690  set expect_result [expect_wrap [list "Starting installer"]\
691    "starting installer log" 900]
692  set expect_result [expect_wrap [list "Use text mode"]\
693    "install mode selection prompt" 120]
694  send_wrap "2"
695  expect_wrap [list $installer_screen_regex] "installation options screen" 15
696
697  installation_destination
698  time_settings
699  software_selection
700  root_password
701
702  # Now begin installation process.
703  set expect_result [expect_wrap\
704    [list $os_repo_url "Processing..."] \
705    "installation source processing" 10 0]
706
707  switch $expect_result {
708    0 {
709      break
710    }
711    1 {
712      expect_wrap [list $os_repo_url] "source processing complete" 240
713    }
714  }
715  send_wrap "b"
716  set expect_result [expect_wrap \
717    [list "Installation complete* Press return to quit"] \
718    "os installation complete message" 2000]
719  send_wrap ""; # Reboots to petitboot.
720  return
721
722}
723
724
725# Main
726
727  gen_get_options $argv
728
729  validate_parms
730
731  qprint_pgm_header
732
733  # Global variables for current prompts of the SOL console.
734  set ssh_password_prompt ".* password: "
735  set os_login_prompt_regex "login: "
736  set os_password_prompt "Password: "
737  set petitboot_screen_regex "Petitboot"
738  set cr_lf_regex "\[\n\r\]"
739  set os_prompt_regex "(\\\[${os_username}@\[^ \]+ ~\\\]# )"
740  set petitboot_shell_prompt_regex "/ #"
741  set installer_screen_regex \
742  {Installation[\r\n].*Please make your choice from above.* to refresh\]: }
743
744  dprintn ; dprint_dict state
745
746  set cmd_buf sol_login
747  qprint_issuing
748  eval ${cmd_buf}
749
750  set cmd_buf get_post_ssh_login_state
751  qprintn ; qprint_issuing
752  eval ${cmd_buf}
753
754  foreach proc_name $proc_names {
755    set cmd_buf ${proc_name}
756    qprintn ; qprint_issuing
757    eval ${cmd_buf}
758  }
759
760  exit_proc