Permalink
Switch branches/tags
Nothing to show
Find file Copy path
3368 lines (2867 sloc) 96.3 KB
#!/bin/ksh
# $OpenBSD: install.sub,v 1.1104 2018/09/22 09:12:36 fcambus Exp $
#
# Copyright (c) 1997-2015 Todd Miller, Theo de Raadt, Ken Westerback
# Copyright (c) 2015, Robert Peichaer <rpe@openbsd.org>
#
# 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.
#
# 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
# ------------------------------------------------------------------------------
# Misc functions
# ------------------------------------------------------------------------------
# Print error message to stderr and exit the script.
err_exit() {
print -u2 -- "$*"
exit 1
}
# Show usage of the installer script and exit.
usage() {
err_exit "usage: ${0##*/} [-a] [-f filename] [-m install | upgrade]"
}
# Wait for the ftp(1) process started in start_cgiinfo() to end and extract
# various informations from the ftplist.cgi output.
wait_cgiinfo() {
local _l _s _key _val
wait "$CGIPID" 2>/dev/null
# Ensure, there is actual data to extract info from.
[[ -s $CGI_INFO ]] || return
# Extract the list of mirror servers.
sed -En 's,^https?://([[A-Za-z0-9:_][]A-Za-z0-9:._-]*),\1,p' \
$CGI_INFO >$HTTP_LIST 2>/dev/null
# Extract the previously selected mirror server (first entry in the
# ftplist.cgi output, if that has no location info).
read -r -- _s _l <$HTTP_LIST
[[ -z $_l ]] && : ${HTTP_SERVER:=${_s%%/*}}
# Extract the previously used install method, timezone information
# and a reference timestamp.
while IFS='=' read -r -- _key _val; do
case $_key=$_val in
method=+([a-z])*([0-9])) CGI_METHOD=$_val;;
TIME=+([0-9])) CGI_TIME=$_val;;
TZ=+([-_/+[:alnum:]])) CGI_TZ=$_val;;
esac
done <$CGI_INFO
}
# ------------------------------------------------------------------------------
# Utils functions
# ------------------------------------------------------------------------------
# Sort and print unique list of provided arguments.
bsort() {
local _a=$1 _b _l
(($#)) && shift || return
for _b; do
[[ $_a == "$_b" ]] && continue
if [[ $_a > $_b ]]; then
_l="$_a $_l" _a=$_b
else
_l="$_b $_l"
fi
done
# Output the smallest value found.
(($#)) && echo -n "$_a " || echo -n "$_a"
# Sort remaining values.
bsort $_l
}
# 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
isin "$_a" $* && echo -n "$*" || echo -n "${*:+$* }$_a"
}
# Remove all occurrences of first argument from list formed by the remaining
# arguments.
rmel() {
local _a=$1 _b _c
shift
for _b; do
[[ $_a != "$_b" ]] && _c="${_c:+$_c }$_b"
done
echo -n "$_c"
}
# If possible, print the timestamp received from the ftplist.cgi output,
# adjusted with the time elapsed since it was received.
http_time() {
local _sec=$(cat $HTTP_SEC 2>/dev/null)
[[ -n $_sec && -n $CGI_TIME ]] &&
echo $((CGI_TIME + SECONDS - _sec))
}
# Prints the supplied parameters properly escaped for future sh/ksh parsing.
# Quotes are added if needed, so you should not do that yourself.
quote() (
# Since this is a subshell we won't pollute the calling namespace.
for _a; do
alias Q=$_a; _a=$(alias Q); print -rn -- " ${_a#Q=}"
done | sed '1s/ //'
echo
)
# Show a list of ordered arguments (read line by line from stdin) in column
# output using ls.
show_cols() {
local _l _cdir=/tmp/i/cdir _clist
mkdir -p $_cdir
rm -rf -- $_cdir/*
while read _l; do
[[ -n $_l ]] || continue
mkdir -p /tmp/i/cdir/"$_l"
_clist[${#_clist[*]}]="$_l"
done
(cd $_cdir; ls -Cdf "${_clist[@]}")
rm -rf -- $_cdir
}
# Echo file $1 to stdout. Skip comment lines and delete everything
# after the first '#' from other lines. Strip leading and trailing
# whitespace if IFS is set.
stripcom() {
local _file=$1 _line
[[ -f $_file ]] || return
set -o noglob
while read _line; do
[[ -n ${_line%%#*} ]] && echo $_line
done <$_file
set +o noglob
}
# Create a temporary directory based on the supplied directory name prefix.
tmpdir() {
local _i=1 _dir
until _dir="${1?}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do
((++_i < 10000)) || return 1
done
echo "$_dir"
}
# Generate unique filename based on the supplied filename $1.
unique_filename() {
local _fn=$1 _ufn
while _ufn=${_fn}.$RANDOM && [[ -e $_ufn ]]; do done
print -- "$_ufn"
}
# Let rc.firsttime feed file $1 using $2 as subject to whatever mail system we
# have at hand by then.
prep_root_mail() {
local _fn=$1 _subject=$2 _ufn
[[ -s $_fn ]] || return
_ufn=$(unique_filename /mnt/var/log/${_fn##*/})
cp $_fn $_ufn
chmod 600 $_ufn
_ufn=${_ufn#/mnt}
cat <<__EOT >>/mnt/etc/rc.firsttime
( /usr/bin/mail -s '$_subject' root <$_ufn && rm $_ufn ) >/dev/null 2>&1 &
__EOT
}
# Examine the contents of the DHCP lease file $1 for a line containing the
# field provided as parameters and return the value of the first field found.
#
# Note that strings are unescaped but not unvis()'d.
lease_value() {
local _lf=$1 _o
[[ -s $_lf ]] || return
shift
for _o; do
sed -E \
-e '/^ *(option )?'"$_o"' (.*);$/!d;s//\2/' \
-e '/^"(.*)"$/{s//\1/;s/\\(.)/\1/g;};q' "$_lf" \
| grep ^ && return
done
}
# Extract one boot's worth of dmesg.
dmesgtail() {
dmesg | sed -n 'H;/^OpenBSD/h;${g;p;}'
}
# ------------------------------------------------------------------------------
# Device related functions
# ------------------------------------------------------------------------------
# Show device name, info, NAA and size for the provided list of disk devices.
# Create device nodes as needed and cleanup afterwards.
diskinfo() {
local _d _i _n _s
for _d; do
# Extract disk information enclosed in <> from dmesg.
_i=$(dmesg | sed -n '/^'$_d' at /h;${g;s/^.*<\(.*\)>.*$/\1/p;}')
_i=${_i##+([[:space:],])}
_i=${_i%%+([[:space:],])}
# Extract Network Address Authority information from dmesg.
_n=$(dmesg | sed -En '/^'$_d' at /h;${g;s/^.* ([a-z0-9]+\.[a-zA-Z0-9_]+)$/\1/p;}')
# Extract disk size from disklabel output.
make_dev $_d
_s=$(disklabel -dpg $_d 2>/dev/null | sed -n '/.*# total bytes: \(.*\)/{s//(\1)/p;}')
rm -f /dev/{r,}$_d?
echo "$_d: $_i $_n $_s"
done
}
# Create devices passed as arguments.
make_dev() {
[[ -z $(cd /dev && sh MAKEDEV "$@" 2>&1) ]]
}
# Sort and print information from dmesg.boot using sed expression $1.
scan_dmesg() {
bsort $(sed -n "$1" /var/run/dmesg.boot)
}
# Extract device names from hw.disknames matching sed expression $1.
scan_disknames() {
local IFS=, _disks=$(sysctl -n hw.disknames)
bsort $(for _n in $_disks; do echo "${_n%%:*} "; done | sed -n "$1")
}
# Return disk devices found in hw.disknames.
get_dkdevs() {
echo $(scan_disknames "${MDDKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}")
}
# Return CDROM devices found in hw.disknames.
get_cddevs() {
echo $(scan_disknames "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}")
}
# Return sorted list of disks not in DISKS_DONE which contains disks already
# initialized during installation.
get_dkdevs_uninitialized() {
local _disks=$(get_dkdevs) _d
for _d in $DISKS_DONE; do
_disks=$(rmel "$_d" $_disks)
done
bsort $_disks
}
# Return list of all network devices, optionally limited by parameters to
# ifconfig. Filter out dynamically created network pseudo-devices except vlan.
get_ifs() {
local _if _if_list=$(rmel vlan $(ifconfig -C))
for _if in $(ifconfig "$@" 2>/dev/null | sed '/^[a-z]/!d;s/:.*//'); do
isin "${_if%%+([0-9])}" $_if_list || echo $_if
done
}
# Return the device name of the disk device $1, which may be a disklabel UID.
get_dkdev_name() {
local _dev=${1#/dev/} _d
_dev=${_dev%.[a-p]}
((${#_dev} < 16)) && _dev=${_dev%[a-p]}
local IFS=,
for _d in $(sysctl -n hw.disknames); do
[[ $_dev == @(${_d%:*}|${_d#*:}) ]] && echo ${_d%:*} && break
done
}
# Inspect disk $1 if it has a partition-table of type $2 and optionally
# if it has a partition of type $3.
disk_has() {
local _disk=$1 _pttype=$2 _part=$3 _cmd _p_pttype _p_part
[[ -n $_disk && -n $_pttype ]] || exit
# Commands to inspect disk. Default: "fdisk $_disk"
local _c_hfs="pdisk -l $_disk"
local _c_sr="bioctl -q $_disk"
# Patterns for partition-table-types and partition-types.
local _p_gpt='Usable LBA:'
local _p_gpt_openbsd='^[ *]...: OpenBSD '
local _p_gpt_efisys='^[ *]...: EFI Sys '
local _p_hfs='^Partition map '
local _p_hfs_openbsd=' OpenBSD OpenBSD '
local _p_mbr='Signature: 0xAA55'
local _p_mbr_openbsd='^..: A6 '
local _p_mbr_dos='^..: 06 '
local _p_mbr_dos_active='^\*.: 06 '
local _p_mbr_linux='^..: 83 '
local _p_sr='OPENBSD, SR'
local _p_sr_crypto='OPENBSD, SR CRYPTO'
# Compose command and patterns based on the parameters.
eval "_cmd=\"\$_c_${_pttype}\""
eval "_p_pttype=\"\$_p_${_pttype}\""
eval "_p_part=\"\$_p_${_pttype}_${_part}\""
# Set the default command if none was defined before.
_cmd=${_cmd:-fdisk $_disk}
# Abort in case of undefined patterns.
[[ -z $_p_pttype ]] && exit
[[ -n $_part && -z $_p_part ]] && exit
if [[ -z $_p_part ]]; then
$_cmd 2>/dev/null | grep -Eq "$_p_pttype"
else
$_cmd 2>/dev/null | grep -Eq "$_p_pttype" &&
$_cmd 2>/dev/null | grep -Eq "$_p_part"
fi
}
# Handle disklabel auto-layout for the root disk $1 during interactive install
# and autopartitioning during unattended install by asking for and downloading
# autopartitioning template. Write the resulting fstab to $2. Abort unattended
# installation if autopartitioning fails.
disklabel_autolayout() {
local _disk=$1 _f=$2 _dl=/tmp/i/disklabel.auto _op _qst
# Skip disklabel auto-layout for any disk except the root disk.
[[ $_disk != $ROOTDISK ]] && return
while $AI; do
ask "URL to autopartitioning template for disklabel?" none
[[ $resp == none ]] && break
if ! $FTP_TLS && [[ $resp == https://* ]]; then
err_exit "https not supported on this platform."
fi
echo "Fetching $resp"
if unpriv ftp -Vo - "$resp" >$_dl && [[ -s $_dl ]]; then
disklabel -T $_dl -F $_f -w -A $_disk && return
err_exit "Autopartitioning failed."
else
err_exit "No autopartitioning template found."
fi
done
_qst="Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout?"
while :; do
echo "The auto-allocated layout for $_disk is:"
disklabel -h -A $_disk | egrep "^# |^ [a-p]:"
ask "$_qst" a
case $resp in
[aA]*) _op=-w;;
[eE]*) _op=-E;;
[cC]*) return 0;;
*) continue;;
esac
disklabel -F $_f $_op -A $_disk
return
done
}
# Create a partition table and configure the partition layout for disk $1.
configure_disk() {
local _disk=$1 _fstab=/tmp/i/fstab.$1 _opt
make_dev $_disk || return
# Deal with disklabels, including editing the root disklabel
# and labeling additional disks. This is machine-dependent since
# some platforms may not be able to provide this functionality.
# /tmp/i/fstab.$_disk is created here with 'disklabel -F'.
rm -f /tmp/i/*.$_disk
md_prep_disklabel $_disk || return
# Make sure a '/' mount point exists on the root disk.
if ! grep -qs ' / ffs ' /tmp/i/fstab.$ROOTDISK; then
echo "'/' must be configured!"
$AI && exit 1 || return 1
fi
if [[ -f $_fstab ]]; then
# Avoid duplicate mount points on different disks.
while read _pp _mp _rest; do
# Multiple swap partitions are ok.
if [[ $_mp == none ]]; then
echo "$_pp $_mp $_rest" >>/tmp/i/fstab
continue
fi
# Non-swap mountpoints must be in only one file.
if [[ $_fstab != $(grep -l " $_mp " /tmp/i/fstab.*) ]]; then
_rest=$_disk
_disk=
break
fi
done <$_fstab
# Duplicate mountpoint.
if [[ -z $_disk ]]; then
# Allow disklabel(8) to read back mountpoint info
# if it is immediately run against the same disk.
cat /tmp/i/fstab.$_rest >/etc/fstab
rm /tmp/i/fstab.$_rest
set -- $(grep -h " $_mp " /tmp/i/fstab.*[0-9])
echo "$_pp and $1 can't both be mounted at $_mp."
$AI && exit 1 || return 1
fi
# Add ffs filesystems to list after newfs'ing them. Ignore
# other filesystems.
while read _pp _mp _fstype _rest; do
[[ $_fstype == ffs ]] || continue
# Use machine-dependent newfs options for the root
# partition if defined.
_opt=
[[ $_mp == / ]] && _opt=$MDROOTFSOPT
newfs -q $_opt ${_pp##/dev/}
# N.B.: '!' is lexically < '/'.
# That is required for correct sorting of mount points.
FSENT="$FSENT $_mp!$_pp"
done <$_fstab
fi
return 0
}
# ------------------------------------------------------------------------------
# Functions for the dmesg listener
# ------------------------------------------------------------------------------
# Acquire lock.
lock() {
while ! mkdir /tmp/i/lock 2>/dev/null && sleep .1; do done
}
# Release lock.
unlock() {
rm -df /tmp/i/lock 2>/dev/null
}
# Add a trap to kill the dmesg listener co-process on exit of the installer.
retrap() {
trap 'kill -KILL $CPPID 2>/dev/null; echo; stty echo; exit 0' \
INT EXIT TERM
}
# Start a listener process looking for dmesg changes which indicates a possible
# plug-in/-out of devices (e.g. usb disks, cdroms, etc.). This is used to abort
# and redraw question prompts, especially in ask_which().
start_dmesg_listener() {
local _update=/tmp/i/update
# Ensure the lock is initially released and that no update files exists.
unlock
rm -f $_update
# Do not start the listener if in non-interactive mode.
$AI && return
# To ensure that only one dmesg listener instance can be active, run it
# in a co-process subshell of which there can always only be one active.
(
while :; do
lock
# The dmesg listener will continously check for the existence of
# the update file and sends a signal to the parent process (that
# is the installer script) if the dmesg output differs from the
# contents of that file.
if [[ -e $_update && "$(dmesgtail)" != "$(<$_update)" ]]; then
dmesgtail >$_update
kill -TERM 2>/dev/null $$ || exit 1
fi
unlock
sleep .5
done
) |&
# Save the co-process PID in a global variable so it can be used in
# the retrap() function which adds a trap to kill the co-process on
# exit of the installer script.
CPPID=$!
retrap
}
# ------------------------------------------------------------------------------
# Functions to ask (or auto-answer) questions
# ------------------------------------------------------------------------------
# Log installer questions and answers so that the resulting file can be used as
# response file for an unattended install/upgrade.
log_answers() {
if [[ -n $1 && -n $2 ]]; then
print -r -- "${1%%'?'*} = $2" >>/tmp/i/$MODE.resp
fi
}
# Fetch response file for autoinstall.
get_responsefile() {
local _rf _if _lf _path _aifile
export AI_HOSTNAME= AI_MAC= AI_MODE= AI_SERVER=
[[ -f /auto_upgrade.conf ]] && _rf=/auto_upgrade.conf AI_MODE=upgrade
[[ -f /auto_install.conf ]] && _rf=/auto_install.conf AI_MODE=install
[[ -f $_rf ]] && cp $_rf /tmp/ai/ai.$AI_MODE.conf && return
for _if in ''; do
[[ -x /sbin/dhclient ]] || break
# Select a network interface for initial dhcp request.
# Prefer the interface the system netbooted from.
set -- $(get_ifs netboot)
(($# == 0)) && set -- $(get_ifs)
(($# == 1)) && _if=$1
# Ask if multiple were found and system was not netbooted.
while (($# > 1)); do
ask_which "network interface" \
"should be used for the initial DHCP request" \
"$*"
isin "$resp" $* && _if=$resp && break
done
# Issue initial dhcp request via the found interface.
[[ -n $_if ]] && dhclient $_if || break
_lf=/var/db/dhclient.leases.$_if
# Extract installer mode and response file path from lease file.
_aifile=$(lease_value $_lf filename bootfile-name)
[[ $_aifile == ?(*/)auto_@(install|upgrade) ]] || _aifile=
_path=${_aifile%auto_@(install|upgrade)}
AI_MODE=${_aifile##*?(/)auto_}
# Extract install server ip address from lease file.
AI_SERVER=$(lease_value $_lf \
server-name tftp-server-name next-server)
# Prime hostname with host-name option from lease file.
AI_HOSTNAME=$(lease_value $_lf host-name)
hostname "$AI_HOSTNAME"
done
# Try to fetch mac-mode.conf, then hostname-mode.conf, and finally
# mode.conf if install server and mode are known, otherwise tell which
# one was missing.
if [[ -n $AI_SERVER && -n $AI_MODE ]]; then
AI_MAC=$(ifconfig $_if | sed 's/.*lladdr \(.*\)/\1/p;d')
for _rf in {$AI_MAC-,${AI_HOSTNAME:+$AI_HOSTNAME-,}}$AI_MODE; do
# Append HTTP_SETDIR as parameter to _url which can be
# used by the webserver to return dynamically created
# response files.
_url="http://$AI_SERVER/$_path$_rf.conf?path=$HTTP_SETDIR"
echo "Fetching $_url"
if unpriv ftp -Vo - "$_url" \
>"/tmp/ai/ai.$AI_MODE.conf" 2>/dev/null; then
ifconfig $_if delete down 2>/dev/null
return 0
fi
done
else
[[ -z $AI_SERVER ]] && echo "Could not determine auto server."
[[ -z $AI_MODE ]] && echo "Could not determine auto mode."
fi
# Ask for url or local path to response file. Provide a default url if
# server was found in lease file.
while :; do
ask "Response file location?" \
"${AI_SERVER:+http://$AI_SERVER/install.conf}"
[[ -n $resp ]] && _rf=$resp && break
done
# Ask for the installer mode only if auto-detection failed.
AI_MODE=$(echo "$_rf" | sed -En 's/^.*(install|upgrade).conf$/\1/p')
while [[ -z $AI_MODE ]]; do
ask "(I)nstall or (U)pgrade?"
[[ $resp == [iI]* ]] && AI_MODE=install
[[ $resp == [uU]* ]] && AI_MODE=upgrade
done
echo "Fetching $_rf"
[[ -f $_rf ]] && _rf="file://$_rf"
if unpriv ftp -Vo - "$_rf" >"/tmp/ai/ai.$AI_MODE.conf" 2>/dev/null; then
ifconfig $_if delete down 2>/dev/null
return 0
fi
return 1
}
# Find a response to question $1 in $AI_RESPFILE and return it via $resp.
# Return default answer $2 if provided and none is found in the file.
#
# Move the existing ai.conf file to a tmp file, read from it line by line
# and write a new ai.conf file skipping the line containing the response.
#
# 1) skip empty and comment lines and lines without =
# 2) split question (_key) and answer (_val) at leftmost =
# 3) strip leading/trailing blanks
# 4) compare questions case insensitive (typeset -l)
#
_autorespond() {
typeset -l _q=$1 _key
local _def=$2 _l _val
[[ -f $AI_RESPFILE && -n $_q ]] || return
mv /tmp/ai/ai.conf /tmp/ai/ai.conf.tmp
while IFS=' ' read -r _l; do
[[ $_l == [!#=]*=?* ]] || continue
_key=${_l%%*([[:blank:]])=*}
_val=${_l##*([!=])=*([[:blank:]])}
[[ $_q == @(|*[[:blank:]])"$_key"@([[:blank:]?]*|) ]] &&
resp=$_val && cat && return
print -r " $_l"
done </tmp/ai/ai.conf.tmp >/tmp/ai/ai.conf
[[ -n $_def ]] && resp=$_def && return
err_exit "\nQuestion has no answer in response file."
}
# Capture user response either by issuing an interactive read or by searching
# the response file and store the response in the global variable $resp.
#
# Optionally present a question $1 and a default answer $2 shown in [].
#
# If the dmesg output is changed while waiting for the interactive response,
# the current read will be aborted and the function will return a non-zero
# value. Normally, the caller function will then reprint any prompt and call
# the function again.
_ask() {
local _q=$1 _def=$2 _int _redo=0 _pid
lock; dmesgtail >/tmp/i/update; unlock
echo -n "${_q:+$_q }${_def:+[$_def] }"
_autorespond "$_q" "$_def" && echo "$resp" && return
trap "_int=1" INT
trap "_redo=1" TERM
read resp
lock; rm /tmp/i/update; unlock
if ((_redo)); then
stty raw
stty -raw
else
case $resp in
!) echo "Type 'exit' to return to install."
sh
_redo=1
;;
!*) eval "${resp#?}"
_redo=1
;;
esac
fi
retrap
((_int)) && kill -INT $$
: ${resp:=$_def}
return $_redo
}
# Ask for user response to question $1 with an optional default answer $2.
# Write the question and the answer to a logfile.
ask() {
# Prompt again in case the dmesg listener detected a change.
while ! _ask "$1" "$2"; do done
log_answers "$1" "$resp"
}
# Ask the user a yes/no question $1 with 'no' as default answer unless $2 is
# set to 'yes' and insist on 'y', 'yes', 'n' or 'no' as response.
# Return response via $resp as 'y' with exit code 0 or 'n' with exit code 1.
ask_yn() {
local _q=$1 _a=${2:-no}
typeset -l _resp
while :; do
ask "$_q" "$_a"
_resp=$resp
case $_resp in
y|yes) resp=y; return 0;;
n|no) resp=n; return 1;;
esac
echo "'$resp' is not a valid choice."
$AI && exit 1
done
}
# Ask for the user to select one value from a list, or 'done'.
# At exit $resp holds selected item, or 'done'.
#
# Parameters:
#
# $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
#
# N.B.! $3 and $4 will be "expanded" using eval, so be sure to escape them
# if they contain spooky stuff
ask_which() {
local _name=$1 _query=$2 _list=$3 _def=$4 _dynlist _dyndef _key _q
_key=$(echo "$_name" | sed 's/[^[:alnum:]]/_/g')
while :; do
eval "_dynlist=\"$_list\""
eval "_dyndef=\"$_def\""
# Clean away whitespace and determine the default.
set -o noglob
set -- $_dyndef; _dyndef="$1"
set -- $_dynlist; _dynlist="$*"
set +o noglob
(($# < 1)) && resp=done && return
: ${_dyndef:=$1}
echo "Available ${_name}s are: $_dynlist."
_q="Which $_name $_query?"
echo -n "$_q (or 'done') ${_dyndef:+[$_dyndef] }"
_autorespond "$_q" "${_dyndef-done}" && echo "$resp" \
|| _ask || continue
[[ -z $resp ]] && resp="$_dyndef"
# Quote $resp to prevent user from confusing isin() by
# entering something like 'a a'.
if isin "$resp" $_dynlist done; then
log_answers "$_q" "$resp"
break
fi
echo "'$resp' is not a valid choice."
$AI && [[ -n $AI_RESPFILE ]] && exit 1
done
}
# Ask for user response to question $1 with an optional default answer $2
# until a non-empty reply is entered.
ask_until() {
resp=
while :; do
ask "$1" "$2"
[[ -n $resp ]] && break
echo "A response is required."
$AI && exit 1
done
}
# Capture a user password and save it in $resp, optionally showing prompt $1.
#
# 1) *Don't* allow the '!' options that ask does.
# 2) *Don't* echo input.
# 3) *Don't* interpret "\" as escape character.
# 4) Preserve whitespace in input
#
ask_pass() {
stty -echo
IFS= read -r resp?"$1 "
stty echo
echo
}
# Ask for a password twice showing prompt $1. Ensure both inputs are identical
# and save it in $_password.
ask_password() {
local _q=$1
if $AI; then
echo -n "$_q "
_autorespond "$_q"
echo '<provided>'
_password=$resp
return
fi
while :; do
ask_pass "$_q (will not echo)"
_password=$resp
ask_pass "$_q (again)"
[[ $resp == "$_password" ]] && break
echo "Passwords do not match, try again."
done
}
# ------------------------------------------------------------------------------
# Support functions for donetconfig()
# ------------------------------------------------------------------------------
# Issue a DHCP request to configure interface $1 and add it to group 'dhcp' to
# later be able to identify DHCP configured interfaces.
dhcp_request() {
local _if=$1
echo "lookup file bind" >/etc/resolv.conf.tail
ifconfig $_if group dhcp >/dev/null 2>&1
dhclient -c /dev/stdin $_if <<__EOT
initial-interval 1;
backoff-cutoff 2;
reboot 5;
timeout 10;
__EOT
# Move resolv.conf to where it will be copied to the installed system.
mv /etc/resolv.conf.tail /tmp/i/resolv.conf.tail
}
# Obtain and output the inet information related to interface $1.
# Outputs one of:
# DOWN
# UP
# UP\n<addr> <netmask> <rest of inet line>[\n<more inet lines>]
v4_info() {
ifconfig $1 inet | sed -n '
1s/.*<UP,.*/UP/p
1s/.*<.*/DOWN/p
/inet/s/netmask //
/.inet /s///p'
}
# Convert a netmask in hex format ($1) to dotted decimal format.
hextodec() {
set -- $(echo ${1#0x} | sed 's/\(..\)/0x\1 /g')
echo $(($1)).$(($2)).$(($3)).$(($4))
}
# Create an entry in the hosts file using IP address $1 and symbolic name $2.
# Treat $1 as IPv6 address if it contains ':', otherwise as IPv4. If an entry
# with the same name and address family already exists, delete it first.
add_hostent() {
local _addr=$1 _name=$2 _delim="."
[[ -z $_addr || -z $_name ]] && return
[[ $_addr == *:* ]] && _delim=":"
sed -i "/^[0-9a-fA-F]*[$_delim].*[ ]$_name\$/d" \
/tmp/i/hosts 2>/dev/null
echo "$_addr $_name" >>/tmp/i/hosts
}
# Configure VLAN interface $1 and create the corresponding hostname.if(5) file.
# Ask the user what parent network interface and vnetid to use.
vlan_config() {
local _if=$1 _hn=/tmp/i/hostname.$1 _hn_vd _vd _vdvi _vdvi_used _vi
local _sed_vdvi='s/.encap: vnetid ([[:alnum:]]+) parent ([[:alnum:]]+)/\2:\1/p'
# Use existing parent device and vnetid for this interface as default in
# case of a restart.
_vdvi=$(ifconfig $_if 2>/dev/null | sed -En "$_sed_vdvi")
_vd=${_vdvi%%:*}
_vi=${_vdvi##*:}
# Use the vlan interface minor as the default vnetid. If it's 0, set it
# to 'none' which equals to the default vlan.
if [[ $_vi == @(|none) ]]; then
((${_if##vlan} == 0)) && _vi=none || _vi=${_if##vlan}
fi
# Use the first non vlan interface as the default parent.
if [[ $_vd == @(|none) ]]; then
_vd=$(get_ifs | sed '/^vlan/d' | sed q)
fi
ask "Which interface:tag should $_if be on?" "$_vd:$_vi"
_vd=${resp%%:*}
_vi=${resp##*:}
# Ensure that the given parent is an existing (non vlan) interface.
if ! isin "$_vd" $(get_ifs | sed '/^vlan/d'); then
echo "Invalid parent interface choice '$_vd'."
return 1
fi
# Get a list of parent:vnetid tuples of all configured vlan interfaces.
_vdvi_used=$(ifconfig vlan 2>/dev/null | sed -En "$_sed_vdvi")
# Ensure that the given vnetid is not already configured on the given
# parent interface.
for _vdvi in $_vdvi_used; do
if [[ $_vdvi == $_vd:* && ${_vdvi##*:} == $_vi ]]; then
echo "vlan tag '$_vi' already used on parent '$_vd'"
return 1
fi
done
# Further ensure that the given vnetid is 'none', or within 1-4095.
if [[ $_vi == none ]]; then
_vi="-vnetid"
elif (($_vi > 0 && $_vi < 4096)); then
_vi="vnetid $_vi"
else
echo "Invalid vlan tag '$_vi'."
return 1
fi
# Write the config to the hostname.if files and set proper permissions.
_hn_vd=/tmp/i/hostname.$_vd
grep -qs "^up" $_hn_vd || echo up >>$_hn_vd
echo "$_vi parent $_vd" >>$_hn
chmod 640 $_hn_vd $_hn
# Bring up the parent interface and configure the vlan interface.
ifconfig $_vd up
ifconfig $_if destroy >/dev/null 2>&1
ifconfig $_if create >/dev/null 2>&1
ifconfig $_if $_vi parent $_vd
}
# Configure IPv4 interface $1, add hostname $2 to the hosts file and create the
# hostname.if file $3. Ask the user for the IPv4 address and network mask if the
# address was not in specified in CIDR notation, unless he chooses 'dhcp'.
v4_config() {
local _if=$1 _name=$2 _hn=$3 _prompt _addr _mask
# Preset the default answers by preserving possibly existing
# configuration from previous runs.
if ifconfig $_if | grep -q 'groups:.* dhcp'; then
_addr=dhcp
else
set -- $(v4_info $_if)
if [[ -n $2 ]]; then
_addr=$2; _mask=$(hextodec $3)
ifconfig $_if inet $_addr delete
fi
fi
if [[ -x /sbin/dhclient ]]; then
_prompt="or 'dhcp' "
# Don't make 'dhcp' the default if dhcp was already used.
ifconfig dhcp >/dev/null 2>&1 || _addr=dhcp
fi
_prompt="IPv4 address for $_if? (${_prompt}or 'none')"
while :; do
ask_until "$_prompt" "$_addr"
case $resp in
none) return
;;
dhcp) if [[ ! -x /sbin/dhclient ]]; then
echo "DHCP not possible - no /sbin/dhclient."
$AI && exit 1 || continue
else
dhcp_request $_if
echo "dhcp" >>$_hn
return
fi
;;
*) _addr=$resp
ifconfig $_if -group dhcp >/dev/null 2>&1
;;
esac
# Ask for the netmask if the user did not use CIDR notation.
if [[ $_addr == */* ]]; then
_mask=
else
ask_until "Netmask for $_if?" "${_mask:=255.255.255.0}"
_mask=$resp
fi
if ifconfig $_if inet $_addr ${_mask:+netmask $_mask} up; then
echo "inet $_addr $_mask" >>$_hn
add_hostent "${_addr%%/*}" "$_name"
break
else
_addr=
_mask=
$AI && exit 1
fi
done
}
# Obtain and output the inet6 information related to interface $1.
# Outputs one of:
# DOWN
# UP
# UP\n<addr> <prefixlen> <rest of inet6 line>[\n<more inet6 lines>]
v6_info() {
ifconfig $1 inet6 | sed -n '
1s/.*<UP,.*/UP/p
1s/.*<.*/DOWN/p
/scopeid/d
/inet6/s/prefixlen //
/.inet6 /s///p'
}
# Configure an IPv6 default route on interface $1 and preserve that information
# in the /etc/mygate file. Ask the user to either select from a list of default
# router candidates or to enter a router IPv6 address.
v6_defroute() {
local _if=$1 _prompt _resp _routers PS3
# Only continue if there is not yet any IPv6 default route set.
route -n show -inet6 | egrep -q '^default[[:space:]]' && return
# Get a sorted, unique list of default router canditates by issuing a
# ping6 to the All-Routers multicast address.
_routers=$(bsort $(ping6 -n -c 2 ff02::2%$_if 2>/dev/null |
sed -En '/^[0-9]+ bytes from /{s///;s/: .*$//p;}'))
_prompt="IPv6 default router?"
if $AI; then
_autorespond "$_prompt" && _resp=$resp && echo "$_prompt $_resp"
else
PS3="$_prompt (${_routers:+list #, }IPv6 address or 'none'): "
select _resp in $_routers; do
[[ ${_resp:=$REPLY} == *:* ]] && break
[[ $_resp == none ]] && return
done
fi
route -n add -inet6 -host default "$_resp" &&
echo "$_resp" >>/tmp/i/mygate
}
# Configure IPv6 interface $1, add hostname $2 to the hosts file, create the
# hostname.if file $3 and finish by executing v6_defroute(). Ask the user for
# the IPv6 address and prefix length if the address was not specified in CIDR
# notation, unless he chooses 'autoconf'.
v6_config() {
local _if=$1 _name=$2 _hn=$3 _addr _prefixlen _prompt
ifconfig lo0 inet6 >/dev/null 2>&1 || return
# Preset the default answers by preserving possibly existing
# configuration from previous runs.
set -- $(v6_info $_if)
[[ -n $2 ]] && { _addr=$2; _prefixlen=$3; }
ifconfig $_if inet6 >/dev/null 2>&1 && _prompt="or 'autoconf' "
_prompt="IPv6 address for $_if? (${_prompt}or 'none')"
while :; do
ask_until "$_prompt" "${_addr:-none}"
case $resp in
none) return
;;
autoconf)
if ! ifconfig $_if inet6 >/dev/null 2>&1; then
echo "No INET6 support."
$AI && exit 1 || return
fi
if ifconfig $_if inet6 autoconf; then
echo "inet6 autoconf" >>$_hn
return
else
echo "inet6 autoconf failed."
$AI && exit 1 || return
fi
;;
*) _addr=$resp
;;
esac
# Ask for prefix lenght if the user did not use CIDR notation.
if [[ $_addr == */* ]]; then
_prefixlen=
else
ask_until "IPv6 prefix length for $_if?" "${_prefixlen:=64}"
_prefixlen=$resp
fi
if ifconfig $_if inet6 $_addr ${_prefixlen:+prefixlen $_prefixlen} up; then
echo "inet6 $_addr $_prefixlen" >>$_hn
add_hostent "${_addr%%/*}" "$_name"
break
else
_addr=
_prefixlen=
$AI && exit 1
fi
done
v6_defroute $_if
}
# Perform an 802.11 network scan on interface $1 and cache the result a file.
ieee80211_scan() {
# N.B. Skipping quoted nwid's for now.
[[ -f $WLANLIST ]] ||
ifconfig $1 scan |
sed -n 's/^ nwid \([^"]\)/\1/p' >$WLANLIST
cat $WLANLIST
}
# Configure 802.11 interface $1 and append ifconfig options to hostname.if $2.
# Ask the user for the access point ESSID, the security protocol and a secret.
ieee80211_config() {
local _if=$1 _hn=$2 _prompt _nwid _haswpa=0 _err
# Reset 802.11 settings and determine wpa capability.
ifconfig $_if -nwid -nwkey
ifconfig $_if -wpa 2>/dev/null && _haswpa=1
# Empty scan cache.
rm -f $WLANLIST
while [[ -z $_nwid ]]; do
ask_until "Access point? (ESSID, 'any', list# or '?')" "any"
case "$resp" in
+([0-9]))
_nwid=$(ieee80211_scan $_if | sed -n "${resp}s/ .*//p")
[[ -z $_nwid ]] && echo "There is no line $resp."
;;
\?) ieee80211_scan $_if |
sed -n 's/^\([^ ]*\) chan .* bssid \([^ ]*\) .*$/ \1 (\2)/p' |
cat -n | more -c
;;
*) _nwid=$resp
;;
esac
done
# 'any' implies that only open access points are considered.
if [[ $_nwid != any ]]; then
ifconfig $_if nwid "$_nwid"
quote nwid "$_nwid" >>$_hn
_prompt="Security protocol? (O)pen, (W)EP"
((_haswpa == 1)) && _prompt="$_prompt, WPA-(P)SK"
while :; do
ask_until "$_prompt" "O"
case "$_haswpa-$resp" in
?-[Oo]) break
;;
?-[Ww]) ask_until "WEP key? (will echo)"
# Make sure ifconfig accepts the key.
if _err=$(ifconfig $_if nwkey "$resp" 2>&1) &&
[[ -z $_err ]]; then
quote nwkey "$resp" >>$_hn
break
fi
echo "$_err"
;;
1-[Pp]) ask_until "WPA passphrase? (will echo)"
# Make sure ifconfig accepts the key.
if ifconfig $_if wpakey "$resp"; then
quote wpakey "$resp" >>$_hn
break
fi
;;
*) echo "'$resp' is not a valid choice."
;;
esac
done
fi
}
# Set up IPv4 and IPv6 interface configuration.
configure_ifs() {
local _first _hn _if _name _p _vi
# Always need lo0 configured.
ifconfig lo0 inet 127.0.0.1/8
# In case of restart, delete previous default gateway config.
rm -f /tmp/i/mygate
while :; do
# Discover last configured vlan interface and increment it's
# minor for the next offered vlan interface.
_vi=$(get_ifs vlan | sed '$!d;s/^vlan//')
[[ -n $_vi ]] && ((_vi++))
ask_which "network interface" "do you wish to configure" \
"\$(get_ifs) vlan${_vi:-0}" \
${_p:-'$( (get_ifs netboot; get_ifs) | sed q )'}
[[ $resp == done ]] && break
_if=$resp
_hn=/tmp/i/hostname.$_if
rm -f $_hn
# If the offered vlan is chosen, ask the relevant
# questions and bring it up.
if [[ $_if == vlan+([0-9]) ]]; then
vlan_config $_if || continue
fi
# Test if it is an 802.11 interface.
ifconfig $_if 2>/dev/null | grep -q "^[[:space:]]*ieee80211:" &&
ieee80211_config $_if $_hn
# First interface configured will use the hostname without
# asking the user.
resp=$(hostname -s)
[[ -n $_first && $_first != $_if ]] &&
ask "Symbolic (host) name for $_if?" "$resp"
_name=$resp
v4_config $_if $_name $_hn
v6_config $_if $_name $_hn
if [[ -f $_hn ]]; then
chmod 640 $_hn
: ${_first:=$_if}
fi
NIFS=$(ls -1 /tmp/i/hostname.* 2>/dev/null | grep -c ^)
_p=done
done
}
# Set up IPv4 default route by asking the user for an IPv4 address and preserve
# that information in /etc/mygate. If setting the default route fails, try to
# revert to a possibly existing previous one.
v4_defroute() {
local _dr _dr_if
# Only configure a default route if an IPv4 address was configured.
[[ -n $(ifconfig | sed -n '/[ ]inet .* broadcast /p') ]] || return
# Check routing table to see if a default route ($1) already exists
# and what interface it is connected to ($2).
set -- $(route -n show -inet |
sed -En 's/^default +([0-9.]+) .* ([a-z0-9]+) *$/\1 \2/p')
[[ -n $1 ]] && _dr=$1 _dr_if=$2
# Don't ask if a default route exits and is handled by dhclient.
[[ -n $_dr ]] && isin "$_dr_if" $(get_ifs dhcp) && return
while :; do
ask_until "Default IPv4 route? (IPv4 address or none)" "$_dr"
[[ $resp == none ]] && break
route delete -inet default >/dev/null 2>&1
if route -n add -inet -host default "$resp"; then
echo "$resp" >>/tmp/i/mygate
break
else
route -n add -inet -host default $_dr >/dev/null 2>&1
fi
done
}
# Extract the domain part from currently configured fully qualified domain name.
# If none is set, use 'my.domain'.
get_fqdn() {
local _dn
_dn=$(hostname)
_dn=${_dn#$(hostname -s)}
_dn=${_dn#.}
echo "${_dn:=my.domain}"
}
# ------------------------------------------------------------------------------
# Support functions for install_sets()
# ------------------------------------------------------------------------------
# SANESETS defines the required list of set files for a sane install or upgrade.
# During install_files(), each successfully installed set file is removed from
# DEFAULTSETS. Check if there are SANESETS still in DEFAULTSETS and if they were
# deliberately skipped. If $1 is not defined, ask the user about each skipped
# set file. Care is taken to make sure the return value is correct.
sane_install() {
local _q=$1 _s
for _s in $SANESETS; do
isin "$_s" $DEFAULTSETS || continue
[[ -n $_q ]] && return 1
if ! ask_yn "Are you *SURE* your $MODE is complete without '$_s'?"; then
$AI && exit 1 || return 1
fi
done
}
# Show list of available sets $1 and let the user select which sets to install.
# Preselect sets listed in $2 and store the list of selected sets in $resp.
#
# If the list of available sets only contains kernels during an upgrade, assume
# that the user booted into the installer using the currently installed bsd.rd
# and specified a set location pointing to a new release. In this case, only
# show and preselect bsd.rd. By setting UPGRADE_BSDRD the signify key for the
# next release is used to verify the downloaded bsd.rd, the current bsd.rd is
# preserved and no questions about missing sets are asked.
select_sets() {
local _avail=$1 _selected=$2 _f _action _col=$COLUMNS
local _bsd_rd _no_sets=true
if [[ $MODE == upgrade ]]; then
for _f in $_avail; do
[[ $_f != bsd* ]] && _no_sets=false
[[ $_f == bsd.rd* ]] && _bsd_rd=$_f
done
$_no_sets && UPGRADE_BSDRD=true _avail=$_bsd_rd _selected=$_bsd_rd
fi
# account for 4 spaces added to the sets list
let COLUMNS=_col-8
cat <<__EOT
Select sets by entering a set name, a file name pattern or 'all'. De-select
sets by prepending a '-', e.g.: '-game*'. Selected sets are labelled '[X]'.
__EOT
while :; do
for _f in $_avail; do
isin "$_f" $_selected && echo "[X] $_f" || echo "[ ] $_f"
done | show_cols | sed 's/^/ /'
ask "Set name(s)? (or 'abort' or 'done')" done
set -o noglob
for resp in $resp; do
case $resp in
abort) _selected=; break 2;;
done) break 2;;
-*) _action=rmel;;
*) _action=addel;;
esac
resp=${resp#[+-]}
[[ $resp == all ]] && resp=*
for _f in $_avail; do
[[ $_f == $resp ]] &&
_selected=$($_action $_f $_selected)
done
done
done
set +o noglob
COLUMNS=$_col
resp=$_selected
}
# Run a command ($2+) as unprivileged user ($1).
# Take extra care that after "cmd" no "user" processes exist.
#
# Optionally:
# - create "file" and chown it to "user"
# - after "cmd", chown "file" back to root
#
# Usage: do_as user [-f file] cmd
do_as() {
(( $# >= 2 )) || return
local _file _rc _user=$1
shift
if [[ $1 == -f ]]; then
_file=$2
shift 2
fi
if [[ -n $_file ]]; then
>$_file
chown "$_user" "$_file"
fi
doas -u "$_user" "$@"
_rc=$?
while doas -u "$_user" kill -9 -1 2>/dev/null; do
echo "Processes still running for user $_user after: $@"
sleep 1
done
[[ -n $_file ]] && chown root "$_file"
return $_rc
}
unpriv() {
do_as _sndio "$@"
}
unpriv2() {
do_as _file "$@"
}
# Find and list filesystems to store the prefetched sets. Prefer filesystems
# which are not used during extraction with 512M free space. Otherwise search
# any other filesystem that has 2 GB free space to prevent overflow during
# extraction.
prefetcharea_fs_list() {
local _fs_list
_fs_list=$( (
for fs in /mnt/{tmp,home,usr{/local,}}; do
df -k $fs 2>/dev/null | grep " $fs\$"
done
df -k
) | (
while read a a a a m m; do
[[ $m == /mnt/@(tmp|home|usr/@(src,obj,xobj))@(|/*) ]] &&
((a > 524288)) && echo $m && continue
[[ $m == /mnt@(|/*) ]] &&
((a > 524288 * 4)) && echo $m
done
) | (
while read fs; do
isin "$fs" $list || list="$list${list:+ }$fs"
done
echo $list
) )
[[ -n $_fs_list ]] && echo $_fs_list || return 1
}
# Install a user-selected subset of the files listed in $2 from the source $1.
# Display an error message for each failed install and ask the user whether to
# continue or not.
install_files() {
local _src=$1 _files=$2 _f _sets _get_sets _n _col=$COLUMNS _tmpfs \
_tmpfs_list _tmpsrc _cfile=/tmp/SHA256 _fsrc _unver _t _issue
local _srclocal=false _unpriv=unpriv
# Fetch sets from local sources (disk, cdrom, nfs) as root.
[[ $_src == file://* ]] && _srclocal=true _unpriv=
# Based on the file list in $_files, create two list for select_sets().
# _sets: the list of files the user can select from
# _get_sets: the list of files that are shown as pre-selectd
#
# Sets will be installed in the order given in ALLSETS to ensure proper
# installation. So, to minimize user confusion display the sets in the
# order in which they will be installed.
for _f in $ALLSETS; do
isin "$_f" $_files || continue
_sets=$(addel $_f $_sets)
isin "$_f" $DEFAULTSETS "site$VERSION-$(hostname -s).tgz" &&
_get_sets=$(addel $_f $_get_sets)
done
if [[ -z $_sets ]]; then
echo -n "Looked at $_src "
echo "and found no $OBSD sets. The set names looked for were:"
let COLUMNS=_col-8
for _n in $ALLSETS; do echo $_n; done | show_cols | sed 's/^/ /'
COLUMNS=$_col
$AI && exit 1
echo
return
fi
isin "INSTALL.$ARCH" $_files ||
ask_yn "INSTALL.$ARCH not found. Use sets found here anyway?" ||
return
select_sets "$_sets" "$_get_sets"
[[ -n $resp ]] || return
_get_sets=$resp
# Reorder $_get_sets.
_get_sets=$(for s in $ALLSETS; do isin "$s" $_get_sets && echo $s; done)
# Note which sets didn't verify ok.
_unver=$_get_sets
# Try to prefetch and control checksum of the set files.
# Use dummy for loop as combined assignment and do { ... } while(0).
for _issue in ''; do
! isin SHA256.sig $_files &&
_issue="Directory does not contain SHA256.sig" && break
if ! $_srclocal; then
! _tmpfs_list=$(prefetcharea_fs_list) &&
_issue="Cannot determine prefetch area" && break
for _tmpfs in $_tmpfs_list; do
# Try to clean up from previous runs, assuming
# the _tmpfs selection yields the same mount
# point.
for _tmpsrc in $_tmpfs/sets.+([0-9]).+([0-9]); do
[[ -d $_tmpsrc ]] && rm -r $_tmpsrc
done
# Create a download directory for the sets and
# check that the _sndio user can read files from
# it. Otherwise cleanup and skip the filesystem.
if _tmpsrc=$(tmpdir "$_tmpfs/sets"); then
(
>$_tmpsrc/t &&
$_unpriv cat $_tmpsrc/t
) >/dev/null 2>&1 && break ||
rm -r $_tmpsrc
fi
done
[[ ! -d $_tmpsrc ]] &&
_issue="Cannot create prefetch area" && break
fi
# Cleanup from previous runs.
rm -f $_cfile $_cfile.sig
_t=Get/Verify
$_srclocal && _t='Verifying '
# Fetch signature file.
! $_unpriv ftp -D "$_t" -Vmo - "$_src/SHA256.sig" >"$_cfile.sig" &&
_issue="Cannot fetch SHA256.sig" && break
# The bsd.rd only download/verify/install assumes that the sets
# location of the next release. So use the right signature file.
$UPGRADE_BSDRD &&
PUB_KEY=/mnt/etc/signify/openbsd-$((VERSION + 1))-base.pub
# Verify signature file with public keys.
! unpriv -f "$_cfile" \
signify -Vep $PUB_KEY -x "$_cfile.sig" -m "$_cfile" &&
_issue="Signature check of SHA256.sig failed" && break
# Fetch and verify the set files.
for _f in $_get_sets; do
rm -f /tmp/h /tmp/fail
# Fetch set file and create a checksum by piping through
# sha256. Create a flag file in case ftp failed. Sets
# from net are written to the prefetch area, the output
# of local sets is discarded.
( $_unpriv ftp -D "$_t" -Vmo - "$_src/$_f" || >/tmp/fail ) |
( $_srclocal && unpriv2 sha256 >/tmp/h ||
unpriv2 -f /tmp/h sha256 -ph /tmp/h >"$_tmpsrc/$_f" )
# Handle failed transfer.
if [[ -f /tmp/fail ]]; then
rm -f "$_tmpsrc/$_f"
if ! ask_yn "Fetching of $_f failed. Continue anyway?"; then
[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
$AI && exit 1
return
fi
_unver=$(rmel $_f $_unver)
_get_sets=$(rmel $_f $_get_sets)
continue
fi
# Verify sets by comparing its checksum with SHA256.
if fgrep -qx "SHA256 ($_f) = $(</tmp/h)" "$_cfile"; then
_unver=$(rmel $_f $_unver)
else
if ! ask_yn "Checksum test for $_f failed. Continue anyway?"; then
[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
$AI && exit 1
return
fi
fi
done
done
[[ -n $_unver ]] && : ${_issue:="Unverified sets:" ${_unver% }}
if [[ -n $_issue ]] &&
! ask_yn "$_issue. Continue without verification?"; then
[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
$AI && exit 1
return
fi
# Install the set files.
for _f in $_get_sets; do
_fsrc="$_src/$_f"
# Take the set file from the prefetch area if possible.
[[ -f $_tmpsrc/$_f ]] && _fsrc="file://$_tmpsrc/$_f"
# Extract the set files and put the kernel files in place.
case $_fsrc in
*.tgz) $_unpriv ftp -D Installing -Vmo - "$_fsrc" |
tar -zxphf - -C /mnt &&
if [[ $_f == ?(x)base*.tgz && $MODE == install ]]; then
ftp -D Extracting -Vmo - \
file:///mnt/var/sysmerge/${_f%%base*}etc.tgz |
tar -zxphf - -C /mnt
fi
;;
*) # Make a backup of the existing ramdisk kernel in the
# bsd.rd only download/verify/install case.
$UPGRADE_BSDRD && [[ $_f == bsd.rd* ]] &&
cp /mnt/$_f /mnt/$_f.old.$VERSION
$_unpriv ftp -D Installing -Vmo - "$_fsrc" >"/mnt/$_f"
;;
esac
if (($?)); then
if ! ask_yn "Installation of $_f failed. Continue anyway?"; then
[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
$AI && exit 1
return
fi
else
# Remove each successfully installed set file from
# DEFAULTSETS which is checked by sane_sets().
DEFAULTSETS=$(rmel $_f $DEFAULTSETS)
# Reset DEFAULTSETS to make sure that sane_sets() does
# not complain about missing set files in the bsd.rd
# only download/verify/install case,
$UPGRADE_BSDRD && DEFAULTSETS=
fi
[[ -d $_tmpsrc ]] && rm -f "$_tmpsrc/$_f"
done
[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc" || true
}
# Fetch install sets from an HTTP server possibly using a proxy.
install_http() {
local _d _f _flist _file_list _prompt _tls _http_proto _url_base
local _idx=/tmp/i/index.txt _sha=/tmp/i/SHA256 _sig=/tmp/i/SHA256.sig
local _iu_url _iu_srv _iu_dir _mirror_url _mirror_srv _mirror_dir
local _ftp_stdout=/tmp/i/ftpstdout _rurl_base
# N.B.: Don't make INSTALL_MIRROR a local variable! It preserves the
# mirror information if install_http() is called multiple times with
# mirror and local servers. That ensures that the mirror server ends
# up in /etc/installurl file if one of the servers is not a mirror.
# N.B.: 'http_proxy' is an environment variable used by ftp(1).
# DON'T change the name or case!
ask "HTTP proxy URL? (e.g. 'http://proxy:8080', or 'none')" \
"${http_proxy:-none}"
unset http_proxy
[[ $resp == none ]] || export http_proxy=$resp
# If the mirror server listfile download failed, inform the user and
# show a reduced prompt.
if [[ -s $HTTP_LIST ]]; then
_prompt="HTTP Server? (hostname, list#, 'done' or '?')"
else
echo "(Unable to get list from ftp.openbsd.org, but that is OK)"
_prompt="HTTP Server? (hostname or 'done')"
fi
# Use information from /etc/installurl as defaults for upgrades.
# Format of installurl: _http_proto://_iu_srv/_iu_dir
# ^--------- _iu_url ---------^
if [[ $MODE == upgrade ]] &&
_iu_url=$(stripcom /mnt/etc/installurl); then
_iu_srv=${_iu_url#*://}
_iu_srv=${_iu_srv%%/*}
_iu_dir=${_iu_url##*$_iu_srv*(/)}
[[ -n $_iu_srv ]] && HTTP_SERVER=$_iu_srv
fi
# Get server IP address or hostname and optionally the http protocol.
while :; do
ask_until "$_prompt" "$HTTP_SERVER"
case $resp in
done) return
;;
"?") [[ -s $HTTP_LIST ]] || continue
# Show a numbered list of mirror servers.
cat -n < $HTTP_LIST | more -c
;;
+([0-9]))
# A number is only used as a line number in $HTTP_LIST.
[[ -s $HTTP_LIST ]] || continue
# Extract the URL from the mirror server listfile.
set -- $(sed -n "${resp}p" $HTTP_LIST)
if (($# < 1)); then
echo "There is no line $resp."
continue
fi
HTTP_SERVER=${1%%/*}
# Repeat loop to get user to confirm server address.
;;
?(http?(s)://)+([A-Za-z0-9:.\[\]_-]))
case $resp in
https://*) _tls=force _http_proto=https;;
http://*) _tls=no _http_proto=http;;
*) _tls=try _http_proto=$HTTP_PROTO;;
esac
if ! $FTP_TLS && [[ $_tls == force ]]; then
echo "https not supported on this platform."
$AI && exit 1 || continue
fi
HTTP_SERVER=${resp#*://}
break
;;
*) echo "'$resp' is not a valid hostname."
;;
esac
done
# Get directory info from *last* line starting with the server
# name. This means the last install from a mirror will not keep
# the specific directory info. But an install from a local
# server *will* remember the specific directory info.
# Format: _mirror_srv/_mirror_dir location_info
# ^---- _mirror_url ----^
set -- $(grep -i "^$HTTP_SERVER" $HTTP_LIST 2>/dev/null | sed '$!d')
_mirror_url=${1%%*(/)}
_mirror_srv=${_mirror_url%%/*}
_mirror_dir=${_mirror_url##*$_mirror_srv*(/)}
# Decide on the default for the "Server directory" question.
if [[ -n $_mirror_url ]]; then
# Use directory information from cgi server if HTTP_SERVER was
# found in HTTP_LIST. That is either an official mirror or the
# server used in a previous installation or upgrade.
_d=$_mirror_dir/$HTTP_SETDIR
# Preserve the information that it is an official mirror if
# location is present in $2.
(($# > 1)) && INSTALL_MIRROR=$_mirror_url
elif [[ -n $_iu_url ]]; then
# Otherwise, if it exists, use directory information from
# installurl(5) during upgrade.
_d=$_iu_dir/$HTTP_SETDIR
else
_d=pub/OpenBSD/$HTTP_SETDIR
fi
ask_until "Server directory?" "$_d"
HTTP_DIR=${resp##+(/)}
_url_base="$_http_proto://$HTTP_SERVER/$HTTP_DIR"
# Fetch SHA256.sig to create the list of files to select from.
rm -f $_idx $_sha $_sig $_ftp_stdout
if ! unpriv -f $_sig \
ftp -w 15 -vMo $_sig "$_url_base/SHA256.sig" \
>$_ftp_stdout 2>/dev/null; then
case $_tls in
force) $AI && exit 1 || return
;;
try) ask_yn "Unable to connect using https. Use http instead?" ||
return
_http_proto=http
_url_base="http://$HTTP_SERVER/$HTTP_DIR"
unpriv -f $_sig ftp -vMo $_sig "$_url_base/SHA256.sig" \
>$_ftp_stdout 2>/dev/null
;;
esac
fi
# In case of URL redirection, use the final location to retrieve the
# rest of the files from. Redirection does not change INSTALL_MIRROR.
_rurl_base=$(sed -n 's/^Requesting //p' $_ftp_stdout | sed '$!d')
_rurl_base=${_rurl_base%/SHA256.sig*}
# Verify SHA256.sig, write SHA256 and extract the list of files.
if unpriv -f $_sha \
signify -Vep $PUB_KEY -x $_sig -m $_sha >/dev/null 2>&1; then
_file_list="$(sed -n 's/^SHA256 (\(.*\)).*$/\1/p' $_sha)"
_file_list="SHA256.sig $_file_list"
else
echo "Unable to get a verified list of distribution sets."
# Deny this server, if it's a mirror without a valid SHA256.sig.
if [[ ${_rurl_base%/$HTTP_SETDIR} == "$_http_proto://$INSTALL_MIRROR" ]]; then
$AI && exit 1 || return
fi
fi
# Fetch index.txt, extract file list but add only entries that are not
# already in _file_list. This allows for a verified list of distribution
# sets from SHA256.sig, siteXX sets or the whole set list from index.txt
# if SHA256.sig was not found (e.g. self compiled sets).
if unpriv -f $_idx \
ftp -VMo $_idx "$_rurl_base/index.txt" 2>/dev/null; then
_flist=$(sed -En 's/^.* ([a-zA-Z][a-zA-Z0-9._-]+)$/\1/p' $_idx)
for _f in $_flist; do
! isin "$_f" $_file_list && _file_list="$_file_list $_f"
done
fi
rm -f $_idx $_sha $_sig $_ftp_stdout
install_files "$_rurl_base" "$_file_list"
# Remember the sets location which is used later for creating the
# installurl(5) file and to tell the cgi server.
if [[ -n $INSTALL_MIRROR ]]; then
INSTALL_URL=$_http_proto://$INSTALL_MIRROR
else
# Remove the architecture and snaphots or version part.
INSTALL_URL=${_url_base%/$ARCH}
INSTALL_URL=${INSTALL_URL%@(/$VNAME|/snapshots)}
fi
}
# Ask for the path to the set files on an already mounted filesystem and start
# the set installation.
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."
$AI && exit 1
done
install_files "file://$_dir" "$(ls $_dir/)"
}
# Install sets from CD-ROM drive $1.
install_cdrom() {
local _drive=$1
make_dev $_drive && mount_mnt2 $_drive || return
install_mounted_fs
}
# Install sets from disk.
# Ask for the disk device containing the set files.
install_disk() {
if ! ask_yn "Is the disk partition already mounted?" yes; then
ask_which "disk" "contains the $MODE media" \
'$(bsort $(get_dkdevs))' \
'$(bsort $(rmel $ROOTDISK $(get_dkdevs)))'
[[ $resp == done ]] && return 1
# Ensure the device file exists and mount the fs on /mnt2.
make_dev $resp && mount_mnt2 $resp || return
fi
install_mounted_fs
}
# Ask for the nfs share details, mount it and start the set installation.
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)" && _tcp=-T
# Mount the server.
mount_nfs $_tcp -o ro -R 5 $NFS_ADDR:$NFS_PATH /mnt2 || return
install_mounted_fs
}
# Mount filesystem containing the set files on device $1, optionally ask the
# user for the device name.
mount_mnt2() {
local _dev=$1 _opts _file=/tmp/i/parts.$1 _parts
disklabel $_dev 2>/dev/null |
sed -En '/swap|unused/d;/^ [a-p]: /p' >$_file
_parts=$(sed 's/^ \(.\): .*/\1/' $_file)
set -- $_parts
(($# == 0)) && { echo "No filesystems found on $_dev."; return 1; }
if isin "c" $_parts; then
# Don't ask questions if 'c' contains a filesystem.
resp=c
elif (($# == 1)); then
# Don't ask questions if there's only one choice.
resp=$1
else
# Display partitions with filesystems and ask which to use.
cat $_file
ask_which "$_dev partition" "has the $MODE sets" \
'$(disklabel '$_dev' 2>/dev/null |
sed -En '\''/swap|unused/d;/^ ([a-p]): .*/s//\1/p'\'')'
[[ $resp == done ]] && return 1
fi
# Always mount msdos partitions with -s to get lower case names.
grep -q "^ $resp: .*MSDOS" $_file && _opts="-s"
mount -o ro,$_opts /dev/$_dev$resp /mnt2
}
# ------------------------------------------------------------------------------
# Functions used in install.sh/upgrade.sh and it's associates
# ------------------------------------------------------------------------------
# Ask for terminal type if on console, otherwise ask for/set keyboard layout.
set_term() {
local _layouts
export TERM=${TERM:-${MDTERM:-vt220}}
if [[ -n $CONSOLE ]]; then
ask "Terminal type?" "$TERM"
TERM=$resp
else
[[ -x /sbin/kbd ]] || return
_layouts=$(bsort $(kbd -l | egrep -v "^(user|tables|encoding)"))
while :; do
ask "Choose your keyboard layout ('?' or 'L' for list)" default
case $resp in
[lL\?]) echo "Available layouts: $_layouts"
;;
default) break
;;
*) if kbd -q "$resp"; then
echo $resp >/tmp/i/kbdtype
break
fi
;;
esac
done
fi
}
# Configure the network.
donetconfig() {
local _dn _ns _f1 _f2 _f3
configure_ifs
v4_defroute
# As dhclient will populate /etc/resolv.conf, a symbolic link to
# /tmp/i/resolv.conf.shadow, mv any such file to /tmp/i/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/i/resolv.conf.shadow ]]; then
mv /tmp/i/resolv.conf.shadow /tmp/i/resolv.conf
# Get/store nameserver address(es) as a blank separated list
# and the default fully qualified domain name from *first*
# domain given on *last* search or domain statement.
while read -r -- _f1 _f2 _f3; do
[[ $_f1 == nameserver ]] && _ns="${_ns:+$_ns }$_f2"
[[ $_f1 == @(domain|search) ]] && _dn=$_f2
done </tmp/i/resolv.conf
fi
# Get & apply fqdn to hostname. Don't ask if there's only one configured
# interface and if it's managed by dhclient and if the domain name is
# configured via dhclient too.
resp="${_dn:-$(get_fqdn)}"
if ifconfig dhcp >/dev/null 2>&1 && [[ $NIFS == 1 && -n $_dn ]]; then
# If we have a 'domain-name' option in the lease file use that.
# It might *NOT* not be the same as the first domain in any
# 'domain-search' option.
set -- $(get_ifs dhcp)
set -- $(lease_value /var/db/dhclient.leases.$1 domain-name)
[[ -n $1 ]] && resp=$1
echo "Using DNS domainname $resp"
else
ask "DNS domain name? (e.g. 'example.com')" "$resp"
fi
hostname "$(hostname -s).$resp"
# Get & add nameservers to /tmp/i/resolv.conf. Don't ask if there's only
# one configured interface and if it's managed by dhclient and if the
# nameserver is configured via dhclient too.
resp="${_ns:-none}"
if ifconfig dhcp >/dev/null 2>&1 && [[ $NIFS == 1 && -n $_ns ]]; then
echo "Using DNS nameservers at $resp"
else
ask "DNS nameservers? (IP address list or 'none')" "$resp"
fi
# Construct appropriate resolv.conf.
if [[ $resp != none ]]; then
echo "lookup file bind" >/tmp/i/resolv.conf
for _ns in $resp; do
echo "nameserver $_ns" >>/tmp/i/resolv.conf
done
cp /tmp/i/resolv.conf /tmp/i/resolv.conf.shadow
fi
}
# Ask user about daemon startup on boot, X Window usage and console setup.
# The actual configuration is done later in apply().
questions() {
local _d _cdef=no
ask_yn "Start sshd(8) by default?" yes
START_SSHD=$resp
APERTURE=
resp=
START_XDM=
if [[ -n $(scan_dmesg '/^wsdisplay[0-9]* /s/ .*//p') ]]; then
if [[ -n $(scan_dmesg '/^[a-z]*[01]: aperture needed/p') ]]; then
ask_yn "Do you expect to run the X Window System?" yes &&
APERTURE=$MDXAPERTURE
fi
if [[ -n $MDXDM && $resp != n ]]; then
ask_yn "Do you want the X Window System to be started by xenodm(1)?"
START_XDM=$resp
fi
fi
if [[ -n $CDEV ]]; then
_d=${CPROM:-$CDEV}
[[ -n $CONSOLE ]] && _cdef=yes
ask_yn "Change the default console to $_d?" $_cdef
DEFCONS=$resp
if [[ $resp == y ]]; then
ask_which "speed" "should $_d use" \
"9600 19200 38400 57600 115200" $CSPEED
case $resp in
done) DEFCONS=n;;
*) CSPEED=$resp;;
esac
fi
fi
}
# Gather information for setting up the user later in do_install().
user_setup() {
local _q="Setup a user? (enter a lower-case loginname, or 'no')"
while :; do
ask "$_q" no
case $resp in
n|no) return
;;
y|yes) _q="No really, what is the lower-case loginname, or 'no'?"
continue
;;
root|daemon|operator|bin|build|sshd|www|nobody|ftp)
;;
[a-z]*([-a-z0-9_]))
((${#resp} <= 31)) && break
;;
esac
echo "$resp is not a useable loginname."
done
ADMIN=$resp
while :; do
ask "Full name for user $ADMIN?" "$ADMIN"
case $resp in
*[:\&,]*)
echo "':', '&' or ',' are not allowed."
;;
*)
((${#resp} <= 100)) && break
echo "Too long."
;;
esac
done
ADMIN_NAME=$resp
ask_password "Password for user $ADMIN?"
ADMIN_PASS=$_password
ADMIN_KEY=
$AI && ask "Public ssh key for user $ADMIN" none &&
[[ $resp != none ]] && ADMIN_KEY=$resp
}
# Ask user whether or not to allow logins to root in case sshd(8) is enabled.
# If no user is setup, show a hint to enable root logins, but warn about risks
# of doing so.
ask_root_sshd() {
typeset -l _resp
[[ $START_SSHD == y ]] || return
if [[ -z $ADMIN ]]; then
echo "Since no user was setup, root logins via sshd(8) might be useful."
fi
echo "WARNING: root is targeted by password guessing attacks, pubkeys are safer."
while :; do
ask "Allow root ssh login? (yes, no, prohibit-password)" no
_resp=$resp
case $_resp in
y|yes) SSHD_ENABLEROOT=yes
;;
n|no) SSHD_ENABLEROOT=no
;;
w|p|without-password|prohibit-password)
SSHD_ENABLEROOT=prohibit-password
;;
*) echo "'$resp' is not a valid choice."
$AI && exit 1
continue
;;
esac
break
done
}
# Set TZ variable based on zonefile $1 and user selection.
set_timezone() {
local _zonefile=$1 _zonepath _zsed _zoneroot=/usr/share/zoneinfo
# If the timezone file is not available,
# return immediately.
[[ ! -f $_zonefile ]] && return
# If configured in a previous call, return immediately.
[[ -n $TZ ]] && return
if [[ -h /mnt/etc/localtime ]]; then
TZ=$(ls -l /mnt/etc/localtime 2>/dev/null)
TZ=${TZ#*${_zoneroot#/mnt}/}
fi
wait_cgiinfo
isin "$CGI_TZ" $(<$_zonefile) && TZ=$CGI_TZ
# If neither the base or HTTP_LIST gave a hint, and this is the
# early question, give up, and ask after the sets are installed.
[[ $_zonefile == /var/tzlist && -z $TZ ]] && return
while :; do
ask "What timezone are you in? ('?' for list)" "$TZ"
_zonepath=${resp%%*(/)}
case $_zonepath in
"") continue
;;
"?") grep -v /. $_zonefile | show_cols
continue
;;
esac
while isin "$_zonepath/" $(<$_zonefile); do
ask "What sub-timezone of '$_zonepath' are you in? ('?' for list)"
_zsed=$(echo $_zonepath/ | sed 's,/,\\/,g')
resp=${resp%%*(/)}
case $resp in
"") ;;
"?") sed -n "/^$_zsed/{s/$_zsed//;/\/./!p;}" $_zonefile | show_cols;;
*) _zonepath=$_zonepath/$resp;;
esac
done
if isin "$_zonepath" $(<$_zonefile); then
TZ=${_zonepath#$_zoneroot}
return
fi
echo -n "'${_zonepath}'"
echo " is not a valid timezone on this system."
done
}
# Determine if the supplied disk is a potential root disk, by:
# - Check the disklabel if there is an 'a' partition of type 4.2BSD
# - Mount the partition (read-only) and look for typical root filesystem layout
is_rootdisk() {
local _d=$1 _rc=1
(
make_dev $_d
if disklabel $_d | grep -q '^ a: .*4\.2BSD ' &&
mount -t ffs -r /dev/${_d}a /mnt; then
ls -d /mnt/{bin,dev,etc,home,mnt,root,sbin,tmp,usr,var}
_rc=$?
umount -f /mnt
fi
rm -f /dev/{r,}$_d?
return $_rc
) >/dev/null 2>&1
}
# Get global root information. ie. ROOTDISK, ROOTDEV and SWAPDEV.
get_rootinfo() {
local _default=$(get_dkdevs) _dkdev
local _q="Which disk is the root disk? ('?' for details)"
while :; do
echo "Available disks are: $(get_dkdevs | sed 's/^$/none/')."
_ask "$_q" $_default || continue
case $resp in
"?") diskinfo $(get_dkdevs);;
'') ;;
*) # Translate $resp to disk dev name in case it is a DUID.
# get_dkdev_name bounces back the disk dev name if not.
_dkdev=$(get_dkdev_name "$resp")
if isin "$_dkdev" $(get_dkdevs); then
[[ $MODE == install ]] && break
is_rootdisk "$_dkdev" && break
echo "$resp is not a valid root disk."
_default="$(rmel "$_dkdev" $_default) $_dkdev"
else
echo "no such disk"
fi
;;
esac
$AI && exit 1
done
log_answers "$_q" "$resp"
make_dev $_dkdev || exit
ROOTDISK=$_dkdev
ROOTDEV=${ROOTDISK}a
SWAPDEV=${ROOTDISK}b
}
# Parse and "unpack" a hostname.if(5) line given as positional parameters.
# Fill the _cmds array with the resulting interface configuration commands.
parse_hn_line() {
local _af=0 _name=1 _mask=2 _bc=3 _prefix=2 _c _cmd _prev _daddr
local _has_dhclient=false _has_inet6=false
set -A _c -- "$@"
set -o noglob
ifconfig $_if inet6 >/dev/null 2>&1 && _has_inet6=true
[[ -x /sbin/dhclient ]] && _has_dhclient=true
case ${_c[_af]} in
''|*([[:blank:]])'#'*)
return
;;
inet) ((${#_c[*]} > 1)) || return
[[ ${_c[_name]} == alias ]] && _mask=3 _bc=4
[[ -n ${_c[_mask]} ]] && _c[_mask]="netmask ${_c[_mask]}"
if [[ -n ${_c[_bc]} ]]; then
_c[_bc]="broadcast ${_c[_bc]}"
[[ ${_c[_bc]} == *NONE ]] && _c[_bc]=
fi
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
;;
inet6) ! $_has_inet6 && return
((${#_c[*]} > 1)) || return
if [[ ${_c[_name]} == autoconf ]]; then
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
V6_AUTOCONF=true
return
fi
[[ ${_c[_name]} == alias ]] && _prefix=3
[[ -n ${_c[_prefix]} ]] && _c[_prefix]="prefixlen ${_c[_prefix]}"
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
;;
dest) ((${#_c[*]} == 2)) && _daddr=${_c[1]} || return
! $_has_inet6 && [[ $_daddr == @(*:*) ]] && return
_prev=$((${#_cmds[*]} - 1))
((_prev >= 0)) || return
set -A _c -- ${_cmds[_prev]}
_name=3
[[ ${_c[_name]} == alias ]] && _name=4
_c[_name]="${_c[_name]} $_daddr"
_cmds[$_prev]="${_c[@]}"
;;
dhcp) ! $_has_dhclient && return
_c[0]=
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]} up;dhclient $_if"
V4_DHCPCONF=true
;;
'!'*|bridge)
# Skip shell commands and bridge in the installer.
return
;;
*) _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
;;
esac
unset _c
set +o noglob
}
# Start interface using the on-disk hostname.if file passed as argument $1.
# Much of this is gratuitously stolen from /etc/netstart.
ifstart() {
local _if=$1 _hn=/mnt/etc/hostname.$1 _cmds _i=0 _line
set -A _cmds
# Create interface if it does not yet exist.
{ ifconfig $_if || ifconfig $_if create; } >/dev/null 2>&1 || return
((NIFS++))
# Parse the hostname.if(5) file and fill _cmds array with interface
# configuration commands.
set -o noglob
while IFS= read -- _line; do
parse_hn_line $_line
done <$_hn
# Apply the interface configuration commands stored in _cmds array.
while ((_i < ${#_cmds[*]})); do
eval "${_cmds[_i]}"
((_i++))
done
unset _cmds
set +o noglob
}
# Configure the network during upgrade based on the on-disk configuration.
enable_network() {
local _f _gw _hn _if _trunks _svlans _vlans
# Use installed network configuration files during upgrade.
for _f in resolv.conf resolv.conf.tail; do
if [[ -f /mnt/etc/$_f ]]; then
cp /mnt/etc/$_f /etc/$_f
fi
done
# Create a minimal hosts file.
echo "127.0.0.1\tlocalhost" >/tmp/i/hosts
echo "::1\t\tlocalhost" >>/tmp/i/hosts
_f=/mnt/etc/soii.key
[[ -f $_f ]] && sysctl "net.inet6.ip6.soiikey=$(<$_f)"
# Set the address for the loopback interface. Bringing the
# interface up, automatically invokes the IPv6 address ::1.
ifconfig lo0 inet 127.0.0.1/8
# 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 prefix to get interface name.
_if=${_hn#/mnt/etc/hostname.}
if isin "${_if%%+([0-9])}" $(ifconfig -C); then
# Dynamic interfaces must be done later.
case ${_if%%+([0-9])} in
trunk) _trunks="$_trunks $_if";;
svlan) _svlans="$_svlans $_if";;
vlan) _vlans="$_vlans $_if";;
esac
else
# 'Real' interfaces (if available) are done now.
ifconfig $_if >/dev/null 2>&1 && ifstart $_if
fi
done
# Configure any dynamic interfaces now that 'real' ones are up.
# ORDER IS IMPORTANT! (see /etc/netstart).
for _if in $_trunks $_svlans $_vlans; do
ifstart $_if
done
# /mnt/etc/mygate, if it exists, contains the address(es) of my
# default gateway(s). Use for ipv4 if no interfaces configured via
# dhcp. Use for ipv6 if no interfaces configured via autoconf.
! $V4_DHCPCONF && stripcom /mnt/etc/mygate |
while read _gw; do
[[ $_gw == @(*:*) ]] && continue
route -qn add -host default $_gw && break
done
! $V6_AUTOCONF && stripcom /mnt/etc/mygate |
while read _gw; do
[[ $_gw == !(*:*) ]] && continue
route -qn add -host -inet6 default $_gw && break
done
route -qn add -net 127 127.0.0.1 -reject >/dev/null
}
# Fetch the list of mirror servers and installer choices from previous runs if
# available from ftplist.cgi. Start the ftp process in the background, but kill
# it if it takes longer than 12 seconds.
start_cgiinfo() {
# If no networks are configured, we do not need the httplist file.
((NIFS < 1)) && return
# Ensure proper name resolution in case there's no dns yet.
add_hostent 129.128.5.191 ftp.openbsd.org
# Make sure the ftp subshell gets its own process group.
set -m
(
unpriv2 ftp -w 15 -Vao - \
"$HTTP_PROTO://ftp.openbsd.org/cgi-bin/ftplist.cgi?dbversion=1" \
2>/dev/null >$CGI_INFO
# Remember finish time for adjusting the received timestamp.
echo -n $SECONDS >$HTTP_SEC
feed_random
) & CGIPID=$!
set +m
# If the ftp process takes more than 12 seconds, kill it.
# XXX We are relying on the pid space not randomly biting us.
# XXX ftp could terminate early, and the pid could be reused.
(sleep 12; kill -INT -$CGIPID >/dev/null 2>&1) &
}
# Create a skeletal but useful /etc/fstab from /tmp/i/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),
# 5) are on a vnd device.
#
# 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),
#
# 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 == @(/dev/vnd*|\#*) ||
$_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 's/softdep//')
# Change read-only ffs to read-write since we'll potentially
# write to these filesystems.
# Mount non-ffs filesystems read only.
if [[ $_fstype == ffs ]]; then
_opt=$(echo $_opt | sed 's/[[:<:]]ro[[:>:]]/rw/')
else
_opt=$(echo $_opt | sed 's/[[:<:]]rw[[:>:]]/ro/')
fi
# Write fs entry in fstab.
# 1) prepend '/mnt' to the mount point.
# 2) remove a trailing '/' from the mount point (e.g. root).
echo $_dev /mnt${_mp%/} $_fstype $_opt $_rest
done </tmp/i/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
}
# Preen all filesystems in /etc/fstab that have a /sbin/fsck_XXX and a
# fs_passno > 0, 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 _dn _mp _fstype _rest _fail _f _passno
ask_yn "Force checking of clean non-root filesystems?" && _f=f
while read _dev _mp _fstype _rest _rest _passno _rest; do
_dn=$(get_dkdev_name "$_dev")
[[ $ROOTDEV == @(${_dev#/dev/}|$_dn${_dev##*.}) ]] && continue
[[ -f /sbin/fsck_$_fstype ]] || continue
# Make sure device exists before fsck'ing it.
make_dev "$_dn" || continue
((_passno > 0)) || continue
echo -n "fsck -${_f}p $_dev..."
if ! fsck -${_f}p $_dev >/dev/null 2>&1; then
echo " FAILED. You must fsck $_dev manually."
_fail=y
else
echo " OK."
fi
done </etc/fstab
[[ -n $_fail ]] && exit
}
# 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 _msg _fail
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. Remember any failure.
_msg=$(mount -v -t $_fstype $_async -o $_opt $_dev $_mp) ||
_fail="$_fail\n$_mp ($_dev)"
echo $_msg | sed 's/, ctime=[^,)]*//'
done </etc/fstab
if [[ -n $_fail ]]; then
# One or more mounts failed. Continue or abort?
echo "\nWARNING! The following filesystems were not properly mounted:$_fail"
ask_yn "Continue anyway?" || exit
fi
}
# Feed the random pool some entropy before we read from it.
feed_random() {
(dmesg; cat $CGI_INFO /*.conf; sysctl; route -n show; df;
ifconfig -A; hostname) >/dev/random 2>&1
if [[ -e /mnt/var/db/host.random ]]; then
dd if=/mnt/var/db/host.random of=/dev/random bs=65536 count=1 \
status=none
fi
}
# 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 _cddevs=$(get_cddevs) _d _im _locs="disk http" _src
echo
# Set default location to method recorded last time.
_d=$CGI_METHOD
# Set default location to HTTP in case we netbooted.
ifconfig netboot >/dev/null 2>&1 && : ${_d:=http}
# Set default location to HTTP if installurl(5) exists.
[[ -s /mnt/etc/installurl ]] && _d=http
# Set default location to the first cdrom device if any are found.
[[ -n $_cddevs ]] && : ${_d:=cd0}
# Add NFS to set locations if the boot kernel supports it.
[[ -x /sbin/mount_nfs ]] && _locs="$_locs nfs"
# In case none of the above applied, set HTTP as default location.
: ${_d:=http}
# If the default location set so far is not one of the cdrom devices or
# is not in the list of valid locations, set a sane default.
if ! isin "$_d" $_cddevs $_locs; then
for _src in http $_cddevs nfs disk; do
isin "$_src" $_cddevs $_locs && _d=$_src && break
done
fi
echo "Let's $MODE the sets!"
while :; do
# Get list of cdroms again in case one just got plugged in.
_cddevs=$(get_cddevs)
umount -f /mnt2 >/dev/null 2>&1
ask "Location of sets? (${_cddevs:+$_cddevs }$_locs or 'done')" "$_d"
case $resp in
done) sane_install && return
;;
[cC]*) if [[ -n $_cddevs ]]; then
set -- $_cddevs
[[ $resp == [cC]?([dD]) ]] && resp=$1
_im=$resp
install_cdrom $resp && INSTALL_METHOD=$_im
fi
;;
[dD]*) install_disk && INSTALL_METHOD=disk
;;
[hH]*) isin http $_locs && install_http && INSTALL_METHOD=http
;;
[nN]*) isin nfs $_locs && install_nfs && INSTALL_METHOD=nfs
;;
*) $AI && err_exit "'$resp' is not a valid choice."
;;
esac
# Preserve the selected install source selection.
[[ -n $INSTALL_METHOD ]] && _d=$INSTALL_METHOD
# Set default to 'done' to leave the while-loop.
sane_install quiet || $AI && _d=done
done
}
# Apply configuration settings based on the previously gathered information.
apply() {
if [[ $START_SSHD == n ]]; then
echo "sshd_flags=NO" >>/mnt/etc/rc.conf.local
elif [[ -n $SSHD_ENABLEROOT ]]; then
# Only change sshd_config if the user choice is not the default.
if ! grep -q "^#PermitRootLogin $SSHD_ENABLEROOT\$" \
/mnt/etc/ssh/sshd_config; then
sed -i "s/^#\(PermitRootLogin\) .*/\1 $SSHD_ENABLEROOT/" \
/mnt/etc/ssh/sshd_config
fi
fi
[[ -n $APERTURE ]] &&
echo "machdep.allowaperture=$APERTURE # See xf86(4)" \
>>/mnt/etc/sysctl.conf
[[ $START_XDM == y && -x /mnt/usr/X11R6/bin/xenodm ]] &&
echo "xenodm_flags=" >>/mnt/etc/rc.conf.local
if [[ $DEFCONS == y ]]; then
cp /mnt/etc/ttys /tmp/i/ttys
sed -e "/^$CTTY/s/std.9600/std.${CSPEED}/" \
-e "/^$CTTY/s/std.115200/std.${CSPEED}/" \
-e "/^$CTTY/s/unknown/vt220 /" \
-e "/$CTTY/s/off.*/on secure/" /tmp/i/ttys >/mnt/etc/ttys
[[ -n $CPROM ]] &&
echo "stty $CPROM $CSPEED\nset tty $CPROM" \
>>/mnt/etc/boot.conf
fi
ln -sf /usr/share/zoneinfo/$TZ /mnt/etc/localtime
}
# Return string suitable for the encrypted password field in master.passwd.
#
# 1) Without argument, return a single '*'.
# 2) Return argument unchanged if it looks like a encrypted password string
# or if it consists of just 13 asterisks.
# 3) Otherwise return encrypted password string.
#
encr_pwd() {
local _p=$1
if [[ -z $_p ]]; then
echo '*'
elif [[ $_p == \$2?\$[0-9][0-9]\$* && ${#_p} > 40 ||
$_p == '*************' ]]; then
echo "$_p"
else
encrypt -b a -- "$_p"
fi
}
# Store entropy for the next boot.
store_random() {
dd if=/dev/random of=/mnt/var/db/host.random bs=65536 count=1 \
status=none
dd if=/dev/random of=/mnt/etc/random.seed bs=512 count=1 status=none
chmod 600 /mnt/var/db/host.random /mnt/etc/random.seed
}
# Final steps common for installs and upgrades.
finish_up() {
local _dev _mp _fstype _rest _d
local _kernel_dir=/mnt/usr/share/relink/kernel
local _kernel=${MDKERNEL:-GENERIC} _syspatch_archs="amd64 arm64 i386"
# 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 </mnt/etc/fstab
fi
# Create /etc/installurl if it does not yet exist.
if [[ ! -f /mnt/etc/installurl ]]; then
echo "${INSTALL_URL:-https://cdn.openbsd.org/pub/OpenBSD}" \
>/mnt/etc/installurl
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 $(get_dkdevs) $(get_cddevs); do
sh MAKEDEV $_dev
done
)
echo " done."
# We may run some programs in chroot, and some of them might be
# dynamic. That is highly discouraged, but let us play it safe.
rm -f /mnt/var/run/ld.so.hints
# Conditionally create /usr/{src,obj,xobj} directories and set
# proper ownership and permissions during install.
if [[ $MODE == install ]]; then
mkdir -p /mnt/usr/{src,{,x}obj} && (
cd /mnt/usr
chmod 770 {,x}obj
chown build:wobj {,x}obj
chmod 775 src
chown root:wsrc src
)
fi
# In case this is a softraid device, make sure all underlying
# device nodes exist before installing boot-blocks on disk.
make_dev $(bioctl $ROOTDISK 2>/dev/null | sed -n 's/.*<\(.*\)>$/\1/p')
md_installboot $ROOTDISK
chmod og-rwx /mnt/bsd{,.mp,.rd} 2>/dev/null
if [[ -f /mnt/bsd.mp ]] && ((NCPU > 1)); then
_kernel=$_kernel.MP
echo "Multiprocessor machine; using bsd.mp instead of bsd."
mv /mnt/bsd /mnt/bsd.sp 2>/dev/null
mv /mnt/bsd.mp /mnt/bsd
fi
# Write kernel.SHA256 matching the just installed kernel and fix path to
# ensure it references the kernel as /bsd.
sha256 /mnt/bsd | (umask 077; sed 's,/mnt,,' >/mnt/var/db/kernel.SHA256)
if [[ -f $_kernel_dir.tgz ]]; then
echo -n "Relinking to create unique kernel..."
(
set -e
rm -rf $_kernel_dir
mkdir -m 700 -p $_kernel_dir
tar -C $_kernel_dir -xzf $_kernel_dir.tgz $_kernel
rm -f $_kernel_dir.tgz
chroot /mnt /bin/ksh -e -c "cd ${_kernel_dir#/mnt}/$_kernel; \
make newbsd; make newinstall"
) >/dev/null 2>&1 && echo " done." || echo " failed."
fi
# Ensure that sysmerge in batch mode is run on reboot.
[[ $MODE == upgrade ]] &&
echo "/usr/sbin/sysmerge -b" >>/mnt/etc/rc.sysmerge
# If a proxy was needed to fetch the sets, use it for fw_update and syspatch
[[ -n $http_proxy ]] &&
quote export "http_proxy=$http_proxy" >>/mnt/etc/rc.firsttime
# Ensure that fw_update is run on reboot.
echo "/usr/sbin/fw_update -v" >>/mnt/etc/rc.firsttime
# Run syspatch -c on reboot if the arch is supported and if it is a
# release system (not -stable or -current). List uninstalled syspatches
# on the console and in the rc.firsttime output mail.
isin "$ARCH" $_syspatch_archs && cat <<__EOT >>/mnt/etc/rc.firsttime
set -A _KERNV -- \$(sysctl -n kern.version |
sed 's/^OpenBSD \([0-9]\.[0-9]\)\([^ ]*\).*/\1 \2/;q')
if ((\${#_KERNV[*]} == 1)) && [[ -s /etc/installurl ]] &&
_CKPATCH=\$(mktemp /tmp/_ckpatch.XXXXXXXXXX); then
echo -n "Checking for available binary patches..."
syspatch -c > \$_CKPATCH && echo -n " done."
echo
if [[ -s \$_CKPATCH ]]; then
echo "Run syspatch(8) to install:"
cat \$_CKPATCH
fi
rm -f \$_CKPATCH
fi
__EOT
# Email installer questions and their answers to root on next boot.
prep_root_mail /tmp/i/$MODE.resp "$(hostname) $MODE response file"
if [[ -x /mnt/$MODE.site ]]; then
if ! chroot /mnt /$MODE.site; then
store_random
err_exit "$MODE.site failed"
fi
fi
# Store entropy for the next boot.
store_random
# Pat on the back.
cat <<__EOT
CONGRATULATIONS! Your OpenBSD $MODE has been successfully completed!
__EOT
[[ $MODE == install ]] && cat <<__EOT
When you login to your new system the first time, please read your mail
using the 'mail' command.
__EOT
md_congrats
$AI && >/tmp/ai/ai.done
}
do_autoinstall() {
rm -f /tmp/ai/ai.done
echo "Performing non-interactive $AI_MODE..."
/$AI_MODE -af /tmp/ai/ai.$AI_MODE.conf 2>&1 </dev/null |
sed "s/^.*$(echo '\r')//;w/tmp/ai/ai.log"
[[ -f /tmp/ai/ai.done ]] || err_exit "failed; check /tmp/ai/ai.log"
# Email autoinstall protocol to root on next boot.
prep_root_mail /tmp/ai/ai.log "$(hostname) $AI_MODE log"
exec reboot
}
do_install() {
local _rootkey _rootpass
# Ask for and set the system hostname and add the hostname specific
# siteXX set.
while :; do
ask_until "System hostname? (short form, e.g. 'foo')" \
"$(hostname -s)"
[[ $resp != *+([[:cntrl:]]|[[:space:]])* ]] && break
echo "Invalid hostname."
$AI && exit 1
done
[[ ${resp%%.*} != $(hostname -s) ]] && hostname "$resp"
ALLSETS="$ALLSETS site$VERSION-$(hostname -s).tgz"
export PS1='\h# '
echo
# Configure the network.
donetconfig
# Fetch list of mirror servers and installer choices from previous runs.
start_cgiinfo
echo
while :; do
ask_password "Password for root account?"
_rootpass="$_password"
[[ -n "$_password" ]] && break
echo "The root password must be set."
done
# Ask for the root user public ssh key during autoinstall.
_rootkey=
if $AI; then
ask "Public ssh key for root account?" none
[[ $resp != none ]] && _rootkey=$resp
fi
# Ask user about daemon startup on boot, X Window usage and console
# setup.
questions
# Gather information for setting up the initial user account.
user_setup
ask_root_sshd
# Set TZ variable based on zonefile and user selection.
set_timezone /var/tzlist
echo
# Get information about ROOTDISK, etc.
get_rootinfo
DISKS_DONE=
FSENT=
# Remove traces of previous install attempt.
rm -f /tmp/i/fstab*
# Configure the disk(s).
while :; do
# Always do ROOTDISK first, and repeat until it is configured.
if ! isin "$ROOTDISK" $DISKS_DONE; then
resp=$ROOTDISK
rm -f /tmp/i/fstab
else
# Force the user to think and type in a disk name by
# making 'done' the default choice.
ask_which "disk" "do you wish to initialize" \
'$(get_dkdevs_uninitialized)' done
[[ $resp == done ]] && break
fi
_disk=$resp
configure_disk $_disk || continue
DISKS_DONE=$(addel $_disk $DISKS_DONE)
done
# Write fstab entries to fstab in mount point alphabetic order
# to enforce a rational mount order.
for _mp in $(bsort $FSENT); do
_pp=${_mp##*!}
_mp=${_mp%!*}
echo -n "$_pp $_mp ffs rw"
# Only '/' is neither nodev nor nosuid. i.e. it can obviously
# *always* contain devices or setuid programs.
[[ $_mp == / ]] && { echo " 1 1"; continue; }
# Every other mounted filesystem is nodev. If the user chooses
# to mount /dev as a separate filesystem, then on the user's
# head be it.
echo -n ",nodev"
# The only directories that the install puts suid binaries into
# (as of 3.2) are:
#
# /sbin
# /usr/bin
# /usr/sbin
# /usr/libexec
# /usr/libexec/auth
# /usr/X11R6/bin
#
# and ports and users can do who knows what to /usr/local and
# sub directories thereof.
#
# So try to ensure that only filesystems that are mounted at
# or above these directories can contain suid programs. In the
# case of /usr/libexec, give blanket permission for
# subdirectories.
case $_mp in
/sbin|/usr) ;;
/usr/bin|/usr/sbin) ;;
/usr/libexec|/usr/libexec/*) ;;
/usr/local|/usr/local/*) ;;
/usr/X11R6|/usr/X11R6/bin) ;;
*) echo -n ",nosuid" ;;
esac
echo " 1 2"
done >>/tmp/i/fstab
# Create a skeletal /etc/fstab which is usable for the installation
# process.
munge_fstab
# Use async options for faster mounts of the filesystems.
mount_fs "-o async"
# Feed the random pool some entropy before we read from it.
feed_random
# Ask the user for locations, and install whatever sets the user
# selected.
install_sets
# Set 'wxallowed' mount option for the filesystem /usr/local resides on.
_mp=$(df /mnt/usr/local | sed '$!d')
_mp=${_mp##*/mnt}
sed -i "s#\(${_mp:-/} ffs rw\)#\1,wxallowed#" /tmp/i/fstab
# If we did not succeed at setting TZ yet, we try again
# using the timezone names extracted from the base set.
if [[ -z $TZ ]]; then
(cd /mnt/usr/share/zoneinfo
ls -1dF $(tar cvf /dev/null [A-Za-y]*) >/mnt/tmp/tzlist )
echo
set_timezone /mnt/tmp/tzlist
rm -f /mnt/tmp/tzlist
fi
# If we got a timestamp from the cgi server, and that time diffs by more
# than 120 seconds, ask if the user wants to adjust the time.
if _time=$(http_time) && _now=$(date +%s) &&
(( _now - _time > 120 || _time - _now > 120 )); then
_tz=/mnt/usr/share/zoneinfo/$TZ
if ask_yn "Time appears wrong. Set to '$(TZ=$_tz date -r "$(http_time)")'?" yes; then
# We do not need to specify TZ below since both date
# invocations use the same one.
date $(date -r "$(http_time)" "+%Y%m%d%H%M.%S") >/dev/null
# N.B. This will screw up SECONDS.
fi
fi
# If we managed to talk to the cgi server before, tell it what
# location we used... so it can perform magic next time.
if [[ -s $HTTP_LIST ]]; then
_i=${INSTALL_URL:+install=$INSTALL_URL&}
_i=$_i${TZ:+TZ=$TZ&}
_i=$_i${INSTALL_METHOD:+method=$INSTALL_METHOD}
_i=${_i%&}
[[ -n $_i ]] && unpriv2 ftp -w 15 -Vao - \
"$HTTP_PROTO://ftp.openbsd.org/cgi-bin/ftpinstall.cgi?dbversion=1&$_i" \
>/dev/null 2>&1 &
fi
# Ensure an enabled console has the correct speed in /etc/ttys.
sed "/^console.*on.*secure.*$/s/std\.[0-9]*/std.$(stty speed </dev/console)/" \
/mnt/etc/ttys >/tmp/i/ttys
mv /tmp/i/ttys /mnt/etc/ttys
echo -n "Saving configuration files..."
# Save any leases obtained during install.
(cd /var/db; for _f in dhclient.leases.*; do
[[ -f $_f ]] && mv $_f /mnt/var/db/.
done)
# Move configuration files from /tmp/i/ to /mnt/etc.
hostname >/tmp/i/myname
# Append entries to installed hosts file, changing '1.2.3.4 hostname'
# to '1.2.3.4 hostname.$FQDN hostname'. Leave untouched lines containing
# domain information or aliases. These are lines the user added/changed
# manually.
# Add common entries.
echo "127.0.0.1\tlocalhost" >/mnt/etc/hosts
echo "::1\t\tlocalhost" >>/mnt/etc/hosts
# Note we may have no hosts file if no interfaces were configured.
if [[ -f /tmp/i/hosts ]]; then
# Remove the entry for ftp.openbsd.org
sed -i '/^129\.128\.5\.191 /d' /tmp/i/hosts
_dn=$(get_fqdn)
while read _addr _hn _aliases; do
if [[ -n $_aliases || $_hn != ${_hn%%.*} || -z $_dn ]]; then
echo "$_addr\t$_hn $_aliases"
else
echo "$_addr\t$_hn.$_dn $_hn"
fi
done </tmp/i/hosts >>/mnt/etc/hosts
rm /tmp/i/hosts
fi
# Possible files to copy from /tmp/i/: fstab hostname.* kbdtype mygate
# myname ttys boot.conf resolv.conf sysctl.conf resolv.conf.tail
# Save only non-empty (-s) regular (-f) files.
(cd /tmp/i; for _f in fstab hostname* kbdtype my* ttys *.conf *.tail; do
[[ -f $_f && -s $_f ]] && mv $_f /mnt/etc/.
done)
echo " done."
# Apply configuration settings based on information from questions().
apply
# Create user account based on information from user_setup().
if [[ -n $ADMIN ]]; then
_encr=$(encr_pwd "$ADMIN_PASS")
_home=/home/$ADMIN
uline="${ADMIN}:${_encr}:1000:1000:staff:0:0:${ADMIN_NAME}:$_home:/bin/ksh"
echo "$uline" >>/mnt/etc/master.passwd
echo "${ADMIN}:*:1000:" >>/mnt/etc/group
echo $ADMIN >/mnt/root/.forward
_home=/mnt$_home
mkdir -p $_home
(cd /mnt/etc/skel; pax -rw -k -pe . $_home)
(umask 077 && sed "s,^To: root\$,To: ${ADMIN_NAME} <${ADMIN}>," \
/mnt/var/mail/root >/mnt/var/mail/$ADMIN )
chown -R 1000:1000 $_home /mnt/var/mail/$ADMIN
sed -i -e "s@^wheel:.:0:root\$@wheel:\*:0:root,${ADMIN}@" \
/mnt/etc/group 2>/dev/null
# During autoinstall, add public ssh key to authorized_keys.
[[ -n "$ADMIN_KEY" ]] &&
print -r -- "$ADMIN_KEY" >>$_home/.ssh/authorized_keys
fi
# Store root password and rebuild password database.
if [[ -n "$_rootpass" ]]; then
_encr=$(encr_pwd "$_rootpass")
sed -i -e "s@^root::@root:${_encr}:@" /mnt/etc/master.passwd \
2>/dev/null
fi
pwd_mkdb -p -d /mnt/etc /etc/master.passwd
# During autoinstall, add root user's public ssh key to authorized_keys.
[[ -n "$_rootkey" ]] && (
umask 077
print -r -- "$_rootkey" >>/mnt/root/.ssh/authorized_keys
)
# Perform final steps common to both an install and an upgrade.
finish_up
}
do_upgrade() {
local _f
# Get $ROOTDISK and $ROOTDEV
get_rootinfo
echo -n "Checking root filesystem (fsck -fp /dev/$ROOTDEV)..."
fsck -fp /dev/$ROOTDEV >/dev/null 2>&1 || { echo "FAILED."; exit; }
echo " OK."
echo -n "Mounting root filesystem (mount -o ro /dev/$ROOTDEV /mnt)..."
mount -o ro /dev/$ROOTDEV /mnt || { echo "FAILED."; exit; }
echo " OK."
# The fstab and myname files are required.
for _f in /mnt/etc/{fstab,myname}; do
[[ -f $_f ]] || { echo "No $_f!"; exit; }
cp $_f /tmp/i/${_f##*/}
done
# Set system hostname and register hostname specific site set.
hostname $(stripcom /tmp/i/myname)
ALLSETS="$ALLSETS site$VERSION-$(hostname -s).tgz"
export PS1='\h# '
# Configure the network.
enable_network
# Fetch list of mirror servers and installer choices from previous runs.
start_cgiinfo
# Create a skeletal /etc/fstab which is usable for the upgrade process.
munge_fstab
# fsck -p non-root filesystems in /etc/fstab.
check_fs
# Mount filesystems in /etc/fstab.
umount /mnt || { echo "Can't umount $ROOTDEV!"; exit; }
mount_fs
# Feed the random pool some entropy before we read from it.
feed_random
# Ensure that previous installer choices (e.g. method) are available.
wait_cgiinfo
# Ask the user for locations, and install whatever sets the user
# selected.
install_sets
# Perform final steps common to both an install and an upgrade.
finish_up
}
# ------------------------------------------------------------------------------
# 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.
# ------------------------------------------------------------------------------
# Parse parameters.
AI=false
MODE=
PROGNAME=${0##*/}
AI_RESPFILE=
while getopts "af:m:" opt; do
case $opt in
a) AI=true;;
f) AI_RESPFILE=$OPTARG;;
m) MODE=$OPTARG;;
*) usage;;
esac
done
shift $((OPTIND-1))
(($# == 0)) || usage
# The installer can be started by using the symbolic links 'install', 'upgrade'
# and 'autoinstall' pointing to this script. Set MODE and AI based on that.
if [[ -z $MODE ]]; then
case $PROGNAME in
autoinstall) AI=true;;
install|upgrade) MODE=$PROGNAME;;
*) exit 1;;
esac
fi
# Do not limit ourselves during installs or upgrades.
for _opt in d f l m n p s; do
ulimit -$_opt unlimited
done
# umount all filesystems, just in case we are re-running install or upgrade.
cd /
umount -af >/dev/null 2>&1
# Make sure only successful dhcp requests retain their state.
for _if in $(get_ifs dhcp); do
set -- $(v4_info $_if)
[[ $1 == UP && -n $2 ]] && continue
ifconfig $_if delete down -group dhcp 2>/dev/null
done
# 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_consoleinfo() - set CDEV, CTTY, CSPEED, CPROM
#
# The following variables can be provided if required:
# MDEFI - set to 'y' on archs that support GPT partitioning
# MDROOTFSOPT - newfs options for the root partition
# MDSETS - list of files to add to DEFAULT and ALLSETS
# MDSANESETS - list of files to add to SANESETS
# MDTERM - 'vt220' 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
# MDXAPERTURE - set machdep.allowaperture=value in sysctl.conf
# MDXDM - ask if xdm should be started if set to 'y'
# NCPU - the number of cpus for mp capable arches
# MDKERNEL - the name of the boot kernel
# MDHALT - default to 'halt' at the end of installs if set to 'y'
. install.md
# Start listener process looking for dmesg changes.
start_dmesg_listener
CGI_INFO=/tmp/i/cgiinfo
CGI_METHOD=
CGI_TIME=
CGI_TZ=
export EDITOR=ed
HTTP_DIR=
HTTP_LIST=/tmp/i/httplist
HTTP_SEC=/tmp/i/httpsec
INSTALL_METHOD=
NIFS=0
export PS1="$MODE# "
PUB_KEY=/etc/signify/openbsd-${VERSION}-base.pub
ROOTDEV=
ROOTDISK=
SETDIR="$VNAME/$ARCH"
UPGRADE_BSDRD=false
V4_DHCPCONF=false
V6_AUTOCONF=false
WLANLIST=/tmp/i/wlanlist
# Save one boot's worth of dmesg.
dmesgtail >/var/run/dmesg.boot
# Are we in a real release, or a snapshot? If this is a snapshot
# install media, default us to a snapshot directory.
HTTP_SETDIR=$SETDIR
set -- $(scan_dmesg "/^OpenBSD $VNAME\([^ ]*\).*$/s//\1/p")
[[ $1 == -!(stable) ]] && HTTP_SETDIR=snapshots/$ARCH
# Detect if ftp(1) has tls support and set defaults based on that.
if [[ -e /etc/ssl/cert.pem ]]; then
FTP_TLS=true
HTTP_PROTO=https
else
FTP_TLS=false
HTTP_PROTO=http
fi
# Scan /var/run/dmesg.boot for console device.
CONSOLE=$(scan_dmesg '/^\([^ ]*\).*: console$/s//\1/p')
[[ -n $CONSOLE ]] && CSPEED=$(stty speed </dev/console)
# Look for the serial device matching the console. If we are not installing
# from a serial console, just find the first serial device that could be used
# as a console. If a suitable device is found, set CDEV, CTTY, CSPEED, CPROM.
md_consoleinfo
# Selected sets will be installed in the order they are listed in $ALLSETS.
# Ensure that siteXX.tgz is the *last* set listed so its contents overwrite
# the contents of the other sets, not the other way around.
SETS=$(echo {base,comp,man,game,xbase,xshare,xfont,xserv}$VERSION.tgz)
DEFAULTSETS="${MDSETS:-bsd bsd.rd} $SETS"
ALLSETS="${MDSETS:-bsd bsd.rd} $SETS site$VERSION.tgz"
SANESETS="${MDSANESETS:-bsd} base${VERSION}.tgz"
if ((NCPU > 1)); then
DEFAULTSETS="${MDSETS:-bsd bsd.mp bsd.rd} $SETS"
ALLSETS="${MDSETS:-bsd bsd.mp bsd.rd} $SETS site$VERSION.tgz"
SANESETS="${MDSANESETS:-bsd bsd.mp} base${VERSION}.tgz"
fi
# Prepare COLUMNS sanely.
export COLUMNS=$(stty -a </dev/console |
sed -n '/columns/{s/^.* \([0-9]*\) columns.*$/\1/;p;}')
((COLUMNS == 0)) && COLUMNS=80
# Interactive or automatic installation?
if ! $AI; then
cat <<__EOT
At any prompt except password prompts you can escape to a shell by
typing '!'. Default answers are shown in []'s and are selected by
pressing RETURN. You can exit this program at any time by pressing
Control-C, but this can leave your system in an inconsistent state.
__EOT
elif [[ -z $AI_RESPFILE ]]; then
get_responsefile ||
err_exit "No response file found; non-interactive mode aborted."
do_autoinstall
else
cp $AI_RESPFILE /tmp/ai/ai.conf || exit
fi
# Configure the terminal and keyboard.
set_term
# In case of restart, delete previously logged answers.
rm -f /tmp/i/$MODE.resp
case $MODE in
install) do_install;;
upgrade) do_upgrade;;
esac
# In case of autoinstall, this is a second process of install.sub.
# Exiting here returns to the original process, which handles the
# automatic reboot in do_autoinstall().
$AI && exit
_d=reboot
[[ $MODE == install && $MDHALT == y ]] && _d=halt
while :; do
ask "Exit to (S)hell, (H)alt or (R)eboot?" "$_d"
case $resp in
[hH]*) exec halt;;
[rR]*) exec reboot;;
[sS]*) break;;
esac
done
# Fall through to .profile which leaves us at the command prompt.
echo "To boot the new system, enter 'reboot' at the command prompt."