# $OpenBSD: install.sub,v 1.398 2005/10/28 13:30:50 jmc Exp $ # $NetBSD: install.sub,v 1.5.2.8 1996/09/02 23:25:02 pk Exp $ # # Copyright (c) 1997-2005 Todd Miller, Theo de Raadt, Ken Westerback # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Copyright (c) 1996 The NetBSD Foundation, Inc. # All rights reserved. # # This code is derived from software contributed to The NetBSD Foundation # by Jason R. Thorpe. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. All advertising materials mentioning features or use of this software # must display the following acknowledgement: # This product includes software developed by the NetBSD # Foundation, Inc. and its contributors. # 4. Neither the name of The NetBSD Foundation nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # OpenBSD install/upgrade script common subroutines and initialization code # Include machine-dependent functions and definitions. # # The following functions must be provided: # md_congrats() - display friendly message # md_installboot() - install boot-blocks on disk # md_prep_disklabel() - put an OpenBSD disklabel on the disk # # The following variables can be provided if required: # MDSETS - list of files to add to THESETS # MDTERM - 'vt220' assumed if not provided # MDFSTYPE - nothing assumed if not provided # MDFSOPTS - nothing assumed if not provided # MDDKDEVS - '/^[sw]d[0-9][0-9]* /s/ .*//p' assumed if not provided # MDCDDEVS - '/^cd[0-9][0-9]* /s/ .*//p' assumed if not provided # MDMTDEVS - '/^[cmsw]t[0-9][0-9]* /s/ .*//p' # MDSERIAL - offer to configure a serial console # MDXAPERTURE - set machdep.allowaperture=value in sysctl.conf . install.md set_term() { local _tables TERM=${TERM:-${MDTERM:-vt220}} ask "Terminal type?" $TERM export TERM=$resp [[ -x /sbin/kbd ]] || return _tables=$(bsort $(kbd -l | egrep -v "^(user|tables|encoding)")) while :; do ask "kbd(8) mapping? ('?' for list)" "none" case $resp in none) return ;; "?") echo "Major tables: $_tables" ;; *) kbd $resp && { echo $resp >/tmp/kbdtype ; return ; } ;; esac done } welcome() { local _q cat <<__EOT Welcome to the $OBSD $MODE program. This program will help you $MODE OpenBSD in a simple and rational way. At any prompt except password prompts you can run a shell command by typing '!foo', or escape to a shell by typing '!'. Default answers are shown in []'s and are selected by pressing RETURN. At any time you can exit this program by pressing Control-C and then RETURN, but quitting during an $MODE can leave your system in an inconsistent state. __EOT # Configure the terminal and keyboard. set_term cat <<__EOT IS YOUR DATA BACKED UP? As with anything that modifies disk contents, this program can cause SIGNIFICANT data loss. __EOT case $MODE in upgrade) cat <<__EOT NOTE: once your system has been upgraded, you must manually merge any changes to files in the 'etc' set into the files already on your system. __EOT _q="Proceed with upgrade?" ;; install) cat <<__EOT It is often helpful to have the installation notes handy. For complex disk configurations, relevant disk hardware manuals and a calculator are useful. __EOT if [ -f /etc/fstab ]; then cat <<__EOT You seem to be trying to restart an interrupted installation! You can skip the disk preparation steps and continue, or you can reboot and start over. __EOT _q="Skip disk initialization?" else _q="Proceed with install?" fi ;; esac ask_yn "$_q" if [[ $resp == n ]]; then cat <<__EOT Enter 'halt' at the prompt to gracefully exit OpenBSD. You can then power cycle the machine and boot your original OS. __EOT exit fi echo "Cool! Let's get to it." } # Echo the file $1 to standard output, skipping any lines that begin with a # '#'. i.e. strip comment lines from the file. stripcom () { local _l [[ -f $1 ]] || return while read _l; do [[ -n ${_l%%#*} ]] && echo $_l done <$1 } scan_dmesg() { bsort $(sed -ne "$1" /var/run/dmesg.boot) } get_ifdevs() { ifconfig -a \ | egrep -v '^[[:space:]]|(bridge|enc|gif|gre|lo|pflog|pfsync|ppp|sl|tun|vlan)[[:digit:]]+:' \ | sed -ne 's/^\(.*\):.*/\1/p' } # Get the first (lowest unit #) serial device if any, if MDSERIAL is set. # # MDSERIAL is of the form # # " " # # So "pccom com tty0" means the dmesg is searched for devices pccom0, pccom1, # ... pccom9. If pccom2 is found, then com2 and tty02 are used in serial # console configuration via boot.conf commands and /etc/ttys. # # NOTE: Only single digit serial devices (0 -> 9) are looked for. get_serialdev() { local _d _bd _td [[ -n $MDSERIAL ]] || exit set -- $MDSERIAL _d=$1 _bd=$2 _td=$3 set -- $(scan_dmesg "/^${_d}\([0-9]\) .*/s//\1/p") echo "$_bd$1 $_td$1" } get_drive() { ask_which "$1" "contains the $MODE media" "$2" [[ $resp == done ]] && return 1 makedev $resp || return 1 return 0 } get_partition() { local _drive=$1 _fstypes=$2 _part _fst # Create file /tmp/parts.$_drive where each line is of the # form " ". disklabel $_drive 2>/dev/null \ | grep '^ [a-p]: ' \ | egrep -v "swap|unused" \ | sed -e 's/^ \(.\): *[^ ]* *[^ ]* *\([^ ]*\) .*/\1 \2/' \ >/tmp/parts.$_drive disklabel $_drive 2>/dev/null | grep '^ .:' ask_which "$_drive partition" "has the $MODE sets" \ "$(sed -e 's/^\(.\).*/\1/' /tmp/parts.$_drive)" [[ $resp == done ]] && return 1 _part=$resp _fst=$(sed -ne "/^$_part /s///p" /tmp/parts.$_drive) ask_which "filesystem type" "should be used to mount $_drive$_part" "$_fst $_fstypes ffs" case $resp in done) return 1 ;; $_fst) resp="$_part" ;; *) resp="$_part $resp" ;; esac return 0 } # Ask for a password, saving the input in $resp. # Display $1 as the prompt. # *Don't* allow the '!' options that ask does. # *Don't* echo input. askpass() { set -o noglob stty -echo read resp?"$1 " stty echo set +o noglob echo } # Ask for user input. # # $1 = the question to ask the user # $2 = the default answer # # Save the user input (or the default) in $resp. # # Allow the user to escape to shells ('!') or execute commands # ('!foo') before entering the input. ask() { local _question=$1 _default=$2 set -o noglob while :; do echo -n "$_question " [[ -z $_default ]] || echo -n "[$_default] " read resp case $resp in !) echo "Type 'exit' to return to install." sh ;; !*) eval ${resp#?} ;; *) : ${resp:=$_default} break ;; esac done set +o noglob } # Ask for user input until a non-empty reply is entered. # # $1 = the question to ask the user # $2 = the default answer # # Save the user input (or the default) in $resp. ask_until() { resp= while [[ -z $resp ]] ; do ask "$1" "$2" done } # Ask the user for a y or n, and insist on 'y', 'yes', 'n' or 'no'. # # $1 = the question to ask the user # $2 = the default answer (assumed to be 'n' if empty). # # Return 'y' or 'n' in $resp. ask_yn() { local _q=$1 _a=${2:-no} _resp typeset -l _resp while :; do ask "$_q" "$_a" _resp=$resp case $_resp in y|yes) resp=y ; return ;; n|no) resp=n ; return ;; esac done } # Ask for the user to select one value from a list, or 'done'. # # $1 = name of the list items (disk, cd, etc.) # $2 = question to ask # $3 = list of valid choices # $4 = default choice, if it is not specified use the first item in $3 # $5 = error message if no items in $3, defaults to 'No $1s found.' # # At exit $resp holds selected item, or 'done' ask_which() { local _name=$1 _query=$2 _list=$3 _def=$4 _err=$5 set -- $_list if (( $# < 1 )); then echo "${_err:=No ${_name}s found}." resp=done return fi : ${_def:=$1} # Eliminate extraneous (especially trailing) whitespace in _list. _list="$*" while :; do # Put both lines in ask prompt, rather than use a # separate 'echo' to ensure the entire question is # re-ask'ed after a '!' or '!foo' shell escape. ask "Available ${_name}s are: $_list.\nWhich one $_query? (or 'done')" "$_def" # Quote $resp to prevent user from confusing isin() by # entering something like 'a a'. isin "$resp" $_list done && break echo "'$resp' is not a valid choice." done } # test the first argument against the remaining ones, return success on a match isin() { local _a=$1 _b shift for _b; do [[ $_a == $_b ]] && return 0 done return 1 } # add first argument to list formed by the remaining arguments # adds to the tail if the element does not already exist addel() { local _a=$1 shift echo -n "$*" isin "$_a" $* || echo -n " $_a" } # remove all occurrences of first argument from list formed by # the remaining arguments rmel() { local _a=$1 _b shift for _b; do [[ $_a != $_b ]] && echo -n "$_b " done } bsort() { local _l _a=$1 _b [[ $# -gt 0 ]] || return shift for _b; do if [[ $_a != $_b ]] ; then if [[ $_a > $_b ]] ; then _l="$_a $_l"; _a=$_b else _l="$_b $_l" fi fi done # Output the smallest value found. echo -n "$_a " # Sort remaining values. bsort $_l } # Add interesting/useful comments from mnt/etc/$1 to /tmp/$1. # # $1 == file in /tmp and /mnt/etc directories save_comments() { local _file=$1 if [[ -f /mnt/etc/$_file ]]; then grep "^#" /mnt/etc/$_file >/tmp/$_file.new [[ -f /tmp/$_file ]] && cat /tmp/$_file >>/tmp/$_file.new mv /tmp/$_file.new /tmp/$_file fi } # Offer to edit a file in /tmp and execute ${EDITOR} to do so if the user # accepts the offer. # # $1 == file in /tmp to edit edit_tmp_file() { local _file=$1 ask_yn "Edit $_file with $EDITOR?" [[ $resp == y ]] && $EDITOR /tmp/$_file } # Offer to shell out for manual network configuration, and do so if # the user accepts the offer. manual_net_cfg() { ask_yn "Do you want to do any manual network configuration?" [[ $resp == y ]] && { echo "Type 'exit' to return to $MODE." ; sh ; } } # log in via ftp to host $1 as user $2 with password $3 # and return a list of all files in the directory $4 on stdout ftp_list_files() { ftp ${_ftp_active} -V -n "$1" <<__EOT user "$2" "$3" cd "$4" ls quit __EOT } # Create a device. # # $1 = name of the device to create. makedev() { local _dev=$1 if [[ ! -r /dev/MAKEDEV ]] ; then echo "MAKEDEV not found. Can't create device nodes." return 1 fi cd /dev ; sh MAKEDEV $_dev || return 1 ; cd - >/dev/null } # Create an entry in the hosts file. If an entry with the # same symbolic name and address family already exists, delete it. # $1 - IP address (v6 if it contains ':', else v4) # $2 - symbolic name addhostent() { local _addr=$1 _name=$2 _delim="." [[ $_addr == *:* ]] && _delim=":" sed "/^[0-9a-fA-F]*[$_delim].* $_name\$/d" /tmp/hosts >/tmp/hosts.new mv /tmp/hosts.new /tmp/hosts echo "$_addr $_name" >>/tmp/hosts } # Show list of available sets and let the user select which sets to install. # # $1 = available sets # $2 = already selected sets # # Set $resp to list of selected sets. select_sets() { local _avail=$1 _selected=$2 _next _f _action cat <<__EOT Select sets by entering a set name, a file name pattern or 'all'. De-select sets by prepending a '-' to the set name, file name pattern or 'all'. Selected sets are labelled '[X]'. __EOT while :; do _action= _next= echo for _f in $_avail; do if isin $_f $_selected; then echo " [X] $_f" else echo " [ ] $_f" : ${_next:=$_f} fi done : ${_next:=done} ask "Set name? (or 'done')" "$_next" case $resp in done) break ;; -*) _action=rmel ;; esac : ${_action:=addel} resp=${resp#+|-} case $resp in "") continue ;; all) resp=* ;; esac # Use @($resp) rather than just $resp to protect # against silly user input that might cause syntax # errors. for _f in $_avail; do eval "case $_f in @($resp)) _selected=\`$_action $_f \$_selected\` ;; esac" done done resp=$_selected } configure_ifs() { local _IFDEVS=$IFDEVS _ifs _name _media _hn while :; do ask_which "interface" "do you wish to initialize" "$_IFDEVS" \ "" "No more interfaces to initialize" [[ $resp == done ]] && break _ifs=$resp _hn=/tmp/hostname.$_ifs # Get symbolic name - will be used in DHCP requests. ask "Symbolic (host) name for $_ifs?" "$(hostname -s)" _name=$resp # Get and apply media options. _media=$(ifconfig -m $_ifs | grep "media ") if [[ -n $_media ]]; then cat <<__EOT The media options for $_ifs are currently $(ifconfig -m $_ifs | sed -n '/supported/D;/media:/p') __EOT ask_yn "Do you want to change the media options?" case $resp in y) cat <<__EOT Supported media options for $_ifs are: $_media __EOT ask "Media options for $_ifs?" _media=$resp ifconfig $_ifs $_media || return 1 ;; n) _media= ;; esac fi rm -f $_hn v4_config "$_ifs" "$_media" "$_name" "$_hn" v6_config "$_ifs" "$_media" "$_name" "$_hn" [[ -f $_hn ]] && _IFDEVS=$(rmel "$_ifs" $_IFDEVS) done } # Output ' [ ]'. # # $1 == interface v4_info() { ifconfig $1 inet | sed -n ' 1s/.* '. # # $1 == interface v6_info() { ifconfig $1 inet6 | sed -n ' 1s/.*/etc/resolv.conf.tail if [[ -n $_hn ]]; then _hn="send host-name \"$_hn\";" echo "Issuing hostname-associated DHCP request for $_ifs." else echo "Issuing free-roaming DHCP request for $_ifs." fi cat >/etc/dhclient.conf <<__EOT initial-interval 1; $_hn request subnet-mask, broadcast-address, routers, domain-name, domain-name-servers, host-name; __EOT ifconfig $_ifs group dhcp >/dev/null 2>&1 dhclient $_ifs set -- $(v4_info $_ifs) if [[ $1 == UP && -n $2 ]]; then # Move configuration files to where they will be copied to the # installed system. Overwrites configuration information from # last successful dhcp attempt. mv /etc/dhclient.conf /tmp/dhclient.conf mv /etc/resolv.conf.tail /tmp/resolv.conf.tail return 0 fi ifconfig $_ifs delete down rm /etc/dhclient.conf /etc/resolv.conf.tail return 1 } v4_config() { local _ifs=$1 _media=$2 _name=$3 _hn=$4 _prompt _addr _mask if ifconfig $_ifs | grep 'groups:.* dhcp' >/dev/null 2>&1; then _addr=dhcp else set -- $(v4_info $_ifs) if [[ -n $2 ]]; then _addr=$2; _mask=$3 ifconfig $_ifs inet $_addr delete fi fi [[ -x /sbin/dhclient ]] && _prompt=" or 'dhcp'" _prompt="IPv4 address for $_ifs? (or 'none'$_prompt)" ask_until "$_prompt" "$_addr" case $resp in none) ;; dhcp) if [[ ! -x /sbin/dhclient ]]; then echo "DHCP not possible - no /sbin/dhclient." elif dhcp_request $_ifs "$_name" || dhcp_request $_ifs ; then addhostent "127.0.0.1" "$_name" echo "dhcp NONE NONE NONE $_media" >>$_hn fi ;; *) _addr=$resp ask_until "Netmask?" "${_mask:=255.255.255.0}" ifconfig $_ifs -group dhcp >/dev/null 2>&1 if ifconfig $_ifs inet $_addr netmask $resp up ; then addhostent "$_addr" "$_name" echo "inet $_addr $resp NONE $_media" >$_hn fi ;; esac } v6_config() { local _ifs=$1 _media=$2 _name=$3 _hn=$4 _addr _prefixlen _prompt ifconfig lo0 inet6 >/dev/null 2>&1 || return set -- $(v6_info $_ifs) [[ -n $2 ]] && { _addr=$2; _prefixlen=$3; } [[ -x /sbin/rtsol ]] && _prompt="or 'rtsol' " _prompt="IPv6 address for $_ifs? (${_prompt}or 'none')" ask_until "$_prompt" "${_addr:-none}" case $resp in none) return ;; rtsol) [[ ! -x /sbin/rtsol ]] && { echo "No /sbin/rtsol." ; return ; } ifconfig $_ifs up rtsol -F $_ifs addhostent "::1" "$_name" echo "up\nrtsol $media" >>$_hn return ;; esac _addr=$resp ask_until "IPv6 prefix length for $_ifs?" "${_prefixlen:=64}" ifconfig $_ifs inet6 $_addr prefixlen $resp up || return echo "inet6 $_addr $resp $media" >>$_hn addhostent "$_addr" "$_name" v6_defroute $_ifs [[ $resp == none ]] && return route -n add -inet6 -host default "$resp" || return echo "$resp" >>/tmp/mygate } v4_defroute() { local _dr _prompt=" or 'none'" [[ -x /sbin/dhclient ]] && _prompt=", 'dhcp'$_prompt" _prompt="Default IPv4 route? (IPv4 address$_prompt)" _dr=$(route -n show -inet | sed -ne '/^default */{s///; s/ .*//; p;}') [[ -f /tmp/dhclient.conf ]] && _dr=dhcp while :; do ask_until "$_prompt" "$_dr" [[ $resp == @(none|dhcp) ]] && break route delete -inet default >/dev/null 2>&1 route -n add -inet -host default "$resp" && { echo "$resp" >/tmp/mygate ; break ; } # Put the old default route back. The new one did not work. route -n add -inet -host default $_dr >/dev/null 2>&1 done } v6_defroute() { local _if=$1 _routers _oifs if [[ -z $(route -n show -inet6 | sed -ne '/^default */{s///; s/ .*//; p;}') ]]; then resp=none return fi if [[ -x /sbin/ping6 ]]; then _routers=$(ping6 -n -c 2 ff02::2%$_if 2>&1 | sed -n \ -e '/bytes from/{s/^.*from //;s/,.*$//;p;}') fi _oifs=$IFS IFS= PS3="IPv6 default router? (list #, IPv6 address or 'none'): " select i in $_routers; do case $i in "") resp=$REPLY [[ -n $resp ]] && break ;; *) resp=$i break ;; esac done IFS=$_oifs } # Much of this is gratuitously stolen from /etc/netstart. enable_network() { local _netfile _gw # Copy any required or optional files found for _netfile in hosts dhclient.conf resolv.conf resolv.conf.tail protocols services; do if [ -f /mnt/etc/${_netfile} ]; then cp /mnt/etc/${_netfile} /etc/${_netfile} fi done # Set the address for the loopback interface. Bringing the # interface up, automatically invokes the IPv6 address ::1. ifconfig lo0 inet 127.0.0.1 # configure all of the non-loopback interfaces which we know about. # refer to hostname.if(5) for hn in /mnt/etc/hostname.*; do # Strip off /mnt/etc/hostname. prefix if=${hn#/mnt/etc/hostname.} # Check for ifconfig'able interface. ifconfig $if >/dev/null 2>&1 || continue # Now parse the hostname.* file while :; do if [ "$cmd2" ]; then # we are carrying over from the 'read dt dtaddr' last time set -- $cmd2 af=$1 name=$2 mask=$3 bcaddr=$4 ext1=$5 cmd2= # make sure and get any remaining args in ext2, like the read below i=1; while [ i -lt 6 -a -n "$1" ]; do shift; let i=i+1; done ext2="$@" else # read the next line or exit the while loop read af name mask bcaddr ext1 ext2 || break fi # $af can be "dhcp", "up", "rtsol", an address family, commands, or # a comment. case $af in "#"*|"!"*|"bridge"|"") # skip comments, user commands, bridges, # and empty lines continue ;; "dhcp") [ "$name" = "NONE" ] && name= [ "$mask" = "NONE" ] && mask= [ "$bcaddr" = "NONE" ] && bcaddr= dhcpif="$dhcpif $if" cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 down" cmd="$cmd; dhclient $if" ;; "rtsol") rtsolif="$rtsolif $if" cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 up" ;; "up") # The only one of these guaranteed to be set is $if # the remaining ones exist so that media controls work cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 up" ;; *) read dt dtaddr if [ "$name" = "alias" ]; then # perform a 'shift' of sorts alias=$name name=$mask mask=$bcaddr bcaddr=$ext1 ext1=$ext2 ext2= else alias= fi cmd="ifconfig $if $af $alias $name " case $dt in dest) cmd="$cmd $dtaddr" ;; [a-z!]*) cmd2="$dt $dtaddr" ;; esac if [ ! -n "$name" ]; then echo "/mnt/etc/hostname.$if: invalid network configuration file" return fi case $af in inet) [ "$mask" ] && cmd="$cmd netmask $mask" if [ "$bcaddr" -a "$bcaddr" != "NONE" ]; then cmd="$cmd broadcast $bcaddr" fi [ "$alias" ] && rtcmd="; route -qn add -host $name 127.0.0.1" ;; inet6) [ "$mask" ] && cmd="$cmd prefixlen $mask" cmd="$cmd $bcaddr" ;; *) cmd="$cmd $mask $bcaddr" esac cmd="$cmd $ext1 $ext2$rtcmd" rtcmd= ;; esac eval "$cmd" done /dev/null 2>&1 route -qn add -host default $_gw && break done [[ -z $rtsolif ]] && stripcom /mnt/etc/mygate | while read _gw; do [[ $_gw == !(*:*) ]] && continue route -qn delete -inet6 default >/dev/null 2>&1 route -qn add -host -inet6 default $_gw && break done # Use loopback, not the wire. route -qn add -host `hostname` 127.0.0.1 >/dev/null route -qn add -net 127 127.0.0.1 -reject >/dev/null # Display results... echo "Network interface configuration:" ifconfig -am # enable the resolver if resolv.conf is available route -n show if [ -f /etc/resolv.conf ]; then echo "\nResolver enabled." else echo "\nResolver not enabled." fi } # Install a user-selected subset of the files in $2 from the source # named in $1. Display an error message for failed installs so the # user will know to try again. install_files() { local _src=$1 _files=$2 _f _sets _get_sets # Initialize _sets to the list of sets found in _src, and initialize # _get_sets to the intersection of _sets and DEFAULTSETS. # # Sets will be installed in the order given in THESETS to ensure proper # installation. So, to minimize user confusion display the sets in the # order in which they will be installed. for _f in $THESETS; do isin $_f $_files || continue; _sets=$(addel $_f $_sets) isin $_f $DEFAULTSETS && _get_sets=$(addel $_f $_get_sets) done if [[ -z $_sets ]]; then # Show $_src, but delete any ftp password. cat <<__EOT No $OBSD sets were found at $(echo $_src | sed -e 's/\(^ftp:\/\/[^/]*\)\(:[^/]*\)\(@.*\)/\1\3/') Set names are: $THESETS __EOT return fi select_sets "$_sets" "$_get_sets" [[ -n $resp ]] || return _get_sets=$resp ask_yn "Ready to $MODE sets?" yes [[ $resp = n ]] && return for _f in $THESETS ; do isin $_f $_get_sets || continue echo "Getting $_f ..." case $_f in *.tgz) ftp $_ftp_active -o - -V -m "$_src/$_f" | tar zxphf - -C /mnt ;; *) ftp $_ftp_active -o "/mnt/$_f" -V -m "$_src/$_f" ;; esac if [ $? -ne 0 ]; then echo "'$_f' did not install correctly." else DEFAULTSETS=$(rmel $_f $DEFAULTSETS) fi done } # Encode $1 as specified for usercodes and passwords in RFC 1738 # section 3.1 and section 5. # # Escape everything between 0x20 and 0x7e to avoid both illegal url # characters and characters causing problems during script processing. # # *NOTE* # 1) quotes around $1 are required to preserve trailing or # embeddded blanks in usercodes and passwords. # 2) substitute '%' FIRST so it doesn't eliminate '%' chars we insert. encode_for_url() { echo "$1" | sed -e " s/%/%25/g s/ /%20/g s/!/%21/g s/\"/%22/g s/#/%23/g s/\\\$/%24/g s/&/%26/g s/'/%27/g s/(/%28/g s/)/%29/g s/\*/%2a/g s/+/%2b/g s/,/%2c/g s/-/%2d/g s/\./%2e/g s/\//%2f/g s/:/%3a/g s/;/%3b/g s//%3e/g s/?/%3f/g s/@/%40/g s/\[/%5b/g s/\\\\/%5c/g s/]/%5d/g s/\^/%5e/g s/_/%5f/g s/\`/%60/g s/{/%7b/g s/|/%7c/g s/}/%7d/g s/~/%7e/g " } # Check for the presence of an error message in the output of the ftp commands # used to get the list of files in a directory. # # $1 = error message to look for # $2 = ftp command output ftp_error() { if [[ -n $(echo "$2" | grep "$1") ]]; then echo $1 return 0 fi return 1 } # Get several parameters from the user, and xfer # files from the server. # $1 = url type (ftp or http) # Note: _ftp_server_ip, _ftp_server_dir, _ftp_server_login, # and _ftp_active must be global. install_url() { local _url_type=$1 _file_list _url_base _oifs _prompt _passwd ask "HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none')" \ "${ftp_proxy:-none}" unset ftp_proxy http_proxy [[ $resp == none ]] || export ftp_proxy=$resp http_proxy=$resp rm -f $SERVERLIST ask_yn "Display the list of known $_url_type servers?" "${_get_server_list:-yes}" _get_server_list=$resp if [[ $_get_server_list == y ]]; then # ftp.openbsd.org == 129.128.5.191 and will remain at # that address for the forseeable future. echo -n "Getting the list from 129.128.5.191 (ftp.openbsd.org)..." ftp $_ftp_active -V -a -o - \ ftp://129.128.5.191/$FTPDIR/ftplist 2>/tmp/ftplisterr \ | sed -ne "/^${_url_type}:\/\//s///p" >$SERVERLIST if [[ -s $SERVERLIST ]]; then echo "done." _prompt="Server? (IP address, hostname, list#, 'done' or '?')" cat -n $SERVERLIST | less -XE else echo "FAILED." cat /tmp/ftplisterr fi fi # Get server IP address or hostname : ${_prompt:="Server? (IP address, hostname or 'done')"} while :; do eval resp=\$_${_url_type}_server_ip ask_until "$_prompt" "$resp" case $resp in done) return ;; "?") [[ -s $SERVERLIST ]] || continue cat -n $SERVERLIST | less -XE ;; +([0-9])) # A numeric hostname is ignored. A number is only used # as a line number in $SERVERLIST. [[ -s $SERVERLIST ]] || continue set -- $(sed -ne "${resp}p" $SERVERLIST) [[ $# -lt 1 ]] && { echo "There is no line $resp." ; continue ; } echo "Using $*" eval _${_url_type}_server_ip=${1%%/*} eval _${_url_type}_server_dir=${1#*/}/$SETDIR # Repeat loop to get user to confirm server address. ;; *) eval _${_url_type}_server_ip=$resp break ;; esac done # Some older servers lie about their support for passive mode ftp, so # ask the user if it worth trying passive mode to the chosen server. # Irrelevant if using a proxy. if [[ $_url_type == ftp && -z $ftp_proxy ]]; then case $_ftp_active in -A) resp=no ;; *) resp=yes ;; esac unset _ftp_active ask_yn "Does the server support passive mode ftp?" $resp [[ $resp == n ]] && _ftp_active=-A fi # Get server directory eval resp=\$_${_url_type}_server_dir ask_until "Server directory?" "${resp:-pub/OpenBSD/$SETDIR}" eval _${_url_type}_server_dir=$resp if [[ $_url_type == ftp ]]; then # Get login name, setting IFS to nothing so trailing or # embedded blanks are preserved! _oifs=$IFS IFS= ask_until "Login?" "${_ftp_server_login:=anonymous}" _ftp_server_login=$resp # Get password unless anonymous _passwd=root@`hostname` if [[ $_ftp_server_login != anonymous ]]; then resp= while [[ -z $resp ]] ; do askpass "Password? (will not echo)" done _passwd=$resp fi IFS=$_oifs fi # Build up the base url since it is so nasty... _url_base=$_url_type:// if [[ $_url_type == ftp && $_ftp_server_login != anonymous ]]; then _url_base=$_url_base$(encode_for_url "$_ftp_server_login"):$(encode_for_url "$_passwd")@ fi eval _url_base=$_url_base\$_${_url_type}_server_ip/\$_${_url_type}_server_dir # XXX Workaround for problems ftp'ing out from a v6 only host. ifconfig lo0 127.0.0.1 # Get list of files from the server. if [[ $_url_type == ftp && -z $ftp_proxy ]] ; then _file_list=$(ftp_list_files "$_ftp_server_ip" "$_ftp_server_login" "$_passwd" "$_ftp_server_dir") ftp_error "Login failed." "$_file_list" && return ftp_error "No such file or directory." "$_file_list" && return else # Assumes index file is "index.txt" for http (or proxy) # We can't use index.html since the format is server-dependent _file_list=$(ftp -o - -V "$_url_base/index.txt" | sed 's/ //') fi install_files "$_url_base" "$_file_list" } install_mounted_fs() { local _dir while :; do ask_until "Pathname to the sets? (or 'done')" "$SETDIR" [[ $resp == done ]] && return # Accept a valid /mnt2 or /mnt relative path. [[ -d /mnt2/$resp ]] && { _dir=/mnt2/$resp ; break ; } [[ -d /mnt/$resp ]] && { _dir=/mnt/$resp ; break ; } # Accept a valid absolute path. [[ -d /$resp ]] && { _dir=/$resp ; break ; } echo "The directory '$resp' does not exist." done install_files "file://$_dir" "$(ls -l $_dir)" } install_cdrom() { local _drive _part=c _fstype get_drive "CD-ROM" "$CDDEVS" || return _drive=$resp set -- $(disklabel $_drive 2>&1 | grep '^ c: ') case $4 in ISO9660) _fstype=cd9660 ;; UDF) _fstype=udf ;; *) get_partition $_drive "cd9660" || return set -- $resp _part=$1 [[ -n $2 ]] && _fstype=$2 ;; esac mount -t $_fstype -o ro /dev/$_drive$_part /mnt2 || return install_mounted_fs } install_disk() { local _drive _dev _fstype _fsopts ask_yn "Is the disk partition already mounted?" if [[ $resp == n ]]; then get_drive "disk" "$DKDEVS" || return _drive=$resp get_partition $_drive "$MDFSTYPE" || return set -- $resp _dev=/dev/$_drive$1 [[ -n $2 ]] && _fstype="-t $2" [[ $_fstype == $MDFSTYPE ]] && _fsopts=$MDFSOPTS if [[ -z $(mount | grep "^$_dev") ]]; then mount $_fstype -o ro,$_fsopts $_dev /mnt2 || return fi fi install_mounted_fs } install_nfs() { local _tcp # Get the IP address of the server. ask_until "Server IP address or hostname?" "$NFS_ADDR" NFS_ADDR=$resp # Get the server path to mount. ask_until "Filesystem on server to mount?" "$NFS_PATH" NFS_PATH=$resp # Determine use of TCP ask_yn "Use TCP transport? (requires TCP-capable NFS server)" [[ $resp == y ]] && _tcp=-T # Mount the server mount_nfs $_tcp -o ro $NFS_ADDR:$NFS_PATH /mnt2 || return install_mounted_fs } install_tape() { local _z _bs # Get the name of the tape device. get_drive "tape drive" "$MTDEVS" || return export TAPE=/dev/nr$resp if [[ ! -c $TAPE ]]; then echo "$TAPE is not a character special file." return fi # Rewind the tape device. echo -n "Rewinding $TAPE (mt rewind)..." mt rewind || return echo "done." # Extract the desired files. while :; do ask_until "Skip how many files? (or 'done')" 0 [[ $resp == done ]] && return [[ $resp == +([0-9]) ]] || continue (($resp < 0)) && continue if (($resp > 0)); then echo -n "Skipping $resp file(s)..." mt fsf $resp || return echo "done." elif [[ -n $_bs ]]; then # Dance to start of next file. mt bsf ; mt fsf fi unset _z ask_yn "Is the file gzipped?" yes [[ $resp == y ]] && _z=z # Get the blocksize to use. If the file isn't gzipped then # default to the 20 x 512 = 10,240 byte tar default. [[ $_z == z ]] || _bs=10240 ask_until "Blocksize for this file?" "${_bs:-8k}" [[ $resp == done ]] && return _bs=$resp dd if=$TAPE bs=$_bs | tar ${_z}xvphf - -C /mnt || return done } set_timezone() { local _zoneroot=/mnt/usr/share/zoneinfo/ _zonepath # If the timezone directory structure is not # available, return immediately. [[ ! -d $_zoneroot ]] && return if [[ -L /mnt/etc/localtime ]]; then TZ=$(ls -l /mnt/etc/localtime 2>/dev/null) TZ=${TZ#*${_zoneroot#/mnt}} fi : ${TZ:=GMT} while :; do _zonepath=$_zoneroot ask "What timezone are you in? ('?' for list)" "$TZ" if [[ $resp == ? ]]; then ls -F ${_zonepath} continue; fi _zonepath=${_zonepath}${resp} while [[ -d $_zonepath ]]; do ask "What sub-timezone of '${_zonepath#$_zoneroot}' are you in? ('?' for list)" case $resp in "") ;; ?) ls -F $_zonepath ;; *) _zonepath=$_zonepath/$resp ;; esac done if [[ -f $_zonepath ]]; then TZ=${_zonepath#$_zoneroot} echo -n "Setting local timezone to '$TZ'..." ln -sf /usr/share/zoneinfo/$TZ /mnt/etc/localtime echo "done." return fi echo -n "'${_zonepath#$_zoneroot}'" echo " is not a valid timezone on this system." done } # Check with the user that missing required sets were deliberately skipped. sane_install() { local _s _m for _s in $SANESETS; do isin $_s $DEFAULTSETS || continue ask_yn "'$_s' was not installed.\nAre you *SURE* your $MODE is complete without '$_s'?" [[ $resp == n ]] && _m="$_m $_s" done [[ -n $_m ]] && return 1 return 0 } # Ask the user for locations of sets, and then install whatever sets the # user selects from that location. Repeat as many times as the user # needs to get all desired sets. install_sets() { local _d=disk _locs="disk ftp http" [[ -n $CDDEVS ]] && { _locs="cd $_locs" ; _d=cd ; } [[ -x /sbin/mount_nfs ]] && _locs="$_locs nfs" [[ -n $MTDEVS && -x /bin/mt ]] && _locs="$_locs tape" echo "\nLet's $MODE the sets!" while :; do umount -f /mnt2 >/dev/null 2>&1 [[ -z $DEFAULTSETS ]] && _d=done ask "Location of sets? ($_locs or 'done')" "$_d" case $resp in done) sane_install && return ;; c*|C*) isin "cd" $_locs && install_cdrom ;; d*|D*) install_disk ;; f*|F*) isin "ftp" $_locs && install_url ftp ;; h*|H*) isin "http" $_locs && install_url http ;; n*|N*) isin "nfs" $_locs && install_nfs ;; t*|T*) isin "tape" $_locs && install_tape ;; esac done } # Create a skeletal but useful /etc/fstab from /tmp/fstab by stripping all # comment lines and dropping all filesystems which # # 1) can't be mounted (no mount_* command is found), # 2) have 'xx' in the option field (usually /altroot), # 3) have 'noauto' in the option field, # 4) are nfs (since name resolution may not be present). # # In addition, # # 1) delete 'softdep' options (no soft updates in ramdisk kernels), # 2) mount non-ffs filesystems read only, # 3) prepend '/mnt' to all mount points, # 4) delete any trailing '/' from the mount point (e.g. root), # 5) leave out fs_freq and fs_passno fields. # # If no /etc/fstab is created, do not proceed with install/upgrade. munge_fstab() { local _dev _mp _fstype _opt _rest while read _dev _mp _fstype _opt _rest; do # Drop irrelevant lines and filesystems. [[ $_dev == \#* || \ $_fstype == nfs || \ ! -f /sbin/mount_$_fstype || \ $_opt == *noauto* || \ $_opt == *xx* ]] && continue # Remove any softdep options, as soft updates are not # available in the ramdisk kernels. _opt=$(echo $_opt | sed -e 's/softdep//') # Mount non-ffs filesystems read only. [[ $_fstype == ffs ]] || _opt=$(echo $_opt | sed -e 's/rw/ro/') # Write fs entry in fstab. # 1) prepend '/mnt' to the mount point. # 2) remove a trailing '/' from the mount point (e.g. root). # 3) leave out fs_freq and fs_passno fields (i.e. $_rest). echo $_dev /mnt${_mp%/} $_fstype $_opt done /etc/fstab # If no /etc/fstab was created, we have nowhere to $MODE to. if [ ! -s /etc/fstab ]; then echo "Unable to create valid /etc/fstab." exit fi } # Must mount filesystems manually, one at a time, so we can make # sure the mount points exist. mount_fs() { local _async=$1 _dev _mp _fstype _opt _rest while read _dev _mp _fstype _opt _rest; do # If not the root filesystem, make sure the mount # point is present. [ "$_mp" = "/mnt" ] || mkdir -p $_mp # Mount the filesystem. If the mount fails, exit. if ! mount -v -t $_fstype $_async -o $_opt $_dev $_mp ; then # In addition to the error message displayed by mount ... cat <<__EOT FATAL ERROR: Cannot mount filesystems. Double-check your configuration and restart the $MODE. __EOT exit fi done /dev/null 2>&1; then echo "FAILED. You must fsck $_dev manually." _fail=y else echo "OK." fi done /tmp/resolv.conf for _ns in $resp; do echo "nameserver $_ns" >>/tmp/resolv.conf done ask_yn "Use the nameserver now?" yes [[ $resp == y ]] && cp /tmp/resolv.conf /tmp/resolv.conf.shadow fi # Get/Confirm an IPv4 default route if an IPv4 address was configured. [[ -n $(ifconfig -a | sed -ne '/[ ]inet .* broadcast /p') ]] && v4_defroute edit_tmp_file hosts manual_net_cfg } populateusrlocal() { if [ -f /mnt/etc/mtree/BSD.local.dist ]; then /mnt/usr/sbin/chroot /mnt /usr/sbin/mtree -Uedqn -p /usr/local -f /etc/mtree/BSD.local.dist >/dev/null fi } questions() { local _bd _td ask_yn "Start sshd(8) by default?" yes if [[ $resp == n ]]; then echo "sshd_flags=NO # disabled during install" \ >>/mnt/etc/rc.conf.local fi ask_yn "Start ntpd(8) by default?" no if [[ $resp == y ]]; then echo "ntpd_flags= # enabled during install" \ >>/mnt/etc/rc.conf.local fi if [[ -n $MDXAPERTURE ]]; then ask_yn "Do you expect to run the X Window System?" yes if [[ $resp == y ]]; then sed -e "/^#\(machdep\.allowaperture=${MDXAPERTURE}\)/s//\1 /" \ /mnt/etc/sysctl.conf >/tmp/sysctl.conf cp /tmp/sysctl.conf /mnt/etc/sysctl.conf fi fi [[ -z $SERIALDEV ]] && return set -- $SERIALDEV _bd=$1 _td=$2 ask_yn "Change the default console to $_bd?" [[ $resp == n ]] && return ask_which "speed" "should $_bd use" "9600 19200 38400 57600 115200" [[ $resp == done ]] && return echo "stty $_bd $resp\nset tty $_bd" >>/mnt/etc/boot.conf sed -e "/^${_td}/s/std.9600/std.${resp}/" \ -e "/^${_td}/s/unknown/vt220 /" \ -e "/${_td}/s/off/on secure/" /mnt/etc/ttys >/tmp/ttys } finish_up() { local _dev _mp _fstype _rest # Mount all known swap partitions. This gives systems with little # memory a better chance at running 'MAKEDEV all'. if [[ -x /mnt/sbin/swapctl ]]; then /mnt/sbin/swapctl -a /dev/$SWAPDEV >/dev/null 2>&1 # Can't do chmod && swapctl -A because devices are not yet # created on install'ed systems. On upgrade'ed system there # is a small chance the device does not exist on the ramdisk # and will thus not get mounted. while read _dev _mp _fstype _rest; do [[ $_fstype == swap ]] && \ /mnt/sbin/swapctl -a $_dev >/dev/null 2>&1 done /tmp/sysctl.conf cp /tmp/sysctl.conf /mnt/etc/sysctl.conf fi echo -n "Making all device nodes..." cd /mnt/dev sh MAKEDEV all # Make sure any devices we found during probe are created in the # installed system. for _dev in $DKDEVS $CDDEVS $MTDEVS; do sh MAKEDEV $_dev done echo "done." cd / md_installboot $ROOTDISK populateusrlocal [ -x /mnt/$MODE.site ] && /mnt/usr/sbin/chroot /mnt /$MODE.site # Pat on the back. cat <<__EOT CONGRATULATIONS! Your OpenBSD $MODE has been successfully completed! To boot the new system, enter halt at the command prompt. Once the system has halted, reset the machine and boot from the disk. __EOT md_congrats } # ####################################################################### # # Initial actions common to both installs and upgrades. # # Some may require machine dependent routines, which may # call functions defined above, so it's safest to put this # code here rather than at the top of the file. # # ####################################################################### ROOTDISK= ROOTDEV= VERSION=38 VNAME="$(( $VERSION / 10 )).$(( $VERSION % 10 ))" SETDIR="$VNAME/$ARCH" FTPDIR="pub/OpenBSD/$VNAME" OBSD="OpenBSD/$ARCH $VNAME" SERVERLIST=/tmp/serverlist # Do not limit ourselves during installs or upgrades. for _opt in d f l m n p s; do ulimit -$_opt unlimited done # Extract and save one boot's worth of dmesg dmesg | sed -ne '/^OpenBSD /h;/^OpenBSD /!H;${g;p;}' >/var/run/dmesg.boot # Scan /var/run/dmesg.boot for interesting devices. DKDEVS=$(scan_dmesg "${MDDKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}") CDDEVS=$(scan_dmesg "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}") MTDEVS=$(scan_dmesg "${MDMTDEVS:-/^[cmsw]t[0-9][0-9]* /s/ .*//p}") IFDEVS=$(get_ifdevs) SERIALDEV=$(get_serialdev) # Selected sets will be installed in the order they are listed in $THESETS. # Ensure that siteXX.tgz is the *last* set listed so its contents overwrite # the contents of the other sets, not the other way around. THESETS="bsd bsd.rd bsd.mp $MDSETS" DEFAULTSETS="bsd bsd.rd" for _set in base etc misc comp man game xbase xetc xshare xfont xserv site ; do [[ $MODE == upgrade && ( $_set == etc || $_set == xetc ) ]] && continue THESETS="$THESETS ${_set}${VERSION}.tgz" isin $_set xbase xetc xshare xfont xserv site && continue DEFAULTSETS="$DEFAULTSETS ${_set}${VERSION}.tgz" done # Since etc${VERSION}.tgz is not in DEFAULTSETS for upgrades, it can always be # in SANESETS. SANESETS="bsd base${VERSION}.tgz etc${VERSION}.tgz" # decide upon an editor : ${EDITOR:=ed} [[ -x /usr/bin/vi ]] && EDITOR=vi export EDITOR # umount all filesystems, just in case we are re-running install or upgrade. [[ -f /etc/fstab ]] && umount -av 1>/dev/null 2>&1 umount -v /mnt 1>/dev/null 2>&1 # Introduce ourselves. welcome # Get ROOTDISK, ROOTDEV and SWAPDEV. if [[ $MODE == install && ! -f /etc/fstab ]]; then cat <<__EOT You will now initialize the disk(s) that OpenBSD will use. To enable all available security features you should configure the disk(s) to allow the creation of separate filesystems for /, /tmp, /var, /usr, and /home. __EOT fi set -- $DKDEVS [[ $# -gt 1 ]] && _defdsk=done ask_which "disk" "is the root disk" "$DKDEVS" "$_defdsk" [[ $resp == done ]] && exit makedev $resp || exit ROOTDISK=$resp ROOTDEV=${ROOTDISK}a SWAPDEV=${ROOTDISK}b