# shellcheck shell=sh # apiscp-borg-common.sh # # Shared routines for the apiscp-borg engine and tools. POSIX sh, no bashisms, # so it runs under dash/ash as well as bash. # # Unlike apiscp-kopia, Borg preserves POSIX ACLs and extended attributes on its # own, so there is no mandatory ACL/xattr sidecar here. Optional sidecar helpers # are provided (off by default) purely as belt-and-braces for operators who want # a human-diffable record alongside the archive. : "${APISCP_BORG_LOG_TAG:=apiscp-borg}" akp_log() { printf '%s %s\n' "$(_akp_now)" "$*" >&2; command -v logger >/dev/null 2>&1 && logger -t "$APISCP_BORG_LOG_TAG" -- "$*" || true; } akp_warn() { akp_log "WARN: $*"; } akp_die() { akp_log "FATAL: $*"; exit 1; } _akp_now() { date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null || date; } # Turn an arbitrary path into a filesystem/archive-safe slug. akp_slug() { printf '%s' "$1" | sed -e 's#^/##' -e 's#[^A-Za-z0-9._-]#_#g' } # akp_borg -- run borg with the configured repository and passphrase in the # environment. BORG_REPO and BORG_PASSPHRASE (or BORG_PASSCOMMAND) must already # be exported by the caller (the config file sets them). Kept as a wrapper so a # future transport tweak is one place. akp_borg() { borg "$@" } # akp_send_mail TO SUBJECT BODY [FROM]: send a plain-text email via the local # sendmail. Best-effort; returns non-zero if no sendmail is found or send fails. akp_send_mail() { _am_to="$1"; _am_subj="$2"; _am_body="$3" _am_from="${4:-root@$(hostname 2>/dev/null || echo localhost)}" [ -n "$_am_to" ] || return 1 _am_snd="" for _c in /usr/sbin/sendmail /usr/lib/sendmail sendmail; do command -v "$_c" >/dev/null 2>&1 && { _am_snd="$_c"; break; } done [ -n "$_am_snd" ] || { akp_warn "no sendmail binary; cannot send mail"; return 1; } printf 'From: %s\nTo: %s\nSubject: %s\nContent-Type: text/plain; charset=UTF-8\n\n%s\n' \ "$_am_from" "$_am_to" "$_am_subj" "$_am_body" | "$_am_snd" -t -i 2>/dev/null } # --- site helpers (shared with the restore/repo tools) ----------------------- # akp_site_domain SITE: echo the site's primary domain from its ApisCP siteinfo. akp_site_domain() { _sd_si="${VIRTBASE:-/home/virtual}/$1/info/current/siteinfo" [ -r "$_sd_si" ] || return 0 sed -n 's/^[[:space:]]*domain[[:space:]]*=[[:space:]]*\([^[:space:]]*\).*/\1/p' "$_sd_si" | head -1 } # akp_site_fst SITE: echo the site's fst root, honouring VIRTBASE. akp_site_fst() { printf '%s/%s/fst' "${VIRTBASE:-/home/virtual}" "$1"; } # akp_site_admin_user SITE: the site's admin_user from its siteinfo, or empty. akp_site_admin_user() { _asau_si="${VIRTBASE:-/home/virtual}/$1/info/current/siteinfo" [ -r "$_asau_si" ] || return 0 sed -n 's/^[[:space:]]*admin_user[[:space:]]*=[[:space:]]*\([^[:space:]]*\).*/\1/p' "$_asau_si" | head -1 } # akp_owner_chown SITE PATH: recursively chown PATH so the site owner can access # it. Site users are namespaced (not in the host passwd), so take ownership from # the admin user's own home directory as a reference, falling back to the numeric # uid:gid parsed from the site's own passwd. akp_owner_chown() { _oc_site="$1"; _oc_path="$2" [ -d "$_oc_path" ] || return 1 _oc_fst=$(akp_site_fst "$_oc_site") _oc_admin=$(akp_site_admin_user "$_oc_site") if [ -n "$_oc_admin" ] && [ -d "$_oc_fst/home/$_oc_admin" ]; then chown -R --reference="$_oc_fst/home/$_oc_admin" "$_oc_path" 2>/dev/null && return 0 fi if [ -n "$_oc_admin" ] && [ -r "$_oc_fst/etc/passwd" ]; then _oc_ug=$(awk -F: -v u="$_oc_admin" '$1==u {print $3":"$4; exit}' "$_oc_fst/etc/passwd") [ -n "$_oc_ug" ] && chown -R "$_oc_ug" "$_oc_path" 2>/dev/null && return 0 fi akp_warn "owner_chown: could not determine owner for $_oc_site; leaving $_oc_path root-owned" return 1 }