stormux/build/build/build.sh

283 lines
10 KiB
Bash
Executable File

#!/bin/bash
# build.sh
# Automate Raspberry Pi image builds
# Take a config file on the command line and pass its options to the various stages of the build process
#
# Copyright 2018, F123 Consulting, <information@f123.org>
# Copyright 2018, Kyle, <kyle@free2.ml>
# Copyright 2018, Storm Dragon, <storm_dragon@linux-a11y.org>
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this package; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
#--code--
export TEXTDOMAIN=build.sh
export TEXTDOMAINDIR=./locale
. gettext.sh
# Log writing function
log() {
# Usage: command | log for just stdout.
# Or command |& log for stderr and stdout.
while read -r line ; do
echo "$line" | sudo tee -a "$logFile"
done
}
# Log file name is /var/log/build.stormux
logFile="/var/log/build.stormux"
set -e
# Load the common functions
. scripts/functions
# Die if not running as root
if ! check_root ; then
PROGNAME=$0
echo $(eval_gettext "Script must be run as root, try: sudo \$PROGNAME")
echo
exit 1
fi
# Clear previous logs
echo -n | sudo tee "$logFile" &> /dev/null
declare -A argList=(
[d:]="work-directory:,"
[H:]="hostname:,"
[h]="help,"
[n:]="network-name:,"
[o:]="output-file:,"
[p:]="password:,"
[r:]="root-password:,"
[s:]="size:,"
[u:]="user:,"
[w:]="wifi-password:"
)
short="${!argList[*]}"
short="${short// /}"
long="${argList[*]}"
long="${long// /}"
# Get command line options
if ! options=$(getopt -o $short -l $long -n "build.sh" -- "$@"); then
echo "Unknown options passed to $0" | log
exit 1
fi
eval set -- "$options"
# Declare the overrides that will replace configuration file variables with command line options
declare -A overrides=()
while [[ $# -gt 0 ]]; do
case $1 in
-d|--work-directory) overrides[workdir]="$2"; shift 2 ;;
-H|--hostname) overrides[hostname]="$2"; shift 2 ;;
-h|--help) cat <<-HELP
$(gettext "Usage")
$(eval_gettext "\$name [options...]")
$(gettext "Options")
-d, --work-directory: $(gettext "The work directory for caching packages and mounting filesystems")
-H, --hostname: $(gettext "The hostname of the installed system")
-h, --help: $(gettext "Print this help and exit")
-n, --network-name: $(gettext "The name of the wifi network. If specified, a wifi password is also required.")
-o, --output-file: $(gettext "The name of the image file to write")
-p, --password: $(gettext "The password of the regular non-root user")
-r, --root-password: $(gettext "The password of the root user")
-s --size: $(gettext "The initial size of the image file in MB")
-u, --user: $(gettext "The name of the regular non-root user")
-w, --wifi-password: $(gettext "the password used to login to the specified wifi network, requires network name")
HELP
exit 0 ;;
-n|--network-name) overrides[networkname]="$2"; shift 2 ;;
-o|--output-file) overrides[imagename]="$2"; shift 2 ;;
-p|--password) overrides[userpass]="$2"; shift 2 ;;
-r|--root-password) overrides[rootpass]="$2"; shift 2 ;;
-s|--size) overrides[imagesize]="$2"; shift 2 ;;
-u|--user) overrides[username]="$2"; shift 2 ;;
-w|--wifi-password) overrides[wifipass]="$2"; shift 2 ;;
(--) shift ;;
(*)
if [[ -f "$1" ]]; then
source "$1"
else
echo "Invalid configuration file specified." | log
exit 1
fi
shift
;;
esac
done
# Replace configuration variables using the overrides gathered from command line options
if [[ "${overrides[workdir]}" ]]; then
workdir="${overrides[workdir]}"
fi
if [[ "${overrides[hostname]}" ]]; then
hostname="${overrides[hostname]}"
fi
if [[ "${overrides[networkname]}" ]]; then
networkname="${overrides[networkname]}"
fi
if [[ "${overrides[imagename]}" ]]; then
imagename="${PWD}/${overrides[imagename]}"
fi
if [[ "${overrides[userpass]}" ]]; then
userpass="${overrides[userpass]}"
fi
if [[ "${overrides[rootpass]}" ]]; then
rootpass="${overrides[rootpass]}"
fi
if [[ "${overrides[imagesize]}" ]]; then
imagesize="${overrides[imagesize]}"
fi
if [[ "${overrides[username]}" ]]; then
username="${overrides[username]}"
fi
if [[ "${overrides[wifipass]}" ]]; then
wifipass="${overrides[wifipass]}"
fi
# export workdir so it is available in subshells.
export workdir
# All build stage scripts run from the scripts directory underneath the directory where this build script resides.
# todo: Call the loaded functions as needed rather than calling the stage scripts.
# First, the current directory must be saved.
current=$PWD
# Now we enter the directory where the stage scripts live.
cd scripts && echo "cd $PWD" | log
# Check for dependencies and install any that are missing.
check_deps
# Before building an image, we must be sure that the work directory is clean except for a package cache.
# There is not yet a facility to complete a half-built failed image, but we can cache software packages.
if [ -d "$workdir" ]; then
clean_up "$workdir" || true
# Don't remove an otherwise empty work directory if a pacman cache is present
[[ -d "${workdir}/pacman-cache" ]] || rm -Rv "$workdir" |& log
fi
# Start by creating an empty image and mounting it.
new_image $workdir $imagename $bootlabel $rootlabel boot root $imagesize 1M 64M |& log
# Install packages available from the official ALARM repositories.
./pacstrap -l "$packagelist" -c "${workdir}/pacman-cache" "$workdir/root" |& log
# Set the system hostname
echo $hostname > $workdir/root/etc/hostname
# The root partition is automatically mounted by systemd. Add only the boot partition information to fstab.
echo -e "\n/dev/mmcblk0p1 /boot vfat defaults 0 0" >> $workdir/root/etc/fstab
# Set the root password
set_password "${workdir}/root" root "${rootpass}"
# Install locale specific packages. The file name looks like the configured package list file, but includes an @ sign followed by the locale followed by .list, e.g. packages@sw_KE.list.
# This file will be ignored if it doesn't exist.
test -f "${packagelist%%.list}@${locale}.list" && ./pacstrap -l "${packagelist%%.list}@${locale}.list" -c "${workdir}/pacman-cache" "$workdir/root"
# Add pulseaudio stuff and other groups
add_groups "${workdir}/root" |& log
# Add the regular user and set its password
add_user "${workdir}/root" "${username}" |& log
set_password "${workdir}/root" "${username}" "${userpass}" |& log
# Copy any override files into the new system. This overrides any system and user configuration files or scripts installed with packages.
# Remove any stale temporary files related to the file and content repositories
rm -Rfv /tmp/stormux |& log
# Start by cloning the stormux repository to /tmp
git clone -b $(git branch | grep \* | cut -f2 -d \ ) --recurse-submodules https://gitlab.com/stormux/stormux.git /tmp/stormux |& log
# Copy in the files.
cp -Rv /tmp/stormux/files/files/* "${workdir}/root/" |& log
# Generate translations:
find /tmp/stormux/files/locales -type f -iname '*.po' -exec bash -c '
for i ; do
# i is in file, o is outfile.
language="$(grep "^\"Language: .*\\n" "${i}" | cut -d " " -f2 | cut -d "\\" -f1)"
# Handle exceptions for languages.
case "$language" in
*) language="${language::2}";;
esac
o="${i##*/}"
o="${o%.po}.mo"
msgfmt "$i" -o "${workdir}/root/usr/share/locale/${language}/LC_MESSAGES/${o}"
done' _ {} +
mkdir -p "${workdir}/root/usr/share/doc/stormux" |& log
cp -Rv /tmp/stormux/content/stormux/* "${workdir}/root/usr/share/doc/stormux/" |& log
# Set the system locale and generate all other locales specified in the configuration file.
# The default system locale must be set before we can copy user files to $HOME.
setup_locales $workdir/root $defaultlocale ${locales[@]} |& log
# Always copy the contents of /etc/skel to the home directory of the user that was created earlier
systemd-nspawn -a -q -D $workdir/root\
sudo -u \#1000\
bash -c "shopt -s dotglob && cp -R /etc/skel/* \"\$HOME\"" |& log
# Enable system services
for service in $services; do
manage_service "${workdir}/root" enable "${service}" |& log
done
# Globally enable dbus-broker
systemd-nspawn -a -q -D $workdir/root\
systemctl --global enable dbus-broker.service |& log
# Download package databases
download_package_databases $workdir/root |& log
set_fake_hwclock $workdir/root |& log
# Configure wifi if network information is present in the configuration file.
if [[ -n "$networkname" ]]; then
setup_wifi $workdir/root wlan0 $networkname $wifipass |& log
fi
# Touch the firstboot file. It will be deleted by the firstboot script that looks for it.
systemd-nspawn -a -q -D $workdir/root\
sudo -u \#1000\
bash -c "touch \$HOME/.firstboot"
# Write a system timestamp.
# This timestamp will determine which incremental updates apply to this image
echo "$(date +%y%m%d%H%M)" > $workdir/root/etc/timestamp-stormux
# Unmount the image file
clean_up "$workdir" |& log
# Keep the work directory if a pacman cache exists, otherwise remove it
if [[ ! -d "${workdir}/pacman-cache" ]]; then
rm -Rv "$workdir" |& log
fi
# Once all scripts have completed, come back to the directory from which this script was launched.
cd $current && echo "cd $current" |& log
newrootsize=$(ls -hs "${imagename}" | cut -f1 -d' ')
relativeimage=$(realpath --relative-to="$PWD" "$imagename")
echo "$relativeimage was built successfully" | log
echo "Size of $relativeimage is ${newrootsize}" | log
exit 0