# $OpenBSD: install.sub,v 1.355 2004/10/12 21:57:07 krw Exp $ # $NetBSD: install.sub,v 1.5.2.8 1996/09/02 23:25:02 pk Exp $ # # Copyright (c) 1997-2004 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 # 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() { [[ -n $TERM ]] && return ask "Terminal type?" ${MDTERM:-vt220} TERM=$resp export TERM md_set_term } 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. 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..." } 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_tapedevs () { bsort $(egrep "^(ct|mt|st|ts|wt)[[:digit:]]+ " /var/run/dmesg.boot \ | sed -e "s/ .*//p" ) } 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. # NOTE: Only single digit serial devices (0 -> 9) are looked for. get_serialdev() { local _devs _d _bd _td [[ -n $MDSERIAL ]] || exit set -- $MDSERIAL _d=$1 _bd=$2 _td=$3 _devs=$(sed -ne "/^${_d}\([0-9]\) .*/s//\1/p" /var/run/dmesg.boot) _devs=$(bsort $_devs) set -- $_devs 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 "$_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 DEVSMADE=$(addel $_dev $DEVSMADE) } # 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 _action= 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 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" [[ -f $_hn ]] && _IFDEVS=$(rmel "$_ifs" $_IFDEVS) done } # Output ' [ ]'. # # $1 == interface v4_info() { ifconfig $1 inet | sed -n ' 1s/.* /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 dhclient $_ifs set -- $(v4_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 } v4_config() { local _ifs=$1 _media=$2 _name=$3 _hn=$4 _prompt set -- $(v4_info $_ifs) if [[ -n $2 ]]; then ifconfig $_ifs inet $2 delete [[ $2 != "0.0.0.0" ]] && { _addr=$2; _mask=$3; } 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}" if ifconfig $_ifs inet $_addr netmask $resp up ; then addhostent "$_addr" "$_name" echo "inet $_addr $resp NONE $_media" > $_hn fi ;; esac } 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" case $resp in none|dhcp) break ;; esac 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 } # Returns true if $1 contains only alphanumerics isalphanumeric() { local _n=$1 while [[ ${#_n} -ne 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 # 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.} # 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 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 -qn 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 /dev/null 2>&1 route -qn add -host default $(< /mnt/etc/mygate) fi # 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 # 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" } # $1 - mount point directory is relative to # $2 - default directory install_mounted_fs() { local _mp=$1 _dir=$2 while : ; do ask_until "Pathname to the sets? (or 'done')" "$_dir" case $resp in done) return ;; *) # Accept a valid $_mp relative path. [[ -d $_mp/$resp ]] && { _dir=$_mp/$resp ; break ; } # Accept a valid absolute path. [[ -d /$resp ]] && { _dir=/$resp ; break ; } # Otherwise ask again, with original default dir. echo "The directory '$resp' does not exist." ;; esac done install_files "file://$_dir" "$(ls -l $_dir)" } install_cdrom() { local _drive _part=c _fstype="-t cd9660" get_drive "CD-ROM" "$CDDEVS" || return _drive=$resp # Only ask detail questions for non-ISO9660 CD-ROMs. if [[ -z $(disklabel $_drive 2>&1 | grep '^ *c: .*ISO9660') ]]; then unset _fstype get_partition $_drive "cd9660" || return set -- $resp _part=$1 [[ -n $2 ]] && _fstype="-t $2" fi mount $_fstype -o ro /dev/$_drive$_part /mnt2 || return install_mounted_fs /mnt2 "$SETDIR" umount -f /mnt2 > /dev/null 2>&1 } install_disk() { local _drive _part _fstype _fsopts get_drive "disk" "$DKDEVS" || return _drive=$resp get_partition $_drive "$MDFSTYPE" || return set -- $resp _part=$1 [[ -n $2 ]] && _fstype="-t $2" [[ $2 == $MDFSTYPE ]] && _fsopts=$MDFSOPTS mount $_fstype -o ro,$_fsopts /dev/$_drive$_part /mnt2 || return install_mounted_fs /mnt2 umount -f /mnt2 > /dev/null 2>&1 } 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 # 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 _nfs_tcp= ask_yn "Use TCP transport? (only works with capable NFS server)" [[ $resp == y ]] && _nfs_tcp=-T # 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 _z _bs # Can't use tape without /bin/mt. if [[ ! -x /bin/mt ]]; then echo "/bin/mt not found. Can't $MODE from tape." return fi # Get the name of the tape device. get_drive "tape drive" "$TAPEDEVS" || 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 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' must be (re)installed: no kernel found. __EOT fi # Check if baseXX is installed. if [[ ! -d /mnt/sbin || ! -d /mnt/dev ]]; then _insane=y DEFAULTSETS=$(addel base${VERSION}.tgz $DEFAULTSETS) cat << __EOT +*** 'base${VERSION}.tgz' must be (re)installed: /sbin or /dev is missing. __EOT fi # Check if etcXX is installed. if [[ ! -s /mnt/etc/rc ]]; then _insane=y DEFAULTSETS=$(addel etc${VERSION}.tgz $DEFAULTSETS) cat << __EOT +*** 'etc${VERSION}.tgz' must be (re)installed: /etc/rc is missing. __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() { local _d 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 _d= 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 [[ -z $DEFAULTSETS ]] && _d=done ask "Where are the $MODE sets? (or 'done')" "$_d" 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 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 < /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 } # 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 configure_ifs # 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_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 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 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 /var/run/dmesg.boot # Scan /var/run/dmesg.boot for interesting devices. DKDEVS=$(get_dkdevs) CDDEVS=$(get_cddevs) IFDEVS=$(get_ifdevs) TAPEDEVS=$(get_tapedevs) SERIALDEV=$(get_serialdev) # 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 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 # 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