commit 06344291fa977a3f8cfb8c4c43fa245c7dff335f Author: Matthias Fulz Date: Tue Jun 29 20:01:25 2021 +0200 initial archbuilder version diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ccdae9e --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +PROGNM ?= archbuilder +PREFIX ?= /usr +SHRDIR ?= $(PREFIX)/share +BINDIR ?= $(PREFIX)/bin +LIBDIR ?= $(PREFIX)/lib +CNFDIR ?= $(PREFIX)/etc +ARCHBUILDER_LIB_DIR ?= $(LIBDIR)/$(PROGNM) +ARCHBUILDER_CONF_DIR ?= $(CNFDIR)/$(PROGNM) +ARCHBUILDER_VERSION ?= 0.9.0 + +.PHONY: install build archbuilder + +build: archbuilder + +archbuilder: archbuilder.in + sed -e 's|ARCHBUILDER_LIB_DIR|$(ARCHBUILDER_LIB_DIR)|' \ + -e 's|ARCHBUILDER_CONF_DIR|$(ARCHBUILDER_CONF_DIR)|' \ + -e 's|ARCHBUILDER_VERSION|$(ARCHBUILDER_VERSION)|' $< >$@ + +install-archbuilder: archbuilder + @install -Dm755 archbuilder -t '$(DESTDIR)$(BINDIR)' + +install: install-archbuilder + @install -Dm644 lib/archbuilder.inc.sh -t '$(DESTDIR)$(LIBDIR)/$(PROGNM)' + @install -Dm644 lib/buildah.inc.sh -t '$(DESTDIR)$(LIBDIR)/$(PROGNM)' + @install -Dm644 lib/ext/slog.sh -t '$(DESTDIR)$(LIBDIR)/$(PROGNM)/ext' + @install -Dm644 lib/ext/bash_log_internals.inc.sh -t '$(DESTDIR)$(LIBDIR)/$(PROGNM)/ext' + @install -Dm644 archbuilder.env -t '$(DESTDIR)$(CNFDIR)/$(PROGNM)' diff --git a/archbuilder.env b/archbuilder.env new file mode 100644 index 0000000..e9a3fce --- /dev/null +++ b/archbuilder.env @@ -0,0 +1,11 @@ +#!/bin/bash + +ARCHBUILDER_BASE_DIR="${HOME}/.archbuilder" +ARCHBUILDER_IMAGE_NAME="archbuilder" +ARCHBUILDER_CACHE_REPO="${ARCHBUILDER_BASE_DIR}/crepo" +ARCHBUILDER_LOG_PATH="${ARCHBUILDER_BASE_DIR}/logs" +ARCHBUILDER_LOG_TO_FILE=1 + +LOG_LEVEL_STDOUT="INFO" +LOG_LEVEL_LOG="INFO" + diff --git a/archbuilder.in b/archbuilder.in new file mode 100644 index 0000000..72baa9b --- /dev/null +++ b/archbuilder.in @@ -0,0 +1,145 @@ +#!/bin/bash + +readonly archbuilder_version='ARCHBUILDER_VERSION' +readonly lib_dir='ARCHBUILDER_LIB_DIR' +readonly conf_dir='ARCHBUILDER_CONF_DIR' + +. "${lib_dir}/ext/slog.sh" +. "${lib_dir}/ext/bash_log_internals.inc.sh" + +. "${conf_dir}/archbuilder.env" + +. "${lib_dir}/archbuilder.inc.sh" +. "${lib_dir}/buildah.inc.sh" + + +# internal params +unset _FLAG_KEEP +unset _FLAG_SILENT + +_OPT_MODE="build" +_OPT_DEPS=() +_OPT_KEYS=() + +_OPT_CON_BUILD_USER="archbuilder" +_OPT_CON_COPTIONS="" + +# actions to initialize runtime +unset _ACT_CREATE_IMAGE +unset _ACT_CREATE_BASE_DIR +unset _ACT_CREATE_CACHE_REPO_PATH +unset _ACT_CREATE_LOG_PATH + +function usage() { + echo "archbuilder is a makepkg wrapper that uses buildah for the build process." + echo "That will lead to a very clean build, where the PKGBUILD and the dependencies," + echo "have to be 100% correct and nothing will pollute the host system." + echo + echo "Usage:" + echo " archbuilder [options] -- " + echo + echo "Options:" + echo -e " -h, --help\t\t\t\t\tPrint this help" + echo -e " -k, --keep\t\t\t\t\tKeep the working container that is used for the build" + echo -e " -n, --name \t\t\t\tImage name that is used to spin up the container (default: ${INAME})" + echo -e " -m, --mode \t\tRun mode: (default: ${MODE})" + echo -e " \t\tcreate will setup the base image" + echo -e " \t\tupdate will update the base image" + echo -e " \t\tbuild will build the PKGBUILD" + echo -e " -d, --dep \t\t\t\tPath to pacman package file that is needed as dependency for the build. (Can be added multiple times)" + echo -e " -e, --key \t\t\t\tPublic signing keys that should be trusted by for the build. (Can be added multiple times)" + echo -e " -r, --repo \t\t\t\tHost path to use as repository inside the container. This can be used to avoid" + echo -e " \t\t\t\thanding over dependencies via command line arguments as they will be added to this repo" + echo -e " -s, --silent \t\t\t\tMake container silent: No output from container commands will be send to shell." + echo -e " --version \t\t\t\tPrint version information." + echo + echo "coptions:" + echo -e " These options will be handed over directly to makepkg inside the buildah container to build the package." + echo -e " coptions has to be added ater the double dash -- to work." +} + +options=$(getopt \ + -o hkn:m:p:d:r:e:s \ + -l "help" \ + -l keep \ + -l name: \ + -l mode: \ + -l dep: \ + -l repo: \ + -l silent: \ + -l version \ + -l key: -- "$@" 2>/dev/null) + +eval set -- "${options}" +while true; do + case "${1}" in + -k|--keep) + _FLAG_KEEP=1 + ;; + -n|--name) + shift + ARCHBUILDER_IMAGE_NAME=${1} + ;; + -m|--mode) + shift + _OPT_MODE="${1}" + ;; + -d|--dep) + shift + _OPT_DEPS[${#_OPT_DEPS[*]}]="${1}" + ;; + -e|--key) + shift + _OPT_KEYS[${#_OPT_KEYS[*]}]="${1}" + ;; + -r|--repo) + shift + ARCHBUILDER_CACHE_REPO="${1}" + ;; + -s|--silent) + _FLAG_SILENT=1 + ;; + --version) + echo -e "archbuilder v${archbuilder_version}" + exit 0 + ;; + --) + shift + break + ;; + -h|--help|*) + usage + exit 0 + ;; + esac + shift +done + +_OPT_CON_COPTIONS=$@ + +set_env +init_env + +buildah_prepare_params + + +function exit_trap() { + buildah_exit +} +trap exit_trap EXIT + +buildah_create + +case "${_OPT_MODE}" in + "create") + buildah_create + ;; + "update") + buildah_update + ;; + "build") + buildah_build + ;; +esac + +exit 0 diff --git a/lib/archbuilder.inc.sh b/lib/archbuilder.inc.sh new file mode 100644 index 0000000..dfc5559 --- /dev/null +++ b/lib/archbuilder.inc.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +function exit_error() { + log_error "${1}" + exit ${2} +} + +function check_mode() { + log_debug "set_mode for val '${1}' called" + if [ -z "${1}" ] + then + err="Mode not given" + return 1 + fi + + case "$1" in + "update") + log_debug "Mode 'update' selected" + return 0 + ;; + "create") + log_debug "Mode 'create' selected" + return 0 + ;; + "build") + log_debug "Mode 'build' selected" + return 0 + ;; + *) + err="Mode '${1}' invalid" + return 1 + ;; + esac +} + +function check_flag() { + if [ -z "${2}" ] + then + log_debug "Flag '${1}' unset" + else + log_debug "Flag '${1}' set" + fi +} + +function set_env() { + log_info "Checking environment and params" + log_debug "Verifying mode '${_OPT_MODE}'" + check_mode "${_OPT_MODE}" \ + || exit_error "${err}" 1 + + check_flag "KEEP" "${_FLAG_KEEP}" + check_flag "SILENT" "${_FLAG_SILENT}" + + test_exists "${ARCHBUILDER_BASE_DIR}" || _ACT_CREATE_BASE_DIR=1 + check_flag "_ACT_CREATE_BASE_DIR" "${_ACT_CREATE_BASE_DIR}" + + test_exists "${ARCHBUILDER_CACHE_REPO}" || _ACT_CREATE_CACHE_REPO_PATH=1 + check_flag "_ACT_CREATE_CACHE_REPO_PATH" "${_ACT_CREATE_CACHE_REPO_PATH}" + + test_exists "${ARCHBUILDER_LOG_PATH}" || _ACT_CREATE_LOG_PATH=1 + check_flag "_ACT_CREATE_LOG_PATH" "${_ACT_CREATE_LOG_PATH}" + + buildah_exists "${ARCHBUILDER_IMAGE_NAME}" || _ACT_CREATE_IMAGE=1 + check_flag "_ACT_CREATE_IMAGE" "${_ACT_CREATE_IMAGE}" + + test_null "ARCHBUILDER_IMAGE_NAME" "${ARCHBUILDER_IMAGE_NAME}" \ + && exit_error "Image name cannot be empty" 1 + + return 0 +} + +function init_env() { + log_info "Initializing environment" + + test_null "_ACT_CREATE_BASE_DIR" "${_ACT_CREATE_BASE_DIR}" \ + || log_info "Creating directory '${ARCHBUILDER_BASE_DIR}'"; { + err="$(mkdir -p "${ARCHBUILDER_BASE_DIR}" 1>/dev/null)" \ + || exit_error "Failed to create directory '${ARCHBUILDER_BASE_DIR}': '${err}'" 1 + } + test_dir "${ARCHBUILDER_BASE_DIR}" \ + || exit_error "Not a directory '${ARCHBUILDER_BASE_DIR}'" 1 + + test_null "_ACT_CREATE_CACHE_REPO_PATH" "${_ACT_CREATE_CACHE_REPO_PATH}" \ + || log_info "Creating directory '${ARCHBUILDER_CACHE_REPO}'"; { + err="$(mkdir -p "${ARCHBUILDER_CACHE_REPO}" 1>/dev/null)" \ + || exit_error "Failed to create directory '${ARCHBUILDER_CACHE_REPO}': '${err}'" 1 + } + test_dir "${ARCHBUILDER_CACHE_REPO}" \ + || exit_error "Not a directory '${ARCHBUILDER_CACHE_REPO}'" 1 + + test_null "_ACT_CREATE_LOG_PATH" "${_ACT_CREATE_LOG_PATH}" \ + || log_info "Creating directory '${ARCHBUILDER_LOG_PATH}'"; { + err="$(mkdir -p "${ARCHBUILDER_LOG_PATH}" 1>/dev/null)" \ + || exit_error "Failed to create directory '${ARCHBUILDER_LOG_PATH}': '${err}'" 1 + } + test_dir "${ARCHBUILDER_LOG_PATH}" \ + || exit_error "Not a directory '${ARCHBUILDER_LOG_PATH}'" 1 + + test_null "ARCHBUILDER_LOG_TO_FILE" "${ARCHBUILDER_LOG_TO_FILE}" \ + || { + LOG_PATH="${ARCHBUILDER_LOG_PATH}/$(date +"%F-%H_%M_%S").log" + log_info "Logfile: '${LOG_PATH}'" + } + + log_info "Environment ready" +} diff --git a/lib/buildah.inc.sh b/lib/buildah.inc.sh new file mode 100644 index 0000000..c19eaab --- /dev/null +++ b/lib/buildah.inc.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +_BUILDAH_CONT="" + +_BUILDAH_BASE_IMAGE="archlinux" +_BUILDAH_BASE_PATH="/home/archbuilder" +_BUILDAH_DEP_PATH="${_BUILDAH_BASE_PATH}/deps" +_BUILDAH_MKPKG_PATH="${_BUILDAH_BASE_PATH}/mkpgs" +_BUILDAH_PKGDEST_PATH="${_BUILDAH_BASE_PATH}/pkgdest" +_BUILDAH_CACHE_REPO_NAME="archbuilder_cache_repo" +_BUILDAH_CACHE_REPO_PATH="${_BUILDAH_BASE_PATH}/crepo" +_BUILDAH_CACHE_REPO="${_BUILDAH_CACHE_REPO_PATH}/${_BUILDAH_CACHE_REPO_NAME}.db.tar.gz" +_BUILDAH_MOUNTS=() + +_BUILDAH_PARAMS="" +_BUILDAH_MAKEPKG_ENV="" +_BUILDAH_MAKEPKG_FLAGS=" --noconfirm" # always noconfirm to avoid hanging + +function buildah_exists() { + log_debug "Checking if buildah image '${1}' exists" + + if buildah inspect "${1}" > /dev/null 2>&1 + then + log_debug "Buildah image '${1}' exists" + return 0 + fi + + log_debug "Buildah image '${1}' does not exist" + return 1 +} + +function buildah_prepare_params() { + _BUILDAH_PARAMS="${_BUILDAH_PARAMS} -v ${ARCHBUILDER_CACHE_REPO}:${_BUILDAH_CACHE_REPO_PATH}:rw,U" + + # adding working directory to container + _BUILDAH_PARAMS="${_BUILDAH_PARAMS} -v $(pwd):${_BUILDAH_MKPKG_PATH}:rw,U" + + log_info "Preparing makepkg environment" + test_null "PKGDEST" "${PKGDEST}" || { + _BUILDAH_PARAMS="${_BUILDAH_PARAMS} -v ${PKGDEST}:${_BUILDAH_PKGDEST_PATH}:rw,U" + _BUILDAH_MAKEPKG_ENV="${_BUILDAH_MAKEPKG_ENV} PKGDEST=${_BUILDAH_PKGDEST_PATH}" + } + + log_debug "Final _BUILDAH_PARAMS '${_BUILDAH_PARAMS}'" +} + +function buildah_exit() { + # fix permissions in any case + log_info "Cleaning up buildah stuff" + test_null "_BUILDAH_CONT" "${_BUILDAH_CONT}" || { + exec_cmd buildah run ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" bash -c "exit 0" + + test_null "_FLAG_KEEP" "${_FLAG_KEEP}" && { + log_info "Deleting working container '${_BUILDAH_CONT}'" + exec_cmd buildah rm "${_BUILDAH_CONT}" + } + unset _BUILDAH_CONT + } +} + +function buildah_create() { + # checking if base image is existing + buildah_exists "${_BUILDAH_BASE_IMAGE}" || { + log_info "Trying to fetch base image '${_BUILDAH_BASE_IMAGE}'" + exec_cmd buildah pull "${_BUILDAH_BASE_IMAGE}" + } + + test_null "_ACT_CREATE_IMAGE" "${_ACT_CREATE_IMAGE}" \ + && return 0 + + log_info "Creating working container '${ARCHBUILDER_IMAGE_NAME}' from '${_BUILDAH_BASE_IMAGE}'" + _BUILDAH_CONT=$(buildah from --name "${ARCHBUILDER_IMAGE_NAME}" "${_BUILDAH_BASE_IMAGE}") + + log_info "Updating working container '${ARCHBUILDER_IMAGE_NAME}'" + exec_cmd buildah run "${_BUILDAH_CONT}" pacman --noconfirm -Syu + + log_info "Installing devel packages" + exec_cmd buildah run "${_BUILDAH_CONT}" pacman --noconfirm -S base-devel sudo vim git + + log_info "Creating user '${_OPT_CON_BUILD_USER}'" + exec_cmd buildah run "${_BUILDAH_CONT}" useradd -m -s /bin/bash -U -u 1000 "${_OPT_CON_BUILD_USER}" + + log_info "Setting up sudo for user '${_OPT_CON_BUILD_USER}'" + exec_cmd buildah run "${_BUILDAH_CONT}" bash -c "echo '${_OPT_CON_BUILD_USER} ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/${_OPT_CON_BUILD_USER}" + + log_info "Creating container folder '${_BUILDAH_DEP_PATH}'" + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" "${_BUILDAH_CONT}" mkdir "${_BUILDAH_DEP_PATH}" + + log_info "Creating container folder '${_BUILDAH_MKPKG_PATH}'" + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" "${_BUILDAH_CONT}" mkdir "${_BUILDAH_MKPKG_PATH}" + + log_info "Creating container folder '${_BUILDAH_PKGDEST_PATH}'" + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" "${_BUILDAH_CONT}" mkdir "${_BUILDAH_PKGDEST_PATH}" + + log_info "Creating container folder '${_BUILDAH_CACHE_REPO_PATH}'" + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" "${_BUILDAH_CONT}" mkdir "${_BUILDAH_CACHE_REPO_PATH}" + + log_info "Adding repository '${_BUILDAH_CACHE_REPO} 'to container" + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" repo-add "${_BUILDAH_CACHE_REPO}" + exec_cmd buildah run "${_BUILDAH_CONT}" bash -c "echo -e \"\n\" >> /etc/pacman.conf" + exec_cmd buildah run "${_BUILDAH_CONT}" bash -c "echo -e \"[${_BUILDAH_CACHE_REPO_NAME}]\" >> /etc/pacman.conf" + exec_cmd buildah run "${_BUILDAH_CONT}" bash -c "echo -e \"SigLevel = Optional TrustAll\" >> /etc/pacman.conf" + exec_cmd buildah run "${_BUILDAH_CONT}" bash -c "echo -e \"Server = file://${_BUILDAH_CACHE_REPO_PATH}\" >> /etc/pacman.conf" + + log_info "Copying host makepkg.conf to container" + exec_cmd buildah copy --chown root:root "${_BUILDAH_CONT}" "/etc/makepkg.conf" "/etc/makepkg.conf" + + log_info "Finalizing image '${ARCHBUILDER_IMAGE_NAME}'" + exec_cmd buildah commit "${_BUILDAH_CONT}" "${ARCHBUILDER_IMAGE_NAME}" + + buildah_exit +} + +function buildah_update() { + buildah_exists "${ARCHBUILDER_IMAGE_NAME}" \ + || exit_error "Build image '${ARCHBUILDER_IMAGE_NAME}' does not exist" 1 + + log_info "Creating working container '${ARCHBUILDER_IMAGE_NAME}' from '${ARCHBUILDER_IMAGE_NAME}'" + _BUILDAH_CONT=$(buildah from --name "${ARCHBUILDER_IMAGE_NAME}" "${ARCHBUILDER_IMAGE_NAME}") + + log_info "Copying host makepkg.conf to container" + exec_cmd buildah copy --chown root:root "${_BUILDAH_CONT}" "/etc/makepkg.conf" "/etc/makepkg.conf" + + log_info "Updating container system" + exec_cmd buildah run --user ${_OPT_CON_BUILD_USER} ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" sudo pacman --noconfirm -Syu + + log_info "Finalizing image '${ARCHBUILDER_IMAGE_NAME}'" + buildah commit "${_BUILDAH_CONT}" "${ARCHBUILDER_IMAGE_NAME}" + + buildah_exit +} + +function buildah_prepare_build() { + buildah_exists "${ARCHBUILDER_IMAGE_NAME}" \ + || exit_error "Build image '${ARCHBUILDER_IMAGE_NAME}' does not exist" 1 + + log_info "Creating working container '${ARCHBUILDER_IMAGE_NAME}' from '${ARCHBUILDER_IMAGE_NAME}'" + _BUILDAH_CONT=$(buildah from --name "${ARCHBUILDER_IMAGE_NAME}" "${ARCHBUILDER_IMAGE_NAME}") + + log_info "Copying host makepkg.conf to container" + exec_cmd buildah copy --chown root:root "${_BUILDAH_CONT}" "/etc/makepkg.conf" "/etc/makepkg.conf" + + log_info "Updating container system" + exec_cmd buildah run --user ${_OPT_CON_BUILD_USER} ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" sudo pacman --noconfirm -Syu + + return 0 + for d in "${DEPS[@]}" + do + buildah copy --chown "${_BUILD_USER}" "${CONT}" "${d}" "${_DEP_PATH}/" + f=$(basename "${d}") + buildah run "${CONT}" pacman --noconfirm -U "${_DEP_PATH}/${f}" + done + + for k in "${KEYS[@]}" + do + buildah run --user "${_BUILD_USER}" "${CONT}" gpg --receive-keys "${k}" + done +} + +function buildah_build() { + buildah_prepare_build + + arrExt=$(grep PKGEXT /etc/makepkg.conf) + arrExt=(${arrExt//=/ }) + ext=$(echo "${arrExt[1]}" | cut -d "'" -f 2) + + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" \ + bash -c "cd ${_BUILDAH_MKPKG_PATH} && ${_BUILDAH_MAKEPKG_ENV} makepkg ${_BUILDAH_MAKEPKG_FLAGS} ${_OPT_CON_COPTIONS}" + + test_null "PKGDEST" "${PKGDEST}" && { + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" bash -c "cp ${_BUILDAH_MKPKG_PATH}/*${ext} ${_BUILDAH_CACHE_REPO_PATH}" + } || { + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" bash -c "cp ${_BUILDAH_PKGDEST_PATH}/*${ext} ${_BUILDAH_CACHE_REPO_PATH}" + } + + exec_cmd buildah run --user "${_OPT_CON_BUILD_USER}" ${_BUILDAH_PARAMS} "${_BUILDAH_CONT}" bash -c "repo-add -n ${_BUILDAH_CACHE_REPO} ${_BUILDAH_CACHE_REPO_PATH}/*${ext}" + + buildah_exit +} + + diff --git a/lib/ext/bash_log_internals.inc.sh b/lib/ext/bash_log_internals.inc.sh new file mode 100644 index 0000000..538db57 --- /dev/null +++ b/lib/ext/bash_log_internals.inc.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +function test_dir() { + log_debug "Testing directory '${1}'" + if [ -d "${1}" ] + then + log_debug "'${1}' is a directory" + return 0 + fi + log_debug "'${1}' is not a directory" + return 1 +} + +function test_file() { + log_debug "Testing file '${1}'" + if [ -f "${1}" ] + then + log_debug "'${1}' is a file" + return 0 + fi + log_debug "'${1}' is not a file" + return 1 +} + +function test_exists() { + log_debug "Testing exists '${1}'" + if [ -e "${1}" ] + then + log_debug "'${1}' exists" + return 0 + fi + log_debug "'${1}' does not exist" + return 1 +} + +function test_null() { + log_debug "Testing null for '${1}'" + if [ -z "${2}" ] + then + log_debug "'${1}' is null" + return 0 + fi + log_debug "'${1}' is not null" + return 1 +} + +function test_not_empty() { + log_debug "Testing not empty for '${1}'" + if [ -n "${2}" ] + then + log_debug "'${1}' is not empty" + return 0 + fi + log_debug "'${1}' is empty" + return 1 +} + +function exec_cmd() { + log_debug "Running cmd '${*}'" + exec 3>&2 + test_null "_FLAG_SILENT" "${_FLAG_SILENT}" && { + { err="$( { "$@"; } 2>&1 1>&3 3>&- )"; } 3>&1 \ + || exit_error "${err}" 1; + return 0 + } + { err="$( { "$@"; } 2>&1 1>/dev/null 3>&- )"; } 3>&1 \ + || exit_error "${err}" 1 +} diff --git a/lib/ext/slog.sh b/lib/ext/slog.sh new file mode 100644 index 0000000..4bbf267 --- /dev/null +++ b/lib/ext/slog.sh @@ -0,0 +1,123 @@ +#!/bin/sh +#-------------------------------------------------------------------------------------------------- +# slog - Makes logging in POSIX shell scripting suck less +# Copyright (c) Fred Palmer +# POSIX version Copyright Joe Cooper +# Licensed under the MIT license +# http://github.com/swelljoe/slog +#-------------------------------------------------------------------------------------------------- +set -e # Fail on first error + +# LOG_PATH - Define $LOG_PATH in your script to log to a file, otherwise +# just writes to STDOUT. + +# LOG_LEVEL_STDOUT - Define to determine above which level goes to STDOUT. +# By default, all log levels will be written to STDOUT. +LOG_LEVEL_STDOUT="INFO" + +# LOG_LEVEL_LOG - Define to determine which level goes to LOG_PATH. +# By default all log levels will be written to LOG_PATH. +LOG_LEVEL_LOG="INFO" + +# Useful global variables that users may wish to reference +SCRIPT_ARGS="$@" +SCRIPT_NAME="$0" +SCRIPT_NAME="${SCRIPT_NAME#\./}" +SCRIPT_NAME="${SCRIPT_NAME##/*/}" + +# Determines if we print colors or not +if [ $(tty -s) ]; then + readonly INTERACTIVE_MODE="off" +else + readonly INTERACTIVE_MODE="on" +fi + +#-------------------------------------------------------------------------------------------------- +# Begin Logging Section +if [ "${INTERACTIVE_MODE}" = "off" ] +then + # Then we don't care about log colors + LOG_DEFAULT_COLOR="" + LOG_ERROR_COLOR="" + LOG_INFO_COLOR="" + LOG_SUCCESS_COLOR="" + LOG_WARN_COLOR="" + LOG_DEBUG_COLOR="" +else + LOG_DEFAULT_COLOR=$(tput sgr0) + LOG_ERROR_COLOR=$(tput setaf 1) + LOG_INFO_COLOR=$(tput sgr 0) + LOG_SUCCESS_COLOR=$(tput setaf 2) + LOG_WARN_COLOR=$(tput setaf 3) + LOG_DEBUG_COLOR=$(tput setaf 4) +fi + +# This function scrubs the output of any control characters used in colorized output +# It's designed to be piped through with text that needs scrubbing. The scrubbed +# text will come out the other side! +prepare_log_for_nonterminal() { + # Essentially this strips all the control characters for log colors + sed "s/[[:cntrl:]]\[[0-9;]*m//g" +} + +log() { + local log_text="$1" + local log_level="$2" + local log_color="$3" + + # Levels for comparing against LOG_LEVEL_STDOUT and LOG_LEVEL_LOG + local LOG_LEVEL_DEBUG=0 + local LOG_LEVEL_INFO=1 + local LOG_LEVEL_SUCCESS=2 + local LOG_LEVEL_WARNING=3 + local LOG_LEVEL_ERROR=4 + + # Default level to "info" + [ -z ${log_level} ] && log_level="INFO"; + [ -z ${log_color} ] && log_color="${LOG_INFO_COLOR}"; + + # Validate LOG_LEVEL_STDOUT and LOG_LEVEL_LOG since they'll be eval-ed. + case $LOG_LEVEL_STDOUT in + DEBUG|INFO|SUCCESS|WARNING|ERROR) + ;; + *) + LOG_LEVEL_STDOUT=INFO + ;; + esac + case $LOG_LEVEL_LOG in + DEBUG|INFO|SUCCESS|WARNING|ERROR) + ;; + *) + LOG_LEVEL_LOG=INFO + ;; + esac + + # Check LOG_LEVEL_STDOUT to see if this level of entry goes to STDOUT. + # XXX This is the horror that happens when your language doesn't have a hash data struct. + eval log_level_int="\$LOG_LEVEL_${log_level}"; + eval log_level_stdout="\$LOG_LEVEL_${LOG_LEVEL_STDOUT}" + if [ $log_level_stdout -le $log_level_int ]; then + # STDOUT + printf "${log_color}[$(date +"%Y-%m-%d %H:%M:%S %Z")] [${log_level}] ${log_text} ${LOG_DEFAULT_COLOR}\n"; + fi + eval log_level_log="\$LOG_LEVEL_${LOG_LEVEL_LOG}" + # Check LOG_LEVEL_LOG to see if this level of entry goes to LOG_PATH + if [ $log_level_log -le $log_level_int ]; then + # LOG_PATH minus fancypants colors + if [ ! -z $LOG_PATH ]; then + printf "[$(date +"%Y-%m-%d %H:%M:%S %Z")] [${log_level}] ${log_text}\n" >> $LOG_PATH; + fi + fi + + return 0; +} + +log_info() { log "$@"; } +log_success() { log "$1" "SUCCESS" "${LOG_SUCCESS_COLOR}"; } +log_error() { log "$1" "ERROR" "${LOG_ERROR_COLOR}"; } +log_warning() { log "$1" "WARNING" "${LOG_WARN_COLOR}"; } +log_debug() { log "$1" "DEBUG" "${LOG_DEBUG_COLOR}"; } + +# End Logging Section +#-------------------------------------------------------------------------------------------------- +