From d7f00d3540f343a7d76dd5bbb988ea421328eab8 Mon Sep 17 00:00:00 2001 From: Ralf Zerres Date: Sat, 25 Jan 2020 13:47:50 +0100 Subject: [PATCH] arch-create-system: Create Arch-Linux system environment * initial version * prepare the disks * prepare the filesystem * install bootloader * install initial packages Signed-off-by: Ralf Zerres --- usr/bin/arch-create-system | 1133 ++++++++++++++++++++++++++++++++++++ usr/bin/arch-create-vm | 962 ++++++++++++++++++++++++++++++ 2 files changed, 2095 insertions(+) create mode 100755 usr/bin/arch-create-system create mode 100755 usr/bin/arch-create-vm diff --git a/usr/bin/arch-create-system b/usr/bin/arch-create-system new file mode 100755 index 0000000..aaac46d --- /dev/null +++ b/usr/bin/arch-create-system @@ -0,0 +1,1133 @@ +#/bin/sh + +# arch-create-system +# https://github.com/rzerres/arch-create-vm +# Copyright (C) 2019 Ralf Zerres + +# This program 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 2 of the License, or +# (at your option) any later version. + +# This program 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 program; if not, write to the Free Software Foundation, Inc., + +# ------------------------------------------------------------------------- + +progname="${0##*/}" +version="0.1.0" + +# global variables +color=1 +config_json="$progname.json" +dryrun=0 +quiet=0 +verbose=0 + +mount_point="/mnt" +label_prefix="BTRFS" +disk_dev_root="/dev/sda" +disk_label_root="System" +disk_uuid_root= +disk_mount_root="/" + +disk_label_cache="System" +disk_mount_cache="/var/cache" + +disk_label_log="System" +disk_mount_log="/var/log" + +disk_label_data="System" +disk_uuid_data= +disk_mount_data="/home" + +disk_label_home="System" +disk_uuid_home= +disk_mount_home="/home" + +disk_label_machines="System" +disk_uuid_machines= +disk_mount_machines="/var/lib/machines" + +disk_label_uefi=UEFI +disk_mount_uefi="/boot" + +# ascii color +BLUE= +GREEN= +MAGENTA= +RED= +YELLOW= +NO_COLOR= + +### +# Functions +### +echo () { printf %s\\n "$*" ; } + +check_prerequisites () { + # requested binaries: + which chattr >/dev/null 2>&1 || { printf "'chattr' is not installed." && exit 1; } + which btrfs >/dev/null 2>&1 || { printf "'btrfs' is not installed." && exit 1; } + which fallocate >/dev/null 2>&1 || { printf "'findmnt' is not installed." && exit 1; } + which gawk >/dev/null 2>&1 || { printf "'gawk' is not installed." && exit 1; } + which mkfs >/dev/null 2>&1 || { printf "'mkfs' is not installed." && exit 1; } + which mkswap >/dev/null 2>&1 || { printf "'mkswap' is not installed." && exit 1; } + which mount >/dev/null 2>&1 || { printf "'mount' is not installed." && exit 1; } + which pacman >/dev/null 2>&1 || { printf "'pacman' is not installed." && exit 1; } + which pacstrap >/dev/null 2>&1 || { printf "'pacstrap' is not installed." && exit 1; } + which sed >/dev/null 2>&1 || { printf "'sed' is not installed." && exit 1; } + which sgdisk >/dev/null 2>&1 || { printf "'sgdisk' is not installed." && exit 1; } + which umount >/dev/null 2>&1 || { printf "'umount' is not installed." && exit 1; } + + if [ $(id -u) -ne 0 ] ; then printf "$progname: must be run as root\n" ; exit 1 ; fi +} + +create_bootloader () { + # Needs to be executed in chroot environment (target) + cmd="bootctl --path=boot install" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + cmd="bootctl status" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +create_btrfs () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + + + if [ $dryrun -eq 1 ]; then + printf "${BLUE}Would prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" \ + "$LABEL" + else + printf "${BLUE}Prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" \ + "$LABEL" + fi + + if [ -h /dev/disk/by-partlabel/$LABEL ]; then + + # create filesystem + cmd="mkfs -t btrfs --force \ + --label $PREFIX-$LABEL \ + --data single \ + --metadata single \ + /dev/disk/by-partlabel/$LABEL" + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + else + eval "$cmd" + fi + + if [ ! -d $MOUNT_POINT ]; then + cmd="mkdir -p $MOUNT_POINT" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + else + eval "$cmd" + fi + fi + fi +} + +create_disk () { + local TARGET=$1 + local LABEL=$2 + local PREFIX=$3 + local NR_PARTITIONS=${4:-1} + + local MOUNT_TARGET=/mnt + local SGDISK="/usr/bin/sgdisk" + local PARTITION=1 + + # test if block-device is available + test -b $TARGET || exit 1 + + printf "${BLUE}Prepare disc ${GREEN}'%s'${NO_COLOR}\n" "$TARGET" + + # Cleanup disk and create GPT-Partition + cmd="sgdisk --zap-all $TARGET" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + while expr $PARTITION '<=' $NR_PARTITIONS >>/dev/null; do + if [ $PARTITION '=' 1 ] && [ $NR_PARTITIONS '>' 1 ]; then + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would create ${GREEN}UEFI-Partition '%s'${NO_COLOR}\n" $PARTITION + else + # create UEFI partitions + sgdisk --new=$PARTITION:2048:+512M $TARGET + + # assign label + sgdisk --change-name=$PARTITION:$disk_label_uefi $TARGET + + # adapt partition-type + sgdisk --typecode=$PARTITION:EF00 $TARGET + fi + + PARTITION=$(expr $PARTITION + 1) + continue + else + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would create ${GREEN}OS-Partition '%s'${NO_COLOR}\n" $PARTITION + else + # create partitions + sgdisk --new=$PARTITION $TARGET + + # assign label + sgdisk --change-name=$PARTITION:${LABEL} $TARGET + + # adapt partition-type + sgdisk --typecode=$PARTITION:8300 $TARGET + fi + + PARTITION=$(expr $PARTITION + 1) + fi + done + + # dump settings + cmd="sgdisk -p $TARGET" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would print created partition table for target ${GREEN}'%s'${NO_COLOR}\n" $TARGET + else + eval "$cmd" + fi +} + +create_fs_structure () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + local SUBVOL=$4 + + printf "${BLUE}Prepare filesystem structure${GREEN}'subvol=%s'${NO_COLOR}\n" \ + "$SUBVOL" + cmd="mount_target $MOUNT_POINT /dev/disk/by-partlabel/$LABEL btrfs $SUBVOL" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + + if [ $LABEL="OS" ]; then + printf "${BLUE}Create target filesystem structure${NO_COLOR}\n" + + cmd="mkdir -p $MOUNT_POINT/var/lib" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + for subdir in log cache; do + cmd="btrfs subvolume create $MOUNT_POINT/var/$subdir" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + done + + # Subvolumes storing custom data + printf "${BLUE} - Create btrfs subvolumes${NO_COLOR}\n" + + for subvol in boot root home data var/lib/machines; do + cmd="btrfs subvolume create $MOUNT_POINT/$subvol" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + else + eval "$cmd" + fi + done + + create_swapfs $PREFIX $MOUNT_POINT + fi + + cmd="umount $MOUNT_POINT" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +create_network_stack () { + # Needs to be executed in chroot environment (target) + systemctl enable systemd-networkd.service + systemctl enable systemd-resolved.service + systemctl enable dropbear.service + + rm /etc/resolv.conf + ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf +} + +create_swapfs () { + local PREFIX=$1 + local MOUNT_POINT=$2 + + printf "${BLUE}Create target swapfs${NO_COLOR}\n" + + # create a subvolume for the swapfs file + cmd="btrfs subvol create $MOUNT_POINT/swap" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + + # Disable COW functionality for Subvolume swap + cmd="chattr +C $MOUNT_POINT/swap" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + chmod 0700 $MOUNT_POINT/swap + fi + + # prepare the swapfile (size: 2x RAM) + RamSize=`cat /proc/meminfo | gawk 'NR==1 {print $2}'` + SwapSize=$(expr 2 '*' $RamSize) + SwapFile=$MOUNT_POINT/swap/swapfile + + cmd="fallocate --length $SwapSize $SwapFile" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + touch $SwapFile + chmod 0600 $SwapFile + eval "$cmd" + fi + + # create the swap filesystem + cmd="mkswap --label $PREFIX-Swap $SwapFile" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +create_fat () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + + if [ $dryrun -eq 1 ]; then + printf "${BLUE}Would prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + else + printf "${BLUE}Prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + fi + + if [ -h /dev/disk/by-partlabel/$LABEL ]; then + # create filesystem + cmd="mkfs -t fat \ + -F 32 \ + -n $LABEL \ + /dev/disk/by-partlabel/$LABEL" + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + fi +} + +create_vfat () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + + if [ $dryrun -eq 1 ]; then + printf "${BLUE}Would prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + else + printf "${BLUE}Prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + fi + + if [ -h /dev/disk/by-partlabel/$LABEL ]; then + # create filesystem + cmd="mkfs -t vfat \ + -n $PREFIX-$LABEL \ + /dev/disk/by-partlabel/$LABEL" + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + fi +} + +die () { + error "$@" + exit 1 +} + +error () { + printf "\n==> ERROR: %s\n" "$@" +} >&2 + +get_config_id () { + local config_json=${configfile:-$config_json} + local config_id=${1} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_id ...${NO_COLOR}\n" + fi + + if test ! -r ${config_json}; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + exit 1 + fi + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigId as \config_id \ + | select(. == \"${ConfigId}\") \ + | \$ConfigName' + ${config_json}" + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigName ' \ + ${config_json}" + config_names=$(eval $cmd) + config_names=$(echo $config_names | sed -e 's/\n//g') + + if [ $verbose -ge 2 ]; then + printf "${MAGENTA}Config environment names: ${GREEN}'%s'${NO_COLOR}\n" \ + "$config_names" + fi +} + +get_config_name () { + local config_json=${configfile:-$config_json} + local disk_name=${1} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_name ...${NO_COLOR}\n" + fi + + if test ! -r ${config_json}; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + exit 1 + fi + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigName ' \ + ${config_json}" + config_names=$(eval $cmd) + config_names=$(echo $config_names | sed -e 's/\n//g') + + if [ $verbose -ge 2 ]; then + printf "${MAGENTA}Config environment names: ${GREEN}'%s'${NO_COLOR}\n" \ + "$config_names" + fi +} + +get_config_names () { + local config_json=${configfile:-$config_json} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_names ...${NO_COLOR}\n" + fi + + if [ ! -r ${config_json} ]; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + exit 1 + fi + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigName ' \ + ${config_json}" + config_names=$(eval $cmd) + config_names=$(echo $config_names | sed -e 's/\n//g') +} + +get_config_disk_names () { + local config_json=${config_file:-$config_json} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_disk_names ...${NO_COLOR}\n" + fi + + if test ! -r ${config_json}; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + return 1 + fi + + cmd="jq --monochrome-output --ascii-output '.ConfigEnvironment[] \ + | select(.ConfigName == \"${config_name}\") \ + | .Disk[].DiskName' \ + ${config_json}" + disk_names=$(eval $cmd) + disk_names=$(echo $disk_names | sed -e 's/["\n]//g') + + if [ $verbose -ge 2 ]; then + printf "${MAGENTA}Disks in config environment ${GREEN}'%s'${MAGENTA} are: ${NO_COLOR}%s${NO_COLOR}\n" \ + "$config_name" "$disk_names" + fi + + if [ $verbose -ge 2 ]; then + i=1 + for disk in $disk_names; do + printf "${MAGENTA}Disk ${GREEN}'%d'${MAGENTA} with name ${GREEN}'%s'${NO_COLOR}\n" \ + "$i" "$disk" + i=$(expr $i + 1) + done + fi +} + +mount_target_filesytems () { + local LABEL=$1 + local MOUNT_POINT=$2 + + printf "${BLUE}Install target system ${GREEN}'%s' -> '%s'${NO_COLOR}\n" "$LABEL" "$MOUNT_POINT" + + mount_target $MOUNT_POINT /dev/disk/by-partlabel/$LABEL btrfs root + + for subdir in boot data home swap var/log var/cache var/lib/machines; do + mkdir -p $MOUNT_POINT/$subdir + done + + mount_target $MOUNT_POINT/boot /dev/disk/by-partlabel/$disk_label_uefi fat + mount_target $MOUNT_POINT/data /dev/disk/by-partlabel/$disk_label_data btrfs / + mount_target $MOUNT_POINT/var/cache /dev/disk/by-partlabel/$LABEL btrfs var/cache + mount_target $MOUNT_POINT/var/lib/machines /dev/disk/by-partlabel/$disk_label_machines btrfs / + mount_target $MOUNT_POINT/var/log /dev/disk/by-partlabel/$LABEL btrfs var/log +} + +install_target_packages () { + local LABEL=$1 + + printf "${BLUE}Install target packages ${GREEN}'%s' -> '%s'${NO_COLOR}\n" \ + "$LABEL" "$MOUNT_POINT" + + # Update available packages + cmd="pacman -Sy" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + # Basic packages + cmd="pacstrap $MOUNT_POINT arch-install-scripts base btrfs-progs intel-ucode linux linux-firmware dosfstools iptables-nft man sudo" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + # Hyper-V packages + cmd="pacstrap $MOUNT_POINT hyperv" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + # SSH-Daemon, FileManager and Editor + cmd="pacstrap $MOUNT_POINT dropbear vim mc bash-completion n" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + cmd="cp $packagename $MOUNT_POINT/usr/bin/" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +install_target () { + # Needs to be executed in chroot environment (target) + prepare_mount_units + + prepare_network_units + create_network_stack + + prepare_bootloader + create_bootloader + + prepare_locale $label_prefix + + #systemctl enable hvkvpdaemon.service + #systemctl enable hvvssdaemon.service + + # if you dislike systemd mount units + #genfstab / >/etc/fstab + + # return to calling process + exit 0 +} + +mount_target () { + local MOUNT_POINT=$1 + local DEV_NAME=$2 + local FS_TYPE=$3 + local FS_SUBVOL=$4 + + case $FS_TYPE in + "btrfs") + if [ -h $DEV_NAME ]; then + cmd="mount -t $FS_TYPE \ + -o subvol=$FS_SUBVOL \ + $DEV_NAME \ + $MOUNT_POINT" + printf "${BLUE}mounting subvol ${GREEN}'%s' ${BLUE}of ${GREEN}'%s' ${BLUE}at ${GREEN}'%s${NO_COLOR}\n" \ + "$FS_SUBVOL" "$DEV_NAME" "$MOUNT_POINT" + fi + ;; + "vfat") + if [ -h $DEV_NAME ]; then + cmd="mount -t $FS_TYPE \ + $DEV_NAME \ + $MOUNT_POINT" + printf "${BLUE}mounting ${GREEN}'%s' ${BLUE}at ${GREEN}'%s${NO_COLOR}\n" \ + "$DEV_NAME" "$MOUNT_POINT" + fi + ;; + esac + + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +parse_config_file () { + # printf "${BLUE}parse_config_file ...${NO_COLOR}\n" + + # check if config is accessible in given path + for config in \ + ${config_json} \ + ./${config_json} \ + ${HOME}/${config_json} \ + /etc/${progname}/${config_json} + do + if [ -r $config ] ; then + config_json=$config + break + else + config_json="NONE" + fi + done + + if [ ${config_json} = "NONE" ]; then + # if [ $verbose -ge 1 ]; then + # printf "${BLUE}No config file!${NO_COLOR}\n" + # fi + return + fi + + if [ ${#config_name} -eq 0 ]; then + get_config_names + fi +} + +parse_params () { + #printf "\n${BLUE}Parse arguments...${NO_COLOR}\n" + + # Evaluate given call parameters + i=0 + while [ $# -gt 0 ]; do + key="$1" + case $key in + -h | --help | \-\? | --usage) + # Call usage() function. + usage + ;; + --color) + color=1 + shift 1 + ;; + -c | --config) + config_json="$2" + shift 2 + ;; + --get-config-name) + shift 1 + config_params=${*} + config_params="${config_params%% -?[a-z-]*}" + params=$* + set -- $config_params + cmd=get-config-name + ;; + --get-config-names) + shift 1 + cmd=get-config-names + ;; + --get-config-disk-names) + shift 1 + config_params=${*} + config_params="${config_params%% -?[a-z]*}" + params=$* + set -- "$config_params" + count=$# + ( test $count -lt 1 || test ${#config_params} -eq 0 ) \ + && printf "missing argument: config_name\n" \ + && exit 1 + test $count -ge 1 && config_name="$1" + #test $count -ge 2 && disk_name="$2" + set -- $params + shift $count + cmd=get-config-disk-names + ;; + --disk_label_data) + disk_label_data="$2" + shift 2 + ;; + --disk_label_machines) + disk_label_machines="$2" + shift 2 + ;; + --disk_label_root) + disk_label_root="$2" + shift 2 + ;; + --disk_data) + disk_dev_data="$2" + shift 2 + ;; + --disk_machines) + disk_dev_machines="$2" + shift 2 + ;; + --disk_root) + disk_dev_root="$2" + shift 2 + ;; + --dry-run|--dryrun) + dryrun=1 + shift 1 + ;; + --mountpoint|--MOUNTPOINT) + mount_point="$2" + shift 2 + ;; + --labelprefix) + label_prefix="$2" + shift 2 + ;; + -v|--verbose) + verbose=$(expr $verbose '+' 1) + shift 1 + ;; + --version) + printf "%s v%s\n" "$progname" "$version" + exit 0 + ;; + --color=*) + case ${1#*=} in + yes | Yes | True | true) + color=1; + ;; + *) + ;; + esac + shift + ;; + --c=* | --config=*) + config_json=${1#*=} + shift + ;; + --disk_label_data=*|--DISK_LABEL_DATA=*) + disk_label_data=${1#*=} + shift + ;; + --disk_label_machines=*|--DISK_LABEL_MACHINES=*) + disk_label_machines=${1#*=} + shift + ;; + --disk_label_root=*|--DISK_LABEL_ROOT=*) + disk_label_root=${1#*=} + shift + ;; + --disk_data=*|--DISK_DATA=*) + disk_dev_data=${1#*=} + shift + ;; + --disk_machines=*|--DISK_MACHINES=*) + disk_dev_machines=${1#*=} + shift + ;; + --disk_root=*|--DISK_ROOT=*) + disk_dev_root=${1#*=} + shift + ;; + --labelprefix=*) + label_prefix=${1#*=} + shift + ;; + --mountpoint=*) + mount_point=${1#*=} + shift + ;; + --v=* | --verbose=*) + verbose=${1#*=} + shift + ;; + --) # End of all options + shift + break + ;; + -*) + printf "WARN: Unknown option (ignored): $1" >&2 + die "Unknown option" + ;; + *) + printf "Unknown option: %s\nRun '%s -h' for valid options.\n" $key $progname + die "Unknown option" + ;; + esac + done + + if [ "$color" ]; then + # ascii color + BLUE='\033[0;34m' + GREEN='\033[0;32m' + MAGENTA='\033[0;35m' + RED='\033[0;31m' + YELLOW='\033[0;33m' + NO_COLOR='\033[0m' + fi +} + +prepare_bootloader () { + + # check for accessible uefi partition + ls /sys/firmware/efi/efivars > /dev/null + if [ $? -eq 0 ]; then + printf "UEFI Environmint is ok'\n" + + mkdir -p /boot/loader/entries + + printf '%s\n' \ + "default arch" \ + "timeout 3" \ + "editor yes" \ + "console-mode max" \ + > /boot/loader/loader.conf + + printf '%s\n' \ + "default arch" \ + "title Arch Linux (Mainline)" \ + "linux /vmlinuz-linux" \ + "initrd /intel-ucode.img" \ + "initrd /initramfs-linux.img" \ + "options rootflags=subvol=root root=LABEL=BTRFS-OS" \ + > /boot/loader/entries/arch.conf + + bootctl status + fi +} + +prepare_locale () { + local LABEL=$1 + + hostnamectl set-hostname $LABEL-lin01 + localectl set-keymap de-latin1-nodeadkeys +} + +prepare_mount_units () { + printf '%s\n' \ + "[Unit]" \ + "Description=root-Snapshots (btrfs)" \ + "Documentation=man:systemd.mount(5)" \ + "DefaultDependencies=yes" \ + "Before=local-fs.target" \ + "" \ + "[Mount]" \ + "#What=LABLE=$disk_label_root" \ + "What=UUID=$disk_uuid_root" \ + "Where=/.snapshots" \ + "Type=btrfs" \ + "Options=defaults,discard,space_cache,autodefrag,compress=lzo,subvol=root_snapshots" \ + "" \ + "[Install]" \ + "WantedBy=multi-user.target" \ + > /etc/systemd/system/\x2esnapshots.mount + + printf '%s\n' \ + "[Unit]" \ + "Description=Caching (btrfs)" \ + "Documentation=man:systemd.mount(5)" \ + "DefaultDependencies=yes" \ + "Before=local-fs.target" \ + "" \ + "[Mount]" \ + "#What=LABLE=$disk_label_root" \ + "What=UUID=$disk_uuid_root" \ + "Where=/var/cache" \ + "Type=btrfs" \ + "Options=defaults,discard,space_cache,autodefrag,compress=lzo,subvol=var/cache" \ + "" \ + "[Install]" \ + "WantedBy=multi-user.target" \ + > /etc/systemd/system/var-cache.mount + + printf '%s\n' \ + "[Unit]" \ + "Description=data (btrfs)" \ + "Documentation=man:systemd.mount(5)" \ + "DefaultDependencies=yes" \ + "Before=local-fs.target" \ + "" \ + "[Mount]" \ + "#What=LABLE=$disk_label_data" \ + "What=UUID=$disk_uuid_data" \ + "Where=/data" \ + "Type=btrfs" \ + "Options=defaults,discard,space_cache,autodefrag,compress=lzo,subvol=data" \ + "" \ + "[Install]" \ + "WantedBy=multi-user.target" \ + > /etc/systemd/system/data.mount + + printf '%s\n' \ + "[Unit]" \ + "Description=Home (btrfs)" \ + "Documentation=man:systemd.mount(5)" \ + "DefaultDependencies=yes" \ + "Before=local-fs.target" \ + "" \ + "[Mount]" \ + "#What=LABLE=$disk_label_home" \ + "What=UUID=$disk_uuid_home" \ + "Where=/home" \ + "Type=btrfs" \ + "Options=defaults,discard,space_cache,autodefrag,compress=lzo,subvol=home" \ + "" \ + "[Install]" \ + "WantedBy=multi-user.target" \ + > /etc/systemd/system/home.mount + + printf '%s\n' \ + "[Unit]" \ + "Description=Logging (btrfs)" \ + "Documentation=man:systemd.mount(5)" \ + "DefaultDependencies=yes" \ + "Before=local-fs.target" \ + "" \ + "[Mount]" \ + "#What=LABLE=$disk_label_log" \ + "What=UUID=$disk_uuid_log" \ + "Where=/var/log" \ + "Type=btrfs" \ + "Options=defaults,discard,space_cache,autodefrag,compress=lzo,subvol=var/log" \ + "" \ + "[Install]" \ + "WantedBy=multi-user.target" \ + > /etc/systemd/system/var-log.mount +} + +prepare_network_units () { + printf '%s\n' \ + "[Match]" \ + "Name=eth0" \ + "" \ + "[Network]" \ + "Description=Slave Bridge-Interface" \ + "Bridge=bridge-lan" \ + > /etc/systemd/network/70-eth0-bridge-slave.netdev + + printf '%s\n' \ + "[Match]" \ + "#Host=Lin01" \ + "#Architecture=x86_64" \ + "" \ + "[NetDev]" \ + "Description=Bridge for Containers" \ + "Name=bridge-lan" \ + "Kind=bridge" \ + > /etc/systemd/network/80-bridge-lan.netdev + + printf '%s\n' \ + "[Match]" \ + "Name=bridge-lan" \ + "Driver=bridge" \ + "" \ + "[Network]" \ + "Description=Bridge for Containers" \ + "IPForward=yes" \ + "IPMasquerqade=yes" \ + "LinkLocalAddressisng=yes" \ + "IPv6AcceptRA=yes" \ + "IgnoreCarrierLoss=yes" \ + > /etc/systemd/network/80-bridge-lan.network +} + +prepare_target_disks () { + create_disk ${disk_dev_root} $disk_label_root $label_prefix 2 + create_disk ${disk_dev_machines} $disk_label_machines $label_prefix + create_disk ${disk_dev_data} $disk_label_data $label_prefix +} + +prepare_target_filesystems () { + create_fat $disk_label_uefi $label_prefix $mount_point + create_btrfs $disk_label_root $label_prefix $mount_point + create_btrfs $disk_label_machines $label_prefix $mount_point + create_btrfs $disk_label_data $label_prefix $mount_point + + create_fs_structure $disk_label_root $label_prefix $mount_point / +} + + +show_config () { + if [ $verbose -ge 1 ]; then + printf "${BLUE}$progname (runtime arguments) ...${NO_COLOR}\n" + i=0 + printf " Config File: '%s'\n" "$config_json" + printf " Mount Point: '%s'\n" "$mount_point" + printf " Label Prefix: '%s'\n" "$label_prefix" + printf " Target OS: '%s'\n" "$disk_dev_root" + printf " Label Root: '%s'\n" "$disk_label_root" + printf " Label UEFI: '%s'\n" "$disk_label_uefi" + if [ ${#disk_dev_machines} -gt 1 ]; then + printf " Target Machines: '%s'\n" "$disk_dev_machines" + printf " Label Machines: '%s'\n" "$disk_label_machines" + fi + if [ ${#disk_dev_data} -gt 1 ]; then + printf " Target Data: '%s'\n" "$disk_dev_data" + printf " Label DATA: '%s'\n" "$disk_label_data" + fi + + options="verbose_level=$verbose" + if [ $dryrun -ge 1 ]; then options="${options}dryrun=true"; fi + if [ $color -ge 1 ]; then options="${options} color=true"; fi + + printf "Options: '%s'\n\n" "${options}" + fi +} + +usage () { + cat < Specify the partion label prefix + -t, --disk_dev_root Specify the blockdevice for the target OS (e.g /dev/sda) + --disk_dev_machines Specify the blockdevice for containers/machines (e.g /dev/sdb) + --disk_dev_data Specify the blockdevice to store data (e.g /dev/sdc) + -v, --verbose Be verbose on what's going on (min: --verbose=0, max: --verbose=3) + --version show program version +EOF + + exit 0 +} + +### +# Main +### +# can't be ported to dash (ERR is not supported) +#trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR +trap trapkill TERM INT + +check_prerequisites + +# validate commandline options, set resonable defaults +parse_params $@ + +# commandline options handled with priority +parse_config_file +#show_config + +case $cmd in + get-config-name) + get_config_name + if test $? -gt 0; then + exit 1 + else + if [ $quiet -eq 0 ]; then + printf "${MAGENTA}Config Environment: ${GREEN}'%s'${NO_COLOR}\n" \ + "${config_name}" + fi + fi + ;; + get-config-names) + get_config_names + if [ $? -gt 0 ]; then + exit 1 + else + if [ $quiet -eq 0 ]; then + printf "${MAGENTA}Config environment names: ${GREEN}'%s'${NO_COLOR}\n" \ + "${config_names}" + fi + fi + ;; + get-config-disk-names) + valid_member=0 + get_config_disk_names "${config_name}" "${disk_name}" + if [ $? -gt 0 ]; then + exit 1 + else + if [ $quiet = 0 ]; then + printf "Config environment ${GREEN}'%s'${NO_COLOR} with disks: ${GREEN}'%s'${NO_COLOR}\n" \ + "${config_name}" "${disk_names}" + fi + valid_member=${#disk_names} + fi + ;; + get-config-partition_names) + ;; + + install-target) + prepare_target_disks + prepare_target_filesystems + + mount_target_filesystems + install_target_packages + + exit 0 + # change new root to target + cmd="arch-chroot $MOUNT_POINT arch-create-system install_target" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + ;; +esac diff --git a/usr/bin/arch-create-vm b/usr/bin/arch-create-vm new file mode 100755 index 0000000..4db86de --- /dev/null +++ b/usr/bin/arch-create-vm @@ -0,0 +1,962 @@ +#/bin/sh + +# arch-create-system +# https://github.com/rzerres/arch-create-vm +# Copyright (C) 2019 Ralf Zerres + +# This program 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 2 of the License, or +# (at your option) any later version. + +# This program 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 program; if not, write to the Free Software Foundation, Inc., + +# ------------------------------------------------------------------------- + +progname="${0##*/}" +version="0.1.0" + +# global variables +color=1 +config_json="$progname.json" +dryrun=0 +quiet=0 +verbose=0 + + +# ascii color +BLUE= +GREEN= +MAGENTA= +RED= +YELLOW= +NO_COLOR= + +### +# Functions +### +echo () { printf %s\\n "$*" ; } + +check_prerequisites () { + # requested binaries: + which chattr >/dev/null 2>&1 || { printf "'chattr' is not installed." && exit 1; } + which btrfs >/dev/null 2>&1 || { printf "'btrfs' is not installed." && exit 1; } + which fallocate >/dev/null 2>&1 || { printf "'findmnt' is not installed." && exit 1; } + which gawk >/dev/null 2>&1 || { printf "'gawk' is not installed." && exit 1; } + which mkfs >/dev/null 2>&1 || { printf "'mkfs' is not installed." && exit 1; } + which mkswap >/dev/null 2>&1 || { printf "'mkswap' is not installed." && exit 1; } + which mount >/dev/null 2>&1 || { printf "'mount' is not installed." && exit 1; } + which pacman >/dev/null 2>&1 || { printf "'pacman' is not installed." && exit 1; } + which pacstrap >/dev/null 2>&1 || { printf "'pacstrap' is not installed." && exit 1; } + which sed >/dev/null 2>&1 || { printf "'sed' is not installed." && exit 1; } + which sgdisk >/dev/null 2>&1 || { printf "'sgdisk' is not installed." && exit 1; } + which umount >/dev/null 2>&1 || { printf "'umount' is not installed." && exit 1; } + + if [ $(id -u) -ne 0 ] ; then printf "$progname: must be run as root\n" ; exit 1 ; fi +} + +create_bootloader () { + cmd="bootctl --path=boot install" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + cmd="bootctl status" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +create_btrfs () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + + + if [ $dryrun -eq 1 ]; then + printf "${BLUE}Would prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" \ + "$LABEL" + else + printf "${BLUE}Prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" \ + "$LABEL" + fi + + if [ -h /dev/disk/by-partlabel/$LABEL ]; then + + # create filesystem + cmd="mkfs -t btrfs --force \ + --label $PREFIX-$LABEL \ + --data single \ + --metadata single \ + /dev/disk/by-partlabel/$LABEL" + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + else + eval "$cmd" + fi + + if [ ! -d $MOUNT_POINT ]; then + cmd="mkdir -p $MOUNT_POINT" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + else + eval "$cmd" + fi + fi + fi +} + +create_disk () { + local TARGET=$1 + local LABEL=$2 + local PREFIX=$3 + local NR_PARTITIONS=${4:-1} + + local MOUNT_TARGET=/mnt + local SGDISK="/usr/bin/sgdisk" + local PARTITION=1 + + # test if block-device is available + test -b $TARGET || exit 1 + + printf "${BLUE}Prepare disc ${GREEN}'%s'${NO_COLOR}\n" "$TARGET" + + # Cleanup disk and create GPT-Partition + cmd="sgdisk --zap-all $TARGET" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + while expr $PARTITION '<=' $NR_PARTITIONS >>/dev/null; do + if [ $PARTITION '=' 1 ] && [ $NR_PARTITIONS '>' 1 ]; then + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would create ${GREEN}UEFI-Partition '%s'${NO_COLOR}\n" $PARTITION + else + # create UEFI partitions + sgdisk --new=$PARTITION:2048:+512M $TARGET + + # assign label + sgdisk --change-name=$PARTITION:$label_uefi $TARGET + + # adapt partition-type + sgdisk --typecode=$PARTITION:EF00 $TARGET + fi + + PARTITION=$(expr $PARTITION + 1) + continue + else + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would create ${GREEN}OS-Partition '%s'${NO_COLOR}\n" $PARTITION + else + # create partitions + sgdisk --new=$PARTITION $TARGET + + # assign label + sgdisk --change-name=$PARTITION:${LABEL} $TARGET + + # adapt partition-type + sgdisk --typecode=$PARTITION:8300 $TARGET + fi + + PARTITION=$(expr $PARTITION + 1) + fi + done + + # dump settings + cmd="sgdisk -p $TARGET" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would print created partition table for target ${GREEN}'%s'${NO_COLOR}\n" $TARGET + else + eval "$cmd" + fi +} + + +create_fs_structure () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + local SUBVOL=$4 + + printf "${BLUE}Prepare filesystem structure${GREEN}'subvol=%s'${NO_COLOR}\n" \ + "$SUBVOL" + cmd="mount_target $MOUNT_POINT /dev/disk/by-partlabel/$LABEL btrfs $SUBVOL" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + + if [ $LABEL="OS" ]; then + printf "${BLUE}Create target filesystem structure${NO_COLOR}\n" + + cmd="mkdir -p $MOUNT_POINT/var/lib" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + for subdir in log cache; do + cmd="btrfs subvolume create $MOUNT_POINT/var/$subdir" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + done + + # Subvolumes storing custom data + printf "${BLUE} - Create btrfs subvolumes${NO_COLOR}\n" + + for subvol in boot root home data var/lib/machines; do + cmd="btrfs subvolume create $MOUNT_POINT/$subvol" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + else + eval "$cmd" + fi + done + + create_swapfs $PREFIX $MOUNT_POINT + fi + + cmd="umount $MOUNT_POINT" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +create_network_stack () { + + systemctl enable systemd-networkd + systemctl enable systemd-resolved + + rm /etc/resolv.conf + ln -s /run/systemd/resolve/resolv.con /etc/resolv.conf + + systemctl enable dropbear.service +} + +create_swapfs () { + local PREFIX=$1 + local MOUNT_POINT=$2 + + printf "${BLUE}Create target swapfs${NO_COLOR}\n" + + # create a subvolume for the swapfs file + cmd="btrfs subvol create $MOUNT_POINT/swap" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + + # Disable COW functionality for Subvolume swap + cmd="chattr +C $MOUNT_POINT/swap" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + chmod 0700 $MOUNT_POINT/swap + fi + + # prepare the swapfile (size: 2x RAM) + RamSize=`cat /proc/meminfo | gawk 'NR==1 {print $2}'` + SwapSize=$(expr 2 '*' $RamSize) + SwapFile=$MOUNT_POINT/swap/swapfile + + cmd="fallocate --length $SwapSize $SwapFile" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + touch $SwapFile + chmod 0600 $SwapFile + eval "$cmd" + fi + + # create the swap filesystem + cmd="mkswap --label $PREFIX-Swap $SwapFile" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +create_fat () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + + if [ $dryrun -eq 1 ]; then + printf "${BLUE}Would prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + else + printf "${BLUE}Prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + fi + + if [ -h /dev/disk/by-partlabel/$LABEL ]; then + # create filesystem + cmd="mkfs -t fat \ + -F 32 \ + -n $LABEL \ + /dev/disk/by-partlabel/$LABEL" + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + fi +} + +create_vfat () { + local LABEL=$1 + local PREFIX=${2:-BTRFS} + local MOUNT_POINT=$3 + + if [ $dryrun -eq 1 ]; then + printf "${BLUE}Would prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + else + printf "${BLUE}Prepare filesystem ${GREEN}'%s'${NO_COLOR}\n" "$LABEL" + fi + + if [ -h /dev/disk/by-partlabel/$LABEL ]; then + # create filesystem + cmd="mkfs -t vfat \ + -n $PREFIX-$LABEL \ + /dev/disk/by-partlabel/$LABEL" + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + fi +} + +die () { + error "$@" + exit 1 +} + +error () { + printf "\n==> ERROR: %s\n" "$@" +} >&2 + +get_config_name () { + local config_json=${configfile:-$config_json} + local disk_name=${1} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_name ...${NO_COLOR}\n" + fi + + if test ! -r ${config_json}; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + exit 1 + fi + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigName as \$MediaPoolName \ + | .Disk[].DiskName \ + | select(. == \"${disk_name}\") \ + | \$ConfigName' + ${config_json}" + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigName ' \ + ${config_json}" + config_names=$(eval $cmd) + config_names=$(echo $config_names | sed -e 's/\n//g') + + if [ $verbose -ge 2 ]; then + printf "${MAGENTA}Config environment names: ${GREEN}'%s'${NO_COLOR}\n" \ + "$config_names" + fi +} + +get_config_names () { + local config_json=${configfile:-$config_json} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_names ...${NO_COLOR}\n" + fi + + if [ ! -r ${config_json} ]; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + exit 1 + fi + + cmd="jq --monochrome-output --ascii-output ' .ConfigEnvironment[] \ + | .ConfigName ' \ + ${config_json}" + config_names=$(eval $cmd) + config_names=$(echo $config_names | sed -e 's/\n//g') +} + +get_config_disk_names () { + local config_json=${config_file:-$config_json} + + if [ $verbose -ge 1 ]; then + printf "${BLUE}get_config_disk_names ...${NO_COLOR}\n" + fi + + if test ! -r ${config_json}; then + if [ $verbose -ge 1 ]; then + printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + "config_json" + fi + return 1 + fi + + cmd="jq --monochrome-output --ascii-output '.ConfigEnvironment[] \ + | select(.ConfigName == \"${config_name}\") \ + | .Disk[].DiskName' \ + ${config_json}" + disk_names=$(eval $cmd) + disk_names=$(echo $disk_names | sed -e 's/["\n]//g') + + if [ $verbose -ge 2 ]; then + printf "${MAGENTA}Disks in config environment ${GREEN}'%s'${MAGENTA} are: ${NO_COLOR}%s${NO_COLOR}\n" \ + "$config_name" "$disk_names" + fi + + if [ $verbose -ge 2 ]; then + i=1 + for disk in $disk_names; do + printf "${MAGENTA}Disk ${GREEN}'%d'${MAGENTA} with name ${GREEN}'%s'${NO_COLOR}\n" \ + "$i" "$disk" + i=$(expr $i + 1) + done + fi +} + +mount_target_filesytems () { + local LABEL=$1 + local MOUNT_POINT=$2 + + printf "${BLUE}Install target system ${GREEN}'%s' -> '%s'${NO_COLOR}\n" "$LABEL" "$MOUNT_POINT" + + mount_target $MOUNT_POINT /dev/disk/by-partlabel/$LABEL btrfs root + + for subdir in boot data home swap var/log var/cache var/lib/machines; do + mkdir -p $MOUNT_POINT/$subdir + done + + mount_target $MOUNT_POINT/var/cache /dev/disk/by-partlabel/$LABEL btrfs var/cache + mount_target $MOUNT_POINT/var/log /dev/disk/by-partlabel/$LABEL btrfs var/log + mount_target $MOUNT_POINT/boot /dev/disk/by-partlabel/$label_uefi fat + mount_target $MOUNT_POINT/var/lib/machines /dev/disk/by-partlabel/$label_machines btrfs / + mount_target $MOUNT_POINT/data /dev/disk/by-partlabel/$label_data btrfs / + + # change new root to target + cmd="arch-chroot $MOUNT_POINT install_target" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +install_target () { + local LABEL=$1 + + printf "${BLUE}Install target system ${GREEN}'%s' -> '%s'${NO_COLOR}\n" "$LABEL" "$MOUNT_POINT" + + # Update available packages + cmd="pacman -Sy" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + # Basic packages + cmd="pacstrap $MOUNT_POINT arch-install-scripts base btrfs-progs intel-ucode linux linux-firmware dosfstools iptables-nft man sudo" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + # Hyper-V packages + cmd="pacstrap $MOUNT_POINT hyperv" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + # SSH-Daemon, FileManager and Editor + cmd="pacstrap $MOUNT_POINT dropbear vim mc bash-completion n" + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi + + prepare_network_stack + create_network_stack + + prepare_bootloader + create_bootloader + + prepare_locale $label_prefix + + #systemctl enable hvkvpdaemon.service + #systemctl enable hvvssdaemon.service + + genfstab / >/etc/fstab +} + +mount_target () { + local MOUNT_POINT=$1 + local DEV_NAME=$2 + local FS_TYPE=$3 + local FS_SUBVOL=$4 + + case $FS_TYPE in + "btrfs") + if [ -h $DEV_NAME ]; then + cmd="mount -t $FS_TYPE \ + -o subvol=$FS_SUBVOL \ + $DEV_NAME \ + $MOUNT_POINT" + printf "${BLUE}mounting subvol ${GREEN}'%s' ${BLUE}of ${GREEN}'%s' ${BLUE}at ${GREEN}'%s${NO_COLOR}\n" \ + "$FS_SUBVOL" "$DEV_NAME" "$MOUNT_POINT" + fi + ;; + "vfat") + if [ -h $DEV_NAME ]; then + cmd="mount -t $FS_TYPE \ + $DEV_NAME \ + $MOUNT_POINT" + printf "${BLUE}mounting ${GREEN}'%s' ${BLUE}at ${GREEN}'%s${NO_COLOR}\n" \ + "$DEV_NAME" "$MOUNT_POINT" + fi + ;; + esac + + cmd=`echo $cmd | tr -s "[:blank:]"` + if [ $dryrun -eq 1 ]; then + printf "${MAGENTA}Would run ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + else + eval "$cmd" + fi +} + +parse_params () { + #printf "\n${BLUE}Parse arguments...${NO_COLOR}\n" + + # Evaluate given call parameters + i=0 + while [ $# -gt 0 ]; do + key="$1" + case $key in + -h | --help | \-\? | --usage) + # Call usage() function. + usage + ;; + --color) + color=1 + shift 1 + ;; + -c | --config) + config_json="$2" + shift 2 + ;; + --get-config-name) + shift 1 + config_params=${*} + config_params="${config_params%% -?[a-z-]*}" + params=$* + set -- $config_params + cmd=get-config-name + ;; + --get-config-names) + shift 1 + config_params=${*} + config_params="${config_params%% -?[a-z])*}" + set -- $config_params + cmd=get-config-names + ;; + --get-config-disk-names) + shift 1 + config_params=${*} + config_params="${config_params%% -?[a-z]*}" + params=$* + set -- "$config_params" + count=$# + test $count -lt 1 && printf "missing argument: config_name\n" && exit 1 + test $count -ge 1 && config_name="$1" + #test $count -ge 2 && disk_name="$2" + set -- $params + shift $count + cmd=get-config-disk-names + ;; + --dry-run|--dryrun) + dryrun=1 + shift 1 + ;; + --mountpoint|--MOUNTPOINT) + mount_point="$2" + shift 2 + ;; + --labelprefix) + label_prefix="$2" + shift 2 + ;; + --label_data) + label_data="$2" + shift 2 + ;; + --target_data) + target_data="$2" + shift 2 + ;; + --target_machines) + target_machines="$2" + shift 2 + ;; + --target_os) + target_os="$2" + shift 2 + ;; + --target_machines|--target_machines) + target_machines="$2" + shift 2 + ;; + --target_os|--target_os) + target_os="$2" + shift 2 + ;; + -v|--verbose) + verbose=$(expr $verbose '+' 1) + shift 1 + ;; + --version) + printf "%s v%s\n" "$progname" "$version" + exit 0 + ;; + --color=*) + case ${1#*=} in + yes | Yes | True | true) + color=1; + ;; + *) + ;; + esac + shift + ;; + --c=* | --config=*) + config_json=${1#*=} + shift + ;; + --mountpoint=*) + mount_point=${1#*=} + shift + ;; + --labelprefix=*) + label_prefix=${1#*=} + shift + ;; + --label_data=*|--LABEL_DATA=*) + label_data=${1#*=} + shift + ;; + --label_machines=*|--LABEL_MACHINES=*) + label_machines=${1#*=} + shift + ;; + --label_os=*|--LABEL_OS=*) + label_os=${1#*=} + shift + ;; + --target_data=*|--TARGET_DATA=*) + target_data=${1#*=} + shift + ;; + --target_machines=*|--TARGET_MACHINES=*) + target_machines=${1#*=} + shift + ;; + --target_os=*|--TARGET_OS=*) + target_os=${1#*=} + shift + ;; + --v=* | --verbose=*) + verbose=${1#*=} + shift + ;; + --) # End of all options + shift + break + ;; + -*) + printf "WARN: Unknown option (ignored): $1" >&2 + die "Unknown option" + ;; + *) + printf "Unknown option: %s\nRun '%s -h' for valid options.\n" $key $progname + die "Unknown option" + ;; + esac + done + + # check if config is accessible in given path + for config in ${config_json} ./${config_json} ${HOME}/${config_json} /etc/${progname}/${config_json}; do + if [ -r $config ] ; then + config_json=$config + break + else + config_json="NONE" + fi + done + if [ ${config_json} = "NONE" ]; then + if [ $verbose -ge 1 ]; then + printf "${BLUE}No config file accessible!${NO_COLOR}\n" \ + # printf "${RED}Error:${MAGENTA} config file ${GREEN}'%s'${MAGENTA} can't be opend!${NO_COLOR}\n" \ + # "$config_json" + fi + #exit 1 + fi + + target_os=${target_os:-"/dev/sda"} + mount_point=${mount_point:-"/mnt"} + label_prefix=${label_prefix:-"BTRFS"} + label_os=${label_os:-"OS"} + label_machines=${label_machines:-"MACHINES"} + label_data=${label_data:-"DATA"} + label_uefi=${label_uefi:-"UEFI"} + + if [ $verbose -eq 0 ]; then quiet=1; fi + + if [ "$color" ]; then + # ascii color + BLUE='\033[0;34m' + GREEN='\033[0;32m' + MAGENTA='\033[0;35m' + RED='\033[0;31m' + YELLOW='\033[0;33m' + NO_COLOR='\033[0m' + fi + + if [ $verbose -ge 1 ]; then + printf "${BLUE}$progname (runtime arguments)...${NO_COLOR}\n" + i=0 + printf "using parameters:\n" + printf " Config File: '%s'\n" "$config_json" + printf " TARGET OS: '%s'\n" "$target_os" + printf " Mount Point: '%s'\n" "$mount_point" + printf " TARGET Machines: '%s'\n" "$target_machines" + printf " TARGET DATA: '%s'\n" "$target_data" + printf " Label Prefix: '%s'\n" "$label_prefix" + printf " Label OS: '%s'\n" "$label_os" + printf " Label Machines: '%s'\n" "$label_machines" + printf " Label DATA: '%s'\n" "$label_data" + printf " Label UEFI: '%s'\n" "$label_uefi" + + if [ $verbose -ge 2 ]; then options="verbose_level=$verbose"; fi + if [ $dryrun -ge 1 ]; then options="${options}dryrun=true"; fi + if [ $color -ge 1 ]; then options="${options} color=true"; fi + + printf "Options: '%s'\n\n" "${options}" + fi +} + +prepare_bootloader () { + + # check for accessible uefi partition + ls /sys/firmware/efi/efivars > /dev/null + if [ $? -eq 0 ]; then + printf "UEFI Environmint is ok'\n" + + mkdir -p /boot/loader/entries + + printf '%s\n' \ + "default arch" \ + "timeout 3" \ + "editor yes" \ + "console-mode max" \ + > /boot/loader/loader.conf + + printf '%s\n' \ + "default arch" \ + "title Arch Linux (Mainline)" \ + "linux /vmlinuz-linux" \ + "initrd /intel-ucode.img" \ + "initrd /initramfs-linux.img" \ + "options rootflags=subvol=root root=LABEL=BTRFS-OS" \ + > /boot/loader/entries/arch.conf + + bootctl status + fi +} + +prepare_locale () { + local LABEL=$1 + + hostnamectl set-hostname $LABEL-lin01 + localectl set-keymap de-latin1-nodeadkeys +} + +prepare_network_stack () { + printf '%s\n' \ + "[Match]" \ + "Name=eth0" \ + "" \ + "[Network]" \ + "Description=Slave Bridge-Interface" \ + "Bridge=bridge-lan" \ + > /etc/systemd/network/70-eth0-bridge-slave.netdev + + printf '%s\n' \ + "[Match]" \ + "#Host=Lin01" \ + "#Architecture=x86_64" \ + "" \ + "[NetDev]" \ + "Description=Bridge for Containers" \ + "Name=bridge-lan" \ + "Kind=bridge" \ + > /etc/systemd/network/80-bridge-lan.netdev + + printf '%s\n' \ + "[Match]" \ + "Name=bridge-lan" \ + "Driver=bridge" \ + "" \ + "[Network]" \ + "Description=Bridge for Containers" \ + "IPForward=yes" \ + "IPMasquerqade=yes" \ + "LinkLocalAddressisng=yes" \ + "IPv6AcceptRA=yes" \ + "IgnoreCarrierLoss=yes" \ + > /etc/systemd/network/80-bridge-lan.network +} + +prepare_target_disks () { + create_disk ${target_os} $label_os $label_prefix 2 + create_disk ${target_machines} $label_machines $label_prefix + create_disk ${target_data} $label_data $label_prefix +} + +prepare_target_filesystems () { + create_fat $label_uefi $label_prefix $mount_point + create_btrfs $label_os $label_prefix $mount_point + create_btrfs $label_machines $label_prefix $mount_point + create_btrfs $label_data $label_prefix $mount_point + + create_fs_structure $label_os $label_prefix $mount_point / +} + +usage () { + cat < Specify the partion label prefix + -t, --target_os Specify the blockdevice for the target OS (e.g /dev/sda) + --target_machines Specify the blockdevice for containers/machines (e.g /dev/sdb) + --target_data Specify the blockdevice to store data (e.g /dev/sdc) + -v, --verbose Be verbose on what's going on (min: --verbose=1, max: --verbose=3) + --version show program version +EOF + + exit 0 +} + +### +# Main +### +# can't be ported to dash (ERR is not supported) +#trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR +trap trapkill TERM INT + +check_prerequisites + +# validate commandline options, set resonable defaults +parse_params $@ +#get_config + +case $cmd in + get-config-name) + get_config_name + if test $? -gt 0; then + exit 1 + else + if [ $quiet -eq 0 ]; then + printf "${MAGENTA}Config Environment: ${GREEN}'%s'${NO_COLOR}\n" \ + "${config_name}" + fi + fi + ;; + get-config-names) + get_config_names + if [ $? -gt 0 ]; then + exit 1 + else + if [ $quiet -eq 0 ]; then + printf "${MAGENTA}Config environment names: ${GREEN}'%s'${NO_COLOR}\n" \ + "${config_names}" + fi + fi + ;; + get-config-disk-names) + valid_member=0 + get_config_disk_names "${config_name}" "${disk_name}" + if [ $? -gt 0 ]; then + exit 1 + else + if [ $quiet = 0 ]; then + printf "Config environment ${GREEN}'%s'${NO_COLOR} with disks: ${GREEN}'%s'${NO_COLOR}\n" \ + "${config_name}" "${disk_names}" + fi + valid_member=${#disk_names} + fi + ;; + get-config-partition_names) + ;; +esac + +exit 0 + + +# prepare target +prepare_target_disks +prepare_target_filesystems + +mount_target_filesystems +#install_target OS $mount_point