#!/usr/bin/env bash # # wrapacker - https://github.com/elasticdog/packer-arch # # A script to automatically ensure the latest ISO download URL and optionally # use a mirror from the provided country in order to build an Arch Linux box # using Packer. # # Copyright (c) 2015 Aaron Bull Schaefer # This source code is provided under the terms of the ICS License # that can be be found in the LICENSE file. # ##### Constants # country codes supported by https://www.archlinux.org/mirrorlist/ readonly VALID_COUNTRIES=(AT AU BD BE BG BR BY CA CH CL CN CO CZ DE DK EC ES FR GB GR HR HU ID IE IL IN IR IS IT JP KR KZ LT LU LV MK NC NL NO NZ PH PL PT RO RS RU SE SG SK TR TW UA US VN ZA) if command -v packer-io > /dev/null 2>&1; then # Older arch linux versions called the packer binary packer-io. readonly PACKER_BIN='packer-io' else readonly PACKER_BIN='packer' fi VALID_TIME_UNITS=(ns us ms s m h) ##### Functions # print a message to stderr warn() { local fmt="$1" shift printf "wrapacker: $fmt\n" "$@" >&2 } # print a message to stderr and exit with either # the given status or that of the most recent command die() { local st="$?" if [[ "$1" != *[^0-9]* ]]; then st="$1" shift fi warn "$@" exit "$st" } # test the VALID_COUNTRIES array for membership of the given value validate_country() { local haystack="VALID_COUNTRIES[@]" local needle=$1 local found=1 for element in "${!haystack}"; do if [[ $element == "$needle" ]]; then found=0 break fi done return $found } # print this script's usage message to stderr usage() { cat <<-EOF >&2 usage: wrapacker [-c COUNTRY] [-p PROVIDER] [-t TIMEOUT] [-w] [-o ON-ERROR ACTION] [-f] [-d] [-h] EOF } # print the list of valid countries to stderr print_valid_countries() { printf '\n*** VALID COUNTRY CODES ***\n\n' >&2 for country in "${VALID_COUNTRIES[@]}"; do printf '%-6s\n' "$country" done | column >&2 } # print the list of valid providers to stderr print_valid_providers() { printf '\n*** VALID PROVIDERS ***\n\n' >&2 for provider in {parallels,virtualbox,vmware,libvirt}; do printf '%-6s\n' "$provider" done | column >&2 } # print the list of valid time units to stderr print_valid_time_units() { printf '\n*** VALID TIME UNITS ***\n\n' >&2 for units in "${VALID_TIME_UNITS[@]}"; do printf '%-6s\n' "$units" done | column >&2 } # print the list of valid on-error actions to stderr print_valid_on_error_actions() { printf '\n*** VALID ON-ERROR ACTIONS ***\n\n' >&2 for action in {cleanup,abort,ask}; do printf '%-6s\n' "$action" done | column >&2 } # print this script's help message to stdout help() { cat <<-EOF NAME wrapacker -- wrap packer to build arch with custom mirror settings SYNOPSIS wrapacker [options...] DESCRIPTION wrapacker will automatically ensure the latest ISO download URL and will optionally use a mirror from the provided country in order to build an Arch Linux box using Packer. OPTIONS -c, --country=COUNTRY the country code to download from; defaults to the kernel.org US mirror -p, --provider=PROVIDER the packer provider to build with; defaults to virtualbox -t, --timeout=TIMEOUT sets the amount of time packer will wait for trying ssh login; defaults to 20m -w, --skip-write-zeros don't inflate the disk to improve virtual disk compaction -o, --on-error=ACTION error handling if the build fails; defaults to cleanup -f, --force force a build to continue if artifacts exist; deletes existing artifacts -d, --dry-run do not actually perform the build, just show what would run -h, --help view this help message -e, --headless do not run the build in the GUI only works with --provider=virtualbox AUTHOR Aaron Bull Schaefer EOF } ##### Main # check for dependencies for cmd in {awk,curl,sed,tr}; do command -v $cmd > /dev/null || die "required command \"$cmd\" was not found" done # reset all variables that might be set country='' provider='' dry_run=false timeout='' skip_write_zeros=false on_error='' force=false headless=false # parse command line options while [[ $1 != '' ]]; do case $1 in -c | --country) country=$(echo "$2" | tr '[:lower:]' '[:upper:]') shift ;; --country=*) country=$(echo "${1#*=}" | tr '[:lower:]' '[:upper:]') ;; -p | --provider) provider=$2 shift ;; --provider=*) provider=${1#*=} ;; -t | --timeout) timeout=$2 shift ;; --timeout=*) timeout=${1#*=} ;; -w | --skip-write-zeros) skip_write_zeros=true ;; -o | --on-error) on_error=$2 shift ;; --on-error=*) on_error=${1#*=} ;; -f | --force) force=true ;; -d| --dry-run) dry_run=true ;; -h | --help | -\?) help print_valid_countries print_valid_providers print_valid_time_units print_valid_on_error_actions exit 0 ;; -e| --headless) headless=true ;; --*) warn "unknown option -- ${1#--}" usage exit 1 ;; *) warn "unknown option -- ${1#-}" usage exit 1 ;; esac shift done if [[ $provider ]]; then case $(echo "$provider" | tr '[:upper:]' '[:lower:]') in parallels | parallels-iso) PACKER_PROVIDER='parallels-iso' ;; virtualbox | virtualbox-iso) PACKER_PROVIDER='virtualbox-iso' ;; vmware | vmware-iso) PACKER_PROVIDER='vmware-iso' ;; libvirt | qemu) PACKER_PROVIDER='qemu' ;; *) warn "unknown provider -- ${provider}" usage print_valid_providers exit 1 ;; esac else PACKER_PROVIDER='virtualbox-iso' fi if [[ $country ]]; then if validate_country "$country"; then MIRRORLIST="https://archlinux.org/mirrorlist/?country=${country}&protocol=http&protocol=https&ip_version=4&use_mirror_status=on" MIRROR=$(curl -s "$MIRRORLIST" | awk '/#Server/{ print $3; exit }' | sed 's|/$repo.*||') else warn 'INVALID COUNTRY SPECIFIED - %s' "$country" usage print_valid_countries exit 1 fi else MIRROR='https://mirrors.kernel.org/archlinux' country='US' fi ISO_CHECKSUM_URL="${MIRROR}/iso/latest/sha256sums.txt" ISO_NAME=$(curl -sL "$ISO_CHECKSUM_URL" | awk '/-x86_64.iso/{ print $2 }' | tail -n 1) ISO_URL="${MIRROR}/iso/latest/${ISO_NAME}" if [[ $timeout ]]; then if [[ "$timeout" =~ ^[0-9]+(ns|us|ms|s|m|h)$ ]]; then SSH_TIMEOUT=$timeout else warn 'INVALID TIME UNITS SPECIFIED or MISSING UNIT - %s' "$timeout" usage print_valid_time_units exit 1 fi else SSH_TIMEOUT='20m' fi if [[ $skip_write_zeros = true ]]; then WRITE_ZEROS="false" else WRITE_ZEROS="true" fi if [[ $on_error ]]; then case $(echo "$on_error" | tr '[:upper:]' '[:lower:]') in cleanup) ON_ERROR='cleanup' ;; abort) ON_ERROR='abort' ;; ask) ON_ERROR='ask' ;; *) warn "unknown on-error action -- ${on_error}" usage print_valid_on_error_actions exit 1 ;; esac else ON_ERROR='cleanup' fi if [[ $dry_run = true ]]; then if [[ $force = true ]]; then cat <<-EOF $PACKER_BIN build \\ -only=$PACKER_PROVIDER \\ -var "iso_url=$ISO_URL" \\ -var "iso_checksum_url=$ISO_CHECKSUM_URL" \\ -var "ssh_timeout=$SSH_TIMEOUT" \\ -var "country=$country" \\ -var "headless=$headless" \\ -var "write_zeros=$WRITE_ZEROS" \\ -on-error=$ON_ERROR \\ -force \\ arch-template.json EOF else cat <<-EOF $PACKER_BIN build \\ -only=$PACKER_PROVIDER \\ -var "iso_url=$ISO_URL" \\ -var "iso_checksum_url=$ISO_CHECKSUM_URL" \\ -var "ssh_timeout=$SSH_TIMEOUT" \\ -var "country=$country" \\ -var "headless=$headless" \\ -var "write_zeros=$WRITE_ZEROS" \\ -on-error=$ON_ERROR \\ arch-template.json EOF fi else if [[ $force = true ]]; then $PACKER_BIN build \ -only=$PACKER_PROVIDER \ -var "iso_url=$ISO_URL" \ -var "iso_checksum_url=$ISO_CHECKSUM_URL" \ -var "ssh_timeout=$SSH_TIMEOUT" \ -var "country=$country" \ -var "headless=$headless" \ -var "write_zeros=$WRITE_ZEROS" \ -on-error=$ON_ERROR \ -force \ arch-template.json else $PACKER_BIN build \ -only=$PACKER_PROVIDER \ -var "iso_url=$ISO_URL" \ -var "iso_checksum_url=$ISO_CHECKSUM_URL" \ -var "ssh_timeout=$SSH_TIMEOUT" \ -var "country=$country" \ -var "headless=$headless" \ -var "write_zeros=$WRITE_ZEROS" \ -on-error=$ON_ERROR \ arch-template.json fi fi exit $?