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.