etch

#!/bin/sh
#$Id$
#This file is part of EdgeBSD etch
#Copyright (c) 2014-2022 Pierre Pronchery <khorben@edgebsd.org>
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
#variables
DESTDIR="$PWD/destdir"
DEVNULL="/dev/null"
DEVZERO="/dev/zero"
ETCH_PREFIX="@PREFIX@"
ETCH_DATADIR="$ETCH_PREFIX/share/etch"
ETCH_SYSCONFDIR="$ETCH_PREFIX/etc/etch"
HOSTNAME="localhost.localdomain"
IMAGE_BS="1024"
IMAGE_SIZE=
PASSWORD=
PROGNAME="etch"
SHELL="/bin/sh"
TARGET=
TIMEZONE="Europe/Berlin"
USERNAME="user"
VERBOSE=1
#executables
CAT="cat"
DD="dd"
DEBUG=
DIRNAME="dirname"
MKDIR="mkdir -p"
MKSPARSE="mksparse"
MV="mv -f"
PROGRESS="_progress"
QEMU_IMG="qemu-img"
RM="rm -f"
SH="/bin/sh"
SUDO=
WC="wc"
WGET="wget"
#platform-specific
PLATFORM="EdgeBSD"
#functions
#debug
_debug()
{
echo "$@" 1>&2
"$@"
}
#download
_download()
{
if [ $# -ne 1 ]; then
_usage "_download url..."
return $?
fi
url="$1"
#FIXME no longer use the standard error
case "$url" in
"ftp://"*)
_download_ftp "$url" 1>&2 || return 2
echo "$PWD/$url"
;;
"http://"*|"https://"*)
_download_http "$url" 1>&2 || return 2
echo "$PWD/$url"
;;
"/"*)
_download_file "$url" 1>&2 || return 2
echo "$url"
;;
*)
_error "$url: Unknown protocol"
;;
esac
}
_download_file()
{
url="$1"
#XXX untested
if [ ! -f "$url" ]; then
_error "$url: File not found"
return 2
fi
}
_download_ftp()
{
url="$1"
#re-use the HTTP code
_download_http "$url"
}
_download_http()
{
url="$1"
[ -f "$url" ] && return 0
dirname=$($DIRNAME "$url")
$DEBUG $MKDIR -- "$dirname" || return 2
_info "Downloading ${url}..."
$DEBUG $WGET -O "${url}.part" -- "$url"
if [ $? -ne 0 ]; then
$DEBUG $RM -- "${url}.part"
return 2
fi
$DEBUG $MV -- "${url}.part" "$url" || return 2
}
#error
_error()
{
echo "$PROGNAME: Error: $@" 1>&2
return 2
}
#file_append
_file_append()
{
if [ $# -ne 1 ]; then
_usage "_file_append filename"
return $?
fi
filename="$1"
$SUDO $SH -c "$CAT >> '$DESTDIR/$filename'" || return 2
return 0
}
#file_create
_file_create()
{
if [ $# -ne 1 ]; then
_usage "_file_create filename"
return $?
fi
filename="$1"
$SUDO $SH -c "$CAT > '$DESTDIR/$filename'" || return 2
return 0
}
#group_get
_group_get()
{
if [ $# -ne 1 ]; then
_usage "_group_get gid"
return $?
fi
while read line; do
groupname="${line%%:*}"
password="${line#*:}"
gid="${password#*:}"
password="${password%%:*}"
gid="${gid%%:*}"
if [ "$gid" = "$1" ]; then
echo "$groupname"
return 0
fi
done < "$DESTDIR/etc/group"
return 2
}
#group_get_id
_group_get_id()
{
if [ $# -ne 1 ]; then
_usage "_group_get_id group"
return $?
fi
while read line; do
groupname="${line%%:*}"
[ "$groupname" = "$1" ] || continue
password="${line#*:}"
gid="${password#*:}"
password="${password%%:*}"
gid="${gid%%:*}"
echo "$gid"
return 0
done < "$DESTDIR/etc/group"
return 2
}
#info
_info()
{
[ $VERBOSE -ge 1 ] && echo "$@"
}
#etch
_etch()
{
ret=2
while true; do
if [ -n "$TARGET" ]; then
_volume_create || break
_volume_partition || break
_volume_format || break
_volume_mount || break
fi
_install
ret=$?
if [ -n "$TARGET" ]; then
_volume_umount || break
if [ $ret -eq 0 ]; then
_volume_convert
ret=$?
fi
fi
break
done
[ $ret -eq 0 ] || return $ret
_info "Installation completed."
}
#install
_install()
{
_install_kernel || return 2
_install_base || return 2
_install_packages || return 2
_install_bootloader || return 2
_install_configure || return 2
}
#install_base
_install_base()
{
:
}
#install_bootloader
_install_bootloader()
{
:
}
#install_configure
_install_configure()
{
:
}
#install_kernel
_install_kernel()
{
:
}
#install_packages
_install_packages()
{
:
}
#progress
_progress()
{
if [ $# -lt 2 ]; then
_usage "_progress filename command [arguments...]"
return $?
fi
filename="$1"
shift
$DEBUG $CAT -- "$filename" | "$@"
}
#trap
_trap()
{
_error "Interrupted by a signal"
ret=$?
_trap_platform
exit $ret
}
#trap_platform
_trap_platform()
{
:
}
#usage
_usage()
{
usage="Usage: $PROGNAME [-cqv][-O name=value][-h hostname][-o filename]
-c Remove the target file and content of staging directory
-h Hostname for etching
-o Target filename
-q Quiet mode
-v Verbose mode"
if [ $# -gt 0 ]; then
usage="Usage: $PROGNAME $@"
fi
echo "$usage" 1>&2
return 1
}
#user_get
_user_get()
{
if [ $# -ne 1 ]; then
_usage "_user_get uid"
return $?
fi
while read line; do
username="${line%%:*}"
password="${line#*:}"
uid="${password#*:}"
password="${password%%:*}"
uid="${uid%%:*}"
if [ "$uid" = "$1" ]; then
echo "$username"
return 0
fi
done < "$DESTDIR/etc/passwd"
return 2
}
#user_get_id
_user_get_id()
{
if [ $# -ne 1 ]; then
_usage "_user_get_id username"
return $?
fi
while read line; do
username="${line%%:*}"
[ "$username" = "$1" ] || continue
password="${line#*:}"
uid="${password#*:}"
password="${password%%:*}"
uid="${uid%%:*}"
echo "$uid"
return 0
done < "$DESTDIR/etc/passwd"
return 2
}
#volume_convert
_volume_convert()
{
convert="$QEMU_IMG convert"
format="${TARGET##*.}"
[ -n "$PS1" -a $VERBOSE -ge 2 ] && convert="$convert -p"
case "$format" in
vdi|vmdk)
_info "Converting image to the $format format..."
#XXX may overwrite an existing file
$DEBUG $convert -O "$format" \
"$TARGET" "$TARGET.$format" || break
$DEBUG $MV -- "$TARGET.$format" "$TARGET"
$DEBUG $RM -- "$TARGET.$format" > "$DEVNULL" 2>&1
return 0
;;
*)
return 0
esac
return 2
}
#volume_create
_volume_create()
{
[ -n "$TARGET" ] || return 0
if [ -f "$TARGET" ]; then
IMAGE_SIZE=$($WC -c < "$TARGET")
IMAGE_SIZE=$((IMAGE_SIZE / IMAGE_BS))
return 0
fi
if [ -z "$IMAGE_SIZE" ]; then
_error "IMAGE_SIZE must be specified"
return $?
fi
[ -f "$TARGET" ] && return 0
_info "Creating image ${TARGET}..."
#XXX verify that the final size is as expected
#FIXME doesn't always work with $PROGRESS
#$DEBUG $PROGRESS "$DEVZERO" \
# $DD of="$TARGET" bs="$IMAGE_BS" count="$IMAGE_SIZE"
#FIXME doesn't always work with $DD either
#$DEBUG $DD if="$DEVZERO" of="$TARGET" bs="$IMAGE_BS" \
# count="$IMAGE_SIZE" conv="sparse"
$DEBUG $MKSPARSE -s "$((IMAGE_SIZE * IMAGE_BS))" "$TARGET"
if [ $? -ne 0 ]; then
$RM -- "$TARGET"
return 2
fi
}
#volume_format
_volume_format()
{
:
}
#volume_mount
_volume_mount()
{
$DEBUG $MKDIR -- "$DESTDIR" || return 2
}
#volume_partition
_volume_partition()
{
:
}
#volume_umount
_volume_umount()
{
:
}
#warning
_warning()
{
echo "$PROGNAME: Warning: $@" 1>&2
return 2
}
#main
clean=0
while getopts "ch:O:o:qv" name; do
case "$name" in
c)
clean=1
;;
h)
HOSTNAME="$OPTARG"
;;
O)
export "${OPTARG%%=*}"="${OPTARG#*=}"
;;
o)
TARGET="$OPTARG"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
?)
_usage
exit $?
;;
esac
done
shift $(($OPTIND - 1))
if [ $# -ne 0 ]; then
_usage
exit $?
fi
if [ $clean -ne 0 ]; then
[ -n "$TARGET" ] && $DEBUG $RM -- "$TARGET" || exit 2
$DEBUG $RM -r -- "$DESTDIR" || exit 2
fi
[ $VERBOSE -ge 4 ] && DEBUG="_debug"
if [ -f "$ETCH_DATADIR/platforms/$PLATFORM" ]; then
. "$ETCH_DATADIR/platforms/$PLATFORM"
else
_error "$PLATFORM: Unknown platform"
exit $?
fi
if [ -f "$ETCH_SYSCONFDIR/hosts/$HOSTNAME" ]; then
. "$ETCH_SYSCONFDIR/hosts/$HOSTNAME"
else
_warning "$HOSTNAME: Unknown host (no definition found)"
fi
#trap signals for recovery
trap "_trap" INT QUIT
#etch
umask 022
_etch