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