Skip to main content

LIBRARY

Constant LIBRARY 

Source
pub const LIBRARY: &str = "# shellcheck shell=sh\n# SPDX-License-Identifier: Apache-2.0\n#\n# Isoloom\'s configuration library: one POSIX sh function per step kind of the configuration\n# model, for apt, dnf (yum on CentOS 7) and apk, and for systemd and OpenRC. A recipe sources it,\n# then calls one function per step, in order. Each function ensures a state behind its guard:\n# a second apply changes nothing. Every change prints `isoloom: changed <what>`, and marks\n# the file `$ISOLOOM_CHANGED` names (when set), which a later `service` step reads to restart\n# its service. A failure prints `isoloom: <why>` on standard error and returns 1.\n\n# Reports a change: its line, and the mark the restart rule reads.\nisoloom_changed() {\n  echo \"isoloom: changed $*\"\n  if [ -n \"${ISOLOOM_CHANGED:-}\" ]; then\n    mkdir -p \"${ISOLOOM_CHANGED%/*}\" && : >\"$ISOLOOM_CHANGED\"\n  fi\n}\n\n# Fails with a reason.\nisoloom_fail() {\n  echo \"isoloom: $*\" >&2\n  return 1\n}\n\n# The package manager: apt, dnf, yum or apk.\nisoloom_pm() {\n  for _pm in apt-get dnf yum apk; do\n    if command -v \"$_pm\" >/dev/null 2>&1; then\n      echo \"${_pm%-get}\"\n      return 0\n    fi\n  done\n  isoloom_fail \"no package manager Isoloom knows (apt-get, dnf, yum, apk)\"\n}\n\n# Whether package SPEC (a name, or name=version) is installed, at that version when given.\nisoloom_installed() {\n  _name=${2%%=*}\n  _version=\n  case $2 in *=*) _version=${2#*=} ;; esac\n  case $1 in\n    apt)\n      # shellcheck disable=SC2016 # dpkg-query\'s own fields, not the shell\'s.\n      _have=$(dpkg-query -W -f=\'${Status} ${Version}\' \"$_name\" 2>/dev/null) || return 1\n      case $_have in \"install ok installed \"*) ;; *) return 1 ;; esac\n      [ -z \"$_version\" ] || [ \"${_have#install ok installed }\" = \"$_version\" ]\n      ;;\n    dnf | yum)\n      # A name another package provides counts (Rocky\'s curl-minimal is its curl).\n      if [ -z \"$_version\" ]; then\n        rpm -q --whatprovides \"$_name\" >/dev/null 2>&1\n      else\n        rpm -q \"$_name-$_version\" >/dev/null 2>&1\n      fi\n      ;;\n    apk)\n      if [ -z \"$_version\" ]; then\n        apk info -e \"$_name\" >/dev/null 2>&1\n      else\n        apk list --installed \"$_name\" 2>/dev/null | grep -q \"^$_name-$_version \"\n      fi\n      ;;\n    *) return 1 ;;\n  esac\n}\n\n# Installs packages SPEC... with package manager PM, its caches cleaned.\nisoloom_install() {\n  _pm=$1\n  shift\n  case $_pm in\n    apt)\n      apt-get update -qq &&\n        DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \"$@\" >/dev/null &&\n        apt-get clean && rm -rf /var/lib/apt/lists/*\n      ;;\n    dnf | yum)\n      for _spec do\n        shift\n        case $_spec in\n          *=*) set -- \"$@\" \"${_spec%%=*}-${_spec#*=}\" ;;\n          *) set -- \"$@\" \"$_spec\" ;;\n        esac\n      done\n      \"$_pm\" install -y -q \"$@\" >/dev/null && \"$_pm\" clean all >/dev/null\n      ;;\n    apk) apk add --no-cache -q \"$@\" ;;\n    *) return 1 ;;\n  esac\n}\n\n# The `packages` step: each package (a name, or name=version) installed.\nisoloom_packages() {\n  _pm=$(isoloom_pm) || return 1\n  for _spec do\n    shift\n    isoloom_installed \"$_pm\" \"$_spec\" || set -- \"$@\" \"$_spec\"\n  done\n  [ \"$#\" -eq 0 ] && return 0\n  isoloom_install \"$_pm\" \"$@\" || isoloom_fail \"installing $* failed\" || return 1\n  isoloom_changed \"packages $*\"\n}\n\n# Makes sure command CMD exists, installing package PACKAGE... when it doesn\'t.\nisoloom_need() {\n  _cmd=$1\n  shift\n  command -v \"$_cmd\" >/dev/null 2>&1 && return 0\n  _pm=$(isoloom_pm) || return 1\n  isoloom_install \"$_pm\" \"$@\" || isoloom_fail \"installing $* (for $_cmd) failed\" || return 1\n  isoloom_changed \"packages $*\"\n}\n\n# The SHA-256 of FILE.\nisoloom_sha256() {\n  sha256sum \"$1\" | cut -d \' \' -f 1\n}\n\n# Downloads URL to FILE, with curl or wget (curl and the CA certificates installed when the\n# machine lacks them).\nisoloom_fetch() {\n  if ! command -v curl >/dev/null 2>&1 && command -v wget >/dev/null 2>&1; then\n    wget -q -O \"$2\" \"$1\"\n    return\n  fi\n  isoloom_need curl curl ca-certificates || return 1\n  if [ ! -s /etc/ssl/certs/ca-certificates.crt ] && [ ! -s /etc/pki/tls/certs/ca-bundle.crt ]; then\n    _pm=$(isoloom_pm) || return 1\n    isoloom_install \"$_pm\" ca-certificates || isoloom_fail \"installing ca-certificates failed\" || return 1\n    isoloom_changed \"packages ca-certificates\"\n  fi\n  curl -fsSL --retry 3 -o \"$2\" \"$1\"\n}\n\n# FILE is at DEST, with the same content (DEST written when it differs).\nisoloom_put() {\n  if [ -f \"$2\" ] && [ ! -L \"$2\" ] && [ \"$(isoloom_sha256 \"$1\")\" = \"$(isoloom_sha256 \"$2\")\" ]; then\n    return 0\n  fi\n  mkdir -p \"$(dirname \"$2\")\" && rm -rf \"$2\" && cp \"$1\" \"$2\" || isoloom_fail \"writing $2 failed\" || return 1\n  isoloom_changed \"$2\"\n}\n\n# TEXT is DEST\'s content.\nisoloom_text() {\n  _tmp=$(mktemp) || return 1\n  printf \'%s\' \"$2\" >\"$_tmp\" && isoloom_put \"$_tmp\" \"$1\"\n  _status=$?\n  rm -f \"$_tmp\"\n  return \"$_status\"\n}\n\n# Downloads URL to DEST, kept only when its SHA-256 is SHA256 (nothing to do when DEST has it).\nisoloom_download() {\n  if [ -f \"$2\" ] && [ \"$(isoloom_sha256 \"$2\")\" = \"$3\" ]; then\n    return 0\n  fi\n  mkdir -p \"$(dirname \"$2\")\" || return 1\n  _tmp=\"$2.isoloom-download\"\n  isoloom_fetch \"$1\" \"$_tmp\" || {\n    rm -f \"$_tmp\"\n    isoloom_fail \"downloading $1 failed\"\n    return 1\n  }\n  _got=$(isoloom_sha256 \"$_tmp\")\n  if [ \"$_got\" != \"$3\" ]; then\n    rm -f \"$_tmp\"\n    isoloom_fail \"$1 has SHA-256 $_got, not $3: the file changed upstream, or the pin is wrong\"\n    return 1\n  fi\n  mv -f \"$_tmp\" \"$2\" && isoloom_changed \"$2\"\n}\n\n# PATH\'s owner, group and mode (an empty one is left as it is; GROUP empty with OWNER set is\n# OWNER\'s own group).\nisoloom_attrs() {\n  _owner=$2\n  _group=$3\n  if [ -n \"$_owner\" ] && [ -z \"$_group\" ]; then\n    _group=$(id -gn \"$_owner\") || return 1\n  fi\n  if [ -n \"$_owner\" ] && [ \"$(stat -c %U \"$1\")\" != \"$_owner\" ]; then\n    chown -h \"$_owner\" \"$1\" || return 1\n    isoloom_changed \"owner of $1\"\n  fi\n  if [ -n \"$_group\" ] && [ \"$(stat -c %G \"$1\")\" != \"$_group\" ]; then\n    chgrp -h \"$_group\" \"$1\" || return 1\n    isoloom_changed \"group of $1\"\n  fi\n  if [ -n \"$4\" ]; then\n    _want=${4#\"${4%%[!0]*}\"}\n    if [ \"$(stat -c %a \"$1\")\" != \"${_want:-0}\" ]; then\n      chmod \"$4\" \"$1\" || return 1\n      isoloom_changed \"mode of $1\"\n    fi\n  fi\n}\n\n# The `copy` and `template` steps: SRC (from the current folder) at DEST, with OWNER, GROUP and\n# MODE. A folder (SRC ending in `/`) puts what it holds into DEST, each entry with them.\nisoloom_copy() {\n  case $1 in\n    */) ;;\n    *)\n      isoloom_put \"$1\" \"$2\" && isoloom_attrs \"$2\" \"$3\" \"$4\" \"$5\"\n      return\n      ;;\n  esac\n  [ -d \"$1\" ] || isoloom_fail \"$1 is no folder\" || return 1\n  if [ ! -d \"$2\" ]; then\n    mkdir -p \"$2\" || return 1\n    isoloom_changed \"$2\"\n  fi\n  (cd \"$1\" && find . -mindepth 1) | while IFS= read -r _entry; do\n    _from=\"$1/${_entry#./}\"\n    _to=\"${2%/}/${_entry#./}\"\n    if [ -d \"$_from\" ] && [ ! -L \"$_from\" ]; then\n      if [ ! -d \"$_to\" ]; then\n        mkdir -p \"$_to\" || exit 1\n        isoloom_changed \"$_to\"\n      fi\n    else\n      isoloom_put \"$_from\" \"$_to\" || exit 1\n    fi\n    isoloom_attrs \"$_to\" \"$3\" \"$4\" \"$5\" || exit 1\n  done\n}\n\n# A `template` step on a VM: the file Isoloom filled in (BASE64, its bytes) at DEST, with\n# OWNER, GROUP and MODE.\nisoloom_template() {\n  _tmp=$(mktemp) || return 1\n  printf \'%s\' \"$5\" | base64 -d >\"$_tmp\" && isoloom_copy \"$_tmp\" \"$1\" \"$2\" \"$3\" \"$4\"\n  _status=$?\n  rm -f \"$_tmp\"\n  return \"$_status\"\n}\n\n# The `file` step: PATH made a directory, absent, a link to TARGET, or a download of URL pinned\n# by SHA256, then given OWNER, GROUP and MODE (`isoloom_file PATH OWNER GROUP MODE\n# directory|absent|link TARGET|download URL SHA256`; an empty one is left as it is).\nisoloom_file() {\n  _path=$1 _file_owner=$2 _file_group=$3 _file_mode=$4 _state=$5\n  shift 5\n  case $_state in\n    directory)\n      if [ ! -d \"$_path\" ] || [ -L \"$_path\" ]; then\n        rm -rf \"$_path\" && mkdir -p \"$_path\" || return 1\n        isoloom_changed \"$_path\"\n      fi\n      ;;\n    absent)\n      if [ -e \"$_path\" ] || [ -L \"$_path\" ]; then\n        rm -rf \"$_path\" || return 1\n        isoloom_changed \"$_path removed\"\n      fi\n      return 0\n      ;;\n    link)\n      if [ ! -L \"$_path\" ] || [ \"$(readlink \"$_path\")\" != \"$1\" ]; then\n        mkdir -p \"$(dirname \"$_path\")\" && rm -rf \"$_path\" && ln -s \"$1\" \"$_path\" || return 1\n        isoloom_changed \"$_path\"\n      fi\n      ;;\n    download) isoloom_download \"$1\" \"$_path\" \"$2\" || return 1 ;;\n    *) isoloom_fail \"no file state $_state\" || return 1 ;;\n  esac\n  isoloom_attrs \"$_path\" \"$_file_owner\" \"$_file_group\" \"$_file_mode\"\n}\n\n# The `repository` step: KIND (apt, rpm or apk) repository NAME at URL, its key from KEY_URL\n# checked against KEY_SHA256, with PRIORITY (empty: the package manager\'s default), and, for\n# apt, its SUITE and COMPONENT... (`isoloom_repository KIND NAME URL KEY_URL KEY_SHA256\n# PRIORITY [SUITE COMPONENT...]`).\nisoloom_repository() {\n  _kind=$1 _name=$2 _url=$3 _key_url=$4 _key_sha=$5 _priority=$6\n  shift 6\n  case $_kind in\n    apt)\n      _key=/usr/share/keyrings/isoloom-$_name.asc\n      isoloom_download \"$_key_url\" \"$_key\" \"$_key_sha\" || return 1\n      _suite=$1\n      shift\n      isoloom_text \"/etc/apt/sources.list.d/isoloom-$_name.sources\" \"Types: deb\nURIs: $_url\nSuites: $_suite\nComponents: $*\nSigned-By: $_key\n\" || return 1\n      if [ -n \"$_priority\" ]; then\n        _host=${_url#https://}\n        isoloom_text \"/etc/apt/preferences.d/isoloom-$_name.pref\" \"Package: *\nPin: origin ${_host%%/*}\nPin-Priority: $_priority\n\" || return 1\n      fi\n      ;;\n    rpm)\n      _key=/etc/pki/rpm-gpg/isoloom-$_name.asc\n      isoloom_download \"$_key_url\" \"$_key\" \"$_key_sha\" || return 1\n      _line=\n      [ -z \"$_priority\" ] || _line=\"priority=$_priority\n\"\n      isoloom_text \"/etc/yum.repos.d/isoloom-$_name.repo\" \"[isoloom-$_name]\nname=$_name\nbaseurl=$_url\nenabled=1\ngpgcheck=1\ngpgkey=file://$_key\n$_line\" || return 1\n      ;;\n    apk)\n      # apk finds a key by the name the repository signs with: the key file\'s own.\n      isoloom_download \"$_key_url\" \"/etc/apk/keys/${_key_url##*/}\" \"$_key_sha\" || return 1\n      grep -qxF \"$_url\" /etc/apk/repositories 2>/dev/null && return 0\n      echo \"$_url\" >>/etc/apk/repositories && isoloom_changed \"repository $_url\"\n      ;;\n    *) isoloom_fail \"no repository kind $_kind\" ;;\n  esac\n}\n\n# useradd, usermod, groupadd and groupmod (Alpine\'s come with its shadow package).\nisoloom_accounts() {\n  isoloom_need useradd shadow\n}\n\n# The `group` step: group NAME, with GID and as a system group (SYSTEM: yes or no) when new\n# (`isoloom_group NAME GID SYSTEM`; an empty GID is the system\'s choice).\nisoloom_group() {\n  isoloom_accounts || return 1\n  _name=$1 _gid=$2 _system=$3\n  if _entry=$(getent group \"$_name\"); then\n    if [ -n \"$_gid\" ] && [ \"$(echo \"$_entry\" | cut -d : -f 3)\" != \"$_gid\" ]; then\n      groupmod -g \"$_gid\" \"$_name\" || return 1\n      isoloom_changed \"gid of $_name\"\n    fi\n    return 0\n  fi\n  set --\n  [ -z \"$_gid\" ] || set -- -g \"$_gid\"\n  [ \"$_system\" != yes ] || set -- \"$@\" -r\n  groupadd \"$@\" \"$_name\" || return 1\n  isoloom_changed \"group $_name\"\n}\n\n# A user\'s field: FIELD (1-based) of its passwd entry.\nisoloom_passwd_field() {\n  getent passwd \"$1\" | cut -d : -f \"$2\"\n}\n\n# The `user` step: user NAME with UID, primary GROUP, supplementary GROUPS (comma-separated),\n# HOME, SHELL, as a system user (SYSTEM: yes or no) when new, and its password HASH (crypt(3);\n# empty leaves it) (`isoloom_user NAME UID GROUP GROUPS HOME SHELL SYSTEM HASH`; an empty field\n# is left as the system makes it).\nisoloom_user() {\n  isoloom_accounts || return 1\n  _user=$1 _uid=$2 _group=$3 _groups=$4 _home=$5 _shell=$6 _system=$7 _hash=$8\n  if getent passwd \"$_user\" >/dev/null; then\n    isoloom_user_fields || return 1\n  else\n    set --\n    [ -z \"$_uid\" ] || set -- \"$@\" -u \"$_uid\"\n    [ -z \"$_group\" ] || set -- \"$@\" -g \"$_group\"\n    [ -z \"$_groups\" ] || set -- \"$@\" -G \"$_groups\"\n    [ -z \"$_home\" ] || set -- \"$@\" -d \"$_home\"\n    [ -z \"$_shell\" ] || set -- \"$@\" -s \"$_shell\"\n    if [ \"$_system\" = yes ]; then set -- \"$@\" -r; else set -- \"$@\" -m; fi\n    useradd \"$@\" \"$_user\" || return 1\n    isoloom_changed \"user $_user\"\n  fi\n  [ -n \"$_hash\" ] || return 0\n  [ \"$(awk -F : -v u=\"$_user\" \'$1 == u { print $2 }\' /etc/shadow)\" = \"$_hash\" ] && return 0\n  printf \'%s:%s\\n\' \"$_user\" \"$_hash\" | chpasswd -e || return 1\n  isoloom_changed \"password of $_user\"\n}\n\n# An existing user\'s fields made the step\'s (its supplementary groups added to, never removed).\nisoloom_user_fields() {\n  if [ -n \"$_uid\" ] && [ \"$(id -u \"$_user\")\" != \"$_uid\" ]; then\n    usermod -u \"$_uid\" \"$_user\" || return 1\n    isoloom_changed \"uid of $_user\"\n  fi\n  if [ -n \"$_group\" ] && [ \"$(id -gn \"$_user\")\" != \"$_group\" ]; then\n    usermod -g \"$_group\" \"$_user\" || return 1\n    isoloom_changed \"group of $_user\"\n  fi\n  if [ -n \"$_home\" ] && [ \"$(isoloom_passwd_field \"$_user\" 6)\" != \"$_home\" ]; then\n    usermod -d \"$_home\" \"$_user\" || return 1\n    isoloom_changed \"home of $_user\"\n  fi\n  if [ -n \"$_shell\" ] && [ \"$(isoloom_passwd_field \"$_user\" 7)\" != \"$_shell\" ]; then\n    usermod -s \"$_shell\" \"$_user\" || return 1\n    isoloom_changed \"shell of $_user\"\n  fi\n  _rest=$_groups\n  while [ -n \"$_rest\" ]; do\n    _g=${_rest%%,*}\n    case $_rest in *,*) _rest=${_rest#*,} ;; *) _rest= ;; esac\n    id -nG \"$_user\" | tr \' \' \'\\n\' | grep -qxF \"$_g\" && continue\n    usermod -a -G \"$_g\" \"$_user\" || return 1\n    isoloom_changed \"$_user in $_g\"\n  done\n}\n\n# Whether an earlier step changed something during this apply: a service restarts then.\nisoloom_restart_due() {\n  [ -n \"${ISOLOOM_CHANGED:-}\" ] && [ -e \"$ISOLOOM_CHANGED\" ]\n}\n\n# A service Isoloom defines: UNIT_TEXT as unit NAME\'s file (a systemd unit, or an OpenRC script\n# when it starts with `#!/sbin/openrc-run`), systemd reloaded when it changed.\nisoloom_unit() {\n  case $2 in\n    \'#!/sbin/openrc-run\'*)\n      isoloom_text \"/etc/init.d/$1\" \"$2\" && isoloom_attrs \"/etc/init.d/$1\" root root 0755\n      ;;\n    *)\n      _unit=/etc/systemd/system/$1.service\n      _old=\n      [ ! -f \"$_unit\" ] || _old=$(isoloom_sha256 \"$_unit\")\n      isoloom_text \"$_unit\" \"$2\" || return 1\n      if [ \"$_old\" != \"$(isoloom_sha256 \"$_unit\")\" ] && [ -d /run/systemd/system ]; then\n        systemctl daemon-reload\n      fi\n      ;;\n  esac\n}\n\n# The `service` step: unit UNIT enabled at boot and running (ENABLED: yes), restarted when an\n# earlier step changed something; or disabled and stopped (ENABLED: no). Without an init\n# system (a container), it says so and leaves it: there the container\'s runner starts it.\nisoloom_service() {\n  if [ -d /run/systemd/system ]; then\n    isoloom_systemd \"$@\"\n  elif command -v rc-service >/dev/null 2>&1 && [ -d /run/openrc ]; then\n    isoloom_openrc \"$@\"\n  else\n    echo \"isoloom: no init system runs here: $1 is left to the container\'s runner\"\n  fi\n}\n\n# isoloom_service under systemd.\nisoloom_systemd() {\n  if [ \"$2\" = no ]; then\n    if systemctl is-enabled --quiet \"$1\" 2>/dev/null; then\n      systemctl disable --quiet \"$1\" || return 1\n      isoloom_changed \"$1 disabled\"\n    fi\n    if systemctl is-active --quiet \"$1\"; then\n      systemctl stop \"$1\" || return 1\n      isoloom_changed \"$1 stopped\"\n    fi\n    return 0\n  fi\n  if ! systemctl is-enabled --quiet \"$1\" 2>/dev/null; then\n    systemctl enable --quiet \"$1\" || return 1\n    isoloom_changed \"$1 enabled\"\n  fi\n  if ! systemctl is-active --quiet \"$1\"; then\n    systemctl start \"$1\" && isoloom_changed \"$1 started\"\n  elif isoloom_restart_due; then\n    systemctl restart \"$1\" && echo \"isoloom: restarted $1\"\n  fi\n}\n\n# isoloom_service under OpenRC.\nisoloom_openrc() {\n  _enabled=$(rc-update show default | awk -v u=\"$1\" \'$1 == u { print \"yes\" }\')\n  if [ \"$2\" = no ]; then\n    if [ -n \"$_enabled\" ]; then\n      rc-update -q del \"$1\" default || return 1\n      isoloom_changed \"$1 disabled\"\n    fi\n    if rc-service -q \"$1\" status >/dev/null 2>&1; then\n      rc-service -q \"$1\" stop || return 1\n      isoloom_changed \"$1 stopped\"\n    fi\n    return 0\n  fi\n  if [ -z \"$_enabled\" ]; then\n    rc-update -q add \"$1\" default || return 1\n    isoloom_changed \"$1 enabled\"\n  fi\n  if ! rc-service -q \"$1\" status >/dev/null 2>&1; then\n    rc-service -q \"$1\" start && isoloom_changed \"$1 started\"\n  elif isoloom_restart_due; then\n    rc-service -q \"$1\" restart && echo \"isoloom: restarted $1\"\n  fi\n}\n\n# The `firewall` step: each PORT/PROTOCOL open in the OS\'s firewall, persistently, when one runs\n# (firewalld, or an active ufw); without one, nothing to open.\nisoloom_firewall() {\n  if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then\n    for _port do\n      firewall-cmd -q --permanent --query-port=\"$_port\" && continue\n      firewall-cmd -q --permanent --add-port=\"$_port\" || return 1\n      firewall-cmd -q --add-port=\"$_port\" || return 1\n      isoloom_changed \"firewall $_port\"\n    done\n  elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q \'^Status: active\'; then\n    for _port do\n      ufw status | grep -Eq \"^${_port}[[:space:]]+ALLOW\" && continue\n      ufw allow \"$_port\" >/dev/null || return 1\n      isoloom_changed \"firewall $_port\"\n    done\n  fi\n}\n\n# Runs the first N of ARGS... as a command.\nisoloom_first() {\n  _n=$1\n  shift\n  _i=0\n  for _arg do\n    shift\n    if [ \"$_i\" -lt \"$_n\" ]; then set -- \"$@\" \"$_arg\"; fi\n    _i=$((_i + 1))\n  done\n  \"$@\"\n}\n\n# The `command` step: ARGS... after its guard, unless the guard says it already ran: `creates`\n# (N is 1: the path whose existence says so) or `unless` (the N words of a check that exits 0)\n# (`isoloom_command creates|unless N GUARD... ARGS...`).\nisoloom_command() {\n  _guard=$1 _n=$2\n  shift 2\n  case $_guard in\n    creates) [ -e \"$1\" ] && return 0 ;;\n    unless) (isoloom_first \"$_n\" \"$@\") >/dev/null 2>&1 && return 0 ;;\n    *) isoloom_fail \"no guard $_guard\" || return 1 ;;\n  esac\n  shift \"$_n\"\n  \"$@\" || isoloom_fail \"the command $1 failed\" || return 1\n  isoloom_changed \"command $1\"\n}\n";
Expand description

The library, as Isoloom ships it.