# $OpenBSD: install.sub,v 1.296 2003/04/23 12:06:55 wilfried Exp $ # $NetBSD: install.sub,v 1.5.2.8 1996/09/02 23:25:02 pk Exp $ # # Copyright (c) 1997-2003 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. # 3. All advertising materials mentioning features or use of this software # must display the following acknowledgement: # This product includes software developed by Todd Miller and # Theo de Raadt # 4. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # 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() - label the root disk # md_set_term() - set up terminal # # 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 # MDDISKDEVS - '/^[sw]d[0-9][0-9]* /s/ .*//p' assumed if not provided # MDCDDEVS - '/^cd[0-9][0-9]* /s/ .*//p' assumed if not provided # MDXAPERTURE - if not empty, set machdep.allowaperture=value in sysctl.conf . install.md set_term() { [ "$TERM" ] && return ask "Terminal type?" ${MDTERM:-vt220} TERM=$resp export TERM md_set_term } welcome() { 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. 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 ask "Proceed with upgrade?" n ;; 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 ask "Skip disk initialization?" n else ask "Proceed with install?" n fi ;; esac case $resp in y*|Y*) echo "Cool! Let's get to it..." ;; *) 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 ;; esac } get_dkdevs() { bsort `sed -ne "${MDDISKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}" /var/run/dmesg.boot` } get_cddevs() { bsort `sed -ne "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}" /var/run/dmesg.boot` } get_ifdevs() { ifconfig -a | egrep -v '^([[:space:]]|(lo|enc|gre|ppp|sl|tun|bridge|pflog|vlan|gif)[[:digit:]])' | cutword -t: 1 } # 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 for the user to select a device from a list generated by scanning # /var/run/dmesg.boot, and make the device if it doesn't exist. # # $1 = device name (disk, cd, etc.) # $2 = question to ask # $3 = list of devices from /var/run/dmesg.boot scan # $4 = default device # # $resp holds device selected at exit, or 'done' ask_which () { local _name=$1 _query=$2 _devs=$3 _defdev=$4 # If not default device is supplied, assume 'done'. : ${_defdev:=done} # A trailing space may be present if _devs list # was manipulated by rmel(). _devs=${_devs% } resp= if [[ -z $_devs ]]; then echo "Done - no available ${_name}s found." resp=done fi while [ -z "$resp" ]; do # Put both lines in ask prompt, rather than use a # separate 'echo', to ensure entire question is # re-ask'ed after a '!' '!foo' shell escape. ask "Available ${_name}s are: ${_devs}.\nWhich one $_query (or 'done')" "$_defdev" # Quote $resp to prevent user from confusing isin() by # entering something like 'a a'. if isin "$resp" $_devs ; then makedev $resp || resp= elif [ "$resp" != "done" ]; then echo "'$resp' is not a valid choice." resp= fi 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 _b _seen=false shift for _b; do echo -n "$_b " [ "$_a" = "$_b" ] && _seen=true done $_seen || 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 } # read lines on stdin, return Nth element of each line, like cut(1) cutword () { local _a _n _oifs=$IFS # optional field separator case $1 in -t?*) IFS=${1#-t}; shift;; esac _n=$1 while read _a; do set -- $_a [ "$1" ] || break eval echo \$$_n done IFS=$_oifs } # read a line of data, return last element. Equiv. of awk '{print $NF}'. cutlast () { local _a read _a; set -- $_a [ $# -gt 0 ] || return eval echo \$\{$#\} } bsort() { local _l _a=$1 _b case $# in 0) return;; 1) echo $1; return;; esac shift for _b; do if [[ "$_a" != "$_b" ]] ; then if [[ "$_a" > "$_b" ]] ; then _l="$_a $_l"; _a=$_b else _l="$_b $_l" fi fi done echo -n $_a # Prevent a trailing blank on the output, and thus a bad value # for cutlast or cutword, by outputting blanks only when $_l # has values to sort. if [[ -n "$_l" ]] ; then echo -n " " bsort $_l fi } # 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 "Edit $_file with ${EDITOR}?" n case $resp in y*|Y*) ${EDITOR} /tmp/$_file ;; esac } # Offer to shell out for manual network configuration, and do so if # the user accepts the offer. manual_net_cfg () { ask "Do you want to do any manual network configuration?" n case $resp in y*|Y*) echo "Type 'exit' to return to ${MODE}." sh ;; esac } # 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 } # Check for the existence of the device nodes for the # supplied device name. If they are missing (as indicated # by r${1}c not being found) then create them. In either # case, return true if the nodes exist and false if not. # # $1 = name of the device that is about to be used. makedev() { local _dev=$1 _node=/dev/r${1}c # Don't need to make network interface devices nodes. If the device # nodes exist, don't need to create them. if isin $_dev $IFDEVS || [[ -c $_node ]] ; then return 0 fi if [[ ! -r /dev/MAKEDEV ]] ; then echo "No /dev/MAKEDEV. Can't create device nodes for ${_dev}." return 1 fi (cd /dev; sh MAKEDEV $_dev) # If the device nodes still do not exist, assume MAKEDEV issued a useful # error message and return false. [[ -c $_node ]] || return 1 DEVSMADE=`addel $_dev $DEVSMADE` } get_rootdisk() { local _defdsk _defdsk=`echo $DKDEVS | cutlast` [ "$_defdsk" = "$DKDEVS" ] || _defdsk= if [ "$MODE" = "install" -a ! -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 ROOTDISK= ROOTDEV= ask_which "disk" "is the root disk?" "$DKDEVS" "$_defdsk" [ "$resp" = "done" ] && exit ROOTDISK=$resp ROOTDEV=${ROOTDISK}a } # Create an entry in the hosts file. If an entry with the # same symbolic name already exists, delete it. # $1 - IP address # $2 - symbolic name addhostent() { sed "/ $2\$/d" /tmp/hosts > /tmp/hosts.new mv /tmp/hosts.new /tmp/hosts echo "$1 $2" >> /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 while : ; do cat << __EOT The following sets are available. Enter a filename, 'all' to select all the sets, or 'done'. You may de-select a set by prepending a '-' to its name. __EOT _next= for _f in $_avail; do if isin $_f $_selected; then echo " [X] $_f" else echo " [ ] $_f" : ${_next:=$_f} fi done : ${_next:=done} ask "\nFile name? (or 'done')" "$_next" case $resp in ""|+|-) continue ;; done) break ;; -*) _action=rmel ;; *) _action=addel ;; esac resp=${resp#+|-} [[ $resp == all ]] && resp=* for _f in $_avail; do eval "case $_f in $resp) _selected=\`$_action $_f \$_selected\` ;; esac" done done resp=$_selected } configure_all_interfaces() { local _IFDEVS=$IFDEVS _ifs while : ; do _IFDEVS=`rmel "$_ifs" $_IFDEVS` ask_which "interface" "do you wish to initialize?" "$_IFDEVS" "`echo $_IFDEVS | cutword 1`" [ "$resp" = "done" ] && break _ifs=$resp configure_ifs $_ifs || _ifs= done } # Obtain and output the inet information related to the given # interface. Should output ' '. # # $1 == interface inet_info () { ifconfig $1 inet | sed -n ' 1s/.**$/DOWN/p /media:/s/^.*$// /status:/s/^.*$// /inet/s/--> [0-9.][0-9.]*// /inet/s/netmask// /inet/s/broadcast// /inet/s/inet// p' } # Construct etc/dhclient.conf and issue DHCP request. Return FALSE if # no IP address or 0.0.0.0 assigned to $1. # # $1 == interface # $2 == hostname (optional). dhcp_request () { local _ifs=$1 _hostname=$2 echo "initial-interval 1;" > /etc/dhclient.conf if [[ -n $_hostname ]]; then echo "send host-name \"$_hostname\";" >> /etc/dhclient.conf echo "Issuing hostname-associated DHCP request for $_ifs." else echo "Issuing free-roaming DHCP request for $_ifs." fi cat >> /etc/dhclient.conf << __EOT request subnet-mask, broadcast-address, routers, domain-name, domain-name-servers, host-name; __EOT cat >> /etc/resolv.conf.tail << __EOT lookup file bind __EOT kill_dhclient dhclient -1 $_ifs set -- $(inet_info $_ifs) if [[ $1 == UP && $2 == "0.0.0.0" ]]; then ifconfig $_ifs delete down rm /etc/dhclient.conf /etc/resolv.conf.tail return 1 fi # 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 } configure_ifs() { local _ifs=$1 _addr _mask _name _prompt _media _config set -- $(inet_info $_ifs) [[ $1 == UP ]] && ifconfig $_ifs delete down [[ -n $2 && $2 != "0.0.0.0" ]] && { _addr=$2; _mask=$3; } # 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 default media for $_ifs is $(ifconfig -m $_ifs | sed -n '/supported/D;/media:/p') __EOT ask "Do you want to change the default media?" n case $resp in y*|Y*) cat << __EOT Supported media options for $_ifs are: $_media __EOT ask "Media options for $_ifs?" _media=$resp ifconfig $_ifs $_media down || return 1 ;; *) _media= ;; esac fi # Get address and mask. _prompt="IP address for ${_ifs}?" [[ -x /sbin/dhclient ]] && _prompt="$_prompt (or 'dhcp')" ask_until "$_prompt" "$_addr" case $resp in dhcp) if [[ ! -x /sbin/dhclient ]]; then echo "DHCP not supported - no /sbin/dhclient found." return 1 fi dhcp_request $_ifs "$_name" || dhcp_request $_ifs || return 1 _config="dhcp NONE NONE" # Fake address for the hosts file. _addr=127.0.0.1 ;; *) _addr=$resp ask_until "Netmask?" "${_mask:=255.255.255.0}" _mask=$resp ifconfig $_ifs inet $_addr netmask $_mask $_media up || return 1 _config="inet $_addr $_mask" ;; esac # Save configuration information. echo "$_config NONE $_media" > /tmp/hostname.$_ifs addhostent $_addr $_name return 0 } # Returns true if $1 contains only alphanumerics isalphanumeric() { local _n _n=$1 while [ ${#_n} != 0 ]; do case $_n in [A-Za-z0-9]*) ;; *) return 1;; esac _n=${_n#?} done return 0 } # Much of this is gratuitously stolen from /etc/netstart. enable_network() { local _netfile # Check for required hosts file. if [ ! -f /mnt/etc/hosts ]; then echo "ERROR: no /mnt/etc/hosts!" return 1 fi # 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 DIDNET=y # set the address for the loopback interface ifconfig lo0 inet localhost # use loopback, not the wire route -n add -host `hostname` localhost > /dev/null route -n add -net 127 127.0.0.1 -reject > /dev/null # 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.} # Interface names must be alphanumeric only. We check to avoid # configuring backup or temp files, and to catch the "*" case. if ! isalphanumeric "$if"; then continue fi ifconfig $if > /dev/null 2>&1 if [ $? -ne 0 ]; then continue fi # 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"|""|"rtsol") # skip comments, user commands, bridges, # IPv6 rtsol and empty lines continue ;; "dhcp") [ "$name" = "NONE" ] && name= [ "$mask" = "NONE" ] && mask= [ "$bcaddr" = "NONE" ] && bcaddr= ifconfig $if $name $mask $bcaddr $ext1 $ext2 down kill_dhclient cmd="dhclient $if" ;; "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 -n add -host $name 127.0.0.1" ;; inet6) # Ignore IPv6 setup continue ;; *) cmd="$cmd $mask $bcaddr" esac cmd="$cmd $ext1 $ext2$rtcmd" rtcmd= ;; esac eval "$cmd" done < /mnt/etc/hostname.$if done # /mnt/etc/mygate, if it exists, contains the name of my gateway host # that name must be in /etc/hosts. if [ -f /mnt/etc/mygate ]; then route delete default > /dev/null 2>&1 route -n add -host default `cat /mnt/etc/mygate` fi # 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 return 0 } # 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 "Ready to $MODE sets?" y case $resp in n*|N*) return ;; esac 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 zxpf - -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 now supported by our in-tree ftp: # # ':' -> '%3a' # '@' -> '%40' # '/' -> '%2f' # # *NOTE* quotes around $1 are required to preserve trailing or # embeddded blanks in usercodes and passwords! encode_for_url() { echo "$1" | sed -e 's/:/%3a/g' -e 's/@/%40/g' -e 's/\//%2f/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, # _ftp_server_password, and _ftp_active must be global. install_url() { local _file_list _url_type=$1 _url_base _url_login _url_pass _oifs donetconfig # Proxy the connections? : ${_proxy_host:=none} ask "HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none')" "$_proxy_host" if [[ $resp == none ]]; then unset _proxy_host ftp_proxy http_proxy else _proxy_host=$resp export ftp_proxy=$_proxy_host export http_proxy=$_proxy_host fi # Provide a list of possible servers : ${_ftp_getlist:=y} ask "Do you want to see a list of potential $_url_type servers?" "$_ftp_getlist" case $resp in n*|N*) _ftp_getlist=n ;; *) _ftp_getlist=y # ftp.openbsd.org == 129.128.5.191 and will remain at # that address for the forseeable future. ftp ${_ftp_active} -V -a -o /tmp/ftplist ftp://129.128.5.191/$FTPDIR/ftplist > /dev/null grep "^${_url_type}:" /tmp/ftplist | cat -n | less -XE ;; esac : ${_ftp_server_dir:=pub/OpenBSD/$SETDIR} # Get server IP address or hostname resp= while [[ -z $resp ]] ; do if [ ! -f /tmp/ftplist ]; then eval ask \"Server IP address, or hostname?\" \"\$_${_url_type}_server_ip\" continue; fi eval ask \"Server IP address, hostname, or list#?\" \"\$_${_url_type}_server_ip\" case $resp in "?") grep "^${_url_type}:" /tmp/ftplist | cat -n | less -XE resp= ;; +([0-9])) maxlines=`grep "^${_url_type}:" /tmp/ftplist | sed -ne '$='` if [ $maxlines -lt $resp -o $resp -lt 1 ]; then echo "There is no ${resp}th line in the list." else tline=`grep "^${_url_type}:" /tmp/ftplist | sed -ne "${resp}p"` echo "Using $tline" url=`echo $tline | sed -e "s/^${_url_type}:\/\///" | cutword -t' ' 1 | cutword -t' ' 1` host=`echo $url | cutword -t/ 1` eval _${_url_type}_server_ip=$host eval _${_url_type}_server_dir=$(echo $url | sed -e "s/^${host}\///")/$SETDIR fi # Always do it again, just to double check resp= ;; *) ;; esac done eval _${_url_type}_server_ip=$resp # 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=n ;; *) resp=y ;; esac ask "Does the server support passive mode ftp?" "$resp" case $resp in n*|N*) _ftp_active=-A ;; *) unset _ftp_active ;; esac fi # Get server directory eval ask_until \"Server directory?\" \"\$_${_url_type}_server_dir\" 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 _ftp_server_password=root@`hostname` if [[ $_ftp_server_login != anonymous ]]; then resp= while [[ -z $resp ]] ; do askpass "Password? (will not echo)" done _ftp_server_password=$resp fi IFS=$_oifs fi # Build up the base url since it is so nasty... if [[ $_url_type == ftp && $_ftp_server_login != anonymous ]]; then _url_login=`encode_for_url "$_ftp_server_login"` _url_pass=`encode_for_url "$_ftp_server_password"` _url_base=ftp://${_url_login}:${_url_pass}@${_ftp_server_ip}/${_ftp_server_dir} else eval _url_base=${_url_type}://\$_${_url_type}_server_ip/\$_${_url_type}_server_dir fi # 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" "$_ftp_server_password" "$_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" } # $1 - mount point directory is relative to # $2 - default directory install_mounted_fs() { local _mp=$1 _dir=$2 while : ; do ask "Pathname to the sets? (or 'done')" "$_dir" case $resp in done) return ;; "") ;; *) if [[ -d $_mp/$resp ]]; then _dir=$_mp/$resp break else echo "The directory '$resp' does not exist." fi ;; esac done install_files "file://$_dir" "`ls -l $_dir`" } install_cdrom() { local _drive _part _fstype _directory _n ask_which "CD-ROM" "contains the ${MODE} media?" "$CDDEVS" "`echo $CDDEVS | cutword 1`" [ "$resp" = "done" ] && return _drive=$resp # If it is an ISO9660 CD-ROM, we don't need to ask any other questions _n=0 until disklabel $_drive >/tmp/label.$_drive 2>&1; do # Try up to 6 times to access the CD if egrep -q '(Input/output error)|(sector size 0)' /tmp/label.$_drive; then _n=$(( $_n + 1 )) if [ _n -le 5 ]; then echo "I/O error accessing $_drive; retrying" sleep 10 else echo "Cannot access $_drive." return fi else break fi done echo if grep -q '^ *c: .*ISO9660' /tmp/label.$_drive; then _fstype=cd9660 _part=c else # Get partition from user resp= while [ -z "$resp" ] ; do ask "CD-ROM partition to mount? (normally 'c')" c case $resp in [a-p]) _part=$resp ;; *) echo "Invalid response: $resp" # force loop to repeat resp= ;; esac done # Ask for filesystem type cat << __EOT Two CD-ROM filesystem types are currently supported by this program: cd9660 ISO-9660 ffs Berkeley Fast Filesystem __EOT resp= while [ -z "$resp" ] ; do ask "Which filesystem type?" cd9660 case $resp in cd9660|ffs) _fstype=$resp ;; *) echo "Invalid response: '$resp'" # force loop to repeat resp= ;; esac done fi rm -f /tmp/label.$_drive # Mount the CD-ROM if ! mount -t ${_fstype} -o ro /dev/${_drive}${_part} /mnt2 ; then echo "Cannot mount CD-ROM drive." return fi install_mounted_fs /mnt2 "$SETDIR" umount -f /mnt2 > /dev/null 2>&1 } # Mount a disk on /mnt2. The set of disk devices to choose from # is $DKDEVS. # returns 0 on success, 1 on failure mount_a_disk() { local _drive _def_partition _partition_range _partition local _fstype _fsopts ask_which "disk" "contains the ${MODE} sets?" "$DKDEVS" "`echo $DKDEVS | cutword 1`" [ "$resp" = "done" ] && return 1 _drive=$resp # Get partition cat << __EOT The following partitions have been found on $_drive: __EOT disklabel $_drive 2>/dev/null | grep '^ .:' echo _likely_partition_range=`disklabel $_drive 2>/dev/null | \ sed -n -e '/swap/s/.*//' -e '/unused/s/.*//' \ -e '/^ .:/{s/^ \(.\).*/\1/;H;}' \ -e '${g;s/\n//g;s/^/[/;s/$/]/p;}'` _partition_range=`disklabel $_drive 2>/dev/null | \ sed -n -e '/^ .:/{s/^ \(.\).*/\1/;H;}' \ -e '${g;s/\n//g;s/^/[/;s/$/]/p;}'` _def_partition=`echo $_likely_partition_range | \ sed -n 's/^\[\(.\).*\]/\1/p'` if [ -z "$_def_partition" ]; then _def_partition=`echo $_partition_range | \ sed -n 's/^\[\(.\).*\]/\1/p'` if [ -z "$_def_partition" ]; then echo "There are no usable partitions on that disk" return 1 fi fi resp= while [ -z "$resp" ]; do ask "Partition?" "$_def_partition" case $resp in $_partition_range) _partition=$resp ;; *) echo "Invalid response: $resp" # force loop to repeat resp= ;; esac done # Ask for filesystem type cat << __EOT The following filesystem types are supported: default (deduced from the disklabel) ffs $MDFSTYPE __EOT resp= while [ -z "$resp" ]; do ask "Which filesystem type?" default case $resp in default) ;; ffs) _fstype="-t ffs" _fsopts=async ;; $MDFSTYPE) _fstype="-t $resp" _fsopts=$MDFSOPTS ;; *) echo "Invalid response: $resp" # force loop to repeat resp= ;; esac done # Mount the disk read-only if ! mount $_fstype -o ro,$_fsopts /dev/${_drive}${_partition} /mnt2; then echo "Cannot mount disk." return 1 fi return 0 } install_disk() { if mount_a_disk; then install_mounted_fs /mnt2 umount -f /mnt2 > /dev/null 2>&1 fi } install_nfs() { # Can we actually mount NFS filesystems? if [ ! -f /sbin/mount_nfs ]; then echo "/sbin/mount_nfs not found. Cannot mount NFS filesystems." return fi donetconfig # Get the IP address of the server ask_until "Server IP address or hostname?" "$_nfs_server_ip" _nfs_server_ip=$resp # Get server path to mount ask_until "Filesystem on server to mount?" "$_nfs_server_path" _nfs_server_path=$resp # Determine use of TCP ask "Use TCP transport? (only works with capable NFS server)" n case $resp in y*|Y*) _nfs_tcp=-T ;; *) _nfs_tcp= ;; esac # Mount the server if ! mount_nfs $_nfs_tcp -o ro ${_nfs_server_ip}:${_nfs_server_path} /mnt2 ; then echo "Cannot mount NFS server." return fi install_mounted_fs /mnt2 umount -f /mnt2 > /dev/null 2>&1 } install_tape() { local _xcmd # Get the name of the tape from the user. cat << __EOT The installation program needs to know which tape device to use. Make sure you use a "no rewind on close" device. __EOT ask_until "Name of tape device?" "${TAPE##*/}" TAPE=/dev/${resp##*/} if [ ! -c $TAPE ]; then echo "$TAPE does not exist or is not a character special file." return fi export TAPE # Rewind the tape device echo -n "Rewinding ${TAPE} (mt rewind)..." if ! mt rewind ; then echo "FAILED." return fi echo "done." # Get the file number resp= while [ -z "$resp" ]; do ask "File number?" case $resp in [1-9]*) _nskip=$(( $resp - 1 )) ;; *) echo "Invalid file number ${resp}." # force loop to repeat resp= ;; esac done # Skip to correct file. if [ $_nskip -ne 0 ]; then echo -n "Skipping to source file (mt fsf ${_nskip})..." if ! mt fsf $_nskip ; then echo "FAILED. Could not skip $_nskip files." return fi echo "done." fi cat << __EOT There are 2 different ways the file can be stored on tape: 1) an image of a gzipped tar file 2) a standard tar image __EOT resp= while [ -z "$resp" ]; do ask "Which way is it?" 1 case $resp in 1) _xcmd="tar -zxvpf -" ;; 2) _xcmd="tar -xvpf -" ;; *) echo "Invalid response: $resp." # force loop to repeat resp= ;; esac ( cd /mnt; dd if=$TAPE | $_xcmd ) done echo "Extraction complete." } 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 | cutlast` 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)" if [ "$resp" = "?" ]; then ls -F ${_zonepath} else _zonepath=${_zonepath}/${resp} fi 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 that required sets were successfully installed by checking # for the presence of a 'random' selection of their contents. # # Required sets are: # 1) bsd # 2) baseXX # 3) etcXX # # If a 'problem' set is found, add it back to DEFAULTSETS. sane_install() { local _insane # Check if bsd is installed and >0 bytes in size. if [[ ! -s /mnt/bsd ]]; then _insane=y DEFAULTSETS=$(addel bsd $DEFAULTSETS) cat << __EOT *** /bsd is not present in the installed system, or is 0 bytes long. OpenBSD cannot boot without a valid kernel! 'bsd' must be (re)installed. __EOT fi # Check if baseXX is installed. if [[ ! -x /mnt/bin/cat || ! -x /mnt/dev/MAKEDEV ]]; then _insane=y DEFAULTSETS=$(addel base${VERSION}.tgz $DEFAULTSETS) cat << __EOT *** One or both of the executable files /bin/cat and /dev/MAKEDEV are not present in the installed system. This indicates that executable files OpenBSD requires are missing. 'base${VERSION}' must be (re)installed. __EOT fi # Check if etcXX is installed. if [[ ! -d /mnt/etc || ! -d /mnt/usr/share/zoneinfo || ! -d /mnt/dev ]]; then _insane=y DEFAULTSETS=$(addel etc${VERSION}.tgz $DEFAULTSETS) cat << __EOT *** One or more of the directories /etc, /usr/share/zoneinfo and /dev are not present in the installed system. This indicates that directories OpenBSD requires are missing. 'etc${VERSION}' must be (re)installed. __EOT fi [[ -n $_insane ]] && 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() { cat << __EOT You will now specify the location and names of the ${MODE} sets you want to load. You will be able to repeat this step until all of your sets have been successfully loaded. If you are not sure what sets to ${MODE}, refer to the installation notes for details on the contents of each. __EOT while : ; do cat << __EOT Sets can be located on a (m)ounted filesystem; a (c)drom, (d)isk or (t)ape device; or a (f)tp, (n)fs or (h)ttp server. __EOT ask "Where are the ${MODE} sets? (or 'done')" case $resp in done) sane_install && return ;; c*|C*) install_cdrom ;; d*|D*) install_disk ;; f*|F*) install_url ftp ;; h*|H*) install_url http ;; m*|M*) install_mounted_fs /mnt ;; n*|N*) install_nfs ;; t*|T*) install_tape ;; *) ;; esac done } # Create a /etc/fstab from /tmp/fstab. Ensure /etc/fstab # 1) contains only filesystems we can mount # 2) contains no altroot filesystem # 3) contains only filesystems without the 'noauto' option # 4) contains no 'softdep' options # 5) mounts all filesystems relative to /mnt # # 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 # Skip comment lines. case $_dev in \#*) continue ;; esac # Skip noauto and altroot filesystems. case $_opt in *noauto*|*xx*) continue ;; esac # Skip filesystems we can't mount. [ -f "/sbin/mount_${_fstype}" ] || continue # Skip nfs filesystems because all name # resolutions (e.g. yp) are not available # and success is not guaranteed. The user # will have to mount nfs filesystems manually. [ "$_fstype" != "nfs" ] || continue # Remove any softdep options, as soft updates are not # available in the ramdisk kernels. _opt="$(echo ${_opt} | \ sed -e 's/^softdep$//; s/^softdep,//; s/,softdep,/,/; s/,softdep$//;')" # Mount non-ffs filesystems read-only [ "$_fstype" = "ffs" ] || \ _opt="$(echo ${_opt} | \ sed -e 's/^rw$/ro/; s/^rw,/ro,/; s/,rw,/,ro,/; s/,rw$/,ro/')" # Avoid '/mnt/' in root fs entry in munged fstab [ "$_mp" = "/" ] && _mp= # Write fs entry in fstab. echo $_dev /mnt$_mp $_fstype $_opt $_rest done < /tmp/fstab > /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 < /etc/fstab } # Try to kill a running dhclient. kill_dhclient () { if [[ -f /var/run/dhclient.pid ]]; then kill -HUP $(sed -ne "1p" /var/run/dhclient.pid) > /dev/null 2>&1 rm -f /var/run/dhclient.pid fi } # Preen all filesystems in /etc/fstab that have a /sbin/fsck_XXX, # showing individual results, but skipping $ROOTDEV. This was already # fsck'ed successfully. # # Exit if any fsck's fail (but do them all before exiting!). check_fs() { local _dev _mp _fstype _rest _fail echo "Checking non-root filesystems..." while read _dev _mp _fstype _rest; do [ "$_dev" != /dev/"$ROOTDEV" ] || continue [ -f "/sbin/fsck_$_fstype" ] || continue # Make sure device exists before fsck'ing it. _rest=${_dev#/dev/} makedev ${_rest%[a-p]} || continue echo -n "fsck -p ${_dev}..." if ! fsck -fp ${_dev} > /dev/null 2>&1; then echo "FAILED. You must fsck $_dev manually." _fail=y else echo "OK." fi done < /etc/fstab echo "...Done." [ "$_fail" ] && exit } # Extract fully qualified domain name from current hostname. If none is # currently set, use 'my.domain'. get_fqdn() { local _dn _dn=$(hostname) _dn=${_dn#$(hostname -s)} _dn=${_dn#.} echo "${_dn:=my.domain}" } donetconfig() { local _dn _ns _dr [[ -n $DIDNET ]] && return DIDNET=y configure_all_interfaces # As dhclient will populate /etc/resolv.conf, a symbolic link to # /tmp/resolv.conf.shadow, mv any such file to /tmp/resolv.conf # so it will eventually be copied to /mnt/etc/resolv.conf and will # not in the meantime remove the user's ability to choose to use it # or not, during the rest of the install. if [ -f /tmp/resolv.conf.shadow ]; then mv /tmp/resolv.conf.shadow /tmp/resolv.conf # Get nameserver address(es). _ns=$(sed -ne '/^nameserver /s///p' /tmp/resolv.conf) # Get default fully qualified domain name from *first* domain # given on *last* search or domain statement. _dn=$(sed -n \ -e '/^domain[[:space:]][[:space:]]*/{s///;s/\([^[:space:]]*\).*$/\1/;h;}' \ -e '/^search[[:space:]][[:space:]]*/{s///;s/\([^[:space:]]*\).*$/\1/;h;}' \ -e '${g;p;}' /tmp/resolv.conf) fi # Get & apply fully qualified domain name to hostname. ask "DNS domain name? (e.g. 'bar.com')" "${_dn:=$(get_fqdn)}" hostname "$(hostname -s).$resp" # Get/Confirm nameservers, and construct appropriate resolv.conf. ask "DNS nameserver? (IP address or 'none')" "${_ns:=none}" if [[ $resp != none ]]; then echo "lookup file bind" > /tmp/resolv.conf for _ns in $resp; do echo "nameserver $_ns" >> /tmp/resolv.conf done ask "Use the nameserver now?" y case $resp in y*|Y*) cp /tmp/resolv.conf /tmp/resolv.conf.shadow ;; esac fi # Get/Confirm the default route. _dr=$(route -n show | sed -ne '/^default */{s///; s/ .*//; p;}') while : ; do ask_until "Default route? (IP address, 'dhcp' or 'none')" "$_dr" case $resp in none|dhcp) break ;; esac route delete default > /dev/null 2>&1 route -n add -host default "$resp" && { echo "$resp" > /tmp/mygate ; break ; } # Put the old default route back. The new one did not work. route -n add -host default $_dr > /dev/null 2>&2 done 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 } set_machdep_apertureallowed() { [ "$MDXAPERTURE" ] || return ask "Do you expect to run the X Window System?" y case $resp in y*|Y*) sed -e "/^#\(machdep\.allowaperture=${MDXAPERTURE}\)/s//\1 /" \ /mnt/etc/sysctl.conf > /tmp/sysctl.conf ;; esac } finish_up() { local _dev set_timezone echo -n "Making all device nodes..." cd /mnt/dev sh MAKEDEV all # Make sure any devices we added as a result of makedev() calls # are recreated in installed system. for _dev in $DEVSMADE; 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=33 VNAME="$(( $VERSION / 10 )).$(( $VERSION % 10 ))" SETDIR="$VNAME/$ARCH" FTPDIR="pub/OpenBSD/$VNAME" OBSD="OpenBSD/$ARCH $VNAME" # 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 disks and cds DKDEVS=`get_dkdevs` CDDEVS=`get_cddevs` IFDEVS=`get_ifdevs` # Devices created with makedev(). DEVSMADE= # 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 $MDSETS" DEFAULTSETS="bsd" for _set in base etc misc comp man game xbase xshare xfont xserv site ; do [[ $MODE == upgrade && $_set == etc ]] && continue THESETS="$THESETS ${_set}${VERSION}.tgz" isin $_set xbase xshare xfont xserv site && continue DEFAULTSETS="$DEFAULTSETS ${_set}${VERSION}.tgz" done # decide upon an editor if [ -z "$EDITOR" ] ; then EDITOR=ed [ -x /usr/bin/vi ] && EDITOR=vi export EDITOR fi # 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 and default ROOTDEV get_rootdisk