#!/usr/bin/env bash # Change the working directory. In usage, this is the same as using cd, # however, it will make additional checks to ensure everything is going fine. chgdir() { debug "bashtard/chgdir" "Changing workdir to $1" cd -- "$1" || die "Failed to change directory to $1" } # Read a particular value from a key/value configuration file. Using this # function introduces a dependency on awk. config() { config_for "${BASHTARD_PLATFORM[fqdn]}" "$@" } config_for() { local host=$1 ; shift local key=$1 ; shift local default=$1 ; shift local default local file local files files=( "$BASHTARD_ETCDIR/hosts.d/$host" "$BASHTARD_ETCDIR/os.d/${BASHTARD_PLATFORM[key]}" "$BASHTARD_ETCDIR/defaults" ) if [[ ! -z "$BASHTARD_PLAYBOOK" ]] then debug "bashtard/config_for" "BASHTARD_PLAYBOOK=$BASHTARD_PLAYBOOK, adding etc entries" files+=( "$BASHTARD_ETCDIR/playbooks.d/$BASHTARD_PLAYBOOK/etc/os.d" "$BASHTARD_ETCDIR/playbooks.d/$BASHTARD_PLAYBOOK/etc/defaults" ) fi files+=( "$BASHTARD_BASEDIR/etc/os.d/${BASHTARD_PLATFORM[key]}" "$BASHTARD_BASEDIR/etc/defaults" ) # Check configuration files for file in "${files[@]}" do debug "bashtard/config_for" "Checking for '$key' in '$file'" [[ ! -f $file ]] && continue value="$(awk -F= '$1 == "'"$key"'" { print $NF }' "$file")" if [[ -n $value ]] then debug "bashtard/config_for" "Found $key=$value in $file" printf "%s" "$value" return fi done # Return default value if [[ -n $default ]] then printf "%s" "$default" return fi # Error alert "bashtard/config_for" "No configuration value for $key" } # Create a datetime stamp. This is a wrapper around the date utility, ensuring # that the date being formatted is always in UTC and respect SOURCE_DATE_EPOCH, # if it is set. datetime() { local date_opts # Apply SOURCE_DATE_EPOCH as the date to base off of. if [[ $SOURCE_DATE_EPOCH ]] then date_opts+=("-d@$SOURCE_DATE_EPOCH") date_opts+=("-u") fi date "${date_opts[@]}" +"${1:-%FT%T}" } # Log a message as error, and exit the program. This is intended for serious # issues that prevent the script from running correctly. The exit code can be # specified with -i, or will default to 1. die() { local OPTIND local code while getopts ":i:" opt do case "$opt" in i) code=$OPTARG ;; *) alert "bashtard/die" "Unused argument specified: $opt" ;; esac done shift $(( OPTIND -1 )) alert "$@" exit "${code:-1}" } # Fetch a file from an URL. Using this function introduces a dependency on curl. fetch_http() { local OPTIND local buffer while getopts ":o:" opt do case "$opt" in o) buffer=$OPTARG ;; *) alert "bashtard/fetch_http" "Unused argument specified: $opt" ;; esac done shift $(( OPTIND -1 )) [[ -z $buffer ]] && buffer="$(tmpfile)" notice "bashtard/fetch_http" "Downloading $1 to $buffer" for util in curl wget do command -v "$util" > /dev/null || continue "fetch_http_$util" "$1" "$buffer" || continue local exit_code=$? printf "%s" "$buffer" return $exit_code done die "bashtard/fetch_http" "Unable to download file over HTTP!" } fetch_http_curl() { curl -Ls "$1" > "$2" } fetch_http_wget() { wget --quiet --output-document "$2" "$1" } # Check if the first argument given appears in the list of all following # arguments. in_args() { local needle="$1" shift for arg in "$@" do [[ $needle == "$arg" ]] && return 0 done return 1 } # Join a list of arguments into a single string. By default, this will join # using a ",", but you can set a different character using -c. Note that this # only joins with a single character, not a string of characters. join_args() { local OPTIND local IFS="," while getopts ":c:" opt do case "$opt" in c) IFS="$OPTARG" ;; *) warn "bashtard/join_args" "Unused opt specified: $opt" ;; esac done shift $(( OPTIND - 1)) printf "%s" "$*" } # OS independent package management pkg() { local system="bashtard/pkg" local action=$1 ; shift local pkg="$(config "pkg.$1")" ; shift if [[ -z $pkg ]] then crit "$system" "No package name for $pkg" return 1 fi if [[ "$(type -t pkg_$action)" != "function" ]] then crit "$system" "Invalid package manager action $action" return 1 fi "pkg_$action" "$pkg" } pkg_install() { local system="bashtard/pkg/install" local app=$1 ; shift case "${BASHTARD_PLATFORM[key]}" in freebsd) set -- /usr/sbin/pkg install -y "$app" ;; linux-debian*) set -- apt install -y "$app" ;; linux-gentoo) set -- emerge --ask=n --update "$app" ;; *) crit "$system" "No package manager configured for ${BASHTARD_PLATFORM[key]}" return 1 ;; esac notice "$system" "$*" $@ } pkg_uninstall() { local system="bashtard/pkg/uninstall" local app=$1 ; shift case "${BASHTARD_PLATFORM[key]}" in freebsd) set -- /usr/sbin/pkg uninstall -y "$app" ;; linux-debian*) set -- apt remove -y "$app" ;; linux-gentoo) set -- emerge --ask=n --unmerge "$app" ;; *) crit "$system" "No package manager configured for ${BASHTARD_PLATFORM[key]}" return 1 ;; esac notice "$system" "$*" $@ } # OS independent service management. svc() { local system="bashtard/svc" local service local action action=$1 ; shift service="$(config svc.$1)" ; shift if [[ -z $service ]] then crit "$system" "No service name for $service" return 1 fi if [[ "$(type -t svc_$action)" != "function" ]] then crit "$system" "Invalid service manager action $action" return 1 fi "svc_$action" "$service" } svc_disable() { local system="bashtard/svc/disable" local service=$1 case "${BASHTARD_PLATFORM[key]}" in linux-gentoo) set -- /sbin/rc-update del "$service" ;; linux-*) set -- systemctl disable "$service" ;; *) crit "$system" "No service manager configured for ${BASHTARD_PLATFORM[key]}" return 1 esac notice "$system" "$*" $@ } svc_enable() { local system="bashtard/svc/enable" local service=$1 case "${BASHTARD_PLATFORM[key]}" in linux-gentoo) set -- /sbin/rc-update add "$service" ;; linux-*) set -- systemctl enable "$service" ;; *) crit "$system" "No service manager configured for ${BASHTARD_PLATFORM[key]}" return 1 esac notice "$system" "$*" $@ } svc_reload() { local system="bashtard/svc/reload" local service=$1 case "${BASHTARD_PLATFORM[key]}" in freebsd) set -- service "$service" reload ;; linux-gentoo) set -- /sbin/rc-service "$service" reload ;; linux-*) set -- systemctl reload "$service" ;; *) crit "$system" "No service manager configured for ${BASHTARD_PLATFORM[key]}" return 1 esac notice "$system" "$*" $@ } svc_restart() { local system="bashtard/svc/restart" local service=$1 case "${BASHTARD_PLATFORM[key]}" in freebsd) set -- service "$service" restart ;; linux-gentoo) set -- /sbin/rc-service "$service" restart ;; linux-*) set -- systemctl restart "$service" ;; *) crit "$system" "No service manager configured for ${BASHTARD_PLATFORM[key]}" return 1 esac notice "$system" "$*" $@ } svc_start() { local system="bashtard/svc/start" local service=$1 case "${BASHTARD_PLATFORM[key]}" in freebsd) set -- service "$service" start ;; linux-gentoo) set -- /sbin/rc-service "$service" start ;; linux-*) set -- systemctl start "$service" ;; *) crit "$system" "No service manager configured for ${BASHTARD_PLATFORM[key]}" return 1 esac notice "$system" "$*" $@ } svc_stop() { local system="bashtard/svc/stop" local service=$1 case "${BASHTARD_PLATFORM[key]}" in freebsd) set -- service "$service" stop ;; linux-gentoo) set -- /sbin/rc-service "$service" stop ;; linux-*) set -- systemctl stop "$service" ;; *) crit "$system" "No service manager configured for ${BASHTARD_PLATFORM[key]}" return 1 esac notice "$system" "$*" $@ } file_template() { local file="$BASHTARD_ETCDIR/playbooks.d/$BASHTARD_PLAYBOOK/share/$1" ; shift local sedfile="$(tmpfile)" if [[ ! -f $file ]] then crit "bashtard/template" "Tried to render template from $file, but it doesn't exist" return fi for kv in "$@" do debug "bashtard/template" "Adding $kv to sedfile at $sedfile" key="$(awk -F= '{ print $1 }' <<< $kv)" if [[ -z "$key" ]] then crit "bashtard/template" "Empty key in '$kv' while rendering $file?" fi value="$(awk -F= '{ print $NF }' <<< $kv)" if [[ -z "$value" ]] then crit "bashtard/template" "Empty key in '$kv' while rendering $file?" fi printf 's@${%s}@%s@g\n' "$key" "$value" >> "$sedfile" done sed -f "$sedfile" "$file" } # Create a temporary directory. Similar to tempfile, but you'll get a directory # instead. tmpdir() { local dir dir="$(mktemp -d)" # Ensure the file was created succesfully if [[ ! -d "$dir" ]] then die "bashtard/tmpdir" "Failed to create a temporary directory at $dir" fi debug "bashtard/tmpdir" "Temporary file created at $dir" printf "%s" "$dir" } # Create a temporary file. In usage, this is no different from mktemp itself, # however, it will apply additional checks to ensure everything is going # correctly, and the files will be cleaned up automatically at the end. tmpfile() { local file file="$(mktemp)" # Ensure the file was created succesfully if [[ ! -f "$file" ]] then die "bashtard/tmpfile" "Failed to create a temporary file at $file" fi debug "bashtard/tmpfile" "Temporary file created at $file" printf "%s" "$file" }