#!/bin/bash ###----------------------------------------### ### ### BOA Meta Installer ### ### Copyright (C) 2009-2026 Omega8.cc ### noc@omega8.cc www.omega8.cc ### ### This program is free software. You can ### redistribute it and/or modify it under ### the terms of the GNU GPL as published by ### the Free Software Foundation, version 2 ### or later. ### ### This program is distributed in the hope ### that it will be useful, but WITHOUT ANY ### WARRANTY; without even the implied ### warranty of MERCHANTABILITY or FITNESS ### FOR A PARTICULAR PURPOSE. See the GNU GPL ### for more details. ### ### You should have received a copy of the ### GNU GPL along with this program. ### If not, see http://www.gnu.org/licenses/ ### ### Code: https://github.com/omega8cc/boa ### ###----------------------------------------### ###----------------------------------------### ### How To: run it with bash, not with sh ### ###----------------------------------------### ### ### $ wget -qO- https://files.boa.io/BOA.sh.txt | bash ### ###----------------------------------------### ### DON'T EDIT ANYTHING BELOW THIS LINE ### ###----------------------------------------### export HOME=/root export SHELL=/bin/bash export PATH=/usr/local/bin:/usr/local/sbin:/opt/local/bin:/usr/bin:/usr/sbin:/bin:/sbin:/usr/libexec export _tRee=lts export _xSrl=588866ltsT01 export _rLsn="BOA-5.88.866" ### ### Avoid too many questions ### export DEBIAN_FRONTEND=noninteractive export APT_LISTCHANGES_FRONTEND=none if [ -z "${TERM+x}" ]; then export TERM=vt100 fi _NOW=$(date +%y%m%d-%H%M%S) export _NOW=${_NOW//[^0-9-]/} _TODAY=$(date +%y%m%d) export _TODAY=${_TODAY//[^0-9]/} # _barCnf="/root/.barracuda.cnf" # Defaults before the guarded cnf source later in this file so the cnf # value wins; the markers stay honoured for one release. _NO_SYSCTL_UPDATE=NO _NO_CRON_AUTOSTART=NO _FORCE_CI_BOX=NO _crlGet="-L --max-redirs 3 -s --fail --retry 9 --retry-delay 9 -A iCab" _wgetGet="--max-redirect=3 -q --tries=9 --wait=9 --user-agent='iCab'" _aptAllow="--allow-unauthenticated" _aptYesUnth="-y ${_aptAllow}" _optBin="/opt/local/bin" _usrBin="/usr/local/bin" _xpthLog="/var/xdrago/log" _pthLog="/var/log/boa" _pthEtc="/etc/boa" _tBn="tools/bin" _vBs="/var/backups" _INITINS="/usr/bin/apt-get ${_aptAllow} -y install" # [ -d "${_pthLog}" ] || mkdir -p "${_pthLog}" # if [ ! -e "${_pthLog}/.migrated.txt" ] && [ -d "${_xpthLog}" ]; then cp -a ${_xpthLog}/*.pid ${_pthLog}/ cp -a ${_xpthLog}/.*pid ${_pthLog}/ cp -a ${_xpthLog}/*.log ${_pthLog}/ cp -a ${_xpthLog}/*.txt ${_pthLog}/ cp -a ${_xpthLog}/usage ${_pthLog}/ cp -a ${_xpthLog}/daily ${_pthLog}/ cp -a ${_xpthLog}/core ${_pthLog}/ cp -a ${_xpthLog}/le ${_pthLog}/ touch "${_pthLog}/.migrated.txt" fi # # One-time copy of operator markers /root -> /etc/boa (originals left for # later cleanup). Guard-once per group so an operator's later removal of an # /etc/boa marker is not undone by a re-copy from the stale /root original. if [ ! -e "${_pthEtc}/.migrated.svc-control.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .allow.nginx.restart.cnf \ .allow.php.fpm.reload.cnf \ .allow.redis.restart.cnf \ .allow.valkey.restart.cnf \ .allow.aegir.queue.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.svc-control.txt" fi if [ ! -e "${_pthEtc}/.migrated.deny-disable.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .deny.jetty9.cnf \ .deny.solr7.cnf \ .deny.solr9.cnf \ .disable_mysql_cleanup.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.deny-disable.txt" fi if [ ! -e "${_pthEtc}/.migrated.behavior-toggles.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .allow.sendmail.cnf \ .allow.du.cnf \ .allow-codebasecheck.cnf \ .dont.touch.permissions.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.behavior-toggles.txt" fi if [ ! -e "${_pthEtc}/.migrated.pause-tasks.txt" ]; then mkdir -p "${_pthEtc}" [ -e "/root/.pause_tasks_maint.cnf" ] && cp -a /root/.pause_tasks_maint.cnf "${_pthEtc}/" 2>/dev/null touch "${_pthEtc}/.migrated.pause-tasks.txt" fi if [ ! -e "${_pthEtc}/.migrated.csf-firewall.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .full.csf.cleanup.cnf \ .ignore.site24x7.firewall.cnf \ .whitelist.dont.cleanup.cnf \ .instant.csf.block.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.csf-firewall.txt" fi if [ ! -e "${_pthEtc}/.migrated.debug-goaccess.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .debug.monitor.cnf \ .debug.monitor.log.cnf \ .debug.email.txt \ .goaccess.all.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.debug-goaccess.txt" fi if [ ! -e "${_pthEtc}/.migrated.misc-toggles.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .instant.busy.mysql.action.cnf \ .mysqladmin.monitor.cnf \ .sql.problematic.users.cnf \ .ice.vm.cnf \ .home.no.wildcard.chmod.cnf \ .run.example.report.cnf \ .silent-octopus-upgrade.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.misc-toggles.txt" fi if [ ! -e "${_pthEtc}/.migrated.lshell-virt.txt" ]; then mkdir -p "${_pthEtc}" for _etcMrk in \ .look.like.jenkins.cnf \ .send-extended-report.cnf ; do [ -e "/root/${_etcMrk}" ] && cp -a "/root/${_etcMrk}" "${_pthEtc}/" 2>/dev/null done touch "${_pthEtc}/.migrated.lshell-virt.txt" fi if [ ! -e "${_pthEtc}/.migrated.newrelic.txt" ]; then mkdir -p "${_pthEtc}" [ -e "/root/.enable.newrelic.sysmond.cnf" ] && cp -a /root/.enable.newrelic.sysmond.cnf "${_pthEtc}/" 2>/dev/null touch "${_pthEtc}/.migrated.newrelic.txt" fi ### One-shot fleet switch: on omega8.cc-hosted systems (hostname ends in ### .aegir.cc) normalize the whole ghost-cleanup family to YES -- every mover ### is a reversible move to an undo/-family location, proven by the 2026-07 ### all-flags crash test. Each flag is collapsed to a single clean NAME=YES ### line (drops =NO leftovers, duplicates and comment-tail variants, so the ### last-match and any-match flag parsers agree), in the system cnf and in ### every per-account octopus cnf regardless of the account name prefix. ### Runs once per box per stamp serial; operator toggles set afterwards ### stick. Decrement the serial in the stamp name to re-run fleet-wide. ### Accounts created later are covered by the hosted cnf seeding instead. ### The stamp is a one-shot fact, so it lives in /etc/boa/control and its ### name carries the stamp serial only (_ghSrl): named after the release as ### well, in /var/log/boa, it ran again at every release, and the *.pid ### clean-up of a broken fetch could take it. A hosted box that ran it under ### that old name with the same stamp serial keeps its toggles: the old stamp ### counts, and stays where it is (this block runs before the once-per-release ### sweep of old *.ctrl.*.pid, which spares these stamps). A box that is not ### hosted writes no stamp, so one renamed into the fleet later gets the run. ### When the stamp cannot be written, the old name with this release is ### written instead, and counts at the next tick. _ghSrl="f99" _ghStamp="${_pthEtc}/control/ghost-family-yes.${_ghSrl}.txt" _ghHn=$(tr -d '\n' 2> /dev/null < /etc/hostname) [ -n "${_ghHn}" ] || _ghHn=$(hostname -f 2> /dev/null) if [ ! -e "${_ghStamp}" ] \ && [[ "${_ghHn}" =~ ".aegir.cc"($) ]] && [ -e "${_barCnf}" ]; then _ghOld=NO for _ghPrev in "${_pthLog}"/ghost-family-yes.ctrl.${_ghSrl}.*.pid; do [ -e "${_ghPrev}" ] && _ghOld=YES && break done if [ "${_ghOld}" = "NO" ]; then for _ghFlag in _GHOST_CODEBASES_CLEANUP _GHOST_PLATFORMS_CLEANUP \ _GHOST_VHOSTS_CLEANUP _GHOST_SITES_CLEANUP _GHOST_SITE_FILES_CLEANUP \ _GHOST_ALIASES_CLEANUP _GHOST_CLIENT_NOTIFY; do sed -i "/^[[:space:]]*${_ghFlag}=/d" "${_barCnf}" 2>/dev/null echo "${_ghFlag}=YES" >> "${_barCnf}" done for _ghOct in /root/.*.octopus.cnf; do [ -e "${_ghOct}" ] || continue for _ghFlag in _GHOST_PLATFORMS_CLEANUP _GHOST_VHOSTS_CLEANUP \ _GHOST_SITES_CLEANUP _GHOST_SITE_FILES_CLEANUP _GHOST_ALIASES_CLEANUP; do sed -i "/^[[:space:]]*${_ghFlag}=/d" "${_ghOct}" 2>/dev/null echo "${_ghFlag}=YES" >> "${_ghOct}" done done fi mkdir -p "${_pthEtc}/control" 2> /dev/null chmod 0755 "${_pthEtc}" "${_pthEtc}/control" &> /dev/null if ! { { echo "${_tRee} ${_xSrl} $(date +%Y-%m-%d)" > "${_ghStamp}.tmp.$$"; } 2> /dev/null \ && chmod 0644 "${_ghStamp}.tmp.$$" && mv -f "${_ghStamp}.tmp.$$" "${_ghStamp}"; }; then rm -f "${_ghStamp}.tmp.$$" 2> /dev/null _ghFb="${_pthLog}/ghost-family-yes.ctrl.${_ghSrl}.${_tRee}.${_xSrl}.pid" if [ ! -e "${_ghFb}" ]; then touch "${_ghFb}" echo "GHOST-FAMILY-ERROR: ${_ghStamp} could not be written at $(date)" >> "${_pthLog}/ghost-family-yes.log" fi fi fi [ ! -d "${_pthLog}/usage" ] && mkdir -p "${_pthLog}/usage" [ ! -d "${_pthLog}/daily" ] && mkdir -p "${_pthLog}/daily" [ ! -d "${_pthLog}/core" ] && mkdir -p "${_pthLog}/core" [ ! -d "${_pthLog}/le" ] && mkdir -p "${_pthLog}/le" # _eldirF="0001-Print-site_footer-if-defined.patch" _eldirP="/var/xdrago/conf/${_eldirF}" # _tenCorePatchFname="drupal-ten-aegir-core-01.patch" _tenCorePatchPath="/data/conf/patches/${_tenCorePatchFname}" # _tenConsolePatchFname="drupal-ten-aegir-console-02.patch" _tenConsolePatchPath="/data/conf/patches/${_tenConsolePatchFname}" # _elevenCorePatchFname="drupal-eleven-aegir-core-01.patch" _elevenCorePatchPath="/data/conf/patches/${_elevenCorePatchFname}" # _elevenConsolePatchFname="drupal-eleven-aegir-console-02.patch" _elevenConsolePatchPath="/data/conf/patches/${_elevenConsolePatchFname}" # _elevenValidatorPatchFname="drupal-eleven-aegir-validator-03.patch" _elevenValidatorPatchPath="/data/conf/patches/${_elevenValidatorPatchFname}" # _provLeInc="provision_hosting_le.drush.inc" _provLeIncFull="/var/xdrago/conf/${_provLeInc}" # _hoLeInc="hosting_le_vhost.drush.inc" _hoLeIncFull="/var/xdrago/conf/${_hoLeInc}" # _dehydName="dehydrated" _dehydSrcPath="/var/xdrago/conf/${_dehydName}" _legacyLeSh="/var/xdrago/conf/letsencrypt.sh" _DEBUG_MODE=$([ -e "/root/.debug-barracuda-installer.cnf" ] && echo "YES" || echo "NO") # Group that owns an account's tree, derived per account: 'users' until the # account carries a private primary group named after itself. The library # copy in helper.sh.inc is authoritative; this fallback covers the window # where this self-updater is newer than the libraries on the box. if ! declare -F _acct_group > /dev/null 2>&1; then _acct_group() { local _a="${1}" _g case "${_a}" in /var/aegir|/var/aegir/*|aegir|root|www-data) echo "users"; return 0 ;; /data/disk/*) _a="${_a#/data/disk/}"; _a="${_a%%/*}" ;; */*) echo "users"; return 0 ;; esac _a="${_a%%.*}" [ -n "${_a}" ] || { echo "users"; return 0; } _g=$(id -gn "${_a}" 2> /dev/null) [ "${_g}" = "${_a}" ] || _g="users" echo "${_g}" } fi # An account owns its static/control, so it can put a link or a FIFO at any # name there. These are helper.sh.inc's, defined here unconditionally: this # runs as root from cron, and a guard must never fall back to an older copy. # Run "$@" inside the real directory $1 (every name below /data/disk and # /home a real directory); ./name then stays there whatever is swapped. _acct_in_real_dir() { local _d="${1}" _a="" _want shift case "${_d}" in /home/?*) _a=/home ;; /data/disk/?*) _a=/data/disk ;; esac if [ -n "${_a}" ]; then _want="$(cd -P -- "${_a}" 2> /dev/null && pwd -P)${_d#"${_a}"}" else _want="$(cd -P -- "${_d%/*}" 2> /dev/null && pwd -P)/${_d##*/}" fi ( cd -P -- "${_d}" 2> /dev/null && [ "$(pwd -P)" = "${_want}" ] && "$@" ) } # ./$1 read without following a link or blocking on a FIFO, at most 1 MiB. _acct_read_here() { timeout 10 dd if="./${1}" iflag=nofollow,nonblock,fullblock \ bs=1048576 count=1 status=none 2> /dev/null } # ./$1 with the content $2 (0644): a fresh file created exclusively, then # renamed over the name, so a link or FIFO put there is never followed. _acct_put_here() { local _t="./.${1}.put.$$.${RANDOM}" rm -f -- "${_t}" ( umask 022 printf '%s\n' "${2}" | dd of="${_t}" conv=excl status=none 2> /dev/null ) \ && mv -f -T -- "${_t}" "./${1}" && return 0 rm -f -- "${_t}" return 1 } # stdin as ./$1 in the current (pinned) directory, handed to uid $2 and gid # $3 with the mode $4: a fresh file created, written, owned and moded through # one handle opened O_EXCL|O_NOFOLLOW, then renamed over the name. A link put # at the name is replaced, never followed, and nothing is chowned or chmoded # by name in a directory the account can write (a hard link renamed over the # temp name before a chown by name would hand the linked file over). _ACCT_PUT_MODE_PL='use Fcntl; my ($n, $u, $g, $m) = @ARGV; local $/; my $d = ; sysopen(my $h, $n, O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0600) or exit 1; (print {$h} $d) or exit 1; chown($u, $g, $h) or exit 1; chmod(oct($m) & 0777, $h) or exit 1; close($h) or exit 1; exit 0' _acct_put_mode_here() { local _t="./.${1}.put.$$.${RANDOM}" [[ "${2}" =~ ^[0-9]+$ && "${3}" =~ ^[0-9]+$ && "${4}" =~ ^[0-7]+$ ]] \ || return 1 rm -f -- "${_t}" if perl -e "${_ACCT_PUT_MODE_PL}" "${_t}" "${2}" "${3}" "${4}" \ && mv -f -T -- "${_t}" "./${1}"; then return 0 fi rm -f -- "${_t}" return 1 } # The same, but only where ./$1 is there already (a link counts: it is # replaced), as a refresh of a file the account was given. _acct_refresh_mode_here() { [ -e "./${1}" ] || [ -L "./${1}" ] || return 0 _acct_put_mode_here "$@" } # The hostmaster root in the alias of the account $1 (/data/disk/), read # bounded and never through a link, printed only when it is one of the # account's own hostmaster platforms (/aegir/distro/): the # account owns its alias and could point it anywhere. _acct_hm_root() { local _r _r=$(_acct_in_real_dir "${1}/.drush" _acct_read_here \ hostmaster.alias.drushrc.php \ | grep "root'" \ | cut -d: -f2 \ | awk '{ print $3}' \ | sed "s/[\,']//g" 2>&1) [[ "${_r}" == "${1}/aegir/distro/"* ]] || return 1 [[ "${_r#"${1}/aegir/distro/"}" =~ ^[0-9]+$ ]] || return 1 echo "${_r}" } # ./$1 as an empty file (0644), put the same way. _acct_touch_here() { local _t="./.${1}.put.$$.${RANDOM}" rm -f -- "${_t}" ( umask 022 dd of="${_t}" conv=excl status=none < /dev/null 2> /dev/null ) \ && mv -f -T -- "${_t}" "./${1}" && return 0 rm -f -- "${_t}" return 1 } # stdin as ./$1 with the mode $3, handed to $2 (owner:group, resolved to # numbers first) through its handle as _acct_put_mode_here puts it. Where # either name does not resolve the file stays root's, as a failed chown by # name used to leave it. _acct_put_own_here() { local _uid _gid _uid=$(id -u -- "${2%%:*}" 2> /dev/null) _gid=$(getent group "${2#*:}" 2> /dev/null | cut -d: -f3) if [[ "${_uid}" =~ ^[0-9]+$ && "${_gid}" =~ ^[0-9]+$ ]]; then _acct_put_mode_here "${1}" "${_uid}" "${_gid}" "${3}" else _acct_put_mode_here "${1}" 0 0 "${3}" fi } # A tree walk that hands names over (find -execdir, inside a pinned # directory): each name is opened without following a link or blocking on a # FIFO, and only a directory or a single-link regular file is changed, # through its handle. A hard link planted in the tree is never handed over. _ACCT_REOWN_PL='use Fcntl; my ($u, $g, @f) = @ARGV; for my $f (@f) { sysopen(my $h, $f, O_RDONLY|O_NOFOLLOW|O_NONBLOCK) or next; my @s = stat($h); chown($u, $g, $h) if @s && (-d _ || (-f _ && $s[3] == 1)); close($h); } exit 0' # A mode ($1, octal) set on the names that follow, in the current (pinned) # directory, the same way: a single-link regular file only, through its # handle, never through a link. _BOA_FCHMOD_PL='use Fcntl; my ($m, @f) = @ARGV; for my $f (@f) { sysopen(my $h, $f, O_RDONLY|O_NOFOLLOW|O_NONBLOCK) or next; my @s = stat($h); chmod(oct($m), $h) if @s && -f _ && $s[3] == 1; close($h); } exit 0' # A mode set on names in the current (pinned) directory the same way, # one text wherever it is defined (satellite.sh.inc): args f|d, the mode # (octal), the names; a directory keeps its set-ID bits unless the mode has # five digits, a regular file is changed only while it has a single link. _SAT_NOFOLLOW_CHMOD_PL='use Fcntl; my ($t, $ms) = (shift @ARGV, shift @ARGV); my ($m, $k) = (oct($ms), length($ms) < 5 ? 06000 : 0); for my $f (@ARGV) { sysopen(my $h, $f, O_RDONLY | O_NOFOLLOW | O_NONBLOCK) or next; my @s = stat($h); if ($t eq "f" && -f _ && $s[3] == 1) { chmod($m, $h); } elsif ($t eq "d" && -d _) { chmod($m | ($s[2] & $k), $h); } close($h); }' # $1 as the directory root works in: itself when it is a real directory; # when it is a link, the real directory it leads to, only when every link # on the way is root's and every directory on the way, the last one # included, is root's and writable by no one else, so no one but root can # change where it leads or what is in it; nothing otherwise (exit 1). With # a second argument "last" the last directory may be anyone's and open to # others (the Octopus store check takes /data/disk/all back); none is given # here. One text wherever it is defined (satellite.sh.inc, xoct, xcopy, # manage_solr_config.sh, night.inc.sh). _ROOT_REAL_PATH_PL='my ($p, $k) = @ARGV; if (!-l $p) { print $p if -d _; exit(-d _ ? 0 : 1); } my ($c, $n, @s) = ("", 0, stat("/")); exit 1 unless $p =~ m{^/} && @s && $s[4] == 0 && !($s[2] & 022); my @w = grep { length && $_ ne "." } split(m{/}, $p); while (@w) { my $e = shift(@w); if ($e eq "..") { $c =~ s{/[^/]*$}{}; next; } my @l = lstat("$c/$e") or exit 1; if (-l _) { exit 1 if $l[4] != 0 || ++$n > 40; my $t = readlink("$c/$e"); exit 1 unless defined $t; $c = "" if $t =~ m{^/}; unshift(@w, grep { length && $_ ne "." } split(m{/}, $t)); next; } exit 1 unless -d _ && (($l[4] == 0 && !($l[2] & 022)) || (defined $k && $k eq "last" && !@w)); $c .= "/$e"; } print($c eq "" ? "/" : $c); exit 0' # $1, the real directory a link of root's at /data/all leads to # (_ROOT_REAL_PATH_PL's answer), is the one the store may be kept in # behind that link (exit 0) only when it is /data/disk/all, the directory # named all directly in the real /data/disk, compared by device and inode # with that name not followed, so a link put at it does not count; any # other directory, or none, exit 1. Besides a real /data/all it is the # one layout of the store BOA knows, and the one other store directory # every PHP-FPM pool's open_basedir names. One text wherever it is defined # (satellite.sh.inc, BOA.sh.txt, xoct, xcopy, manage_solr_config.sh, # night.inc.sh). _SAT_STORE_TARGET_PL='my @t = lstat(defined $ARGV[0] ? $ARGV[0] : ""); my @a = lstat("/data/disk/all"); exit((@t && @a && -d _ && $t[0] == $a[0] && $t[1] == $a[1]) ? 0 : 1)' # The two names given swapped in one rename (renameat2 with # RENAME_EXCHANGE, its number read from perl's syscall.ph): exit 0 only # when they were. One text wherever it is defined (satellite.sh.inc, # BOA.sh.txt). # shellcheck disable=SC2016 _SAT_SWAP_PL='my ($a, $b) = @ARGV; my $n = eval { require "syscall.ph"; &SYS_renameat2 }; exit 1 unless defined $n && $n =~ /^[0-9]+$/; exit(syscall($n + 0, -100, $a, -100, $b, 2) == 0 ? 0 : 1)' # ./$1 in the current (pinned) directory edited by the sed script $2 as # sed -i edited it, unless it holds the text $3 (when given): only a # single-link regular file under 1 MiB is read, once, bounded and never # through a link or from a FIFO, into a root-only copy; a changed result is # put back as a fresh file with the owner, group and mode the file had # (_acct_put_mode_here), renamed over the name. _acct_sed_here() { local _st _typ _uid _gid _mod _sz _nl _w _rc=1 _st=$(stat -c '%F|%u|%g|%a|%s|%h' -- "./${1}" 2> /dev/null) || return 1 IFS='|' read -r _typ _uid _gid _mod _sz _nl <<< "${_st}" case "${_typ}" in "regular file"|"regular empty file") ;; *) return 1 ;; esac [[ "${_sz}" =~ ^[0-9]+$ ]] && [ "${_sz}" -lt 1048576 ] \ && [ "${_nl}" = "1" ] || return 1 _w=$(mktemp -d /run/.boa-acct-edit.XXXXXX 2> /dev/null) || return 1 if _acct_read_here "${1}" > "${_w}/o" \ && [ "$(stat -c %s -- "${_w}/o" 2> /dev/null)" = "${_sz}" ]; then if [ -n "${3}" ] && grep -qF -- "${3}" "${_w}/o"; then _rc=0 elif sed "${2}" "${_w}/o" > "${_w}/n"; then if cmp -s "${_w}/o" "${_w}/n"; then _rc=0 else _acct_put_mode_here "${1}" "${_uid}" "${_gid}" "${_mod}" \ < "${_w}/n" && _rc=0 fi fi fi rm -rf -- "${_w}" return "${_rc}" } # ./control made a real directory, run inside the real static/: a link put # at the name is removed, a missing directory created 0755. _acct_ctrl_dir_here() { [ -L ./control ] && rm -f ./control [ -d ./control ] || ( umask 022; mkdir -p ./control ) } # The per-serial defaults in an account's control directory (run inside the # real directory): each written only where missing, and cli.info taken from # fpm.info only when that is a regular file. _ctrl_seed_here() { local _v if [ ! -e ./MyQuick.info ] && [ ! -e ./MyClassic.info ]; then _acct_put_here MyQuick.info ON fi if [ ! -e ./.disFastTrack.pid ]; then rm -f -- ./FastTrack.info _acct_touch_here .disFastTrack.pid fi if [ ! -e ./FastTrack.info ] && [ ! -e ./ClassicTrack.info ]; then _acct_put_here ClassicTrack.info ON fi if [ -e ./fpm.info ] && [ ! -e ./cli.info ]; then _v="$(_acct_read_here fpm.info)" [ -n "${_v}" ] && _acct_put_here cli.info "${_v}" fi return 0 } # fpm.info and cli.info of an account that has none (run inside the real # control directory), handed to the main login: fpm.info the default # version, cli.info the fpm.info version or the default. Each is a fresh # 0644 file owned through its handle (_acct_put_own_here): the main login # can rename a hard link over a name between a put and a chown by name. _ctrl_pins_here() { local _v if [ ! -e ./fpm.info ] && [ -n "${_useFpm}" ]; then printf '%s\n' "${_useFpm}" \ | _acct_put_own_here fpm.info "${_tUsr}.ftp:${_usrGroup}" 644 fi if [ ! -e ./cli.info ]; then if [ -e ./fpm.info ]; then _v="$(_acct_read_here fpm.info)" else _v="${_useCli}" fi [ -n "${_v}" ] && printf '%s\n' "${_v}" \ | _acct_put_own_here cli.info "${_tUsr}.ftp:${_usrGroup}" 644 fi return 0 } # The control directory on the first pass of a serial (run inside the real # directory), as the ltd worker's _ltd_ctrl_init does it: the README put in # place as a fresh 0644 file, the tree handed to the main login, old stamps # swept and this serial's written exclusively. The hand-over walks the tree # with find -execdir (_ACCT_REOWN_PL): a directory or a single-link regular # file is re-owned through its handle, and a hard link the main login put # in the tree (to any file it can read and write) is left alone. _ctrl_init_here() { local _uid _gid chmod 755 . if [ -e "/var/xdrago/conf/control-readme.txt" ]; then ( umask 022 cp -T --no-preserve=mode --remove-destination \ /var/xdrago/conf/control-readme.txt ./README.txt ) &> /dev/null fi _uid=$(id -u -- "${_tUsr}.ftp" 2> /dev/null) _gid=$(getent group "${_usrGroup}" 2> /dev/null | cut -d: -f3) if [[ "${_uid}" =~ ^[0-9]+$ && "${_gid}" =~ ^[0-9]+$ ]]; then env PATH=/usr/local/bin:/usr/bin:/bin find . \( -type d -o -type f \) \ -execdir perl -e "${_ACCT_REOWN_PL}" "${_uid}" "${_gid}" {} + fi rm -f -- ./.ctrl.* _acct_put_here ".ctrl.${_tRee}.${_xSrl}.pid" OK } # An octopus upgrade queued from the account's control directory (run inside # the real directory): platforms.info seeded when empty and run-upgrade.pid # created, both exclusively and handed to the main login through their # handles (_acct_put_own_here), never chowned by name. True only when this # call queued it. $1 = owner:group. _ctrl_upgrade_here() { if [ ! -s ./platforms.info ]; then printf '%s\n' FOOBAR | _acct_put_own_here platforms.info "${1}" 644 fi [ -e ./run-upgrade.pid ] && return 1 _acct_put_own_here run-upgrade.pid "${1}" 644 < /dev/null || return 1 return 0 } ### One-shot fleet heal: the hostmaster site's drushrc.php carries the ### instance DB user (ALL PRIVILEGES) and only its owner ever reads it, yet ### it sat at 0440 with group users, a group every shell account on the box ### is in. Provision now writes it 0400 and the permission passes keep it ### there; this closes the gap on every box before those tools arrive. ### Real files only: never chmod through a planted link. An account owns ### every directory on the way to its own drushrc.php, so there the mode is ### set through a handle inside the pinned real site directory, on a ### single-link regular file only. Runs once per box per stamp serial; ### decrement the serial to re-run fleet-wide. if [ ! -e "${_pthLog}/hostmaster-drushrc-0400.ctrl.f99.${_tRee}.${_xSrl}.pid" ]; then for _hmRc in /var/aegir/host_master/*/sites/*/drushrc.php; do [ -f "${_hmRc}" ] && [ ! -L "${_hmRc}" ] && chmod 0400 "${_hmRc}" &> /dev/null done for _hmRc in /data/disk/*/aegir/distro/*/sites/*/drushrc.php; do [ -f "${_hmRc}" ] || continue _acct_in_real_dir "${_hmRc%/*}" \ perl -e "${_BOA_FCHMOD_PL}" 0400 drushrc.php &> /dev/null done touch "${_pthLog}/hostmaster-drushrc-0400.ctrl.f99.${_tRee}.${_xSrl}.pid" fi _os_detection_minimal() { _APT_UPDATE="apt-get update" _OS_CODE=$(lsb_release -ar 2>/dev/null | grep -i codename | cut -s -f2) _OS_LIST="excalibur daedalus chimaera beowulf buster bullseye bookworm trixie" for e in ${_OS_LIST}; do if [ "${e}" = "${_OS_CODE}" ]; then _APT_UPDATE="apt-get update --allow-releaseinfo-change" fi done } _apt_clean_update() { ${_APT_UPDATE} -qq 2>/dev/null _CALLER_SCRIPT="$(basename "${BASH_SOURCE[-1]}")" _CALLER_SCRIPT="${_CALLER_SCRIPT//[^a-zA-Z0-9._-]/_}" date +%s > "/run/_latest_apt_clean_update.${_CALLER_SCRIPT}.pid" } _if_hosted_sys() { # A test, not ||: tr exits 0 on an empty /etc/hostname _hName="$(cat /etc/hostname 2>/dev/null | tr -d '\n')" [ -n "${_hName}" ] || _hName="$(hostname -f 2>/dev/null)" if [ -e "/root/.host8.cnf" ] \ || [[ "${_hName}" =~ ".aegir.cc"($) ]]; then _hostedSys=YES else _hostedSys=NO fi } # # Find server city. _find_server_city() { if [ -e "/root/.found_correct_city.cnf" ]; then _LOC_CITY=$(cat /root/.found_correct_city.cnf 2>/dev/null | tr -d '\n') else if [ -e "/root/.found_correct_ipv4.cnf" ]; then _LOC_IP=$(cat /root/.found_correct_ipv4.cnf 2>/dev/null | tr -d '\n') _LOC_CITY=$(curl ${_crlGet} ipinfo.io/${_LOC_IP}/city 2>&1) _LOC_CITY=$(echo -n ${_LOC_CITY} | tr -d "\n" 2>&1) fi if [ ! -z "${_LOC_CITY}" ]; then _LOC_CITY=$(echo "${_LOC_CITY}" | tr ' ' '+' 2>&1) echo ${_LOC_CITY} > /root/.found_correct_city.cnf fi fi } # # Find correct IP. _find_correct_ip() { if [ -e "/root/.found_correct_ipv4.cnf" ]; then _LOC_IP=$(cat /root/.found_correct_ipv4.cnf 2>/dev/null | tr -d '\n') else _LOC_IP=$(curl ${_crlGet} https://api.ipify.org | sed 's/[^0-9\.]//g') if [ -z "${_LOC_IP}" ]; then _LOC_IP=$(curl ${_crlGet} http://ipv4.icanhazip.com | sed 's/[^0-9\.]//g') fi if [ ! -z "${_LOC_IP}" ]; then echo ${_LOC_IP} > /root/.found_correct_ipv4.cnf fi fi if [ -n "${_LOC_IP}" ] && grep -qE "${_LOC_IP}\s" /etc/hosts; then cp -af /etc/hosts /etc/.was.hosts sed -i "s/^${_LOC_IP}.*//g" /etc/hosts [ -x "/etc/init.d/unbound" ] && [ ! -e "/usr/etc/unbound/unbound.conf.d" ] && mkdir -p /usr/etc/unbound/unbound.conf.d [ -x "/etc/init.d/unbound" ] && service unbound restart &> /dev/null fi } _fix_dns_settings() { [ ! -d "${_vBs}" ] && mkdir -p ${_vBs} rm -f ${_vBs}/resolv.conf.tmp _noUp="/etc/dhcp/dhclient-enter-hooks.d/nodnsupdate" if [ -x "/usr/sbin/unbound" ] && [ -e "/run/unbound/unbound.pid" ]; then if ! grep -q "nameserver 127.0.0.1" /etc/resolv.conf; then _FORCE_RESOLV_UPDATE=YES else _FORCE_RESOLV_UPDATE=NO fi [ ! -e "${_noUp}" ] && _FORCE_RESOLV_UPDATE=YES fi if ! grep -q "BOA-DNS-Config" /etc/resolv.conf || [ "${_FORCE_RESOLV_UPDATE}" = "YES" ]; then echo "### BOA-DNS-Config ###" > ${_vBs}/resolv.conf.tmp if [ -x "/usr/sbin/unbound" ] && [ -e "/run/unbound/unbound.pid" ]; then echo "nameserver 127.0.0.1" >> ${_vBs}/resolv.conf.tmp fi echo "nameserver 1.1.1.1" >> ${_vBs}/resolv.conf.tmp echo "nameserver 8.8.8.8" >> ${_vBs}/resolv.conf.tmp echo "nameserver 9.9.9.9" >> ${_vBs}/resolv.conf.tmp fi if [ -e "${_vBs}/resolv.conf.tmp" ]; then chattr -i /etc/resolv.conf rm -f /etc/resolv.conf cp -a ${_vBs}/resolv.conf.tmp /etc/resolv.conf chmod 0644 /etc/resolv.conf chattr +i /etc/resolv.conf cp -a ${_vBs}/resolv.conf.tmp ${_vBs}/resolv.conf.vanilla mkdir -p /etc/dhcp/dhclient-enter-hooks.d if [ -d "/etc/dhcp/dhclient-enter-hooks.d/" ]; then cat > ${_noUp} <<'EOF' make_resolv_conf() { :; } EOF chmod +x ${_noUp} fi fi if [ -x "/usr/sbin/unbound-control" ]; then unbound-control reload &> /dev/null fi } _check_dns_settings() { _EHU=NO if ! grep -q "127.0.0.1 localhost" /etc/hosts; then sed -i "s/^127.0.0.1.*//g" /etc/hosts echo "" >> /etc/hosts echo "127.0.0.1 localhost" >> /etc/hosts _EHU=YES fi if grep -q "files.boa.io" /etc/hosts; then sed -i "s/.*files.boa.io.*//g" /etc/hosts _EHU=YES fi if grep -q "github" /etc/hosts; then sed -i "s/.*github.*//g" /etc/hosts _EHU=YES fi if [ "${_EHU}" = "YES" ]; then echo >>/etc/hosts sed -i "/^$/d" /etc/hosts fi if [ -L "/etc/resolv.conf" ]; then _fix_dns_settings return 1 # Exit the function but continue the script fi if [ -x "/usr/sbin/unbound" ] && [ -e "/run/unbound/unbound.pid" ]; then if ! grep -q "nameserver 127.0.0.1" /etc/resolv.conf; then _fix_dns_settings return 1 # Exit the function but continue the script fi _noUp="/etc/dhcp/dhclient-enter-hooks.d/nodnsupdate" if [ ! -e "${_noUp}" ] ; then _fix_dns_settings return 1 # Exit the function but continue the script fi fi if [ -e "/root/.use.default.nameservers.cnf" ]; then if [ -e "/root/.use.local.nameservers.cnf" ]; then rm -f /root/.use.local.nameservers.cnf fi _USE_DEFAULT_DNS=YES if ! grep -q "BOA-DNS-Config" /etc/resolv.conf; then _fix_dns_settings return 1 # Exit the function but continue the script fi fi if [ -e "/root/.use.local.nameservers.cnf" ]; then _USE_PROVIDER_DNS=YES else _REMOTE_DNS_TEST=$(host files.boa.io 1.1.1.1 -w 10 2>&1) if ! grep -q "BOA-DNS-Config" /etc/resolv.conf; then _fix_dns_settings return 1 # Exit the function but continue the script fi fi if [[ "${_REMOTE_DNS_TEST}" =~ "no servers could be reached" ]] \ || [[ "${_REMOTE_DNS_TEST}" =~ "Host files.boa.io not found" ]] \ || [ "${_USE_PROVIDER_DNS}" = "YES" ]; then _fix_dns_settings fi } _find_fast_mirror_early() { _isNetc="$(which netcat)" if [ ! -x "${_isNetc}" ] || [ -z "${_isNetc}" ]; then if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _apt_clean_update apt-get install netcat-traditional ${_aptYesUnth} -qq 2> /dev/null fi _ffMirr=/opt/local/bin/ffmirror if [ -x "${_ffMirr}" ]; then _ffList="/var/backups/boa-mirrors-2026-07.txt" [ -d "/var/backups" ] || mkdir -p /var/backups if [ ! -e "${_ffList}" ]; then echo "files.boa.io" > ${_ffList} echo "files.o8.io" >> ${_ffList} echo "files.host8.biz" >> ${_ffList} echo "files.aegir.biz" >> ${_ffList} echo "files.aoboshi.com" >> ${_ffList} fi if [ -e "${_ffList}" ]; then _BROKEN_FFMIRR_TEST=$(grep -E "ffmirror-ready|stuff" ${_ffMirr} 2>&1) if [[ "${_BROKEN_FFMIRR_TEST}" =~ "ffmirror-ready" ]] \ || [[ "${_BROKEN_FFMIRR_TEST}" =~ "stuff" ]]; then _CHECK_MIRROR=$(bash ${_ffMirr} < ${_ffList} 2>&1) _CHECK_MIRROR=$(bash ${_ffMirr} < ${_ffList} 2>&1) _USE_MIR="${_CHECK_MIRROR}" [[ "${_USE_MIR}" =~ "printf" ]] && _USE_MIR="files.boa.io" else _USE_MIR="files.boa.io" fi else _USE_MIR="files.boa.io" fi else _USE_MIR="files.boa.io" fi _urlDev="https://${_USE_MIR}/dev" _urlHmr="https://${_USE_MIR}/versions/${_tRee}/boa/aegir" } # # Extract archive. tar never keeps the owners the archive recorded: run by # root it would give each file that owner, by name or number, which can be # one of this box's accounts. _extract_archive() { if [ ! -z "${1}" ]; then case $1 in *.tar.bz2) tar --no-same-owner -xjf ${1} ;; *.tar.gz) tar --no-same-owner -xzf ${1} ;; *.tar.xz) tar --no-same-owner -xvf ${1} ;; *.bz2) bunzip2 ${1} ;; *.rar) unrar x ${1} ;; *.gz) gunzip -q ${1} ;; *.tar) tar --no-same-owner -xf ${1} ;; *.tbz2) tar --no-same-owner -xjf ${1} ;; *.tgz) tar --no-same-owner -xzf ${1} ;; *.zip) unzip -qq ${1} ;; *.Z) uncompress ${1} ;; *.7z) 7z x ${1} ;; *) echo "'${1}' cannot be extracted via >extract<"; false ;; esac # The unpacker's own status, so a cut-short archive is not taken as done. local _xRc=$? [ -z "${2}" ] && rm -f "${1}" return ${_xRc} fi } # # Download and extract from dev/contrib mirror. _get_dev_contrib() { if [ ! -z "${1}" ]; then _max_attempts=10 _attempt_num=1 _success=0 while [ ${_attempt_num} -le ${_max_attempts} ]; do [ "${_DEBUG_MODE}" = "YES" ] && echo "DNLD: Attempt ${_attempt_num} of ${_max_attempts}: Downloading $1..." if curl ${_crlGet} "${_urlDev}/${_tRee}/contrib/$1" -o "$1"; then _success=1 break else [ "${_DEBUG_MODE}" = "YES" ] && echo "DNLD: Attempt ${_attempt_num} failed." _attempt_num=$((_attempt_num+1)) if [ "${_attempt_num}" -le "${_max_attempts}" ]; then [ "${_DEBUG_MODE}" = "YES" ] && echo "DNLD: Retrying in 9 seconds..." sleep 9 fi fi done if [ "${_success}" -eq 1 ]; then if [ ! -z "${2}" ]; then _extract_archive "${1}" "${2}" else _extract_archive "${1}" fi else echo "OOPS: Failed to download ${_urlDev}/${_tRee}/contrib/$1 after ${_max_attempts} attempts" return 1 # Exit the function but continue the script fi fi } # # Replace one shared module under ${_Dir} from the dev/contrib mirror. It is # fetched and unpacked in a directory of this run's own, stamped there, and # swapped in only when the unpack finished: a copy cut short (an archive # another run was replacing, a broken download) never replaces the one in # place, is reported in the fetch-failure line and is fetched again on the # next run, where the old code stamped it as deployed. The new copy and the # one in place swap names in one rename (_SAT_SWAP_PL), so a site loading the # module through a bundle's link finds the old copy or the new one, never # none; where one rename cannot do it (nothing in place yet, or no renameat2 # on the box), the copy in place is moved aside first and put back when the # new one cannot go in. # Two stamps: the gates read the ".full" one, which only this path writes, so # a copy the old code stamped is replaced once; the plain one keeps an # Octopus not yet upgraded from fetching it again the old way. Owner "root" # re-owns the copy, whatever the archive recorded. Every step runs from # inside the real ${_Dir}, each of its last three names entered as the real # directory (outside /home and /data/disk _acct_in_real_dir checks only the # last name, and a link an earlier release let an account leave at # /data/all/000 must never be followed), and names the unpack directory # relative to it, never by the store's path; that directory is this run's # own (mktemp, root's, 0700), so chown -R there hands over only what the # archive holds. The store's modes are then set through handles opened # without following a link, a regular file only while it has a single link, # so a link or a hard link left in the store is never followed or re-moded. # Nothing is fetched on a replication standby: its store comes from the # active with every sync, so a copy put here would be a local write into a # synced tree, and two boxes on different trees would replace each other's # modules every few minutes. The first agents pass after the promotion # fetches whatever is stale then. _shared_module_update() { local _url="${_urlDev}/${_tRee}/contrib/${1}-${2}.tar.gz" _up="${_Dir%/*}" local _st [ -e "/root/.standby.cnf" ] && return 0 # made where missing, each name from inside its real parent (the store # in root's /data, or /data/disk/all when a link of root's there leads # to it through root's own directories only, _ROOT_REAL_PATH_PL and # _SAT_STORE_TARGET_PL; any other link the pin refuses), so a link at # the last two names is never followed mkdir -p "${_up%/*}" &> /dev/null _st="$(perl -e "${_ROOT_REAL_PATH_PL}" -- "${_up%/*}" 2> /dev/null)" perl -e "${_SAT_STORE_TARGET_PL}" -- "${_st}" &> /dev/null || _st="" _st="${_st:-${_up%/*}}" _acct_in_real_dir "${_st}" mkdir -p -m 0755 -- "./${_up##*/}" &> /dev/null _acct_in_real_dir "${_st}" _acct_in_real_dir "./${_up##*/}" \ mkdir -p -m 0755 -- "./${_Dir##*/}" &> /dev/null _acct_in_real_dir "${_st}" _acct_in_real_dir "./${_up##*/}" \ _acct_in_real_dir "./${_Dir##*/}" _shared_module_update_here "$@" case $? in 0) ;; 2) echo "OOPS: ${1}-${2} was not updated (the new copy could not be put in place); $(_shared_module_kept "${1}")" _fetch_failed_note "${1}" "${_url}" "swap" ;; 3) echo "OOPS: ${1}-${2} was not updated (download or unpack failed); $(_shared_module_kept "${1}")" _fetch_failed_note "${1}" "${_url}" "unpack" ;; 4) echo "OOPS: ${1}-${2} was not updated; the copy in place, flagged, was taken out of use" _fetch_failed_note "${1}" "${_url}" "sealed" ;; 5) echo "OOPS: ${1}-${2} was not updated; the copy in place, flagged, could not be set aside" _fetch_failed_note "${1}" "${_url}" "flagged" ;; *) echo "OOPS: ${1}-${2} was not updated (no unpack directory in the real ${_Dir}); $(_shared_module_kept "${1}")" _fetch_failed_note "${1}" "${_url}" "store" # nothing can be set aside where the store is not the real one; a # flagged module there is said all the same _st="$(_shared_module_flagged "${1}")" [ -n "${_st}" ] && _shared_module_alarm "${1}" "${_st}" NO ;; esac } # The first entry of the module $1 under ${_Dir} (or of ./$1, the module in # the current directory, when ${_Dir} is empty), the name itself included, # that is a link, is not root's or is writable by its group or others, # found without following a link (an earlier release let an account own the # store, and its recursive chown made what the account left there root's); # nothing when every entry is root's alone or the module is not there. _shared_module_flagged() { find -P "${_Dir:-.}/${1}" \( -type l -o \! -user 0 -o -perm /022 \) \ -print -quit 2> /dev/null } # What a failed fetch leaves of the module ${_Dir}/$1, for its OOPS line. _shared_module_kept() { if [ ! -e "${_Dir}/${1}" ] && [ ! -L "${_Dir}/${1}" ]; then echo "nothing is in place" elif [ -d "${_Dir}/${1}" ] && [ ! -L "${_Dir}/${1}" ] \ && [ -z "$(find -P "${_Dir}/${1}" -mindepth 1 -print -quit 2> /dev/null)" ] \ && [ -z "$(_shared_module_flagged "${1}")" ]; then echo "the module stays out of use until a fetch succeeds" else echo "the copy in place is kept" fi } # The store's modes in the current (pinned) directory and below it: # directories 0755, single-link files 0644, through handles that never # follow a link. _shared_module_modes_here() { perl -e "${_SAT_NOFOLLOW_CHMOD_PL}" d 0755 . find . -mindepth 1 -type d -execdir perl -e "${_SAT_NOFOLLOW_CHMOD_PL}" d 0755 {} + find . -mindepth 1 -type f -execdir perl -e "${_BOA_FCHMOD_PL}" 0644 {} + } # A fetch of the shared module ${_Dir}/$1 is due: its stamp $2 is not # there, or the module is flagged (_shared_module_flagged). Nothing is # trusted through a link. The fetch swaps the module out without following # a link; when the fetch fails and the module is flagged, an empty directory # takes its place (_shared_module_seal_here), otherwise what is there stays # until a fetch succeeds. _shared_module_stale() { [ -n "$(_shared_module_flagged "${1}")" ] && return 0 [ -e "${_Dir}/${1}/${2}" ] || return 0 # current and root's alone, however it got so: a later alarm for it is a # new state rm -f -- "${_pthLog}/shared-module.${1}.alarm" return 1 } # The alarm for the flagged module $1 (its first flagged entry $2): $3 YES # when it was taken out of use, NO when it could not be set aside. Said on # the run's output every time, and in ${_pthLog}/shared-module.incident.log # and by mail once for each state (the module, its entry, the outcome): a # state that lasts is not mailed again every five minutes. The marker goes # when the module is found root's alone or is fetched. _shared_module_alarm() { local _m _s _k="${_pthLog}/shared-module.${1}.alarm" if [ "${3}" = "YES" ]; then _m="ALRT: ${1} in ${_Dir} held ${2#./} (a link, or not root's alone) and could not be fetched again: taken out of use, sites use their database cache until the next fetch" _s="Shared module ${1} taken out of use" else _m="ALRT: ${1} in ${_Dir} held ${2#./} (a link, or not root's alone), could not be fetched again and could not be set aside: sites still load it" _s="Shared module ${1} could NOT be taken out of use" fi echo "${_m}" [ -d "${_pthLog}" ] || return 0 [ "$(cat -- "${_k}" 2> /dev/null)" = "${3} ${2}" ] && return 0 printf '%s\n' "${3} ${2}" > "${_k}.$$" && mv -f -- "${_k}.$$" "${_k}" echo "$(date) ${_m}" >> "${_pthLog}/shared-module.incident.log" if [ -n "${_MY_EMAIL}" ] && [ "${_INCIDENT_REPORT}" != "OFF" ]; then echo "${_m}" | s-nail -s "${_s} on [${_hName:-$(hostname -f 2> /dev/null)}]" ${_MY_EMAIL} &> /dev/null fi return 0 } # A flagged module that could not be fetched again is taken out of every # site's load path: an empty directory of root's takes its name in the # current (pinned) ${_Dir}, swapped in without following a link, and the # alarm says so (_shared_module_alarm). BOA's settings includes reach a # module only through is_readable(), so the sites fall back to their # database cache until a fetch puts it back; the empty directory has no # stamp, so the next run fetches again, and being root's alone it is never # sealed twice. What came out is put back when it is root's alone after all # (a concurrent pass swapped a good copy in since the test). Status: 0 # nothing flagged (or put back), 4 taken out of use, 5 flagged and not set # aside. _shared_module_seal_here() { local _nm="$1" _e _f _tmp="" _swp=NO _e="$(_Dir="" _shared_module_flagged "${_nm}")" if [ -z "${_e}" ]; then rm -f -- "${_pthLog}/shared-module.${_nm}.alarm" return 0 fi if _tmp="$(mktemp -d "./.${_nm}.XXXXXX")" \ && mkdir -m 0755 -- "${_tmp}/${_nm}"; then if perl -e "${_SAT_SWAP_PL}" -- "${_tmp}/${_nm}" "./${_nm}" 2> /dev/null; then _swp=YES if [ -z "$(_Dir="${_tmp}" _shared_module_flagged "${_nm}")" ] \ && perl -e "${_SAT_SWAP_PL}" -- "${_tmp}/${_nm}" "./${_nm}" 2> /dev/null; then _swp=BACK fi elif mv -T -- "./${_nm}" "${_tmp}/.old" &> /dev/null; then if [ -z "$(_Dir="${_tmp}" _shared_module_flagged .old)" ] \ && mv -T -- "${_tmp}/.old" "./${_nm}" &> /dev/null; then _swp=BACK elif mv -T -- "${_tmp}/${_nm}" "./${_nm}" &> /dev/null; then _swp=YES elif [ -e "./${_nm}" ] || [ -L "./${_nm}" ]; then # another pass put a copy at the name meanwhile: that copy's own test _f="$(_Dir="" _shared_module_flagged "${_nm}")" if [ -z "${_f}" ]; then _swp=BACK else _e="${_f}" fi else # nothing at the name: out of use all the same _swp=YES fi else # nothing moved: what is at the name now has its own test (another # pass may have swapped a copy in since this one's) _f="$(_Dir="" _shared_module_flagged "${_nm}")" if [ -z "${_f}" ]; then _swp=BACK else _e="${_f}" fi fi fi [ -n "${_tmp}" ] && rm -rf -- "${_tmp}" case "${_swp}" in BACK) return 0 ;; YES) _shared_module_alarm "${_nm}" "${_e}" YES; return 4 ;; esac _shared_module_alarm "${_nm}" "${_e}" NO return 5 } # The fetch, unpack and swap in the current (pinned) ${_Dir}, run in a # subshell: 0 done, 2 not swapped in, 3 not fetched or unpacked, 1 no unpack # directory, 4 or 5 a flagged module taken out of use or not set aside # after either (_shared_module_seal_here); _shared_module_update reports it. _shared_module_update_here() { local _nm="$1" _ver="$2" _own="$3" _tmp _rc=3 _swp=NO _got=NO # a directory a killed run left behind; a live run's is never this old find . -maxdepth 1 -type d -name ".${_nm}.*" -mmin +60 -exec rm -rf -- {} + &> /dev/null if ! _tmp="$(mktemp -d "./.${_nm}.XXXXXX")"; then _shared_module_seal_here "${_nm}" _rc=$? [ "${_rc}" = "0" ] && _rc=1 return "${_rc}" fi if ( cd -P -- "${_tmp}" && _get_dev_contrib "${_nm}-${_ver}.tar.gz" ) \ && [ -d "${_tmp}/${_nm}" ] && [ ! -L "${_tmp}/${_nm}" ]; then [ "${_own}" = "root" ] && chown -R root:root "${_tmp}/${_nm}" &> /dev/null rm -f -- "${_tmp}/${_nm}/ver-${_ver}.${_xSrl}.info" \ "${_tmp}/${_nm}/ver-${_ver}.${_xSrl}.full.info" echo update > "${_tmp}/${_nm}/ver-${_ver}.${_xSrl}.info" echo update > "${_tmp}/${_nm}/ver-${_ver}.${_xSrl}.full.info" # the store's modes before the copy goes in, its stamps included (some # archives record modes writable by the group, which root's tar keeps): # a copy is never flagged as it enters the store, and one the walk # cannot make root's alone (a link in it) counts as a failed unpack _acct_in_real_dir "${_tmp}/${_nm}" _shared_module_modes_here &> /dev/null _got=YES fi if [ "${_got}" = "YES" ] \ && [ -z "$(_Dir="${_tmp}" _shared_module_flagged "${_nm}")" ]; then # swapped, the copy that was in place is left at ${_tmp}/${_nm} if [ -e "./${_nm}" ] || [ -L "./${_nm}" ]; then if perl -e "${_SAT_SWAP_PL}" -- "${_tmp}/${_nm}" "./${_nm}" 2> /dev/null; then _swp=YES else mv -T -- "./${_nm}" "${_tmp}/.old" &> /dev/null fi fi if [ "${_swp}" = "YES" ] \ || mv -T -- "${_tmp}/${_nm}" "./${_nm}" &> /dev/null; then _shared_module_modes_here &> /dev/null touch "${_pthLog}/${_nm}.ctrl.${_xSrl}.log" rm -f -- "${_pthLog}/shared-module.${_nm}.alarm" _rc=0 else if [ -e "${_tmp}/.old" ] || [ -L "${_tmp}/.old" ]; then [ -e "./${_nm}" ] || [ -L "./${_nm}" ] \ || mv -T -- "${_tmp}/.old" "./${_nm}" &> /dev/null fi _rc=2 fi fi rm -rf -- "${_tmp}" if [ "${_rc}" != "0" ]; then _shared_module_seal_here "${_nm}" case $? in 4) _rc=4 ;; 5) _rc=5 ;; esac fi return "${_rc}" } # # Download and extract archive from dev/src mirror. _get_dev_src() { if [ ! -z "${1}" ]; then _max_attempts=10 _attempt_num=1 _success=0 while [ ${_attempt_num} -le ${_max_attempts} ]; do [ "${_DEBUG_MODE}" = "YES" ] && echo "DNLD: Attempt ${_attempt_num} of ${_max_attempts}: Downloading $1..." if curl ${_crlGet} "${_urlDev}/src/$1" -o "$1"; then _success=1 break else [ "${_DEBUG_MODE}" = "YES" ] && echo "DNLD: Attempt ${_attempt_num} failed." _attempt_num=$((_attempt_num+1)) if [ "${_attempt_num}" -le "${_max_attempts}" ]; then [ "${_DEBUG_MODE}" = "YES" ] && echo "DNLD: Retrying in 9 seconds..." sleep 9 fi fi done if [ "${_success}" -eq 1 ]; then if [ ! -z "${2}" ]; then _extract_archive "${1}" "${2}" else _extract_archive "${1}" fi else echo "OOPS: Failed to download ${_urlDev}/src/$1 after ${_max_attempts} attempts" return 1 # Exit the function but continue the script fi fi } _if_clean_boa_env() { if [ ! -x "/etc/init.d/clean-boa-env" ]; then curl ${_crlGet} "${_urlHmr}/conf/var/clean-boa-env" -o /etc/init.d/clean-boa-env if [ -e "/etc/init.d/clean-boa-env" ]; then chmod 700 /etc/init.d/clean-boa-env chown root:root /etc/init.d/clean-boa-env update-rc.d clean-boa-env defaults &> /dev/null fi fi } ### ### Function to verify BOA keys ### _verify_boa_keys() { if [ -e "/root/.dev.server.cnf" ]; then echo "PROC: _verify_boa_keys in BOA.sh.txt" fi if [ "${_tRee}" = "pro" ] || [ "${_tRee}" = "dev" ]; then _if_hosted_sys _allw=NO _urlEnc="https://${_USE_MIR}/enc/2024" _encName=$(echo ${_hName} \ | openssl md5 \ | awk '{ print $2}' \ | tr -d "\n" 2>&1) if [[ "${_hName}" =~ ".aegir.cc"($) ]] \ || [[ "${_hName}" =~ ".o8.io"($) ]] \ || [[ "${_hName}" =~ ".boa.io"($) ]]; then _allw=YES fi # *.sslip.io hostnames (wildcard DNS to the embedded IP, zero setup) # pass on the dev tree only: test boxes run the dev tree end to end # without a per-box key; pro stays licensed outside the fleet domains. if [ "${_tRee}" = "dev" ] \ && [[ "${_hName}" =~ ".sslip.io"($) ]]; then _allw=YES fi mkdir -p /var/opt rm -f /var/opt/_encN* curl ${_crlGet} "${_urlEnc}/${_encName}" -o /var/opt/_encN.${_encName}.tmp wait echo "${_hName}.${_encName}" > /var/opt/_encN_local.${_encName}.tmp wait if [ -e "/var/opt/_encN.${_encName}.tmp" ] && [ -e "/var/opt/_encN_local.${_encName}.tmp" ]; then _diffTestIf=$(diff -w -B /var/opt/_encN.${_encName}.tmp /var/opt/_encN_local.${_encName}.tmp 2>&1) if [ ! -z "${_diffTestIf}" ] && [ "${_allw}" = "NO" ]; then echo echo "Your system requires valid license for access to ${_rLsn}-${_tRee}" echo "Please visit https://omega8.cc/licenses to purchase your own" echo if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ ! -e "/var/aegir/key/barracuda_key.txt" ]; then mkdir -p /var/aegir/key cat /var/opt/_encN_local.${_encName}.tmp > /var/aegir/key/barracuda_key.txt fi rm -f /var/opt/_encN* exit 0 else if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ ! -e "/var/aegir/key/barracuda_key.txt" ]; then mkdir -p /var/aegir/key cat /var/opt/_encN_local.${_encName}.tmp > /var/aegir/key/barracuda_key.txt fi fi elif [ "${_allw}" = "YES" ]; then # A fleet-domain or sslip.io host is allowed on its name alone; the key # fetch that failed here decides nothing for it. Right after a reboot # (the codename drivers relaunch within 30 seconds) DNS may not answer # yet, and refusing then stopped a running upgrade chain silently. echo echo "NOTE: the key server could not be reached; ${_hName} is allowed on the ${_tRee} tree by its hostname" echo rm -f /var/opt/_encN* else echo echo "Your system requires valid license to use this BOA version (${_tRee})" echo "Unfortunately it was not possible to verify your system status" echo "Please contact our support but visit https://omega8.cc/licenses first" echo exit 0 fi fi } _locales_check_fix_early() { _isLoc="$(which locale)" if [ ! -x "${_isLoc}" ] || [ -z "${_isLoc}" ]; then apt-get update -qq &> /dev/null ${_INITINS} locales locales-all &> /dev/null fi _LOC_TEST=$(locale 2>&1) if [[ "${_LOC_TEST}" =~ LANG=.*UTF-8 ]]; then _LOCALE_TEST=OK fi if [[ "${_LOC_TEST}" =~ "Cannot" ]]; then _LOCALE_TEST=BROKEN fi if [ "${_LOCALE_TEST}" = "BROKEN" ]; then _LOCALE_GEN_TEST=$(grep -v "^#" /etc/locale.gen 2>&1) if [[ ! "${_LOCALE_GEN_TEST}" =~ "en_US.UTF-8 UTF-8" ]]; then echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen fi sed -i "/^$/d" /etc/locale.gen locale-gen &> /dev/null locale-gen en_US.UTF-8 &> /dev/null # Explicitly enforce all locale settings update-locale \ LANG=en_US.UTF-8 \ LC_CTYPE=en_US.UTF-8 \ LC_COLLATE=POSIX \ LC_NUMERIC=POSIX \ LC_TIME=en_US.UTF-8 \ LC_MONETARY=en_US.UTF-8 \ LC_MESSAGES=en_US.UTF-8 \ LC_PAPER=en_US.UTF-8 \ LC_NAME=en_US.UTF-8 \ LC_ADDRESS=en_US.UTF-8 \ LC_TELEPHONE=en_US.UTF-8 \ LC_MEASUREMENT=en_US.UTF-8 \ LC_IDENTIFICATION=en_US.UTF-8 \ LC_ALL= &> /dev/null # Define all locale settings on the fly to prevent unnecessary # warnings during installation of packages. export LANG=en_US.UTF-8 &> /dev/null export LC_CTYPE=en_US.UTF-8 &> /dev/null export LC_COLLATE=POSIX &> /dev/null export LC_NUMERIC=POSIX &> /dev/null export LC_TIME=en_US.UTF-8 &> /dev/null export LC_MONETARY=en_US.UTF-8 &> /dev/null export LC_MESSAGES=en_US.UTF-8 &> /dev/null export LC_PAPER=en_US.UTF-8 &> /dev/null export LC_NAME=en_US.UTF-8 &> /dev/null export LC_ADDRESS=en_US.UTF-8 &> /dev/null export LC_TELEPHONE=en_US.UTF-8 &> /dev/null export LC_MEASUREMENT=en_US.UTF-8 &> /dev/null export LC_IDENTIFICATION=en_US.UTF-8 &> /dev/null export LC_ALL= &> /dev/null else _LOCALE_GEN_TEST=$(grep -v "^#" /etc/locale.gen 2>&1) if [[ ! "${_LOCALE_GEN_TEST}" =~ "en_US.UTF-8 UTF-8" ]]; then echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen fi sed -i "/^$/d" /etc/locale.gen locale-gen &> /dev/null locale-gen en_US.UTF-8 &> /dev/null # Explicitly enforce locale settings required for consistency update-locale \ LANG=en_US.UTF-8 \ LC_CTYPE=en_US.UTF-8 \ LC_COLLATE=POSIX \ LC_NUMERIC=POSIX \ LC_ALL= &> /dev/null # Define locale settings required for consistency also on the fly export LC_COLLATE=POSIX &> /dev/null export LC_NUMERIC=POSIX &> /dev/null export LC_ALL= &> /dev/null fi _LOCALES_BASHRC_TEST=$(grep LC_COLLATE /root/.bashrc 2>&1) if [[ ! "${_LOCALES_BASHRC_TEST}" =~ "LC_COLLATE" ]]; then printf "\n" >> /root/.bashrc echo "export LANG=en_US.UTF-8" >> /root/.bashrc echo "export LC_CTYPE=en_US.UTF-8" >> /root/.bashrc echo "export LC_COLLATE=POSIX" >> /root/.bashrc echo "export LC_NUMERIC=POSIX" >> /root/.bashrc echo "export LC_TIME=en_US.UTF-8" >> /root/.bashrc echo "export LC_MONETARY=en_US.UTF-8" >> /root/.bashrc echo "export LC_MESSAGES=en_US.UTF-8" >> /root/.bashrc echo "export LC_PAPER=en_US.UTF-8" >> /root/.bashrc echo "export LC_NAME=en_US.UTF-8" >> /root/.bashrc echo "export LC_ADDRESS=en_US.UTF-8" >> /root/.bashrc echo "export LC_TELEPHONE=en_US.UTF-8" >> /root/.bashrc echo "export LC_MEASUREMENT=en_US.UTF-8" >> /root/.bashrc echo "export LC_IDENTIFICATION=en_US.UTF-8" >> /root/.bashrc echo "export LC_ALL=" >> /root/.bashrc printf "\n" >> /root/.bashrc fi } _if_fix_iptables_symlinks() { ### ### Fix for iptables paths backward compatibility ### if [ -x "/sbin/iptables" ] && [ ! -e "/usr/sbin/iptables" ]; then ln -sfn /sbin/iptables /usr/sbin/iptables fi if [ -x "/usr/sbin/iptables" ] && [ ! -e "/sbin/iptables" ]; then ln -sfn /usr/sbin/iptables /sbin/iptables fi if [ -x "/sbin/iptables-save" ] && [ ! -e "/usr/sbin/iptables-save" ]; then ln -sfn /sbin/iptables-save /usr/sbin/iptables-save fi if [ -x "/usr/sbin/iptables-save" ] && [ ! -e "/sbin/iptables-save" ]; then ln -sfn /usr/sbin/iptables-save /sbin/iptables-save fi if [ -x "/sbin/iptables-restore" ] && [ ! -e "/usr/sbin/iptables-restore" ]; then ln -sfn /sbin/iptables-restore /usr/sbin/iptables-restore fi if [ -x "/usr/sbin/iptables-restore" ] && [ ! -e "/sbin/iptables-restore" ]; then ln -sfn /usr/sbin/iptables-restore /sbin/iptables-restore fi if [ -x "/sbin/ip6tables" ] && [ ! -e "/usr/sbin/ip6tables" ]; then ln -sfn /sbin/ip6tables /usr/sbin/ip6tables fi if [ -x "/usr/sbin/ip6tables" ] && [ ! -e "/sbin/ip6tables" ]; then ln -sfn /usr/sbin/ip6tables /sbin/ip6tables fi if [ -x "/sbin/ip6tables-save" ] && [ ! -e "/usr/sbin/ip6tables-save" ]; then ln -sfn /sbin/ip6tables-save /usr/sbin/ip6tables-save fi if [ -x "/usr/sbin/ip6tables-save" ] && [ ! -e "/sbin/ip6tables-save" ]; then ln -sfn /usr/sbin/ip6tables-save /sbin/ip6tables-save fi if [ -x "/sbin/ip6tables-restore" ] && [ ! -e "/usr/sbin/ip6tables-restore" ]; then ln -sfn /sbin/ip6tables-restore /usr/sbin/ip6tables-restore fi if [ -x "/usr/sbin/ip6tables-restore" ] && [ ! -e "/sbin/ip6tables-restore" ]; then ln -sfn /usr/sbin/ip6tables-restore /sbin/ip6tables-restore fi ### ### Fix for iptables paths backward compatibility ### } ### ### Prefer Devuan APT sources ### _prefer_devuan_repositories() { # Prefer Devuan; force base-files from Devuan (handles lower version vs Debian). mkdir -p /etc/apt/preferences.d cat >/etc/apt/preferences.d/99-prefer-devuan <<'EOF' Package: * Pin: release o=Devuan Pin-Priority: 700 Package: base-files Pin: release o=Devuan Pin-Priority: 1001 EOF _apt_clean_update } ### ### Display not supported VM or bare metal info ### _not_supported_virt() { echo echo "=== OOPS! ===" echo echo "You are running not supported virtualization system:" echo " $1" echo echo "If you wish to try BOA on this system anyway," echo "please create an empty control file:" echo " /root/.allow.any.virt.cnf" echo echo "Please be aware that it may not work at all," echo "or you can experience errors breaking BOA." echo echo "WARNING! BOA IS NOT DESIGNED TO RUN DIRECTLY ON A BARE METAL." echo "WARNING! IT IS VERY DANGEROUS AND THUS EXTREMELY BAD IDEA!" echo "WARNING! You are free to experiment but don't expect *ANY* support." echo echo "BOA is known to work well on:" echo echo " * Linux Containers (LXC)" echo " * Linux KVM guest" echo " * Microsoft Hyper-V" echo " * OpenVZ Containers" echo " * Parallels guest" echo " * Red Hat KVM guest" echo " * VirtualBox guest" echo " * VMware ESXi guest (but excluding vCloud Air)" echo " * VServer guest" echo " * Xen guest fully virtualized (HVM)" echo " * Xen guest" echo " * Xen paravirtualized guest domain" echo echo "Bye" echo exit 1 } # --- internal: print message only in debug mode _msg() { if [ "${_DEBUG_MODE}" = "YES" ]; then echo "[virt-what-fix] $*" fi } # Diagnostics from the three discovery functions go to stderr: the caller # captures their stdout as the helper path, so a message printed there became # the path (a dangling symlink to the message text) and, being non-empty, also # made the dpkg and find fallbacks unreachable. # --- internal: run virt-what under strace and parse the helper's exec path _discover_with_strace() { local _path_found="" if ! command -v strace >/dev/null 2>&1; then _msg "strace not available, skipping strace-based discovery" >&2 echo "" return 0 fi # Temporarily extend PATH so virt-what can exec the helper for strace to see. PATH="${PATH}:${_CANDIDATE_PATHS}" strace -f -qq -e trace=execve -o "${_TRACE}" virt-what >/dev/null 2>&1 # mawk-safe parsing: pull the first quoted arg from execve("…") and check suffix if [ -s "${_TRACE}" ]; then _path_found=$( awk -v n="${_HELPER_NAME}" ' /execve\("/ { # Find start of execve(" then extract up to next quote i = index($0, "execve(\"") if (i) { s = substr($0, i + 8) # after execve(" j = index(s, "\"") if (j) { p = substr(s, 1, j - 1) # the path inside quotes if (p ~ ("/" n "$")) { print p; exit } } } } ' "${_TRACE}" ) fi rm -f "${_TRACE}" if [ -n "${_path_found}" ] && [ -x "${_path_found}" ]; then _msg "strace discovered helper at: ${_path_found}" >&2 echo "${_path_found}" return 0 fi _msg "strace discovery failed" >&2 echo "" return 0 } # --- internal: dpkg-based discovery (Debian/Devuan) _discover_with_dpkg() { local _p="" if command -v dpkg >/dev/null 2>&1; then _p=$(dpkg -L virt-what 2>/dev/null | grep -E "/${_HELPER_NAME}$" | head -n1) if [ -n "${_p}" ] && [ -x "${_p}" ]; then _msg "dpkg discovered helper at: ${_p}" >&2 echo "${_p}" return 0 fi fi echo "" return 0 } # --- internal: filesystem search fallback (bounded) _discover_with_find() { local _p="" # Keep it bounded to /usr to stay fast/noisy-free. _p=$(find /usr -maxdepth 4 -type f -name "${_HELPER_NAME}" 2>/dev/null | head -n1) if [ -n "${_p}" ] && [ -x "${_p}" ]; then _msg "find discovered helper at: ${_p}" >&2 echo "${_p}" return 0 fi echo "" return 0 } # --- main: ensure symlink _ensure_virt_what_helper_symlink() { # If the symlink already exists and is working, nothing to do. if [ -L "${_SYMLINK}" ] && [ -x "${_SYMLINK}" ] && [ -e "$(readlink -f "${_SYMLINK}")" ]; then _msg "Symlink already present and valid: ${_SYMLINK} -> $(readlink -f "${_SYMLINK}")" return 0 fi local _helper_path="" _helper_path="$(_discover_with_strace)" if [ -z "${_helper_path}" ]; then _helper_path="$(_discover_with_dpkg)" fi if [ -z "${_helper_path}" ]; then _helper_path="$(_discover_with_find)" fi if [ -z "${_helper_path}" ]; then echo "ERROR: Could not locate ${_HELPER_NAME} anywhere under /usr." 1>&2 return 1 fi # Safety: if a non-symlink file already exists at the target, back it up once. if [ -e "${_SYMLINK}" ] && [ ! -L "${_SYMLINK}" ]; then _msg "Backing up existing non-symlink at ${_SYMLINK} to ${_SYMLINK}.orig" mv -f "${_SYMLINK}" "${_SYMLINK}.orig" fi ln -sfn "${_helper_path}" "${_SYMLINK}" if [ -x "${_SYMLINK}" ]; then _msg "Symlink created: ${_SYMLINK} -> ${_helper_path}" return 0 else echo "ERROR: Failed to create working symlink ${_SYMLINK} -> ${_helper_path}" 1>&2 return 2 fi } ### ### Fix VM system detection ### _fix_virt_what() { _VIRT_TEST="$(which virt-what)" if [ -n "${_VIRT_TEST}" ] && [ -x "${_VIRT_TEST}" ]; then _SHELL_TEST_A=$(grep -I -o "\#\!.*/usr/bin/sh" ${_VIRT_TEST} 2>&1) _SHELL_TEST_B=$(grep -I -o "\#\!.*/bin/sh" ${_VIRT_TEST} 2>&1) if [[ "${_SHELL_TEST_A}" =~ "/usr/bin/sh" ]]; then sed -i "s/\/usr\/bin\/sh/\/bin\/dash/g" ${_VIRT_TEST} fi if [[ "${_SHELL_TEST_B}" =~ "/bin/sh" ]]; then sed -i "s/\/bin\/sh/\/bin\/dash/g" ${_VIRT_TEST} fi _HELPER_NAME="virt-what-cpuid-helper" _SYMLINK="/usr/sbin/${_HELPER_NAME}" _TRACE="/tmp/virtwhat.$$.strace" # Extra dirs we temporarily expose to PATH so virt-what can exec the helper for strace discovery _CANDIDATE_PATHS="/usr/libexec:/usr/lib/x86_64-linux-gnu:/usr/lib64/virt-what:/usr/lib/virt-what" if [ ! -e "${_SYMLINK}" ]; then echo "INFO: virt-what tool requires small update, fixing..." if ! command -v strace &> /dev/null; then _apt_clean_update apt-get install strace ${_aptYesUnth} -qq 2> /dev/null fi _ensure_virt_what_helper_symlink fi fi } ### ### Fix or install VM system detection ### _fix_or_install_virt_what() { _VIRT_TEST="$(which virt-what)" if [ -n "${_VIRT_TEST}" ] && [ -x "${_VIRT_TEST}" ]; then _fix_virt_what else echo "INFO: installing required virt-what tool ..." if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _apt_clean_update apt-get install virt-what ${_aptYesUnth} -qq 2> /dev/null wait _fix_virt_what fi } _check_virt() { _fix_or_install_virt_what _VIRT_TOOL="$(which virt-what)" if [ -x "${_VIRT_TOOL}" ]; then _VIRT_TEST=$(virt-what 2>&1) _VIRT_TEST=$(echo -n ${_VIRT_TEST} | fmt -su -w 2500 2>&1) if [[ "${_VIRT_TEST}" =~ "program not found" ]]; then echo "ERROR: virt-what says: ${_VIRT_TEST}" echo "ERROR: virt-what detection fails for unknown reason" fi if [ ! -e "/root/.allow.any.virt.cnf" ]; then if [ -e "/proc/self/status" ]; then _VS_GUEST_TEST=$(grep -E "VxID:[[:space:]]*[0-9]{2,}$" /proc/self/status 2> /dev/null) _VS_HOST_TEST=$(grep -E "VxID:[[:space:]]*0$" /proc/self/status 2> /dev/null) fi if [ ! -z "${_VS_HOST_TEST}" ] || [ ! -z "${_VS_GUEST_TEST}" ]; then if [ -z "${_VS_HOST_TEST}" ] && [ ! -z "${_VS_GUEST_TEST}" ]; then _VIRT_IS="Linux VServer guest" else if [ ! -z "${_VS_HOST_TEST}" ]; then _not_supported_virt "Linux VServer host" else _not_supported_virt "unknown / not a virtual machine" fi fi else if [ -z "${_VIRT_TEST}" ] || [ "${_VIRT_TEST}" = "0" ]; then _not_supported_virt "unknown / not a virtual machine" elif [[ "${_VIRT_TEST}" =~ "xen-dom0" ]]; then _not_supported_virt "Xen privileged domain" elif [[ "${_VIRT_TEST}" =~ "linux_vserver-host" ]]; then _not_supported_virt "Linux VServer host" else if [[ "${_VIRT_TEST}" =~ "xen xen-hvm" ]]; then _VIRT_TEST="xen-hvm" elif [[ "${_VIRT_TEST}" =~ "xen xen-domU" ]]; then _VIRT_TEST="xen-domU" elif [[ "${_VIRT_TEST}" =~ "virtualbox kvm" ]]; then _VIRT_TEST="virtualbox" elif [[ "${_VIRT_TEST}" =~ "hyperv qemu" ]]; then _VIRT_TEST="hyperv" elif [[ "${_VIRT_TEST}" =~ "kvm aws" ]]; then _VIRT_TEST="kvm" elif [[ "${_VIRT_TEST}" =~ "redhat kvm" ]]; then _VIRT_TEST="redhat-kvm" elif [[ "${_VIRT_TEST}" =~ "openvz lxc" ]]; then _VIRT_TEST="openvz" fi case "${_VIRT_TEST}" in hyperv) _VIRT_IS="Microsoft Hyper-V" ;; kvm) _VIRT_IS="Linux KVM guest" ;; lxc) _VIRT_IS="Linux Containers (LXC)" ;; openvz) _VIRT_IS="OpenVZ Containers" ;; parallels) _VIRT_IS="Parallels guest" ;; redhat-kvm) _VIRT_IS="Red Hat KVM guest" ;; virtualbox) _VIRT_IS="VirtualBox guest" ;; vmware) _VIRT_IS="VMware ESXi guest" ;; xen-domU) _VIRT_IS="Xen paravirtualized guest domain" ;; xen-hvm) _VIRT_IS="Xen guest fully virtualized (HVM)" ;; xen) _VIRT_IS="Xen guest" ;; *) _not_supported_virt "${_VIRT_TEST}" ;; esac fi fi else if [ -z "${_VIRT_TEST}" ] || [ "${_VIRT_TEST}" = "0" ]; then _VIRT_TEST="unknown / not a virtual machine" fi fi fi } ### ### Make local OpenSSL new/legacy ssl/certs symlinked to system ssl/certs ### _fix_sync_system_ssl_certs() { if [ -e "/etc/ssl/certs/ca-certificates.crt" ] \ && [ ! -e "/usr/local/ssl3/.old-certs" ] \ && [ -d "/usr/local/ssl3/certs" ] \ && [ ! -L "/usr/local/ssl3/certs" ]; then mv -f /usr/local/ssl3/certs /usr/local/ssl3/.old-certs ln -sfn /etc/ssl/certs /usr/local/ssl3/certs fi if [ -e "/etc/ssl/certs/ca-certificates.crt" ] \ && [ ! -e "/usr/local/ssl/.old-certs" ] \ && [ -d "/usr/local/ssl/certs" ] \ && [ ! -L "/usr/local/ssl/certs" ]; then mv -f /usr/local/ssl/certs /usr/local/ssl/.old-certs ln -sfn /etc/ssl/certs /usr/local/ssl/certs fi } # Fetch + serial-gate one updater script/tool (replaces the hand-rolled blocks # in _update_agents). Stamp: .ctrl..${_tRee}.${_xSrl}.pid. # Self-heals (re-fetches if dest/symlink missing), cleans old stamps so serials # are direction-free, preserves perms per caller. Per-block prereqs stay at the # call site. Usage: # [ ] && _fetch_versioned \ # [--mode 700] [--owner root:root] [--symlink P] [--guard PAT] \ # [--require-token TOK] # TOK is matched as a fixed string against the LAST 4 KiB of the payload (and # of the deployed copy), so it must be a token the genuine file carries near # its end -- that placement is what makes it a truncation check as well. # # Per-fetch silence is deliberate (see _update_boa_tools): a failed fetch keeps # the working copy, arms no marker and is retried next pass, so a mirror hiccup # can never latch a tool as skipped. What was missing is the AGGREGATE: a pass # where all 148 fetches failed looked exactly like a pass where everything was # already current -- no line, rc 0 -- and a fresh box or a tree switch then kept # the previous tree's launchers with nothing to say so (seen 2026-09-06, cause # unpinned for a day for want of this line). Count the mute discards and say it # once per pass, in the log an operator actually reads: cron sends this script's # stdout to /dev/null. # A HEAD of the mirror ROOT cannot see the failure that actually costs a pass: # a mirror that serves / but not (yet) the versioned tree this box fetches from # -- an rsync mid-pull, or a tree that host never pulled. Every fetch under # ${_urlHmr} then fails and, one by one, silently. Probe the exact tree by # CONTENT, because a missing path here answers 200 with an HTML stub, and fall # back to the publisher rather than spending the pass on a mirror that has # nothing to give. _mirror_serves_tree() { local _mir="$1" _probe _probe=$(curl ${_crlGet} --max-time 30 \ "https://${_mir}/versions/${_tRee}/boa/aegir/conf/version/barracuda-version.txt" 2> /dev/null) [[ "${_probe}" =~ ^BOA- ]] } _use_mirror_serving_tree() { local _ctx="$1" _mirror_serves_tree "${_USE_MIR}" && return 0 if [ "${_USE_MIR}" != "files.boa.io" ] && _mirror_serves_tree files.boa.io; then echo "OOPS: ${_USE_MIR} serves no ${_tRee} tree (${_ctx}); using files.boa.io" [ -d "${_pthLog}" ] \ && echo "$(date) OOPS: ${_USE_MIR} serves no ${_tRee} tree (${_ctx}); using files.boa.io" \ >> ${_pthLog}/mirror.incident.log _USE_MIR=files.boa.io _urlDev="https://${_USE_MIR}/dev" return 0 fi echo "OOPS: no mirror is serving the ${_tRee} tree right now (${_ctx})" [ -d "${_pthLog}" ] \ && echo "$(date) OOPS: no mirror is serving the ${_tRee} tree right now (${_ctx})" \ >> ${_pthLog}/mirror.incident.log return 1 } _fetch_failed_note() { _FETCH_FAIL_CNT=$(( ${_FETCH_FAIL_CNT:-0} + 1 )) _FETCH_FAIL_LAST="$1" _FETCH_FAIL_URL="$2" _FETCH_FAIL_COD="$3" } _fetch_failed_report() { local _ctx="$1" _msg [ "${_FETCH_FAIL_CNT:-0}" -gt 0 ] 2> /dev/null || return 0 _msg="OOPS: ${_FETCH_FAIL_CNT} fetch(es) failed in ${_ctx} from ${_USE_MIR}" _msg="${_msg} (last: ${_FETCH_FAIL_LAST} ${_FETCH_FAIL_COD}, ${_FETCH_FAIL_URL})" echo "${_msg}" if [ -d "${_pthLog}" ]; then echo "$(date) ${_msg}" >> ${_pthLog}/mirror.incident.log fi _FETCH_FAIL_CNT=0 } # A runner a migration tool parked as /var/xdrago/..off stays parked # while its park marker lives (xoct/xcopy pre-mig until post-mig, an xmass # cutover until its unpark; 24 h at most: clear.sh expires the marker). # Fetching into the empty name undid the park within one clear.sh tick, so # the target ran the queue and the upgrade create had armed in the middle of # the move. This test skips the fetch; _fetch_versioned checks the park again # right before it writes, for a park that lands while the fetch runs. _runner_parked() { [[ -e "/run/boa_migration_park.pid" ]] || return 1 [[ ! -e "/var/xdrago/${1}" ]] && [[ -e "/var/xdrago/.${1}.off" ]] } # A copy fetched by _fetch_versioned ($1) whose bytes a later step puts or # edits elsewhere. The step is due once the copy carries a marker of this # tree and release (none yet while the fetcher holds a serial move for a # mirror that has not caught up, or cannot reach the mirror), and either the # copy changed in this pass ($2, its md5 before the fetch) or the step's own # once-per-release stamp $3 is not there yet. The stamp alone was written # on whatever bytes a bare fetch got, and nothing fetched them again. _source_due() { [ -s "${1}" ] || return 1 ls "${_pthLog}/${1##*/}.ctrl."*".${_tRee}.${_xSrl}.pid" &> /dev/null || return 1 [ ! -e "${3}" ] || [ "$(md5sum "${1}" 2> /dev/null | cut -d' ' -f1)" != "${2}" ] } _fetch_versioned() { local _dst="$1" _url="$2" _srl="$3" shift 3 local _mode="700" _own="root:root" _lnk="" _grd="" _lbl="" _cnm="" _cnt _tok="" while [[ "$#" -gt 0 ]]; do case "$1" in --mode) _mode="$2"; shift 2 ;; --owner) _own="$2"; shift 2 ;; --symlink) _lnk="$2"; shift 2 ;; --guard) _grd="$2"; shift 2 ;; --require-token) _tok="$2"; shift 2 ;; *) echo "_fetch_versioned: unknown option '$1'" >&2; return 2 ;; esac done [[ -n "${_cnm}" ]] || _cnm="${_dst##*/}" [[ -n "${_lbl}" ]] || _lbl="${_grd##*/}" # default notice subject = basename of guard # shellcheck disable=SC2154 # _pthLog/_tRee/_xSrl are globals provided by BOA.sh.txt local _ctl="${_pthLog}/${_cnm}.ctrl.${_srl}.${_tRee}.${_xSrl}.pid" # When this box last compared the deployed bytes with the source's: the last # fetch that carried a body. A 304 re-arms the marker above but not this # stamp, so the daily unconditional request below counts from the last real # comparison. Counted from the marker, every hourly 304 moved it and the day # never ran out. local _cmp="${_pthLog}/${_cnm}.compared.pid" # Revalidation window (minutes). Even a serial-matched marker is re-checked # against the mirror once it is older than this, so a box that ever recorded a # marker against STALE bytes -- a publish race (the serial in BOA.sh.txt visible # before the file content propagated), CDN per-file TTL skew, or a partially # synced mirror -- self-heals instead of being pinned to stale content forever. # The serial alone cannot detect this: the marker says "f78 fetched" while the # bytes are pre-f78. Overridable. One hour, not a day: revalidation is a CONDITIONAL # request (see below), so an unchanged source costs a 304 with no body and a box that # ever mis-stamped heals within the hour instead of being visibly stale for a day. local _reMin="${_FETCH_REVALIDATE_MIN:-60}" # A mirror box already HOLDS the published tree this URL is served from, so it must # never fetch its own bytes back over the network: an intermediary can answer with # pre-publish content long after the local tree is current. Measured 2026-08-10: the # tree went current at 19:01:29 and a mirror still received the previous bytes at # 19:03:10, 101 seconds later. Cache purges are best-effort and cannot be relied on. # The webroot mirrors the URL path 1:1, so drop scheme+host and graft it on; # /var/www/static/getboa marks a box that carries the tree (every mirror has it, # publisher or passive). A URL that is not ours, or a tree this box does not hold, # simply has no local file and falls through to curl. local _lcl="" if [[ -e "/var/www/static/getboa" ]]; then _lcl="${_url#*://}" _lcl="/var/www/static/${_lcl#*/}" [[ -s "${_lcl}" ]] || _lcl="" fi # Up to date? marker present AND dest non-empty AND (symlink intact if # required) AND (tail token present if required -- a DEPLOYED copy that lost # its sentinel is damaged and gets refetched now, not at the next window) # AND the marker still within the revalidation window. "find -mmin +N" prints # the marker only when it is OLDER than N minutes, so an empty result == fresh. # AND, where a local source exists, the deployed bytes still MATCH it. The serial # alone cannot detect "stamped against content older than the serial implies": the # marker says f52 while the bytes are pre-f52, and nothing re-triggers a fetch until # the revalidation window expires -- a full day of a tool that visibly will not # update while the correct file sits on local disk (measured on a mirror, 2026-08-10). # Comparing against the local source is exact and costs one local read, so a # mis-stamped box self-heals on the very next pass instead of a day later. Boxes with # no local source keep the window as their only backstop. if [[ -e "${_ctl}" ]] && [[ -s "${_dst}" ]] \ && { [[ -z "${_lnk}" ]] || [[ -L "${_lnk}" ]]; } \ && { [[ -z "${_tok}" ]] || tail -c 4096 "${_dst}" 2>/dev/null | grep -qF -- "${_tok}"; } \ && { [[ -z "${_lcl}" ]] || cmp -s "${_lcl}" "${_dst}"; } \ && [[ -z "$(find "${_ctl}" -mmin "+${_reMin}" 2>/dev/null)" ]]; then return 0 fi # A move over a working copy refused within the revalidation window (see # the install below) is not tried again before the window ends: each try # fetched the whole file and wrote a report line on every tick for as long # as the name stayed pinned. Its link is still repaired. A serial change is # another stamp, so a publish is tried at once, and xmass's forced refresh # and the barracuda system stage remove the stamp. Not named like a marker, # so no marker test reads it. A copy that lost its sentinel is not held: # the up-to-date test above refetches such a copy at once, and so does this. local _mvf="${_pthLog}/${_cnm}.mvfail.${_srl}.${_tRee}.${_xSrl}.pid" if [[ -e "${_mvf}" ]] && [[ -s "${_dst}" ]] \ && { [[ -z "${_tok}" ]] || tail -c 4096 "${_dst}" 2>/dev/null | grep -qF -- "${_tok}"; } \ && [[ -z "$(find "${_mvf}" -mmin "+${_reMin}" 2>/dev/null)" ]]; then [[ -n "${_lnk}" ]] && [[ ! -L "${_lnk}" ]] && ln -sf "${_dst}" "${_lnk}" return 0 fi # Never clobber a running tool. Only applicable when the dest exists # (a missing dest cannot be running -- skipping then would leave a # dangling symlink behind). Matched against genuine EXECUTIONS only: # an interpreter with the tool path as its script argument, or the # path as argv[0]. A bare substring pgrep refused updates whenever any # command line merely mentioned the path -- an editor, a checksum, an # operator's ssh probe -- observed live when a validation command made # the guard hold a tool update back. # The interpreter class must include python alongside sh/bash/dash: # duplicity executes as its venv python with the console script as the # script argument (/bin/python -E /usr/local/bin/duplicity ...), # never as argv[0] and never under a shell, so an sh-only class left # the backup family's --guard duplicity permanently inert (2026-08-30). if [[ -n "${_grd}" ]] && [[ -s "${_dst}" ]]; then _grdEsc="${_grd//./\\.}" _cnt="$(pgrep -fc "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*${_grdEsc}( |$)" 2>/dev/null)" _cnt="${_cnt:-0}" _cntD="$(pgrep -fc "^[^ ]*${_grdEsc}( |$)" 2>/dev/null)" _cntD="${_cntD:-0}" if (( _cnt + _cntD > 0 )); then echo "The ${_lbl} is running!" return 0 fi fi # Fetch to a private temp; the live dest is only ever touched AFTER a non-empty # download, so a failed or short fetch can never blank a working copy. This one # path serves both first install (marker absent / serial change) and periodic # revalidation (marker past the window): if the mirror's current bytes differ # from what is deployed we replace and re-stamp; if they are identical we keep # the bytes, take the source's time where that is safe (below) and re-arm the # marker; either way the comparison is stamped. If the fetch failed we leave the # working copy and its marker alone. local _new="${_dst}.new.$$" _cod="" _znc="" _t0="" # A box with no local tree cannot compare content locally, so it asks the server # instead: -z sends If-Modified-Since from the DEPLOYED copy's mtime, so an unchanged # source answers 304 with no body. That makes revalidation nearly free, which is what # lets the window above be an hour rather than a day, and a 304 is a POSITIVE # confirmation that what we hold is current (handled below) rather than a failed # fetch. Safe against a stale copy suppressing its own update: published trees are # re-cloned, so source timestamps only ever move forward. A server that ignores the # header simply returns 200 and we behave exactly as before. # Offered ONLY when all of these hold, because each one is a case where a 304 would be # the wrong answer: # - the deployed copy is INTACT (an empty one, or one that lost its required # sentinel, must pull real bytes rather than be told "not modified"); # - a marker for THIS serial already exists, i.e. we are merely revalidating. When # the serial changed we are being told new content exists, so never risk a 304 # against it -- that is exactly how a box would re-arm the new marker over old # bytes; # - the last real comparison is younger than the hard window. At least once a day # we ask unconditionally, so a deployed copy carrying a wrong mtime (written # before -R, by another installer, or touched by hand) can never answer 304 # forever. Worst case degrades to the unconditional refetch this replaced -- it # can delay a heal, never prevent one. No comparison stamp yet counts as past # the window. if [[ -s "${_dst}" ]] && [[ -e "${_ctl}" ]] && [[ -e "${_cmp}" ]] \ && { [[ -z "${_tok}" ]] || tail -c 4096 "${_dst}" 2>/dev/null | grep -qF -- "${_tok}"; } \ && [[ -z "$(find "${_cmp}" -mmin "+${_FETCH_HARD_REVALIDATE_MIN:-1440}" 2>/dev/null)" ]]; then _znc="-z ${_dst}" fi # The local source resolved above wins: no network, and it is the very content this # box publishes. Falls through to curl when there is none. if [[ -n "${_lcl}" ]]; then cp -pf "${_lcl}" "${_new}" 2>/dev/null || rm -f "${_new}" else # the moment the fetch began: a Last-Modified is adopted only when earlier _t0="$(date +%s)" # shellcheck disable=SC2086,SC2154 # intentional _crlGet/_znc flag-string split; _crlGet is a BOA.sh.txt global # A non-zero curl exit can still leave a PARTIAL body on disk (the fleet curl # floor forbids --remove-on-error), and a partial file passes every simple # content test -- so discard the temp whenever curl itself reports failure. # -R is LOAD-BEARING, not cosmetic: it stamps the deployed copy with the SOURCE's # Last-Modified, so the -z above compares "is the source newer than the VERSION I # HOLD". Without it the comparison is against the time this box happened to WRITE # the file, and a box that ever wrote stale bytes later than the source was # published answers 304 to every probe and stays pinned forever -- strictly worse # than the unconditional refetch this replaced. Same reason cp -p above. _cod="$(curl ${_crlGet} ${_znc} -R -w '%{http_code}' "${_url}" -o "${_new}" 2>/dev/null)" \ || rm -f "${_new}" fi # A mirror answers a MISSING path with HTTP 200 + an HTML "Under Construction" # stub (a soft 404 that --fail cannot catch). No versioned BOA file is HTML -- # scripts start with #!, PHP with ), and was refused as a stub. local _noted=NO if [[ -s "${_new}" ]] && head -c 64 "${_new}" | grep -qiE '^[[:space:]]*<(html|!doctype[[:space:]]+html|head)'; then rm -f "${_new}" _fetch_failed_note "${_cnm}" "${_url}" "soft-404 (HTML stub)" _noted=YES fi # Optional content sentinel: a caller can require a token the genuine file # carries NEAR ITS END (the check reads only the payload's last 4 KiB, so # pick a token that lives there). A wrong payload fails it outright and a # payload truncated anywhere before the token fails it too, so neither can # replace the working copy -- critical when the dest is load-bearing beyond # itself, e.g. websh doubling as /bin/sh on hosted boxes. if [[ -s "${_new}" ]] && [[ -n "${_tok}" ]] \ && ! tail -c 4096 "${_new}" 2>/dev/null | grep -qF -- "${_tok}"; then echo "The ${_cnm} fetch was refused: content sentinel missing!" rm -f "${_new}" fi if [[ -s "${_new}" ]]; then if [[ -e "${_dst}" ]] && cmp -s "${_new}" "${_dst}"; then # Only a marker of a DIFFERENT fNN, with none for this one, promises new # content (see the hold below). local _fnnMove=NO _tmt if [[ ! -e "${_ctl}" ]] \ && ls "${_pthLog}/${_cnm}.ctrl."* 2>/dev/null | grep -qv "\.ctrl\.${_srl}\."; then _fnnMove=YES fi # Already current -- no disruption, but take the source's time: a # publish re-clones the tree and gives every file a new Last-Modified, # and a copy that kept its older time was sent the whole file again at # every hourly -z. Only a time -R really set, strictly before this # fetch began: with no Last-Modified the temp carries its own write # time, and adopting that (or a future one) would answer 304 to a # correct source. Not on an fNN change, where identical bytes may come # from a mirror that has not caught up; not from a local source. _tmt="$(stat -c %Y "${_new}" 2>/dev/null)" if [[ -z "${_lcl}" ]] && [[ "${_fnnMove}" = "NO" ]] \ && [[ "${_tmt}" =~ ^[0-9]+$ ]] && [[ "${_t0}" =~ ^[0-9]+$ ]] \ && [[ "${_tmt}" -lt "${_t0}" ]]; then touch -c -r "${_new}" "${_dst}" fi rm -f "${_new}" # A serial CHANGE promises new content, so identical bytes mean the # mirror answering us has not caught up with the publish yet (a passive # mirror pulls the tree once a minute; measured 2026-09-06: a box fetched # 66 s after the origin moved, received the previous bytes, and armed the # new serial over them -- pinned stale until the revalidation window). # Do not arm the marker on such a fetch; the next pass asks again. Bounded: # a serial bumped without a content change must not refetch forever, so # after an hour of identical answers the marker is armed anyway. A first # install (no marker of any serial yet) keeps arming: the tarball-deployed # copy legitimately equals the published one. # Only a marker of a DIFFERENT fNN promises new content. One that # differs in the tree or release serial alone (a release bump with the # tool unchanged) is the same bytes by definition and arms at once; # holding it re-downloaded every unchanged tool on every pass for an # hour after each release, unarmed and with its symlink unrepaired. if [[ "${_fnnMove}" = "YES" ]]; then local _stl="${_pthLog}/${_cnm}.stale.${_srl}.${_tRee}.${_xSrl}.pid" if [[ -z "$(find "${_stl}" -mmin +60 2>/dev/null)" ]]; then [[ -e "${_stl}" ]] || touch "${_stl}" [[ -n "${_lnk}" ]] && ln -sf "${_dst}" "${_lnk}" echo "The ${_cnm} ${_srl} fetch returned the bytes already deployed -- mirror not caught up, retrying next pass" return 0 fi fi else chmod "${_mode}" "${_new}" chown "${_own}" "${_new}" # The park is checked again at the write: a migration tool can park # this name after the caller's _runner_parked test, while the fetch # runs, and the move would then bring the runner back mid-migration. # Dropped unarmed, so the next pass asks again once the park is over. if [[ -e "/run/boa_migration_park.pid" ]] && [[ ! -e "${_dst}" ]] \ && [[ -e "${_dst%/*}/.${_dst##*/}.off" ]]; then rm -f "${_new}" return 0 fi # A move that fails (an immutable or append-only name, a file mounted # over it) leaves the deployed copy as it was: the download goes, no # marker is armed, the link to the working copy is still repaired, and # the pass's failure report counts it. The hold stamp (see the top) # keeps the next try to the revalidation window. Arming here recorded # the new serial over the old bytes until the hourly re-check, and # every try left its download beside the name. if ! mv -f "${_new}" "${_dst}"; then # first install or self-heal (atomic) rm -f "${_new}" [[ -n "${_lnk}" ]] && [[ -s "${_dst}" ]] && ln -sf "${_dst}" "${_lnk}" touch "${_mvf}" _fetch_failed_note "${_cnm}" "${_url}" "not installed (mv failed)" return 0 fi fi [[ -n "${_lnk}" ]] && ln -sf "${_dst}" "${_lnk}" rm -f "${_pthLog}/${_cnm}.ctrl."* "${_pthLog}/${_cnm}.stale."* "${_pthLog}/${_cnm}.mvfail."* touch "${_ctl}" # (re-)arm marker + revalidation window touch "${_cmp}" # the bytes were compared: restart the day elif [[ "${_cod}" == "304" ]]; then # The server confirmed the deployed copy is current, so this is success with an # empty body -- NOT a failed fetch. Re-arm the marker (otherwise every pass would # re-probe) and still repair the symlink, which a missing-link revalidation came # here to fix and which the body-carrying branch above would have handled. The # comparison stamp stays as it is: a 304 compared no bytes, only timestamps. rm -f "${_new}" [[ -n "${_lnk}" ]] && ln -sf "${_dst}" "${_lnk}" rm -f "${_pthLog}/${_cnm}.ctrl."* touch "${_ctl}" else rm -f "${_new}" # fetch failed -- keep the working copy # One note per failed fetch: a soft 404 already counted itself above, and # its empty temp then lands here too. [ "${_noted}" = "YES" ] || _fetch_failed_note "${_cnm}" "${_url}" "${_cod:-transport}" fi return 0 } # The cron directory of an account's hostmaster platform (run inside the # real directory): stale patch leftovers dropped, and hosting_cron.module, # when it is a regular file without the queue patch, or one this step put # from an earlier template (its md5 among $3: the puts of the completed # loops, of the loops begun and of the template on the box before this # pass's fetch, space separated), replaced by the patched template $1 # with the box IP $2. Any other module with the patch (the hosting # module's own) is kept. The module is read bounded and never through a # link, and the template is put as a fresh file with the template's # owner, group and mode, as cp -a left it, renamed over the name (the # account owns this directory and could put a link there). _hm_cron_queue_here() { local _st _uid _gid _mod _sum if [ -e ./hosting_cron.module.orig ]; then rm -f -- ./hosting_cron.module.orig fi if [ -e ./hosting_cron.module.rej ]; then rm -f -- ./hosting_cron.module.rej fi [ -e "${1}" ] || return 0 [ -f ./hosting_cron.module ] && [ ! -L ./hosting_cron.module ] || return 0 if [[ "$(_acct_read_here hosting_cron.module)" == *"core/cron.php"* ]]; then [ -n "${3}" ] || return 0 _sum=$(_acct_read_here hosting_cron.module | md5sum | cut -d' ' -f1) [[ "${_sum}" =~ ^[0-9a-f]{32}$ ]] || return 0 [[ " ${3} " == *" ${_sum} "* ]] || return 0 [ "${_sum}" != "$(sed "s/127.0.0.1/${2}/g" "${1}" | md5sum | cut -d' ' -f1)" ] \ || return 0 fi _st=$(stat -c '%u %g %a' -- "${1}" 2> /dev/null) || return 1 read -r _uid _gid _mod <<< "${_st}" sed "s/127.0.0.1/${2}/g" "${1}" \ | _acct_put_mode_here hosting_cron.module "${_uid}" "${_gid}" "${_mod}" } # True when the file $2 of any account directory below the account's # tools/le/accounts $1 holds the text $3: each read bounded, never through a # link or from a FIFO, inside its pinned real directory. _le_acct_has() { local _d for _d in "${1}"/*; do [ -d "${_d}" ] && [ ! -L "${_d}" ] || continue _acct_in_real_dir "${_d}" _acct_read_here "${2}" \ | grep -q -- "${3}" && return 0 done return 1 } # The register markers in the account's tools/le/.ctrl (run inside the real # directory) removed. _le_ctrl_clear_here() { rm -f -- ./.forced* ./.normal* ./forced* ./normal* } # aegir2boa's own root state for the account $1 says its db-import, or the # revert of one, is unfinished: the dispatcher it held aside is still there, # a hold xmass carried here with a failover from the box the import ran on # stands (failover-hold) on a box that is no standby, or on any box with $2 # promoted (a standby's own passes never act on it; a promotion's own steps, # which run before its marker goes, do), a revert marked itself started # (revert-db.started, cleared only once the revert's dispatcher is back), or # a step marker of the import stands with neither its done marker nor the # marker of its hand-back (written right before the import returns the # dispatcher). # Root's /var/backups/a2b only: a link on the way reads as no state, and so # does a state recorded for another account of the name (its uid, or its # home's inode or birth time, not the account's now; a birth time either # side does not know is not compared). The record's first line is read # whether or not a newline ends it; an empty record, or none, is the # account's own. One text wherever it is defined. _a2b_import_unfinished() { local _b="/var/backups/a2b/${1}" _m _iu="" _ii _ib _n case "${1}" in ""|*[!a-z0-9-]*) return 1 ;; esac [ -d "${_b}" ] && [ ! -L "${_b}" ] && [ ! -L /var/backups/a2b ] || return 1 if [ -f "${_b}/identity" ] && [ ! -L "${_b}/identity" ]; then read -r _iu _ii _ib _ < "${_b}/identity" fi if [ -n "${_iu}" ]; then _n=$(stat -c '%i %W' "/data/disk/${1}" 2> /dev/null) [ "${_iu}" = "$(id -u "${1}" 2> /dev/null)" ] && [ "${_ii}" = "${_n% *}" ] || return 1 [ "${_ib}" = "0" ] || [ "${_n#* }" = "0" ] || [ "${_ib}" = "${_n#* }" ] || return 1 fi [ -e "${_b}/held-run-${1}" ] && return 0 [ -e "${_b}/failover-hold" ] && { [ "${2:-}" = "promoted" ] \ || { [ ! -e /root/.standby.cnf ] && [ ! -e /root/.standby.prep.cnf ]; }; } \ && return 0 [ -d "${_b}/markers" ] && [ ! -L "${_b}/markers" ] || return 1 [ -e "${_b}/markers/revert-db.started" ] && return 0 [ -e "${_b}/markers/import.db-import.done" ] && return 1 [ -e "${_b}/markers/dbimport.12.done" ] && return 1 for _m in "${_b}"/markers/dbimport.*.done; do [ -e "${_m}" ] && return 0 done return 1 } # A root process runs aegir2boa-stage2 for the account $2: the tool's name # in any word of its argv (bash -x and sh -c count) and --account with the # next argv element, read whole and cut down as stage 2 cuts down its own # --account, naming $2, read from the proc root $1. Only a process whose # real and effective uid are root's is read (a tenant sets its own argv), # 64 KiB of each file at most. A /proc that cannot be read counts as one. _A2B_LIVE_PL='my ($p, $acct) = @ARGV; opendir(my $d, $p) or exit 0; for my $n (readdir($d)) { next unless $n =~ /^[0-9]+$/ && $n != $$; my $s = ""; if (open(my $sh, "<", "$p/$n/status")) { sysread($sh, $s, 65536); close($sh); } next unless $s =~ /^Uid:\s+0\s+0\s/m; open(my $h, "<", "$p/$n/cmdline") or next; my $c = ""; sysread($h, $c, 65536); close($h); my @w = grep { length } split(/[\0\s]+/, $c); next unless grep { (my $x = $_) =~ s{.*/}{}; $x eq "aegir2boa-stage2" } @w; my @e = split(/\0/, $c); for my $i (0 .. $#e - 1) { next unless $e[$i] eq "--account"; (my $z = $e[$i + 1]) =~ tr/a-z0-9//cd; exit 0 if $z eq $acct; } } exit 1' _a2b_import_live() { perl -e "${_A2B_LIVE_PL}" /proc "${1}" 2> /dev/null } _update_agents() { _if_hosted_sys if [ "${_hostedSys}" = "YES" ]; then if [ ! -e "/root/.extended.firewall.exceptions.cnf" ]; then echo host8 > /root/.extended.firewall.exceptions.cnf fi fi if [ "${_VMFAMILY}" = "HOSTED" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -d "/data/u" ] \ && [ -e "/var/xdrago" ]; then [ ! -e "/root/.fast.cron.cnf" ] && echo ON > /root/.fast.cron.cnf _PrTestPower=$(grep "POWER" /root/.*.octopus.cnf 2>&1) _PrTestPhantom=$(grep "PHANTOM" /root/.*.octopus.cnf 2>&1) _PrTestCluster=$(grep "CLUSTER" /root/.*.octopus.cnf 2>&1) _PrTestUltra=$(grep "ULTRA" /root/.*.octopus.cnf 2>&1) _PrTestMonster=$(grep "MONSTER" /root/.*.octopus.cnf 2>&1) _InTest=$(ls /data/disk/*/static/control/cli.info | wc -l 2>&1) # (Removed dead `_SQL_PSWD=$(cat /root/.my.pass.txt)` read — never # referenced. The mysql calls below this block use /root/.my.cnf # implicitly via `mysql -u root` with no -p.) if [ "${_InTest}" -lt 9 ] \ && [[ ! "${_PrTestPower}" =~ "POWER" ]] \ && [[ ! "${_PrTestPhantom}" =~ "PHANTOM" ]] \ && [[ ! "${_PrTestCluster}" =~ "CLUSTER" ]] \ && [[ ! "${_PrTestUltra}" =~ "ULTRA" ]] \ && [[ ! "${_PrTestMonster}" =~ "MONSTER" ]]; then [ ! -e "/root/.fast.cron.cnf" ] && echo ${_InTest} > /root/.fast.cron.cnf [ -e "/root/.slow.cron.cnf" ] && [ ! -e "/root/.slow.cron.cnf.protected" ] && rm -f /root/.slow.cron.cnf [ -e "/root/.tg.cnf" ] && rm -f /root/.tg.cnf mysql -u root -e "SET GLOBAL max_connect_errors = 555;" mysql -u root -e "SET GLOBAL max_connections = 111;" mysql -u root -e "SET GLOBAL max_user_connections = 111;" mysql -u root -e "SET GLOBAL group_concat_max_len = 10000;" fi if [ "${_InTest}" -ge 9 ] && [ "${_InTest}" -le 50 ]; then [ ! -e "/root/.fast.cron.cnf" ] && echo ${_InTest} > /root/.fast.cron.cnf [ -e "/root/.slow.cron.cnf" ] && [ ! -e "/root/.slow.cron.cnf.protected" ] && rm -f /root/.slow.cron.cnf [ -e "/root/.tg.cnf" ] && rm -f /root/.tg.cnf mysql -u root -e "SET GLOBAL max_connect_errors = 777;" mysql -u root -e "SET GLOBAL max_connections = 555;" mysql -u root -e "SET GLOBAL max_user_connections = 111;" mysql -u root -e "SET GLOBAL group_concat_max_len = 10000;" fi if [ "${_InTest}" -gt 50 ]; then [ -e "/root/.fast.cron.cnf" ] && rm -f /root/.fast.cron.cnf [ ! -e "/root/.tg.cnf" ] && echo ${_InTest} > /root/.tg.cnf [ ! -e "/root/.slow.cron.cnf" ] && echo ${_InTest} > /root/.slow.cron.cnf mysql -u root -e "SET GLOBAL max_connect_errors = 999;" mysql -u root -e "SET GLOBAL max_connections = 777;" mysql -u root -e "SET GLOBAL max_user_connections = 111;" mysql -u root -e "SET GLOBAL group_concat_max_len = 10000;" fi if [[ "${_PrTestPower}" =~ "POWER" ]]; then [ ! -e "/root/.tg.cnf" ] && echo ${_InTest} > /root/.tg.cnf [ ! -e "/root/.fast.cron.cnf" ] && echo ${_InTest} > /root/.fast.cron.cnf [ -e "/root/.slow.cron.cnf" ] && [ ! -e "/root/.slow.cron.cnf.protected" ] && rm -f /root/.slow.cron.cnf mysql -u root -e "SET GLOBAL max_connect_errors = 555;" mysql -u root -e "SET GLOBAL max_connections = 333;" mysql -u root -e "SET GLOBAL max_user_connections = 111;" mysql -u root -e "SET GLOBAL group_concat_max_len = 10000;" fi if [[ "${_PrTestPhantom}" =~ "PHANTOM" ]]; then [ ! -e "/root/.tg.cnf" ] && echo ${_InTest} > /root/.tg.cnf [ ! -e "/root/.fast.cron.cnf" ] && echo ${_InTest} > /root/.fast.cron.cnf [ -e "/root/.slow.cron.cnf" ] && [ ! -e "/root/.slow.cron.cnf.protected" ] && rm -f /root/.slow.cron.cnf mysql -u root -e "SET GLOBAL max_connect_errors = 777;" mysql -u root -e "SET GLOBAL max_connections = 555;" mysql -u root -e "SET GLOBAL max_user_connections = 333;" mysql -u root -e "SET GLOBAL group_concat_max_len = 10000;" fi if [[ "${_PrTestCluster}" =~ "CLUSTER" ]]; then [ ! -e "/root/.tg.cnf" ] && echo ${_InTest} > /root/.tg.cnf [ ! -e "/root/.fast.cron.cnf" ] && echo ${_InTest} > /root/.fast.cron.cnf [ -e "/root/.slow.cron.cnf" ] && [ ! -e "/root/.slow.cron.cnf.protected" ] && rm -f /root/.slow.cron.cnf mysql -u root -e "SET GLOBAL max_connect_errors = 999;" mysql -u root -e "SET GLOBAL max_connections = 777;" mysql -u root -e "SET GLOBAL max_user_connections = 555;" mysql -u root -e "SET GLOBAL group_concat_max_len = 10000;" fi mysql -u root -e "SET GLOBAL optimizer_switch='derived_merge=off';" mysql -u root -e "SET GLOBAL sort_buffer_size = 262144;" if [ -e "/root/.tg.cnf" ]; then if [ ! -e "/root/.fixed_fpm_workers.cnf" ]; then sed -i "s/^_PHP_FPM_WORKERS=.*/_PHP_FPM_WORKERS=100/g" ${_barCnf} touch /root/.fixed_fpm_workers.cnf fi fi if [ ! -e "/root/.high_traffic.cnf" ]; then echo ${_InTest} > /root/.high_traffic.cnf echo ${_InTest} > /root/.no.swap.clear.cnf fi [ -e "/root/.randomize_duplicity_full_backup_day.cnf" ] && rm -f /root/.randomize_duplicity_full_backup_day.cnf [ -e "/root/.skip_duplicity_monthly_cleanup.cnf" ] && rm -f /root/.skip_duplicity_monthly_cleanup.cnf [ -e "/root/.my.batch_innodb.cnf" ] && rm -f /root/.my.batch_innodb.cnf [ -e "/root/.batch_innodb.cnf" ] && rm -f /root/.batch_innodb.cnf [ -e "/root/.force.drupalgeddon.cnf" ] && rm -f /root/.force.drupalgeddon.cnf ### Removed only from inside each account's pinned real control ### directory: the account can point static or control elsewhere. for _dgdUsr in /data/disk/*; do [ -e "${_dgdUsr}/static/control/drupalgeddon.info" ] \ || [ -L "${_dgdUsr}/static/control/drupalgeddon.info" ] || continue _acct_in_real_dir "${_dgdUsr}/static/control" \ rm -f -- ./drupalgeddon.info done [ -e "/root/.skip_cleanup.cnf" ] && rm -f /root/.skip_cleanup.cnf [ -e "/root/.giant_traffic.cnf" ] && rm -f /root/.giant_traffic.cnf [ -e "/root/.default.cnf" ] && rm -f /root/.default.cnf [ -e "/root/.debug.cnf" ] && rm -f /root/.debug.cnf if [ -e "/data/conf/override.global.inc" ] \ && [ ! -e "/data/conf/.prev6.override.global.inc.off" ]; then mv -f /data/conf/override.global.inc /data/conf/.prev6.override.global.inc.off fi # if [ ! -e "/data/conf/override.global.inc" ]; then # echo " /data/conf/override.global.inc.tmp # echo "" >> /data/conf/override.global.inc.tmp # echo "\$use_valkey = TRUE;" >> /data/conf/override.global.inc.tmp # chmod 644 /data/conf/override.global.inc.tmp # mv -f /data/conf/override.global.inc.tmp /data/conf/override.global.inc # fi fi if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _pthCtrl="/root/.remote_backups/ctrl" # Own-fleet domains keep PRO backups on any tree: the backup tools # (dcysetup/multiback/mybackup/backboa) hardcode the pro tree plus this # same domain pass, so a tree-only gate here wiped /root/.remote_backups # on own lts boxes and dcysetup could never bootstrap there. if [ "${_tRee}" = "pro" ] || [ "${_tRee}" = "dev" ] \ || [[ "${_hName}" =~ ".aegir.cc"($) ]] \ || [[ "${_hName}" =~ ".o8.io"($) ]] \ || [[ "${_hName}" =~ ".boa.io"($) ]]; then [ ! -e "${_pthCtrl}" ] && mkdir -p ${_pthCtrl} [ ! -e "/root/.remote_backups/run" ] && mkdir -p /root/.remote_backups/run else rm -rf /root/.remote_backups fi [ ! -e "/var/xdrago/monitor/check" ] && mkdir -p /var/xdrago/monitor/check [ ! -e "/var/xdrago/monitor/log" ] && mkdir -p /var/xdrago/monitor/log [ ! -e "/var/xdrago/night" ] && mkdir -p /var/xdrago/night # Once per release: sweep stamps left by PREVIOUS releases and legacy # blocks. Current-release stamps are excluded -- _update_boa_tools runs # earlier in this same pass and has already stamped its whole set; wiping # those too would force one full redundant refetch round per release. # (Functionally the sweep is hygiene only: every stamp name embeds # ${_xSrl}, so a release re-fetches everything regardless.) # A name _fetch_versioned compares (its .compared.pid is there) keeps its # marker: the fetcher clears it itself, and reads it to tell a serial # moved with the release, which the mirror may not serve yet, from a # release that left the file as it was. Swept, the fetches below armed # the new serial on the previous bytes. Dot-named stamps are swept too. if [ ! -e "${_pthLog}/.force.f89.${_tRee}.${_xSrl}.ctrl" ]; then for _ctlOld in "${_pthLog}"/*.ctrl.*.pid "${_pthLog}"/.*.ctrl.*.pid; do [ -e "${_ctlOld}" ] || continue case "${_ctlOld}" in *".${_tRee}.${_xSrl}.pid") continue ;; # the ghost-cleanup one-shot's old stamps: a launcher from before # its move, served by a mirror that has not pulled yet, reads them */ghost-family-yes.ctrl.*) continue ;; esac [ -e "${_ctlOld%%.ctrl.*}.compared.pid" ] && continue rm -f "${_ctlOld}" done touch ${_pthLog}/.force.f89.${_tRee}.${_xSrl}.ctrl fi [ ! -e "/var/xdrago/ai_policy.sh" ] && rm -f ${_pthLog}/ai_policy.sh.ctrl.*.pid [ ! -e "/var/xdrago/checksql.sh" ] && rm -f ${_pthLog}/checksql.sh.ctrl.*.pid [ ! -e "/var/xdrago/clear.sh" ] && rm -f ${_pthLog}/clear.sh.ctrl.*.pid [ ! -e "/var/xdrago/cloudflare_realip.sh" ] && rm -f ${_pthLog}/cloudflare_realip.sh.ctrl.*.pid [ ! -e "/var/xdrago/owl.sh" ] && rm -f ${_pthLog}/owl.sh.ctrl.*.pid [ ! -e "/var/xdrago/graceful.sh" ] && rm -f ${_pthLog}/graceful.sh.ctrl.*.pid [ ! -e "/var/xdrago/guest-fire.sh" ] && rm -f ${_pthLog}/guest-fire.sh.ctrl.*.pid [ ! -e "/var/xdrago/guest-water.sh" ] && rm -f ${_pthLog}/guest-water.sh.ctrl.*.pid [ ! -e "/var/xdrago/ip_access.sh" ] && rm -f ${_pthLog}/ip_access.sh.ctrl.*.pid [ ! -e "/var/xdrago/manage_ltd_users.sh" ] && rm -f ${_pthLog}/manage_ltd_users.sh.ctrl.*.pid [ ! -e "/var/xdrago/manage_solr_config.sh" ] && rm -f ${_pthLog}/manage_solr_config.sh.ctrl.*.pid [ ! -e "/var/xdrago/minute.sh" ] && rm -f ${_pthLog}/minute.sh.ctrl.*.pid [ ! -e "/var/xdrago/move_sql.sh" ] && rm -f ${_pthLog}/move_sql.sh.ctrl.*.pid [ ! -e "/var/xdrago/mysql_backup.sh" ] && rm -f ${_pthLog}/mysql_backup.sh.ctrl.*.pid [ ! -e "/var/xdrago/mysql_cleanup.sh" ] && rm -f ${_pthLog}/mysql_cleanup.sh.ctrl.*.pid [ ! -e "/var/xdrago/mysql_cluster_backup.sh" ] && rm -f ${_pthLog}/mysql_cluster_backup.sh.ctrl.*.pid [ ! -e "/var/xdrago/mysql_repair.sh" ] && rm -f ${_pthLog}/mysql_repair.sh.ctrl.*.pid [ ! -e "/var/xdrago/nginx_deny.sh" ] && rm -f ${_pthLog}/nginx_deny.sh.ctrl.*.pid [ ! -e "/var/xdrago/nginx_deny6.sh" ] && rm -f ${_pthLog}/nginx_deny6.sh.ctrl.*.pid [ ! -e "/var/xdrago/nginx_fleet.sh" ] && rm -f ${_pthLog}/nginx_fleet.sh.ctrl.*.pid [ ! -e "/var/xdrago/night/night.inc.sh" ] && rm -f ${_pthLog}/night.inc.sh.ctrl.*.pid [ ! -e "/var/xdrago/night/20-sites.sh" ] && rm -f ${_pthLog}/20-sites.sh.ctrl.*.pid [ ! -e "/var/xdrago/night/10-account.sh" ] && rm -f ${_pthLog}/10-account.sh.ctrl.*.pid [ ! -e "/var/xdrago/night/90-global-post.sh" ] && rm -f ${_pthLog}/90-global-post.sh.ctrl.*.pid [ ! -e "/var/xdrago/proc_num_ctrl.pl" ] && rm -f ${_pthLog}/proc_num_ctrl.pl.ctrl.*.pid [ ! -e "/var/xdrago/purge_binlogs.sh" ] && rm -f ${_pthLog}/purge_binlogs.sh.ctrl.*.pid [ ! -e "/var/xdrago/runner.sh" ] && rm -f ${_pthLog}/runner.sh.ctrl.*.pid [ ! -e "/var/xdrago/second.sh" ] && rm -f ${_pthLog}/second.sh.ctrl.*.pid [ ! -e "/var/xdrago/usage.sh" ] && rm -f ${_pthLog}/usage.sh.ctrl.*.pid [ ! -e "/var/xdrago/user_admin_access.sh" ] && rm -f ${_pthLog}/user_admin_access.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/java.sh" ] && rm -f ${_pthLog}/java.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/mysql.sh" ] && rm -f ${_pthLog}/mysql.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/nginx.sh" ] && rm -f ${_pthLog}/nginx.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/php.sh" ] && rm -f ${_pthLog}/php.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/valkey.sh" ] && rm -f ${_pthLog}/valkey.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/redis.sh" ] && rm -f ${_pthLog}/redis.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/scan_nginx.sh" ] && rm -f ${_pthLog}/scan_nginx.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/fpm_tune.sh" ] && rm -f ${_pthLog}/fpm_tune.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/sqlprobe.sh" ] && rm -f ${_pthLog}/sqlprobe.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/sqlsnap.sh" ] && rm -f ${_pthLog}/sqlsnap.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/xdr9000.sh" ] && rm -f ${_pthLog}/xdr9000.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/batch_guard.sh" ] && rm -f ${_pthLog}/batch_guard.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/task_guard.sh" ] && rm -f ${_pthLog}/task_guard.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/cron_guard.sh" ] && rm -f ${_pthLog}/cron_guard.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/autosync.sh" ] && rm -f ${_pthLog}/autosync.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/system.sh" ] && rm -f ${_pthLog}/system.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/unbound.sh" ] && rm -f ${_pthLog}/unbound.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/escapecheck.sh" ] && rm -f ${_pthLog}/escapecheck.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/hackcheck.sh" ] && rm -f ${_pthLog}/hackcheck.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/hackftp.sh" ] && rm -f ${_pthLog}/hackftp.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/segfault_alert.pl" ] && rm -f ${_pthLog}/segfault_alert.pl.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/sqlcheck.sh" ] && rm -f ${_pthLog}/sqlcheck.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/bind9.sh" ] && rm -f ${_pthLog}/bind9.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/proxysql.sh" ] && rm -f ${_pthLog}/proxysql.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/droplet.sh" ] && rm -f ${_pthLog}/droplet.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/newrelic_daemon.sh" ] && rm -f ${_pthLog}/newrelic_daemon.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/newrelic_sysmond.sh" ] && rm -f ${_pthLog}/newrelic_sysmond.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/collectd.sh" ] && rm -f ${_pthLog}/collectd.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/xinetd.sh" ] && rm -f ${_pthLog}/xinetd.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/lsyncd.sh" ] && rm -f ${_pthLog}/lsyncd.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/syslog_legacy.sh" ] && rm -f ${_pthLog}/syslog_legacy.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/sendmail_guard.sh" ] && rm -f ${_pthLog}/sendmail_guard.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/convert_guard.sh" ] && rm -f ${_pthLog}/convert_guard.sh.ctrl.*.pid [ ! -e "/var/xdrago/monitor/check/hostname_sync.sh" ] && rm -f ${_pthLog}/hostname_sync.sh.ctrl.*.pid [ -e "/var/xdrago/proc_num_ctrl.cgi" ] && rm -f /var/xdrago/proc_num_ctrl.cgi [ -e "/var/xdrago/checksql.cgi" ] && rm -f /var/xdrago/checksql.cgi [ -e "/var/xdrago/mysql_hourly.sh" ] && rm -f /var/xdrago/mysql_hourly.sh [ -e "/var/xdrago/proc_num_ctrl.pl" ] && rm -f /var/xdrago/proc_num_ctrl.pl [ -e "/var/xdrago/checksql.pl" ] && rm -f /var/xdrago/checksql.pl [ -e "/var/xdrago/monitor/check/escapecheck.pl" ] && rm -f /var/xdrago/monitor/check/escapecheck.pl [ -e "/var/xdrago/monitor/check/hackcheck.pl" ] && rm -f /var/xdrago/monitor/check/hackcheck.pl [ -e "/var/xdrago/monitor/check/hackftp.pl" ] && rm -f /var/xdrago/monitor/check/hackftp.pl [ -e "/var/xdrago/monitor/check/sqlcheck.pl" ] && rm -f /var/xdrago/monitor/check/sqlcheck.pl [ -e "/var/xdrago/monitor/check/scan_nginx.pl" ] && rm -f /var/xdrago/monitor/check/scan_nginx.pl [ -e "/var/xdrago/monitor/check/locked_nginx.pl" ] && rm -f /var/xdrago/monitor/check/locked_nginx.pl [ -e "/var/xdrago/monitor/check/locked_java.pl" ] && rm -f /var/xdrago/monitor/check/locked_java.pl [ -e "/var/xdrago/monitor/check/syslog_legacy.sh" ] && rm -f /var/xdrago/monitor/check/syslog_legacy.sh # One-time sweep of stale *.old fetch-rollback backups left behind by the # fetch helpers on successful updates (now dropped at the source). Script # backups only; conf/*.old keep-references are deliberately not touched. rm -f /var/xdrago/*.sh.old /var/xdrago/*.pl.old rm -f /var/xdrago/monitor/check/*.sh.old /var/xdrago/monitor/check/*.pl.old [ -e "${_optBin}/vmnetfix.old" ] && rm -f ${_optBin}/vmnetfix.old [ -e "/var/xdrago/monitor/check/sqlcheck" ] && rm -f ${_pthLog}/*.ctrl.*.pid [ -e "/var/xdrago/monitor/check/sqlcheck" ] && rm -f /var/xdrago/monitor/check/* [ -e "/var/xdrago/monitor/hackcheck.archive.log" ] && rm -f /var/xdrago/monitor/.scan_nginx_arch* [ -e "/var/xdrago/monitor/hackcheck.archive.log" ] && mv -f /var/xdrago/monitor/*.log /var/xdrago/monitor/log/ fi if [ -e "/root/.remote_backups/schedule/backup_schedule.txt" ] \ && [ -d "/var/aegir/drush" ]; then if grep -q "Out of memory: Killed process.*duplicity" /var/log/iptables.log; then if [ ! -e "/root/.remote_backups/schedule/backup_schedule.txt-off" ]; then cp -a /root/.remote_backups/schedule/backup_schedule.txt /root/.remote_backups/schedule/backup_schedule.txt-off echo "# Backup schedule (service user) OFF" > /root/.remote_backups/schedule/backup_schedule.txt chattr +i /root/.remote_backups/schedule/backup_schedule.txt fi else if [ -e "/root/.remote_backups/schedule/backup_schedule.txt-off" ]; then chattr -i /root/.remote_backups/schedule/backup_schedule.txt rm -f /root/.remote_backups/schedule/backup_schedule.txt mv /root/.remote_backups/schedule/backup_schedule.txt-off /root/.remote_backups/schedule/backup_schedule.txt fi fi # Genuine executions only, the same anchored pair as this function's # fetch guard: the bare substrings here SIGKILLed any command line # that merely mentioned duplicity (an editor, a tail on this very # log), and any mention of a wrapper name anywhere spared the reap. # The sparing list also covers the daily backboa/duobackboa pair, # whose legitimately parented in-flight duplicity a pass otherwise # reaped mid-backup whenever no multiback happened to be running. # A wrapper that died leaves its duplicity a genuine orphan (none of # the five counts as alive), so orphan recovery is unchanged. _dcyLive="$(pgrep -fc "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*duplicity( |$)" 2>/dev/null)" _dcyLiveD="$(pgrep -fc "^[^ ]*duplicity( |$)" 2>/dev/null)" _dcyLive=$(( ${_dcyLive:-0} + ${_dcyLiveD:-0} )) _bupLive=0 for _bupTool in dcysetup mybackup multiback backboa duobackboa; do _bupCnt="$(pgrep -fc "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*${_bupTool}( |$)" 2>/dev/null)" _bupLive=$(( _bupLive + ${_bupCnt:-0} )) _bupCnt="$(pgrep -fc "^[^ ]*${_bupTool}( |$)" 2>/dev/null)" _bupLive=$(( _bupLive + ${_bupCnt:-0} )) done if [ "${_dcyLive}" -gt 0 ] && [ "${_bupLive}" -lt 1 ]; then pkill -9 -f "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*duplicity( |$)" pkill -9 -f "^[^ ]*duplicity( |$)" rm -rf /tmp/duplicity* rm -rf /root/.cache/duplicity/*/duplicity-*tempdir rm -f /root/.cache/duplicity/*/lockfile echo "$(date) Orphaned duplicity processes killed" >> /var/log/duplicity-cleanup.log fi fi # The core tool set every box carries (barracuda, boa, octopus, x*, auto*, # reports, backup tools, synproxy family...) is fetched ONLY by # _update_boa_tools, which runs on every pass in every context this function # runs in (and more: pre-install, _SKYNET_MODE=OFF). This function fetches # only what an installed Skynet-active box additionally needs: agents, # monitors, system scripts, and the few tools scoped to such boxes below. # Boot-path file: only ever installed when absent (a distro-provided or # vmnetfix-written init script is never clobbered), and only through the # validating helper -- a soft-404 stub or short body is refused, the dest # stays absent, and the next pass simply retries instead of latching a # poisoned boot script behind the [ ! -e ] gate forever. if [ ! -e "/etc/init.d/networking" ]; then mkdir -p /etc/init.d _fetch_versioned /etc/init.d/networking "${_urlHmr}/conf/network/networking" f01 \ --mode 755 --require-token force-reload if [ -s "/etc/init.d/networking" ]; then update-rc.d networking defaults >/dev/null 2>&1 || true fi fi _fetch_versioned ${_optBin}/edgetest "${_urlHmr}/tools/bin/edgetest" f85 --symlink ${_usrBin}/edgetest --guard /local/bin/edgetest _fetch_versioned ${_optBin}/clearwebbans "${_urlHmr}/tools/bin/clearwebbans" f94 --symlink ${_usrBin}/clearwebbans --guard /local/bin/clearwebbans _fetch_versioned ${_optBin}/defaultkeys "${_urlHmr}/tools/bin/defaultkeys" f99 --symlink ${_usrBin}/defaultkeys --guard /local/bin/defaultkeys # PHP-selecting bee launcher (identity verb policy). Deployed unconditionally # but inert until the gated _get_bee installs /opt/tools/bee; canonical path # /opt/local/bin/bee (no /usr/bin symlink, websh's PATH resolves it); # clients execute it, so it ships world-executable. _fetch_versioned ${_optBin}/bee "${_urlHmr}/tools/bin/bee" f90 --mode 755 --guard /local/bin/bee # PHP-selecting Grav CLI wrapper (identity verb policy + the enforced # 8.3 floor). Deployed unconditionally but inert # without a Grav install in reach (it resolves the target from CWD and # refuses vendor-less trees); the gated surfaces are the platforms and the # release staging, not this launcher. Canonical path /opt/local/bin/grav # (no /usr/bin symlink; websh's PATH resolves it). _fetch_versioned ${_optBin}/grav "${_urlHmr}/tools/bin/grav" f91 --mode 755 --guard /local/bin/grav # Mirror-source build boxes only, marked by /root/.stackbuild.cnf (even an # empty file opts a box in): staticbuild (distribution catalogue, core and # Backdrop-family builder) and stackbuild (heavy stack-component packager). # Both need root and fail closed elsewhere - staticbuild also requires an # existing build user (o8 unless -u says otherwise) and never guesses one # from /data/disk. if [ -e "/root/.stackbuild.cnf" ]; then _fetch_versioned ${_optBin}/staticbuild "${_urlHmr}/tools/bin/staticbuild" f36 --mode 755 --symlink ${_usrBin}/staticbuild --guard /local/bin/staticbuild _fetch_versioned ${_optBin}/stackbuild "${_urlHmr}/tools/bin/stackbuild" f90 --mode 755 --symlink ${_usrBin}/stackbuild --guard /local/bin/stackbuild else # A box that is not a build box carries no copy of either. Boxes that had # fetched staticbuild before this gate existed kept an unmanaged copy the # channel never touched again: permanently stale to every audit, a builder # nobody maintains on a box that must not build. Markers go with the files # so a later opt-in starts clean; a box that opts back in re-fetches both # on its next pass. rm -f ${_optBin}/staticbuild ${_usrBin}/staticbuild ${_optBin}/stackbuild ${_usrBin}/stackbuild \ ${_pthLog}/staticbuild.ctrl.* ${_pthLog}/stackbuild.ctrl.* \ ${_pthLog}/staticbuild.stale.* ${_pthLog}/stackbuild.stale.* &> /dev/null fi # The mirror publisher, getboa, only where its Cloudflare purge config # /root/.cf-purge.cnf exists (the test getboa itself makes): the mirror # master, which publishes boa.tar.gz and answers the builders' purge # hooks. Root-only like the token it reads, at the path those hooks # name; no /usr/local/bin link, root's PATH carries /opt/local/bin. Never # removed: a box without the file keeps whatever copy it has, so a # missing or moved config can never take the publisher off a mirror # master. if [ -e "/root/.cf-purge.cnf" ]; then _fetch_versioned ${_optBin}/getboa "${_urlHmr}/tools/bin/getboa" f99 --guard /local/bin/getboa fi if [ -e "/root/.remote_backups/schedule/backup_schedule.txt" ]; then # Fetch stamps for the backup family live in ${_pthLog} since the # _fetch_versioned collapse; the old ${_pthCtrl} location holds none, # so clearing it healed nothing. _BROKEN_UPDATE_TEST=$(grep "Under Construction" /root/.remote_backups/run/*.sh 2>&1) if [ ! -z "${_BROKEN_UPDATE_TEST}" ]; then rm -f ${_pthLog}/install_dependencies.sh.ctrl.* ${_pthLog}/create_*.sh.ctrl.* rm -f ${_pthLog}/dcysetup.ctrl.* ${_pthLog}/multiback.ctrl.* ${_pthLog}/mybackup.ctrl.* fi _BROKEN_UPDATE_TEST=$(grep "404 Not Found" /root/.remote_backups/run/*.sh 2>&1) if [ ! -z "${_BROKEN_UPDATE_TEST}" ]; then rm -f ${_pthLog}/install_dependencies.sh.ctrl.* ${_pthLog}/create_*.sh.ctrl.* rm -f ${_pthLog}/dcysetup.ctrl.* ${_pthLog}/multiback.ctrl.* ${_pthLog}/mybackup.ctrl.* fi fi if [ -e "/var/xdrago/monitor/check" ] && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _fetch_versioned /var/xdrago/monitor/check/scan_nginx.sh "${_urlHmr}/tools/system/monitor/check/scan_nginx.sh" f33 _fetch_versioned /var/xdrago/monitor/check/java.sh "${_urlHmr}/tools/system/monitor/check/java.sh" f77 _fetch_versioned /var/xdrago/monitor/check/mysql.sh "${_urlHmr}/tools/system/monitor/check/mysql.sh" f51 _fetch_versioned /var/xdrago/monitor/check/nginx.sh "${_urlHmr}/tools/system/monitor/check/nginx.sh" f67 _fetch_versioned /var/xdrago/monitor/check/nginx_guard.sh "${_urlHmr}/tools/system/monitor/check/nginx_guard.sh" f91 _fetch_versioned /var/xdrago/monitor/check/php.sh "${_urlHmr}/tools/system/monitor/check/php.sh" f57 _fetch_versioned /var/xdrago/monitor/check/fpm_tune.sh "${_urlHmr}/tools/system/monitor/check/fpm_tune.sh" f95 _fetch_versioned /var/xdrago/monitor/check/sqlprobe.sh "${_urlHmr}/tools/system/monitor/check/sqlprobe.sh" f90 _fetch_versioned /var/xdrago/monitor/check/sqlsnap.sh "${_urlHmr}/tools/system/monitor/check/sqlsnap.sh" f94 _fetch_versioned /var/xdrago/monitor/check/xdr9000.sh "${_urlHmr}/tools/system/monitor/check/xdr9000.sh" f90 _fetch_versioned /var/xdrago/monitor/check/batch_guard.sh "${_urlHmr}/tools/system/monitor/check/batch_guard.sh" f91 _fetch_versioned /var/xdrago/monitor/check/task_guard.sh "${_urlHmr}/tools/system/monitor/check/task_guard.sh" f99 _fetch_versioned /var/xdrago/monitor/check/cron_guard.sh "${_urlHmr}/tools/system/monitor/check/cron_guard.sh" f86 _fetch_versioned /var/xdrago/monitor/check/autosync.sh "${_urlHmr}/tools/system/monitor/check/autosync.sh" f00 _fetch_versioned /var/xdrago/monitor/check/valkey.sh "${_urlHmr}/tools/system/monitor/check/valkey.sh" f75 _fetch_versioned /var/xdrago/monitor/check/system.sh "${_urlHmr}/tools/system/monitor/check/system.sh" f63 _fetch_versioned /var/xdrago/monitor/check/unbound.sh "${_urlHmr}/tools/system/monitor/check/unbound.sh" f78 _fetch_versioned /var/xdrago/monitor/check/escapecheck.sh "${_urlHmr}/tools/system/monitor/check/escapecheck.sh" f97 _fetch_versioned /var/xdrago/monitor/check/hackcheck.sh "${_urlHmr}/tools/system/monitor/check/hackcheck.sh" f88 _fetch_versioned /var/xdrago/monitor/check/hackftp.sh "${_urlHmr}/tools/system/monitor/check/hackftp.sh" f94 _fetch_versioned /var/xdrago/monitor/check/segfault_alert.pl "${_urlHmr}/tools/system/monitor/check/segfault_alert.pl" f92 _fetch_versioned /var/xdrago/monitor/check/sqlcheck.sh "${_urlHmr}/tools/system/monitor/check/sqlcheck.sh" f85 _fetch_versioned /var/xdrago/monitor/check/bind9.sh "${_urlHmr}/tools/system/monitor/check/bind9.sh" f99 _fetch_versioned /var/xdrago/monitor/check/proxysql.sh "${_urlHmr}/tools/system/monitor/check/proxysql.sh" f99 _fetch_versioned /var/xdrago/monitor/check/droplet.sh "${_urlHmr}/tools/system/monitor/check/droplet.sh" f99 # written once by aptcleanup on a DO box, refreshed here from then on (its # stop arm used to TERM the init script itself) [ -e "/etc/init.d/droplet-agent" ] \ && _fetch_versioned /etc/init.d/droplet-agent "${_urlHmr}/conf/droplet/droplet-agent" f99 --mode 755 _fetch_versioned /var/xdrago/monitor/check/newrelic_daemon.sh "${_urlHmr}/tools/system/monitor/check/newrelic_daemon.sh" f99 _fetch_versioned /var/xdrago/monitor/check/newrelic_sysmond.sh "${_urlHmr}/tools/system/monitor/check/newrelic_sysmond.sh" f97 _fetch_versioned /var/xdrago/monitor/check/collectd.sh "${_urlHmr}/tools/system/monitor/check/collectd.sh" f99 _fetch_versioned /var/xdrago/monitor/check/xinetd.sh "${_urlHmr}/tools/system/monitor/check/xinetd.sh" f99 _fetch_versioned /var/xdrago/monitor/check/lsyncd.sh "${_urlHmr}/tools/system/monitor/check/lsyncd.sh" f99 _fetch_versioned /var/xdrago/monitor/check/sendmail_guard.sh "${_urlHmr}/tools/system/monitor/check/sendmail_guard.sh" f00 _fetch_versioned /var/xdrago/monitor/check/convert_guard.sh "${_urlHmr}/tools/system/monitor/check/convert_guard.sh" f00 _fetch_versioned /var/xdrago/monitor/check/hostname_sync.sh "${_urlHmr}/tools/system/monitor/check/hostname_sync.sh" f01 _fetch_versioned /etc/init.d/clean-boa-env "${_urlHmr}/conf/var/clean-boa-env" f94 _fetch_versioned /var/xdrago/mysql_backup.sh "${_urlHmr}/tools/system/mysql_backup.sh" f64 --guard mysql_backup.sh _fetch_versioned /usr/libexec/unbound-helper "${_urlHmr}/conf/dns/unbound-helper" f95 --mode 755 _fetch_versioned /var/xdrago/mysql_cleanup.sh "${_urlHmr}/tools/system/mysql_cleanup.sh" f83 _fetch_versioned /var/xdrago/mysql_cluster_backup.sh "${_urlHmr}/tools/system/mysql_cluster_backup.sh" f70 --guard mysql_cluster_backup.sh _runner_parked runner.sh \ || _fetch_versioned /var/xdrago/runner.sh "${_urlHmr}/tools/system/runner.sh" f58 _fetch_versioned /var/xdrago/minute.sh "${_urlHmr}/tools/system/minute.sh" f73 _fetch_versioned /var/xdrago/second.sh "${_urlHmr}/tools/system/second.sh" f64 _fetch_versioned /var/xdrago/ip_access.sh "${_urlHmr}/tools/system/ip_access.sh" f73 _fetch_versioned /var/xdrago/user_admin_access.sh "${_urlHmr}/tools/system/user_admin_access.sh" f79 _fetch_versioned /var/xdrago/cloudflare_realip.sh "${_urlHmr}/tools/system/cloudflare_realip.sh" f93 # Activate Cloudflare realip immediately by writing the set_real_ip_from # ranges include now, so a fresh/updated box does not wait for the daily # cron; the tool no-ops when nginx is absent and is idempotent otherwise. # Only while the ranges file is missing: this runs on every pass, and the # daily cron keeps an existing file current without fetching Cloudflare's # lists every few minutes. [ -e "/var/xdrago/cloudflare_realip.sh" ] \ && [ ! -s "/data/conf/nginx_cloudflare_real_ip.conf" ] \ && bash /var/xdrago/cloudflare_realip.sh >/dev/null 2>&1 _fetch_versioned /var/xdrago/ai_policy.sh "${_urlHmr}/tools/system/ai_policy.sh" f90 _fetch_versioned /var/xdrago/nginx_deny.sh "${_urlHmr}/tools/system/nginx_deny.sh" f93 _fetch_versioned /var/xdrago/nginx_deny6.sh "${_urlHmr}/tools/system/nginx_deny6.sh" f93 _fetch_versioned /var/xdrago/nginx_fleet.sh "${_urlHmr}/tools/system/nginx_fleet.sh" f89 # The per-minute line otherwise arrives only with the crontab template on a # full up-* run, so a fetched detector would sit idle until then; ensure it # whenever the script is present. A line an operator commented out still # matches the grep and is left alone. if [[ -x "/var/xdrago/nginx_fleet.sh" ]] \ && [[ -e "/var/spool/cron/crontabs/root" ]] \ && ! grep -q "nginx_fleet" /var/spool/cron/crontabs/root 2>/dev/null; then echo '* * * * * /usr/bin/nice -n5 /usr/bin/ionice -c2 -n7 bash /var/xdrago/nginx_fleet.sh >/dev/null 2>&1' >> /var/spool/cron/crontabs/root chown root:crontab /var/spool/cron/crontabs/root &> /dev/null chmod 600 /var/spool/cron/crontabs/root &> /dev/null service cron reload &> /dev/null || /etc/init.d/cron reload &> /dev/null fi # The migration-proxy trio previously reached boxes only inside the full # up-* tar, so a source box that had not upgraded since the certificate # mirror shipped had NO mirror at all -- the exact box that needs it. _fetch_versioned /var/xdrago/migration_proxy_trust.sh "${_urlHmr}/tools/system/migration_proxy_trust.sh" f93 _fetch_versioned /var/xdrago/migration_proxy_realip.sh "${_urlHmr}/tools/system/migration_proxy_realip.sh" f93 _fetch_versioned /var/xdrago/migration_proxy_certs.sh "${_urlHmr}/tools/system/migration_proxy_certs.sh" f95 # A fetch-delivered mirror without its schedule is a false comfort: the # daily line normally arrives only with the crontab template on a full # up-* run, so ensure it whenever the script is present. if [ -x "/var/xdrago/migration_proxy_certs.sh" ] \ && [ -e "/var/spool/cron/crontabs/root" ] \ && ! grep -q "migration_proxy_certs" /var/spool/cron/crontabs/root 2>/dev/null; then echo '45 6 * * * /usr/bin/nice -n5 /usr/bin/ionice -c2 -n7 bash /var/xdrago/migration_proxy_certs.sh >/dev/null 2>&1' >> /var/spool/cron/crontabs/root chown root:crontab /var/spool/cron/crontabs/root &> /dev/null chmod 600 /var/spool/cron/crontabs/root &> /dev/null service cron reload &> /dev/null || /etc/init.d/cron reload &> /dev/null fi _fetch_versioned /var/xdrago/move_sql.sh "${_urlHmr}/tools/system/move_sql.sh" f80 _fetch_versioned /var/xdrago/mysql_repair.sh "${_urlHmr}/tools/system/mysql_repair.sh" f89 _fetch_versioned /var/xdrago/purge_binlogs.sh "${_urlHmr}/tools/system/purge_binlogs.sh" f88 _fetch_versioned /var/xdrago/checksql.sh "${_urlHmr}/tools/system/checksql.sh" f95 _fetch_versioned /var/xdrago/clear.sh "${_urlHmr}/tools/system/clear.sh" f66 _fetch_versioned /var/xdrago/night/night.inc.sh "${_urlHmr}/tools/system/night/night.inc.sh" f55 _fetch_versioned /var/xdrago/night/20-sites.sh "${_urlHmr}/tools/system/night/20-sites.sh" f14 _fetch_versioned /var/xdrago/night/10-account.sh "${_urlHmr}/tools/system/night/10-account.sh" f20 _fetch_versioned /var/xdrago/night/90-global-post.sh "${_urlHmr}/tools/system/night/90-global-post.sh" f42 _runner_parked owl.sh \ || _fetch_versioned /var/xdrago/owl.sh "${_urlHmr}/tools/system/owl.sh" f64 ### ### Migration: daily.sh was renamed to owl.sh. Carry an xoct/xcopy-disabled ### state (.daily.sh.off) across to owl so a box that had the nightly run ### disabled stays disabled. ### if [ -e "/var/xdrago/.daily.sh.off" ]; then [ -e "/var/xdrago/owl.sh" ] && [ ! -e "/var/xdrago/.owl.sh.off" ] \ && mv -f /var/xdrago/owl.sh /var/xdrago/.owl.sh.off rm -f /var/xdrago/.daily.sh.off fi ### ### Retire the legacy daily.sh and its ctrl stamps only once a full system ### upgrade has re-rendered the active root crontab to invoke owl.sh. Until ### then the deployed /var/spool/cron/crontabs/root still calls daily.sh, so ### removing it on this self-update path would silently stop the nightly ### maintenance run until the next barracuda upgrade. Gating on the live ### crontab keeps this self-correcting and idempotent. ### if ! grep -q "/var/xdrago/daily\.sh" /var/spool/cron/crontabs/root 2>/dev/null; then rm -f /var/xdrago/daily.sh rm -f ${_pthLog}/daily.sh.ctrl.*.pid fi _runner_parked graceful.sh \ || _fetch_versioned /var/xdrago/graceful.sh "${_urlHmr}/tools/system/graceful.sh" f67 _runner_parked usage.sh \ || _fetch_versioned /var/xdrago/usage.sh "${_urlHmr}/tools/system/usage.sh" f50 fi if [ -e "/var/xdrago/monitor/check" ] \ && [ ! -e "${_pthLog}/cv-phar-symlink.ctrl.${_tRee}.${_xSrl}.pid" ]; then if [ -x "/usr/local/bin/cv.phar" ] \ && [ -L "/usr/bin/cv" ]; then _CV_SYMLINK=$(readlink -n /usr/bin/cv 2>&1) _CV_SYMLINK=$(echo -n ${_CV_SYMLINK} | tr -d "\n" 2>&1) if [ "${_CV_SYMLINK}" != "/usr/local/bin/cv.phar" ]; then rm -f /usr/bin/cv ln -sfn /usr/local/bin/cv.phar /usr/bin/cv touch ${_pthLog}/cv-phar-symlink.ctrl.${_tRee}.${_xSrl}.pid fi fi fi if [ -e "/var/xdrago/monitor/check" ] \ && [ ! -e "${_pthLog}/drush8-classic-symlink.ctrl.${_tRee}.${_xSrl}.pid" ]; then if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -L "/usr/bin/drush8" ]; then _DRUSH_SYMLINK=$(readlink -n /usr/bin/drush8 2>&1) _DRUSH_SYMLINK=$(echo -n ${_DRUSH_SYMLINK} | tr -d "\n" 2>&1) if [ "${_DRUSH_SYMLINK}" != "/opt/tools/drush/8/drush/drush.php" ]; then rm -f /usr/bin/drush8 rm -f /usr/bin/drush ln -sfn /opt/tools/drush/8/drush/drush.php /usr/bin/drush8 ln -sfn /opt/tools/drush/8/drush/drush.php /usr/bin/drush touch ${_pthLog}/drush8-classic-symlink.ctrl.${_tRee}.${_xSrl}.pid fi fi fi if [ -e "/var/xdrago/monitor/check" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then # Through _fetch_versioned like every other fetched tool, not a bare curl # that stamps the serial marker on whatever bytes it got: a box whose # mirror had not caught up yet installed the PREVIOUS worker under the new # marker and never re-fetched it (seen 2026-09-15). The fetcher refuses # bytes already deployed as "mirror not caught up" and retries next pass, # revalidates within the hour, and never clobbers a running pass; the # post-install steps run only when the file actually changed. if ! _runner_parked manage_ltd_users.sh; then _ltdSum="$(md5sum /var/xdrago/manage_ltd_users.sh 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/manage_ltd_users.sh "${_urlHmr}/tools/system/manage_ltd_users.sh" f22 --guard /xdrago/manage_ltd_users.sh if [ -s "/var/xdrago/manage_ltd_users.sh" ] \ && [ "$(md5sum /var/xdrago/manage_ltd_users.sh 2>/dev/null | cut -d' ' -f1)" != "${_ltdSum}" ]; then [ -e "/run/manage_ltd_users.pid" ] && rm -f /run/manage_ltd_users.pid [ -d "/var/backups/ltd/log" ] && rm -rf /var/backups/ltd/log mkdir -p /var/backups/ltd/{conf,log,old} fi fi fi if [ -e "/var/xdrago/monitor/check" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then # Through _fetch_versioned, as the ltd worker above: a bare curl stamped # the serial marker on whatever bytes it got, so a box whose mirror had # not caught up kept the previous tool under the new marker for good. # The post-install step runs only when the file actually changed. _slrSum="$(md5sum /var/xdrago/manage_solr_config.sh 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/manage_solr_config.sh "${_urlHmr}/tools/system/manage_solr_config.sh" f51 if [ -s "/var/xdrago/manage_solr_config.sh" ] \ && [ "$(md5sum /var/xdrago/manage_solr_config.sh 2>/dev/null | cut -d' ' -f1)" != "${_slrSum}" ]; then rm -f /run/manage_solr_config.pid fi fi if [ -e "/var/xdrago/monitor/check" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ ! -e "${_pthLog}/fast_shutdown.ctrl.${_tRee}.${_xSrl}.pid" ]; then sed -i "s/.*opcache.fast_shutdown.*//g" /opt/etc/fpm/fpm-pool-commo*.conf _PHP_V="86 85 84 83 82 81 80 74 73 72 71 70 56" for e in ${_PHP_V}; do if [ -e "/etc/init.d/php${e}-fpm" ] && [ -e "/opt/php${e}/bin/php" ]; then service "php${e}-fpm" reload &> /dev/null fi done _PHP_V="55 54 53" for e in ${_PHP_V}; do if [ -e "/etc/init.d/php${e}-fpm" ] && [ -e "/opt/php${e}/bin/php" ]; then service "php${e}-fpm" force-quit &> /dev/null fi done touch ${_pthLog}/fast_shutdown.ctrl.${_tRee}.${_xSrl}.pid fi [ -e "/var/xdrago/monitor/check" ] && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] && [ -x "/usr/sbin/csf" ] && [ -e "/etc/csf/csf.deny" ] \ && _fetch_versioned /var/xdrago/guest-fire.sh "${_urlHmr}/tools/system/guest-fire.sh" f84 [ -e "/var/xdrago/monitor/check" ] && [ -e "/var/xdrago" ] && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] && [ -x "/usr/sbin/csf" ] && [ -e "/etc/csf/csf.deny" ] \ && _fetch_versioned /var/xdrago/guest-water.sh "${_urlHmr}/tools/system/guest-water.sh" f68 # Re-put trigger for the current lshell.conf. The sentinel MUST be a # literal that exists in the shipped file (here the comment that opens the # ltd-shell-more section, which no pass edits), and should be one the # previous template lacks, so a template put from older bytes is put again. # A sentinel that can never match would drop the stamp and put this # security-critical file again on EVERY pass, on every box, forever -- the # stamp exists to prevent exactly that (as-shipped E2E catch). if ! grep -q "^## the only group that runs PHP by name" /var/xdrago/conf/lshell.conf; then rm -f ${_pthLog}/lshell.ctrl.* # The template that arrives carries node, npm, npx and scp; the plan check # below strips them again, but only once per release serial. Dropping its # marker with the stamp keeps a mid-serial re-put from re-granting # those commands to every tenant until the next release. rm -f ${_pthLog}/node.lshell-fix-npx.ctrl.* rm -f ${_pthLog}/node.manage.lshell.ctrl.* fi # The shipped template is fetched as lshell.conf.dist through # _fetch_versioned, which holds a new serial while the mirror still serves # the bytes already there and revalidates within the hour. The plan checks # edit the template in place, so the copy they edit is put from the fetched # one once per release and whenever that changes. if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then if [ -z "${_CUSTOM_CONFIG_LSHELL}" ] \ || [ "${_CUSTOM_CONFIG_LSHELL}" = "NO" ]; then _lshSum="$(md5sum /var/xdrago/conf/lshell.conf.dist 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/conf/lshell.conf.dist "${_urlHmr}/tools/system/conf/lshell.conf" f79 --mode 644 if _source_due /var/xdrago/conf/lshell.conf.dist "${_lshSum}" \ "${_pthLog}/lshell.ctrl.${_tRee}.${_xSrl}.pid"; then mv -f /var/xdrago/conf/lshell.conf /var/xdrago/conf/lshell.conf.old cp -f /var/xdrago/conf/lshell.conf.dist /var/xdrago/conf/lshell.conf if [ -e "/var/xdrago/conf/lshell.conf" ]; then chmod 644 /var/xdrago/conf/lshell.conf chown root:root /var/xdrago/conf/lshell.conf touch ${_pthLog}/lshell.ctrl.${_tRee}.${_xSrl}.pid else mv -f /var/xdrago/conf/lshell.conf.old /var/xdrago/conf/lshell.conf fi fi fi fi if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _BROKEN_UPDATE_TEST=$(grep "Under Construction" /var/xdrago/conf/fpm-pool* 2>&1) if [ ! -z "${_BROKEN_UPDATE_TEST}" ]; then rm -f /var/xdrago/conf/fpm-pool* rm ${_pthLog}/multi.ctrl.* rm ${_pthLog}/legacy.ctrl.* rm ${_pthLog}/modern.ctrl.* rm ${_pthLog}/single.ctrl.* rm ${_pthLog}/common.ctrl.* fi _BROKEN_UPDATE_TEST=$(grep "404 Not Found" /var/xdrago/conf/fpm-pool* 2>&1) if [ ! -z "${_BROKEN_UPDATE_TEST}" ]; then rm -f /var/xdrago/conf/fpm-pool* rm ${_pthLog}/multi.ctrl.* rm ${_pthLog}/legacy.ctrl.* rm ${_pthLog}/modern.ctrl.* rm ${_pthLog}/single.ctrl.* rm ${_pthLog}/common.ctrl.* fi _BROKEN_UPDATE_TEST=$(grep "max_execution_time" /var/xdrago/conf/fpm-pool* 2>&1) if [ -z "${_BROKEN_UPDATE_TEST}" ]; then rm -f /var/xdrago/conf/fpm-pool* rm ${_pthLog}/multi.ctrl.* rm ${_pthLog}/legacy.ctrl.* rm ${_pthLog}/modern.ctrl.* rm ${_pthLog}/single.ctrl.* rm ${_pthLog}/common.ctrl.* fi _BROKEN_UPDATE_TEST=$(grep "max_accelerated_files" /var/xdrago/conf/fpm-pool* 2>&1) if [ ! -z "${_BROKEN_UPDATE_TEST}" ]; then rm -f /var/xdrago/conf/fpm-pool* rm ${_pthLog}/multi.ctrl.* rm ${_pthLog}/legacy.ctrl.* rm ${_pthLog}/modern.ctrl.* rm ${_pthLog}/single.ctrl.* rm ${_pthLog}/common.ctrl.* fi fi # Each pool template is fetched as .dist through _fetch_versioned, # which holds a new serial while the mirror still serves the bytes already # there and revalidates within the hour. The copy below is edited (the # box IP added), so it is put from the fetched one once per release and # whenever that changes. if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _poolSum="$(md5sum /var/xdrago/conf/fpm-pool-common.conf.dist 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/conf/fpm-pool-common.conf.dist "${_urlHmr}/conf/php/fpm-pool-common.conf" f97 --mode 644 if _source_due /var/xdrago/conf/fpm-pool-common.conf.dist "${_poolSum}" \ "${_pthLog}/common.ctrl.${_tRee}.${_xSrl}.pid"; then mv -f /var/xdrago/conf/fpm-pool-common.conf /var/xdrago/conf/fpm-pool-common.conf.old cp -f /var/xdrago/conf/fpm-pool-common.conf.dist /var/xdrago/conf/fpm-pool-common.conf if [ -e "/var/xdrago/conf/fpm-pool-common.conf" ]; then sed -i "s/127.0.0.1/127.0.0.1,${_LOC_IP}/g" /var/xdrago/conf/fpm-pool-common.conf chmod 644 /var/xdrago/conf/fpm-pool-common.conf chown root:root /var/xdrago/conf/fpm-pool-common.conf touch ${_pthLog}/common.ctrl.${_tRee}.${_xSrl}.pid else mv -f /var/xdrago/conf/fpm-pool-common.conf.old /var/xdrago/conf/fpm-pool-common.conf fi fi fi if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _poolSum="$(md5sum /var/xdrago/conf/fpm-pool-common-legacy.conf.dist 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/conf/fpm-pool-common-legacy.conf.dist "${_urlHmr}/conf/php/fpm-pool-common-legacy.conf" f97 --mode 644 if _source_due /var/xdrago/conf/fpm-pool-common-legacy.conf.dist "${_poolSum}" \ "${_pthLog}/legacy.ctrl.${_tRee}.${_xSrl}.pid"; then mv -f /var/xdrago/conf/fpm-pool-common-legacy.conf /var/xdrago/conf/fpm-pool-common-legacy.conf.old cp -f /var/xdrago/conf/fpm-pool-common-legacy.conf.dist /var/xdrago/conf/fpm-pool-common-legacy.conf if [ -e "/var/xdrago/conf/fpm-pool-common-legacy.conf" ]; then sed -i "s/127.0.0.1/127.0.0.1,${_LOC_IP}/g" /var/xdrago/conf/fpm-pool-common-legacy.conf chmod 644 /var/xdrago/conf/fpm-pool-common-legacy.conf chown root:root /var/xdrago/conf/fpm-pool-common-legacy.conf touch ${_pthLog}/legacy.ctrl.${_tRee}.${_xSrl}.pid else mv -f /var/xdrago/conf/fpm-pool-common-legacy.conf.old /var/xdrago/conf/fpm-pool-common-legacy.conf fi fi fi if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _poolSum="$(md5sum /var/xdrago/conf/fpm-pool-common-modern.conf.dist 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/conf/fpm-pool-common-modern.conf.dist "${_urlHmr}/conf/php/fpm-pool-common-modern.conf" f97 --mode 644 if _source_due /var/xdrago/conf/fpm-pool-common-modern.conf.dist "${_poolSum}" \ "${_pthLog}/modern.ctrl.${_tRee}.${_xSrl}.pid"; then mv -f /var/xdrago/conf/fpm-pool-common-modern.conf /var/xdrago/conf/fpm-pool-common-modern.conf.old cp -f /var/xdrago/conf/fpm-pool-common-modern.conf.dist /var/xdrago/conf/fpm-pool-common-modern.conf if [ -e "/var/xdrago/conf/fpm-pool-common-modern.conf" ]; then sed -i "s/127.0.0.1/127.0.0.1,${_LOC_IP}/g" /var/xdrago/conf/fpm-pool-common-modern.conf chmod 644 /var/xdrago/conf/fpm-pool-common-modern.conf chown root:root /var/xdrago/conf/fpm-pool-common-modern.conf touch ${_pthLog}/modern.ctrl.${_tRee}.${_xSrl}.pid else mv -f /var/xdrago/conf/fpm-pool-common-modern.conf.old /var/xdrago/conf/fpm-pool-common-modern.conf fi fi fi # The two pool templates the workers copy as they are: through # _fetch_versioned like every other fetched file. if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _fetch_versioned /var/xdrago/conf/fpm-pool-foo-multi.conf "${_urlHmr}/conf/php/fpm-pool-foo-multi.conf" f98 --mode 644 fi if [ -e "/opt/tools/drush" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _fetch_versioned /var/xdrago/conf/fpm-pool-foo.conf "${_urlHmr}/conf/php/fpm-pool-foo.conf" f98 --mode 644 fi # The policy is fetched through _fetch_versioned, which holds a new serial # while the mirror still serves the bytes already there and revalidates # within the hour, and put in place once per release and whenever it # changes. if [ -e "/etc/ImageMagick-6/policy.xml" ] \ && [ -e "/var/xdrago" ]; then _imgSum="$(md5sum /var/xdrago/conf/etc-ImageMagick-6-policy.xml 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/xdrago/conf/etc-ImageMagick-6-policy.xml "${_urlHmr}/conf/etc/etc-ImageMagick-6-policy.xml" f99 --mode 644 if _source_due /var/xdrago/conf/etc-ImageMagick-6-policy.xml "${_imgSum}" \ "${_pthLog}/policymap-hf-06.ctrl.${_tRee}.${_xSrl}.pid"; then _isCurlBin="$(which curl)" chmod 755 ${_isCurlBin} &> /dev/null chgrp root ${_isCurlBin} &> /dev/null cp -af /etc/ImageMagick-6/policy.xml /var/xdrago/conf/etc-ImageMagick-6-policy.xml.hf-06.old cp -af /var/xdrago/conf/etc-ImageMagick-6-policy.xml /etc/ImageMagick-6/policy.xml chmod 644 /etc/ImageMagick-6/policy.xml chown root:root /etc/ImageMagick-6/policy.xml touch ${_pthLog}/policymap-hf-06.ctrl.${_tRee}.${_xSrl}.pid _PHP_V="86 85 84 83 82 81 80 74 73 72 71 70 56" for e in ${_PHP_V}; do if [ -e "/etc/init.d/php${e}-fpm" ]; then service "php${e}-fpm" reload &> /dev/null fi done fi fi if [ -e "/opt/tools/drush" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -d "/data/u" ] \ && [ ! -e "${_pthLog}/dispatch.ctrl.${_tRee}.${_xSrl}.pid" ]; then ### aegir.sh sits in the account's own home, where the account can put ### a FIFO (a sed -i would hang this pass on it) or a link at the name: ### edit it inside the pinned real home, read bounded (_acct_sed_here). for _pthSysUsr in /data/disk/*; do [ -e "${_pthSysUsr}/aegir.sh" ] || continue _acct_in_real_dir "${_pthSysUsr}" _acct_sed_here aegir.sh \ "s/.*cache.*//g; s/.*cc drush.*//g; s/ *$//g; /^$/d" done touch ${_pthLog}/dispatch.ctrl.${_tRee}.${_xSrl}.pid fi if [ -e "/var/xdrago/monitor/check" ] \ && [ -e "/var/xdrago/conf/control-readme.txt" ]; then # The bare fetch's release stamp (control-readme.txt.ctrl..) # is named like this file's fetch marker: the fetcher takes it for # another serial's marker and would hold the unchanged readme an hour. rm -f "${_pthLog}/control-readme.txt.ctrl.${_tRee}."*".pid" _fetch_versioned /var/xdrago/conf/control-readme.txt "${_urlHmr}/tools/system/conf/control-readme.txt" f85 --mode 644 fi # The queue template is fetched through _fetch_versioned, which holds a # new serial while the mirror still serves the bytes already there and # revalidates within the hour, so a changed template reaches the box # whatever text it keeps. The accounts' copies are put from it # (_hm_cron_queue_here) once per release and whenever its put, the # template with the box address, is not the one the last completed loop # used, and only from a template that carries the patch: a file without # it is neither put nor stamped, and the next fetch asks again. The puts # of the completed loops are kept in hosting.cron.queue.puts (md5s, the # last 20, newest last), the put of every loop begun in # hosting.cron.queue.begun (the last 20), and a module that is one of # them, or the put of the template on the box before this pass's fetch, # takes the current template: a pass stopped in the loop leaves the # record as it was, so the next pass runs the loop again, whatever # passes and template changes come before one completes, and an account # the loop skipped once (its alias naming a platform not here, as on a # standby) is put by the next release's loop. With no box address # nothing is recorded and only a module without the patch is put. if [ -d "/data/u" ]; then _hQueueF="hosting_cron.module" _hQueueP="/var/xdrago/conf/${_hQueueF}" _hQueueR="${_pthLog}/hosting.cron.queue.puts" _hQueueB="${_pthLog}/hosting.cron.queue.begun" _hQueueM="" _hQueueS="" _hQueueN="" _hQueueL="" _hQueueO="" _hQueueA=NO [[ "${_LOC_IP}" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] && _hQueueA=YES if [ -s "${_hQueueP}" ]; then _hQueueS="$(md5sum "${_hQueueP}" 2> /dev/null | cut -d' ' -f1)" if [ "${_hQueueA}" = "YES" ] \ && grep -q "core/cron.php" "${_hQueueP}" 2> /dev/null; then _hQueueO="$(sed "s/127.0.0.1/${_LOC_IP}/g" "${_hQueueP}" 2> /dev/null \ | md5sum | cut -d' ' -f1)" fi # No record yet: the loops so far put the template already here. if [ "${_hQueueA}" = "YES" ] && [ ! -s "${_hQueueR}" ] \ && grep -q "core/cron.php" "${_hQueueP}" 2> /dev/null; then sed "s/127.0.0.1/${_LOC_IP}/g" "${_hQueueP}" 2> /dev/null \ | md5sum | cut -d' ' -f1 > "${_hQueueR}" fi fi _fetch_versioned "${_hQueueP}" "${_urlHmr}/patches/${_hQueueF}" f93 --mode 644 if [ "${_hQueueA}" = "YES" ] && [ -s "${_hQueueP}" ]; then _hQueueL="$(grep -xE '[0-9a-f]{32}' "${_hQueueR}" 2> /dev/null | tail -n 20)" _hQueueN="$(sed "s/127.0.0.1/${_LOC_IP}/g" "${_hQueueP}" 2> /dev/null \ | md5sum | cut -d' ' -f1)" # A put the last completed loop did not use counts as a change. if [ "${_hQueueN}" != "$(printf '%s\n' "${_hQueueL}" | tail -n 1)" ]; then _hQueueS="" fi fi if grep -q "core/cron.php" "${_hQueueP}" 2> /dev/null \ && _source_due "${_hQueueP}" "${_hQueueS}" \ "${_pthLog}/hosting.cron.queue.ctrl.f95.${_tRee}.${_xSrl}.pid"; then # The put this loop makes is listed before it starts, so a module a # stopped loop put matches whatever passes come before one completes. if [ -n "${_hQueueN}" ]; then { grep -xE '[0-9a-f]{32}' "${_hQueueB}" 2> /dev/null \ | grep -vxF -e "${_hQueueN}" | tail -n 19 echo "${_hQueueN}"; } > "${_hQueueB}.tmp" \ && mv -f "${_hQueueB}.tmp" "${_hQueueB}" _hQueueM="$(printf '%s\n' "${_hQueueL}" "${_hQueueO}" \ | cat - "${_hQueueB}" 2> /dev/null | grep -xE '[0-9a-f]{32}' \ | sort -u | tr '\n' ' ')" fi for _pthSysUsr in `find /data/disk/ -maxdepth 1 -mindepth 1 | sort`; do _tUsr= _tUsr=$(echo ${_pthSysUsr} | cut -d'/' -f4 | awk '{ print $1}' 2>&1) if [ -n "${_tUsr}" ] && [ "${_tUsr}" != "arch" ]; then ### The account owns its alias and the whole platform it names: the ### root is taken only when it is one of the account's own ### hostmaster platforms (_acct_hm_root), and the module is put ### only from inside the pinned real cron directory. _hmPlr=$(_acct_hm_root "${_pthSysUsr}") || continue _hmDir="${_hmPlr}/profiles/hostmaster/modules/aegir/hosting" _acct_in_real_dir "${_hmDir}/cron" \ _hm_cron_queue_here "${_hQueueP}" "${_LOC_IP}" "${_hQueueM}" fi done touch ${_pthLog}/hosting.cron.queue.ctrl.f95.${_tRee}.${_xSrl}.pid if [ -n "${_hQueueN}" ]; then { printf '%s\n' "${_hQueueL}" | grep -vxF -e "${_hQueueN}" -e '' | tail -n 19 echo "${_hQueueN}"; } > "${_hQueueR}.tmp" \ && mv -f "${_hQueueR}.tmp" "${_hQueueR}" fi fi fi # An account's log/hosting_cron_use_backend.txt is the operator's switch # for Aegir's backend cron (the nightly and every Octopus pass set the # Hostmaster variable from it): BOA never removes it. Both the loop above # and a once-per-release block took it from every account, which undid # BOA's own default writes of one night in 2024 and then, at every # release and template change, every operator's choice. if [ -e "/var/xdrago/monitor/check" ] \ && [ -d "/data/u" ] \ && [ -e "/usr/sbin/csf" ] \ && [ ! -e "${_pthLog}/fpm-cli.ctrl.${_tRee}.${_xSrl}.pid" ]; then _usrGroup=users for _pthSysUsr in `find /data/disk/ -maxdepth 1 -mindepth 1 | sort`; do _tUsr= _tUsr=$(echo ${_pthSysUsr} | cut -d'/' -f4 | awk '{ print $1}' 2>&1) _usrGroup=$(_acct_group "${_tUsr}") if [ "${_tUsr}" != "arch" ]; then _acct_in_real_dir "${_pthSysUsr}/static/control" _ctrl_seed_here ### proxy-failed.pid: a cutover could not convert the account, and its ### panel database lives on the target; its dispatcher would run the ### travelled queue against the stale local copy until xoct repairs it if [ -e "${_pthSysUsr}/log/CANCELLED" ] \ || [ -e "${_pthSysUsr}/log/proxied.pid" ] \ || [ -e "${_pthSysUsr}/log/proxy-failed.pid" ] \ || [ ! -e "${_pthSysUsr}/static/control/cli.info" ]; then if [ -e "/var/xdrago/run-${_tUsr}" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then [ -d "/var/backups/off-run" ] || mkdir -p /var/backups/off-run mv -f /var/xdrago/run-${_tUsr} /var/backups/off-run/ fi else ### This pass hands a parked dispatcher back only here, after the ### park test and only when none is live: copied back ahead of the ### test, a held one would sit in /var/xdrago, where runner.sh can ### fire it, until moved out; copied over a live one, a stale ### parked copy would replace the one octopus last wrote. Beside a ### live one the parked copy is stale: set it aside, so off-run ### holds only dispatchers that are parked now if [ -e "/var/backups/off-run/run-${_tUsr}" ]; then if [ -s /etc/boa/control/instgrp-web-arm.ready.txt ] \ && _a2b_import_unfinished "${_tUsr}"; then # an unfinished aegir2boa db-import holds it: only the # import's success or revert hands a dispatcher back : elif [ ! -e "/var/xdrago/run-${_tUsr}" ]; then mv -f "/var/backups/off-run/run-${_tUsr}" /var/xdrago/ &> /dev/null else mv -f "/var/backups/off-run/run-${_tUsr}" \ "/var/backups/off-run/.superseded-run-${_tUsr}" &> /dev/null fi fi _dscUsr="/data/disk/${_tUsr}" ### The account owns config/includes and can put a FIFO or a link ### at this name: read it bounded inside the pinned real directory ### and edit only a copy without the marker (_acct_sed_here). _ngxCnf="${_dscUsr}/config/includes/nginx_vhost_common.conf" _acct_in_real_dir "${_ngxCnf%/*}" _acct_sed_here "${_ngxCnf##*/}" \ "s/args.*q=/args ~* \"foobaroff=/g" foobaroff for _version in 84 85 86 83 82 81 74 56; do if [ -x "/opt/php${_version}/bin/php" ]; then if [ "${_version}" = "74" ]; then _useCli="7.4" _useFpm="7.4" elif [ "${_version}" = "56" ]; then _useCli="5.6" _useFpm="5.6" else _useCli="8.${_version:1}" _useFpm="8.${_version:1}" fi break fi done if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _acct_in_real_dir "${_dscUsr}/static/control" _ctrl_pins_here fi if [ ! -e "${_dscUsr}/static/control/.ctrl.${_tRee}.${_xSrl}.pid" ] \ && [ -e "/home/${_tUsr}.ftp/clients" ]; then _acct_in_real_dir "${_dscUsr}/static" _acct_ctrl_dir_here _acct_in_real_dir "${_dscUsr}/static/control" _ctrl_init_here fi fi fi done touch ${_pthLog}/fpm-cli.ctrl.${_tRee}.${_xSrl}.pid fi # Create the destination directory if it doesn't exist [ -d "/var/backups/off-run" ] || mkdir -p /var/backups/off-run if [ -e "/var/xdrago/monitor/check" ] \ && [ -d "/data/u" ] \ && [ -e "/usr/sbin/csf" ]; then # Loop through all files matching the pattern /var/xdrago/run-USER for _file in /var/xdrago/run-*; do # Skip iteration if no files match the pattern [ -e "${_file}" ] || continue # Extract the _USER from the filename _USER=${_file#/var/xdrago/run-} # Define the paths to check _USER_DIR="/data/disk/${_USER}" _CANCELLED_FILE="${_USER_DIR}/log/CANCELLED" _PROXIED_PID_FILE="${_USER_DIR}/log/proxied.pid" _PROXY_FAILED_FILE="${_USER_DIR}/log/proxy-failed.pid" _CLI_INFO_FILE="${_USER_DIR}/static/control/cli.info" # Check the conditions if [ ! -d "${_USER_DIR}" ] || \ [ -f "${_CANCELLED_FILE}" ] || \ [ -f "${_PROXIED_PID_FILE}" ] || \ [ -f "${_PROXY_FAILED_FILE}" ] || \ [ ! -f "${_CLI_INFO_FILE}" ]; then # Move the file if any condition is met mv -f "${_file}" /var/backups/off-run/ continue fi # An unfinished aegir2boa db-import keeps its account's dispatcher # held (a half-imported panel must not dispatch), on a box armed for # the web group and never while an import of the account runs, which # hands its dispatcher back before it writes its done marker. Whether # one runs is read first and the import's state after it, so a run # that ends between the two reads is judged by the state it left. A # live one an earlier Octopus pass wrote goes into aegir2boa's own # hold, which the import's success or revert returns, or into off-run # beside one already held there. if [ -s /etc/boa/control/instgrp-web-arm.ready.txt ] \ && [ -d "/var/backups/a2b/${_USER}" ] \ && ! _a2b_import_live "${_USER}" && _a2b_import_unfinished "${_USER}"; then if [ -e "/var/backups/a2b/${_USER}/held-run-${_USER}" ]; then mv -f "${_file}" /var/backups/off-run/ else mv -f "${_file}" "/var/backups/a2b/${_USER}/held-run-${_USER}" fi continue fi if grep -q "renice 0" "${_file}"; then sed -i "s/renice 0/renice 9/g" "${_file}" fi done fi if [ -x "/opt/tools/drush/8/drush/drush.php" ] \ && [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -d "/data/u" ] \ && [ ! -e "${_pthLog}/le_renewal_days_69.ctrl.${_tRee}.${_xSrl}.pid" ]; then local _leHeld=NO [ -e "/root/.standby.cnf" ] && _leHeld=YES for _pthSysUsr in `find /data/disk/ -maxdepth 1 -mindepth 1 | sort`; do if [ -e "${_pthSysUsr}/config/server_master/nginx/vhost.d" ] \ && [ -e "${_pthSysUsr}/static/control/cli.info" ] \ && [ ! -e "/root/.standby.cnf" ] \ && [ ! -e "${_pthSysUsr}/log/proxied.pid" ] \ && [ ! -e "${_pthSysUsr}/log/CANCELLED" ]; then _tUsr= _validReg= _validIPr= _tUsr=$(echo ${_pthSysUsr} | cut -d'/' -f4 | awk '{ print $1}' 2>&1) _dscUsr="/data/disk/${_tUsr}" ### Resolve the owning group per account: this loop used to inherit ### whatever the FPM/CLI loop above left behind, so the chowns below ### stamped every tree with the last account's group (or, with that ### loop's gate already closed, fell back to the login group). _usrGroup=$(_acct_group "${_tUsr}") ### The account owns its tools/le: each copy is put through a pinned ### real directory of the account's own tree, owned and moded through ### its handle, so a link the account put at a name is replaced, ### never written through or handed over. _tUid=$(id -u -- "${_tUsr}" 2> /dev/null) _tGid=$(getent group "${_usrGroup}" 2> /dev/null | cut -d: -f3) _leRoot="${_dscUsr}/tools/le" _exeLe="${_leRoot}/dehydrated" _dehydFull="${_leRoot}/${_dehydName}" _legacyLeShFile="${_leRoot}/letsencrypt.sh" _lockLeFile="${_leRoot}/lock" _configIni="${_leRoot}/config" _acctsDir="${_leRoot}/accounts" _acctsDemoDir="${_leRoot}/accounts-demo" _demoPid="${_leRoot}/.ctrl/ssl-demo-mode.pid" _normalRegPid="${_leRoot}/.ctrl/normal-re6-register.pid" _forcedRegPid="${_leRoot}/.ctrl/forced-re6-register.pid" _onDemandRegPid="${_leRoot}/.ctrl/onDemand-register.pid" ### Everything below tools/le is the account's: its files are read ### bounded inside their pinned real directories, every name is ### created, removed or moved only from inside its pinned real ### directory, and its dehydrated (run as the account) is bounded in ### time, so no name there can steer or stall this root pass. _validIdn= _le_acct_has "${_acctsDir}" account_id.json letsencrypt \ && _validIdn=letsencrypt _le_acct_has "${_acctsDir}" registration_info.json valid \ && _validReg=valid ### The old registering-IP probe is retired: the ACME account object ### has not carried any IP field for years, so a grep for the box IP ### could never match -- which made the forced re-register arm below ### fire on EVERY box each time the once-per-serial pass landed in ### its hour window, destroying healthy account keys to heal a ### condition that does not exist. _HOUR=$(date +%H 2>&1) _HOUR=${_HOUR//[^0-9-]/} if [ -e "${_dehydSrcPath}" ]; then _acct_in_real_dir "${_leRoot}" _acct_put_mode_here "${_dehydName}" \ "${_tUid}" "${_tGid}" 700 < "${_dehydSrcPath}" fi if [ -e "${_dehydFull}" ] \ && [ ! -e "${_normalRegPid}" ]; then if [ "${_HOUR}" = "5" ] \ || [ "${_HOUR}" = "17" ] \ || [ -e "${_onDemandRegPid}" ]; then local _leRegRc timeout -k 10 300 \ su -s /bin/bash - ${_tUsr} -c "bash ${_exeLe} --register --accept-terms" _leRegRc=$? wait # 124/137: killed by the timeout, so no marker: the next run of # this block registers again if [[ "${_leRegRc}" != "124" && "${_leRegRc}" != "137" ]]; then _acct_in_real_dir "${_leRoot}/.ctrl" \ _acct_touch_here "${_normalRegPid##*/}" else echo "NOTE: Let's Encrypt registration for ${_tUsr} timed out; not recorded as done" fi fi fi if [ -e "${_lockLeFile}" ]; then _acct_in_real_dir "${_leRoot}" rm -f -- "./${_lockLeFile##*/}" sleep 1 fi if [ -e "${_demoPid}" ]; then _acct_in_real_dir "${_leRoot}/.ctrl" rm -f -- "./${_demoPid##*/}" fi if [ "${_HOUR}" = "11" ] \ || [ "${_HOUR}" = "23" ] \ || [ -e "${_onDemandRegPid}" ]; then if [ -e "${_legacyLeShFile}" ] \ || [ -e "${_acctsDemoDir}" ] \ || [[ ! "${_validIdn}" =~ "letsencrypt" ]] \ || [[ ! "${_validReg}" =~ "valid" ]] \ || [ ! -e "${_forcedRegPid}" ]; then _acct_in_real_dir "${_leRoot}" rm -f -- "./${_legacyLeShFile##*/}" _acct_in_real_dir "${_leRoot}" rm -rf -- "./${_acctsDemoDir##*/}" ### Archive, never destroy: the accounts dir holds the tenant's ### only ACME account key. A mis-fired heal that deleted it cost ### a fresh registration; an archived copy costs a mv back. The ### weekly graceful.sh prune keeps the newest three per account. if [ -d "${_acctsDir}" ]; then mkdir -p /var/backups/le-accounts/${_tUsr} _acct_in_real_dir "${_leRoot}" mv -f -- "./${_acctsDir##*/}" \ "/var/backups/le-accounts/${_tUsr}/accounts-$(date +%Y%m%d%H%M%S 2>/dev/null)" fi _acct_in_real_dir "${_leRoot}/.ctrl" _le_ctrl_clear_here if [ -e "${_exeLe}" ]; then local _leRegRc timeout -k 10 300 \ su -s /bin/bash - ${_tUsr} -c "bash ${_exeLe} --register --accept-terms" _leRegRc=$? wait if [[ "${_leRegRc}" != "124" && "${_leRegRc}" != "137" ]]; then _acct_in_real_dir "${_leRoot}/.ctrl" \ _acct_touch_here "${_forcedRegPid##*/}" _acct_in_real_dir "${_leRoot}/.ctrl" \ _acct_touch_here "${_normalRegPid##*/}" else echo "NOTE: Let's Encrypt registration for ${_tUsr} timed out; not recorded as done" fi fi fi fi fi done ### A standby skips every account above: stamped there, the promoted box ### would never run this serial's account pass. Stamped only when the ### standby marker was absent before the loop and after it (a promotion ### during the loop leaves the accounts before it undone), so the first ### agents pass after promotion runs them. if [ "${_leHeld}" = "NO" ] && [ ! -e "/root/.standby.cnf" ]; then touch ${_pthLog}/le_renewal_days_69.ctrl.${_tRee}.${_xSrl}.pid fi fi # websh the FILE is fetched by _update_boa_tools (atomic replace + a # _forward_to_dash content sentinel; websh doubles as /bin/sh on hosted # boxes, so a partial or wrong payload must never reach the live path). # Here only enforce the shell symlinks on installed hosted boxes. if [ -e "/var/xdrago/monitor/check" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _WEB_SH="$(readlink -n /bin/sh)" if [ -x "/opt/local/bin/websh" ] \ && grep -i '_forward_to_dash' /opt/local/bin/websh &> /dev/null; then [ -x "/bin/websh" ] && [ ! -L "/bin/websh" ] && ln -sfn /opt/local/bin/websh /bin/websh if [ "${_WEB_SH}" != "/opt/local/bin/websh" ]; then ln -sfn /opt/local/bin/websh /bin/sh if [ -e "/usr/bin/sh" ]; then ln -sfn /opt/local/bin/websh /usr/bin/sh fi fi fi fi [ -e "/var/xdrago/monitor/check" ] && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && _fetch_versioned /etc/cron.hourly/systemtime "${_urlHmr}/helpers/systemtime" f94 --mode 755 _Dir="/data/all/000/modules" _REDIS_E_VERSION=8.x-1.11.4 if [ -e "/var/xdrago/manage_solr_config.sh" ]; then if _shared_module_stale redis_ten_eleven "ver-${_REDIS_E_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_ten_eleven "${_REDIS_E_VERSION}" fi fi _Dir="/data/all/000/modules" _REDIS_T_VERSION=8.x-1.8.2 if [ -e "/var/xdrago/manage_solr_config.sh" ]; then if _shared_module_stale redis_nine_ten "ver-${_REDIS_T_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_nine_ten "${_REDIS_T_VERSION}" fi fi _Dir="/data/all/000/modules" _REDIS_C_VERSION=com-19-04-2021 if [ -e "/var/xdrago/manage_solr_config.sh" ]; then if _shared_module_stale redis_compr "ver-${_REDIS_C_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_compr "${_REDIS_C_VERSION}" fi fi _Dir="/data/all/000/modules" _REDIS_L_VERSION=7.x-3.19.3 if [ -e "/var/xdrago/manage_solr_config.sh" ]; then if _shared_module_stale redis_edge "ver-${_REDIS_L_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_edge "${_REDIS_L_VERSION}" fi fi _Dir="/data/all/000/modules" _REDIS_N_VERSION=com-19-04-2021 if [ -e "/var/xdrago/manage_solr_config.sh" ]; then if _shared_module_stale redis_eight "ver-${_REDIS_N_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_eight "${_REDIS_N_VERSION}" fi fi # Drupal 6's cache layer every D6 bundle links to: updated (never created) # here, where an Octopus pass made it, so a copy taken out of use or gone # stale is fetched again within one agents pass, not at the next pass that # builds platforms. Its archive records another user: the copy is root's. # The same version as satellite.sh.inc's _CACHE_BACKPORT_VERSION. _Dir="/data/all/000/modules" _CACHE_BACKPORT_VERSION=6.x-1.0-rc4 if [ -e "/var/xdrago/manage_solr_config.sh" ] \ && { [ -e "${_Dir}/cache_backport" ] || [ -L "${_Dir}/cache_backport" ]; }; then if _shared_module_stale cache_backport "ver-${_CACHE_BACKPORT_VERSION}.${_xSrl}.full.info"; then _shared_module_update cache_backport "${_CACHE_BACKPORT_VERSION}" root fi fi # Backdrop shared contrib store: updated (never created) here -- the extra # o_contrib_backdrop gate means only boxes where Octopus has established the # Backdrop bundle refresh it, so opted-out boxes never fetch the tarball. # Every instance's Backdrop sites run this copy: root's, whatever owner the # archive recorded. _Dir="/data/all/000/modules" _REDIS_B_VERSION=1.x-3.20.1 if [ -e "/var/xdrago/manage_solr_config.sh" ] \ && [ -d "${_Dir}/o_contrib_backdrop" ]; then if _shared_module_stale redis_backdrop "ver-${_REDIS_B_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_backdrop "${_REDIS_B_VERSION}" root fi fi # Drupal 12's Valkey integration (BOA's own redis 2.x line), refreshed but # never created here: only where Octopus has made the o_contrib_twelve # shelf, and only once the pin names a published tarball (empty: no box # fetches it). The same version as OCTOPUS.sh.txt's _REDIS_W_VERSION. _Dir="/data/all/000/modules" _REDIS_W_VERSION=2.0.0-alpha2.1 if [ -n "${_REDIS_W_VERSION}" ] \ && [ -e "/var/xdrago/manage_solr_config.sh" ] \ && [ -d "${_Dir}/o_contrib_twelve" ]; then if _shared_module_stale redis_twelve "ver-${_REDIS_W_VERSION}.${_xSrl}.full.info"; then _shared_module_update redis_twelve "${_REDIS_W_VERSION}" fi fi _fetch_failed_report "the agents pass" } _fix_core_dgd() { # sed -i "s/^_PERMISSIONS_FIX=.*/_PERMISSIONS_FIX=YES/g" ${_barCnf} _saCoreS="${_saCoreN}-D7" _saIncDb="includes/database/database.inc" _saPatch="/var/xdrago/conf/${_saCoreS}.patch" _saQCoreN="${_saCoreN}" _saQCoreS="${_saQCoreN}-D8" _saQIncDb="core/includes/database.inc" _saQPatch="/var/xdrago/conf/${_saQCoreS}.patch" _saXCoreN="${_saCoreN}" _saXCoreS="${_saXCoreN}-D6" _saXIncDb="includes/database.inc" _saXPatch="/var/xdrago/conf/${_saXCoreS}.patch" _saBCoreP="${_saCoreN}-provision" _saBPatch="/var/xdrago/conf/${_saBCoreP}.patch" # static/ and each Octopus hostmaster platform belong to the account's # users, so any name below them can be a link: a platform is patched only # from inside its real directory. The patch $1 is applied unless the # platform is of the other generation (core/ present, or absent when $3 is # d8) or already carries the stamp profiles/$2 (empty: no stamp). The # stamp is a fresh file put inside the real profiles/, never written # through a link the account put on the way. _boa_core_dgd_here() { if [ "${3}" = "d8" ]; then [ -e ./core ] || return 0 else [ ! -e ./core ] || return 0 fi [ -z "${2}" ] || [ ! -e "./profiles/${2}" ] || return 0 patch -p1 &> /dev/null < "${1}" [ -z "${2}" ] \ || _acct_in_real_dir "$(pwd -P)/profiles" _acct_put_here "${2}" fixed } # SA-CORE D8 patch if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ ! -e "${_saQPatch}" ]; then mkdir -p /var/xdrago/conf curl ${_crlGet} "${_urlHmr}/patches/8-core/${_saQCoreS}.patch" -o ${_saQPatch} fi # SA-CORE D7 patch if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ ! -e "${_saPatch}" ]; then mkdir -p /var/xdrago/conf curl ${_crlGet} "${_urlHmr}/patches/7-core/${_saCoreS}.patch" -o ${_saPatch} fi # SA-CORE D6 patch # if [ -e "/var/xdrago" ] \ # && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ # && [ ! -e "${_saXPatch}" ]; then # mkdir -p /var/xdrago/conf # curl ${_crlGet} "${_urlHmr}/patches/6-core/${_saXCoreS}.patch" -o ${_saXPatch} # fi # SA-CORE for Octopus hostmaster platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -d "/data/u" ] \ && [ -e "${_saPatch}" ]; then if [ -d "/data/all" ] \ && [ ! -e "${_pthLog}/hostmaster-octopus-${_saCoreN}-fixed-d7.log" ]; then for _File in /data/disk/*/aegir/distro/*/${_saIncDb}; do [ -e "${_File}" ] || continue _acct_in_real_dir "${_File%/"${_saIncDb}"}" \ _boa_core_dgd_here "${_saPatch}" "" d7 done touch ${_pthLog}/hostmaster-octopus-${_saCoreN}-fixed-d7.log fi cd fi # SA-CORE for Barracuda hostmaster platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saPatch}" ]; then if [ -d "/data/all" ] \ && [ ! -e "${_pthLog}/hostmaster-barracuda-${_saCoreN}-fixed-d7.log" ]; then for _File in `find /var/aegir/host_master/*/${_saIncDb} \ -maxdepth 0 -mindepth 0 | sort`; do _Core=$(echo ${_File} \ | sed 's/\/includes.*//g' \ | awk '{print $1}' 2> /dev/null) if [ -d "${_Core}" ] && [ ! -e "${_Core}/core" ]; then cd ${_Core} patch -p1 &> /dev/null < ${_saPatch} fi done for _File in `find /var/aegir/hostmaster*/${_saIncDb} \ -maxdepth 0 -mindepth 0 | sort`; do _Core=$(echo ${_File} \ | sed 's/\/includes.*//g' \ | awk '{print $1}' 2> /dev/null) if [ -d "${_Core}" ] && [ ! -e "${_Core}/core" ]; then cd ${_Core} patch -p1 &> /dev/null < ${_saPatch} fi done touch ${_pthLog}/hostmaster-barracuda-${_saCoreN}-fixed-d7.log fi cd fi # SA-CORE for built-in D7 platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saPatch}" ] \ && [ ! -e "${_pthLog}/${_saCoreN}-fixed-d7.log" ]; then if [ -d "/data/all/000/core" ]; then for _Core in `find /data/all/000/core/drupal-7* \ -maxdepth 0 -mindepth 0 | sort`; do cd ${_Core} patch -p1 &> /dev/null < ${_saPatch} done elif [ -d "/data/disk/all/000/core" ]; then for _Core in `find /data/disk/all/000/core/drupal-7* \ -maxdepth 0 -mindepth 0 | sort`; do cd ${_Core} patch -p1 &> /dev/null < ${_saPatch} done fi touch ${_pthLog}/${_saCoreN}-fixed-d7.log cd fi # SA-CORE for ancient D7 platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saPatch}" ]; then if [ -d "/data/all" ] \ && [ ! -e "${_pthLog}/legacy-${_saCoreN}-fixed-d7.log" ]; then for _File in `find /data/all/*/*/${_saIncDb} \ -maxdepth 0 -mindepth 0 | sort`; do _Core=$(echo ${_File} \ | sed 's/\/includes.*//g' \ | awk '{print $1}' 2> /dev/null) if [ -d "${_Core}" ] && [ ! -e "${_Core}/core" ]; then cd ${_Core} patch -p1 &> /dev/null < ${_saPatch} fi done touch ${_pthLog}/legacy-${_saCoreN}-fixed-d7.log elif [ -d "/data/disk/all" ] \ && [ ! -e "${_pthLog}/legacy-${_saCoreN}-fixed-d7eee.log" ]; then for _File in `find /data/disk/all/*/*/${_saIncDb} \ -maxdepth 0 -mindepth 0 | sort`; do _Core=$(echo ${_File} \ | sed 's/\/includes.*//g' \ | awk '{print $1}' 2> /dev/null) if [ -d "${_Core}" ] && [ ! -e "${_Core}/core" ]; then cd ${_Core} patch -p1 &> /dev/null < ${_saPatch} fi done touch ${_pthLog}/legacy-${_saCoreN}-fixed-d7eee.log fi cd fi # SA-CORE for custom D7 platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saPatch}" ]; then if [ -d "/data/u" ] \ && [ ! -e "${_pthLog}/batch-custom-${_saCoreN}-fixed-d7.log" ]; then for _File in /data/disk/*/static/*/${_saIncDb} \ /data/disk/*/static/*/*/${_saIncDb} \ /data/disk/*/static/*/*/*/${_saIncDb} \ /data/disk/*/static/*/*/*/*/${_saIncDb} \ /data/disk/*/static/*/*/*/*/*/${_saIncDb}; do [ -e "${_File}" ] || continue _acct_in_real_dir "${_File%/"${_saIncDb}"}" \ _boa_core_dgd_here "${_saPatch}" "${_saCoreS}-fix.info" d7 done fi cd touch ${_pthLog}/batch-custom-${_saCoreN}-fixed-d7.log fi # SA-CORE for D8 platforms in ~/static if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saQPatch}" ]; then if [ -d "/data/u" ] \ && [ ! -e "${_pthLog}/batch-custom-${_saQCoreN}-fixed-d8.log" ]; then for _File in /data/disk/*/static/*/${_saQIncDb} \ /data/disk/*/static/*/*/${_saQIncDb} \ /data/disk/*/static/*/*/*/${_saQIncDb} \ /data/disk/*/static/*/*/*/*/${_saQIncDb} \ /data/disk/*/static/*/*/*/*/*/${_saQIncDb}; do [ -e "${_File}" ] || continue _acct_in_real_dir "${_File%/"${_saQIncDb}"}" \ _boa_core_dgd_here "${_saQPatch}" "${_saQCoreS}-fix.info" d8 done fi cd touch ${_pthLog}/batch-custom-${_saQCoreN}-fixed-d8.log fi # SA-CORE for built-in D6 platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saXPatch}" ] \ && [ ! -e "${_pthLog}/${_saXCoreN}-finally-fixed-d6.log" ]; then if [ -d "/data/all/000/core" ]; then for _Core in `find /data/all/000/core/pressflow-6* \ -maxdepth 0 -mindepth 0 | sort`; do cd ${_Core} patch -p1 &> /dev/null < ${_saXPatch} done elif [ -d "/data/disk/all/000/core" ]; then for _Core in `find /data/disk/all/000/core/pressflow-6* \ -maxdepth 0 -mindepth 0 | sort`; do cd ${_Core} patch -p1 &> /dev/null < ${_saXPatch} done fi touch ${_pthLog}/${_saXCoreN}-finally-fixed-d6.log cd fi # SA-CORE for ancient D6 platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saXPatch}" ]; then if [ -d "/data/all" ] \ && [ ! -e "${_pthLog}/legacy-${_saXCoreN}-finally-fixed-d6.log" ]; then for _File in `find /data/all/*/*/${_saXIncDb} \ -maxdepth 0 -mindepth 0 | sort`; do _Core=$(echo ${_File} \ | sed 's/\/includes.*//g' \ | awk '{print $1}' 2> /dev/null) if [ -d "${_Core}" ] && [ ! -e "${_Core}/core" ]; then cd ${_Core} patch -p1 &> /dev/null < ${_saXPatch} fi done touch ${_pthLog}/legacy-${_saXCoreN}-finally-fixed-d6.log elif [ -d "/data/disk/all" ] \ && [ ! -e "${_pthLog}/legacy-${_saXCoreN}-finally-fixed-d6eee.log" ]; then for _File in `find /data/disk/all/*/*/${_saXIncDb} \ -maxdepth 0 -mindepth 0 | sort`; do _Core=$(echo ${_File} \ | sed 's/\/includes.*//g' \ | awk '{print $1}' 2> /dev/null) if [ -d "${_Core}" ] && [ ! -e "${_Core}/core" ]; then cd ${_Core} patch -p1 &> /dev/null < ${_saXPatch} fi done touch ${_pthLog}/legacy-${_saXCoreN}-finally-fixed-d6eee.log fi cd fi # SA-CORE for custom D6 platforms if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ -e "${_saXPatch}" ]; then if [ -d "/data/u" ] \ && [ ! -e "${_pthLog}/batch-custom-${_saXCoreN}-finally-fixed-d6.log" ]; then for _File in /data/disk/*/static/*/${_saXIncDb} \ /data/disk/*/static/*/*/${_saXIncDb} \ /data/disk/*/static/*/*/*/${_saXIncDb} \ /data/disk/*/static/*/*/*/*/${_saXIncDb} \ /data/disk/*/static/*/*/*/*/*/${_saXIncDb}; do [ -e "${_File}" ] || continue _acct_in_real_dir "${_File%/"${_saXIncDb}"}" \ _boa_core_dgd_here "${_saXPatch}" "${_saXCoreS}-fix-finally.info" d6 done fi cd touch ${_pthLog}/batch-custom-${_saXCoreN}-finally-fixed-d6.log fi } # /tmp must be 1777 root:root: a box was found with it 0700 root (writer # unknown), which leaves every tenant's composer and PHP CLI without a temp # directory and nothing in any log saying so. Heal it on every pass and date # the repair, so a recurrence is caught the moment it happens. _fix_tmp_perms() { local _tmp_stat _tmp_note [ -d "/tmp" ] && [ ! -L "/tmp" ] || return 0 _tmp_stat=$(stat -c '%a %U:%G' /tmp 2> /dev/null) [ "${_tmp_stat}" = "1777 root:root" ] && return 0 chown root:root /tmp &> /dev/null chmod 1777 /tmp &> /dev/null _tmp_note="NOTE: /tmp was ${_tmp_stat:-unreadable}, set back to 1777 root:root" _msg "${_tmp_note}" if [ -d "${_pthLog}" ]; then echo "$(date) ${_tmp_note}" >> ${_pthLog}/tmp-perms.incident.log fi } # The master Drush arm, run from the barracuda process with no _ROOT exported, # copied the Drush 8 tree to /drush and rotated the previous copy under # /backups/system. The arm is guarded now and heals its own leftovers, but # only inside a barracuda pass, and boxes on the stable trees pass weekly. # Drush 10/11 search /drush with an empty root, so the stray is read by # every tenant's modern Drush, and the lshell Landlock domain refuses that # read: the preflight dies. Heal it here, on every pass, and date the repair. # Only the tree BOA itself copies is removed: the fork's own marker files rule # out a /drush the operator made on purpose, and a link is never followed. _fix_stray_drush() { local _sd_note _sd_dir if [ -d "/drush" ] && [ ! -L "/drush" ] \ && [ -f "/drush/drush.php" ] \ && [ -f "/drush/drush.info" ] \ && [ -f "/drush/includes/boa_extension_filter.inc" ]; then rm -rf /drush _sd_note="NOTE: stray Drush tree at /drush removed (the master Drush arm's empty-root copy)" _msg "${_sd_note}" if [ -d "${_pthLog}" ]; then echo "$(date) ${_sd_note}" >> ${_pthLog}/stray-drush.incident.log fi fi for _sd_dir in /backups/system/drush-pre-*; do if [ -d "${_sd_dir}" ] && [ ! -L "${_sd_dir}" ] && [ -f "${_sd_dir}/drush.php" ]; then rm -rf "${_sd_dir}" _sd_note="NOTE: stray Drush copy at ${_sd_dir} removed" _msg "${_sd_note}" if [ -d "${_pthLog}" ]; then echo "$(date) ${_sd_note}" >> ${_pthLog}/stray-drush.incident.log fi fi done # The directories the same copy created; rmdir removes only an empty one, # and /backups is left alone when anything mounts there. rmdir /backups/system &> /dev/null if [ -d "/backups" ] && ! mountpoint -q /backups 2> /dev/null \ && ! grep -qs "[[:space:]]/backups[[:space:]]" /etc/fstab; then rmdir /backups &> /dev/null fi } _fix_ping_perms() { if [ -e "/bin/ping" ]; then _PING_TEST=$(ls -la /bin/ping | grep rwsr-xr-x 2>&1) if [ -z "${_PING_TEST}" ]; then chown root:root /bin/ping chmod 4755 /bin/ping fi fi } _fix_fpm_process_max() { if [ ! -e "${_pthLog}/process.max.ctrl.${_tRee}.${_xSrl}.pid" ]; then sed -i "s/process.max =.*/process.max = 0/g" /opt/php*/etc/php*-fpm.conf touch ${_pthLog}/process.max.ctrl.${_tRee}.${_xSrl}.pid fi } _fix_node_in_lshell_access() { if [ ! -e "${_pthLog}/node.lshell-fix-npx.ctrl.${_tRee}.${_xSrl}.pid" ] \ && [ -e "/etc/lshell.conf" ]; then _PrTestPhantom=$(grep "PHANTOM" /root/.*.octopus.cnf 2>&1) _PrTestCluster=$(grep "CLUSTER" /root/.*.octopus.cnf 2>&1) _PrTestUltra=$(grep "ULTRA" /root/.*.octopus.cnf 2>&1) _PrTestMonster=$(grep "MONSTER" /root/.*.octopus.cnf 2>&1) if [[ "${_PrTestPhantom}" =~ "PHANTOM" ]] \ || [[ "${_PrTestCluster}" =~ "CLUSTER" ]] \ || [[ "${_PrTestUltra}" =~ "ULTRA" ]] \ || [[ "${_PrTestMonster}" =~ "MONSTER" ]] \ || [ -e "/root/.allow.node.lshell.cnf" ]; then _ALLOW_NODE=YES else _ALLOW_NODE=NO sed -i \ -e "s/, 'node', 'npm', 'npx',/,/gi" \ -e "s/, 'scp',/,/gi" \ /etc/lshell.conf /var/xdrago/conf/lshell.conf fi touch ${_pthLog}/node.lshell-fix-npx.ctrl.${_tRee}.${_xSrl}.pid fi } _fix_php_in_lshell_access() { if [ ! -e "${_pthLog}/php.lshell-fix-php.ctrl.${_tRee}.${_xSrl}.pid" ] \ && [ -e "/etc/lshell.conf" ]; then _PrTestPhantom=$(grep "PHANTOM" /root/.*.octopus.cnf 2>&1) _PrTestCluster=$(grep "CLUSTER" /root/.*.octopus.cnf 2>&1) _PrTestUltra=$(grep "ULTRA" /root/.*.octopus.cnf 2>&1) _PrTestMonster=$(grep "MONSTER" /root/.*.octopus.cnf 2>&1) if [[ "${_PrTestPhantom}" =~ "PHANTOM" ]] \ || [[ "${_PrTestCluster}" =~ "CLUSTER" ]] \ || [[ "${_PrTestUltra}" =~ "ULTRA" ]] \ || [[ "${_PrTestMonster}" =~ "MONSTER" ]] \ || [ -e "/root/.allow.php.lshell.cnf" ]; then _ALLOW_PHP=YES else _ALLOW_PHP=NO sed -i \ -e "s/, 'php.*':.*php',/,/gi" \ -e "s/, '\/opt\/php.*',/,/gi" \ /etc/lshell.conf /var/xdrago/conf/lshell.conf fi touch ${_pthLog}/php.lshell-fix-php.ctrl.${_tRee}.${_xSrl}.pid fi } _if_fix_lshell() { if [ ! -e "/usr/local/etc/lshell.conf" ] \ && [ ! -L "/usr/local/etc/lshell.conf" ] \ && [ -e "/etc/lshell.conf" ]; then [ ! -d "/usr/local/etc" ] && mkdir -p /usr/local/etc ln -sfn /etc/lshell.conf /usr/local/etc/lshell.conf fi _LSHELL_VRN=0.11.15 # 0.11.x needs Python 3.10+; a box whose python3 is older (chimaera 3.9, # beowulf 3.7) keeps 0.10, which stays installable there so a broken shell # on a legacy box still heals instead of failing the 0.11 build every pass. _LSHELL_LEGACY_VRN=0.10 _pyMinor=$(python3 -c 'import sys; print(sys.version_info[1])' 2> /dev/null) if [ -n "${_pyMinor}" ] && [ "${_pyMinor}" -lt 10 ] 2> /dev/null; then _LSHELL_VRN="${_LSHELL_LEGACY_VRN}" fi _PATH_LSHELL="${_usrBin}/lshell" _LSHELL_CHK_VRN="${_LSHELL_VRN}" # This fetched path keeps lshell current like every other key tool (a # publish to a tree is its rollout), and it HEALS: a box whose lshell is # missing or broken gets its version back on the next quiet pass without # waiting for a barracuda upgrade (the incident case). local _LSHELL_NOW _LSHELL_BROKEN _LSHELL_SRC_MISSING _LSHELL_FORCE_REINSTALL=NO _LSHELL_BROKEN=NO _isLshell="$(which lshell)" # Broken shapes are read on the RAW output: the cut|awk token keeps one # word after the first hyphen, which turns "bad interpreter: ..." into # "bash:" and a Traceback into a path fragment -- neither matches anything. _LSHELL_RAW=$(${_isLshell} --version 2>&1 | tr -d "\n") _LSHELL_ITD=$(printf '%s' "${_LSHELL_RAW}" \ | cut -d"-" -f2 \ | awk '{ print $1}' 2>&1) if [ -z "${_isLshell}" ] \ || [ -z "${_PATH_LSHELL}" ] \ || [ -z "${_LSHELL_ITD}" ] \ || [[ "${_LSHELL_RAW}" =~ "Traceback" ]] \ || [[ "${_LSHELL_RAW}" =~ "bad interpreter" ]] \ || [[ "${_LSHELL_RAW}" =~ "ImportError" ]] \ || [[ "${_LSHELL_RAW}" =~ "ModuleNotFoundError" ]]; then _LSHELL_BROKEN=YES _LSHELL_FORCE_REINSTALL=YES elif [ "${_LSHELL_ITD}" != "${_LSHELL_CHK_VRN}" ]; then _LSHELL_FORCE_REINSTALL=YES fi # A version step whose tarball is not on the mirror yet is retried once an # hour, not on every pass: the apt, pip and fetch preamble below is the # price of a real reinstall, not of a missing file. A broken shell never # waits. _LSHELL_SRC_MISSING="${_pthLog}/lshell-src-missing-${_LSHELL_VRN}.log" if [ "${_LSHELL_FORCE_REINSTALL}" = "YES" ] \ && [ "${_LSHELL_BROKEN}" = "NO" ] \ && [ -n "$(find "${_LSHELL_SRC_MISSING}" -mmin -60 2>/dev/null)" ]; then _LSHELL_FORCE_REINSTALL=NO fi if [ "${_LSHELL_FORCE_REINSTALL}" = "YES" ]; then [ -f "/etc/lshell.conf" ] && cp -af /etc/lshell.conf /etc/lshell.conf-bak-${_LSHELL_VRN} _apt_clean_update # 0.11 needs pyparsing at runtime and setuptools>=61 to build; the Debian # packages satisfy both without a PyPI round-trip. apt-get install python3-pip python3-setuptools python3-pyparsing ${_aptYesUnth} -qq 2> /dev/null if [ -x "/usr/bin/pip3" ]; then _usePip=/usr/bin/pip3 elif [ -x "/usr/local/bin/pip3" ]; then _usePip=/usr/local/bin/pip3 fi _PIP_TEST=$(${_usePip} --version 2>&1) # The minor of the interpreter pip runs on, read from pip's own version # line: every 3.11+ interpreter is PEP 668 managed, not only the three # that were once listed here (a newer OS fell to the bare upgrade and # reprinted the externally-managed block every pass). _PIP_PY_MINOR=$(echo "${_PIP_TEST}" | sed -n 's/.*(python 3\.\([0-9][0-9]*\)).*/\1/p' | head -n 1) if [ -n "${_PIP_PY_MINOR}" ] && [ "${_PIP_PY_MINOR}" -ge 11 ] 2> /dev/null; then # PEP 668 (python 3.11+) refuses a system-wide pip upgrade without it, so # without the flag this printed a full "error: externally-managed-environment" # block into every lshell install log and changed nothing. The upgrade arm in # lib/functions/system.sh.inc asks the same way, and the two must not disagree # about the state of the interpreter they share. ${_usePip} install --upgrade pip --break-system-packages --root-user-action ignore else ${_usePip} install --upgrade pip fi cd /var/opt rm -rf lshell* _get_dev_src "lshell-${_LSHELL_VRN}.tar.gz" # A pin that runs ahead of its tarball on the mirror must not turn every # pass into a failed build that still kicks the logged-in shell users: # without an extracted source tree there is nothing to install, so the # installed lshell is kept, the kick below is skipped and the retry # marker holds the next attempts to once an hour. _LSHELL_NOW="${_LSHELL_ITD}" if [ -e "/var/opt/lshell-${_LSHELL_VRN}/pyproject.toml" ] \ || [ -e "/var/opt/lshell-${_LSHELL_VRN}/setup.py" ]; then rm -f "${_LSHELL_SRC_MISSING}" for _Files in `find /var/opt/lshell-${_LSHELL_VRN} -type f`; do sed -i "s/kicked/logged/g" ${_Files} &> /dev/null wait sed -i "s/Kicked/Logged/g" ${_Files} &> /dev/null wait done # No pre-emptive removal of the installed package: pip replaces the # previous dist only after the new one has BUILT, so a failed fetch or # build leaves the old lshell in place instead of locking every shell # user out. Verify the result and say so when it did not land. cd /var/opt/lshell-${_LSHELL_VRN} _PIP_TEST=$(${_usePip} --version 2>&1) _PIP_PY_MINOR=$(echo "${_PIP_TEST}" | sed -n 's/.*(python 3\.\([0-9][0-9]*\)).*/\1/p' | head -n 1) if [ -n "${_PIP_PY_MINOR}" ] && [ "${_PIP_PY_MINOR}" -ge 11 ] 2> /dev/null; then ${_usePip} install . --break-system-packages --root-user-action ignore else ${_usePip} install . fi _LSHELL_NOW=$(${_PATH_LSHELL} --version 2>&1 | tr -d "\n" | cut -d"-" -f2 | awk '{ print $1}' 2>&1) if [ "${_LSHELL_NOW}" != "${_LSHELL_CHK_VRN}" ]; then echo "OOPS: lshell ${_LSHELL_VRN} did not install (have: ${_LSHELL_NOW}); previous version kept" else touch ${_pthLog}/lshell-fix-build-${_LSHELL_VRN}.log fi else echo "OOPS: no lshell-${_LSHELL_VRN} source tree in /var/opt (fetch or extract failed); installed lshell ${_LSHELL_ITD:-none} kept" touch "${_LSHELL_SRC_MISSING}" fi [ -f "/etc/lshell.conf-bak-${_LSHELL_VRN}" ] && cp -af /etc/lshell.conf-bak-${_LSHELL_VRN} /etc/lshell.conf rm -f /etc/logrotate.d/lshell addgroup --system lshellg &> /dev/null addgroup --system ltd-shell-more &> /dev/null mkdir -p /var/log/lsh chown :lshellg /var/log/lsh # 1770: tenants write their own log here; the sticky bit keeps one off another's chmod 1770 /var/log/lsh &> /dev/null # Kick the logged-in lshell users, so the new lshell takes effect; # nothing to take effect when it did not land. Only members of the # lshell group: their login shell is mysecureshell, which hands lshellg # to lshell (/etc/ssh/sftp_config), so the shell field is not the # discriminator; an instance user or operator logged in over ssh is in # no such group, runs no lshell and has nothing to pick up. if [ "${_LSHELL_NOW}" = "${_LSHELL_CHK_VRN}" ] \ && [ "${_LSHELL_NOW}" != "${_LSHELL_ITD}" ]; then for _kickUsr in $(who | awk '$1 !~ /^root$/ { print $1 }' | sort -u); do if id -nG "${_kickUsr}" 2> /dev/null | grep -qw "lshellg" \ || [[ "$(getent passwd "${_kickUsr}" 2> /dev/null | cut -d: -f7)" =~ /lshell$ ]]; then pkill -KILL -u "${_kickUsr}" fi done fi fi if [ -e "${_usrBin}/lshell" ]; then chown root:users ${_usrBin}/lshell chmod 750 ${_usrBin}/lshell if [ ! -L "/usr/bin/lshell" ]; then ln -sfn ${_usrBin}/lshell /usr/bin/lshell &> /dev/null fi fi } ### ### The dump callers chunk large tables in parallel again on mydumper 1.x ### and reach every box within minutes of a publish, while the package pin ### in BARRACUDA.sh.txt lands only on a barracuda pass. Left to that, a box ### runs the adapted callers against the old build, with chunking off and a ### large table dumped and restored on one thread, until its next full ### upgrade. Bring the packaged build to the pin on this pass instead: the ### same deb from the same mirror path barracuda fetches, one completed ### attempt per pin recorded in a marker -- a pass that changed nothing ### leaves no marker and retries -- the barracuda pass staying the ### fallback. Only the packaged layout is handled here; a source build and ### its myloader are reconciled by barracuda. The pin below moves together ### with _MYQUICK_VRN_TWO in BARRACUDA.sh.txt; the commit guard checks ### parity. ### ### Busy when an install, a dump or a restore may be running: the markers ### are read the way their own readers read them -- a pid-bearing lock ### counts only while its owner is alive, the maintenance marker only ### while younger than four hours -- so a lock leaked by a killed run ### cannot hold the pin off a box. _myquick_busy() { local _mqPidFile _mqPid _install_in_flight && return 0 for _mqPidFile in /run/boa_sql_backup.pid /run/xoct.verb.pid /run/xcopy.verb.pid; do [ -s "${_mqPidFile}" ] || continue _mqPid=$(tr -dc '0-9' 2>/dev/null < "${_mqPidFile}" | head -c 12) [ -n "${_mqPid}" ] && kill -0 "${_mqPid}" 2>/dev/null && return 0 done [ -n "$(find /run/boa_sql_cluster_backup.pid -mmin -720 2>/dev/null)" ] && return 0 [ -n "$(find /run/boa_sql_maintenance.pid -mmin -240 2>/dev/null)" ] && return 0 pgrep -x mydumper > /dev/null 2>&1 && return 0 pgrep -x myloader > /dev/null 2>&1 && return 0 return 1 } _if_fix_myquick() { _MYQUICK_VRN_TWO=1.0.5-1 local _mqCode="" _mqDeb _mqNow _mqWas _mqVer _mqRan _mqHelp _mqMark _mqLocal _mqMark="${_pthLog}/myquick-deb-${_MYQUICK_VRN_TWO}.log" _mqLocal="/usr/local/bin/myloader" case "${_OS_CODE}" in excalibur|trixie) _mqCode=trixie ;; daedalus|bookworm) _mqCode=bookworm ;; chimaera|bullseye) _mqCode=bullseye ;; *) return 0 ;; esac [ -x "/usr/bin/mydumper" ] || return 0 ### A marker with an outcome line is a completed attempt. An empty one ### is the signature of the first cut of this arm, which touched it on ### failure too, so it must not stop the box from being looked at again. [ -s "${_mqMark}" ] && return 0 ### Packaged layout only: /usr/local/bin/mydumper absent, dangling, or ### the symlink barracuda points at the packaged binary. A real file ### there is a source build, left to barracuda's reconcile. if [ -e "/usr/local/bin/mydumper" ] \ && [ "$(readlink -f /usr/local/bin/mydumper 2>/dev/null)" != "/usr/bin/mydumper" ]; then return 0 fi _mqWas=$(/usr/bin/mydumper -V 2>&1 \ | grep -m1 "^mydumper v" \ | cut -d" " -f2 \ | tr -d "v,") [ "${_mqWas}" = "${_MYQUICK_VRN_TWO}" ] && return 0 ### Never under an install or upgrade (barracuda installs this package ### itself and stages its deb in /var/opt) and never during a dump or a ### restore: the nightly and cluster scripts, xoct and xcopy hold their ### markers for the whole run, and a swap between two databases would ### hand the remaining ones to the new build. Asked again after the ### fetch, which can take minutes. _myquick_busy && return 0 _mqDeb="mydumper_${_MYQUICK_VRN_TWO}.${_mqCode}_amd64.deb" _mqRan=NO rm -rf /var/opt/mydumper_* curl ${_crlGet} "${_urlDev}/${_mqDeb}.gz" -o "/var/opt/${_mqDeb}.gz" [ -e "/var/opt/${_mqDeb}.gz" ] && gunzip -q "/var/opt/${_mqDeb}.gz" ### The mirror answers a missing file with a page rather than a 404, and ### the file name is built here, not confirmed by the mirror: the deb's ### own Version field, pin and ~codename suffix together, is the proof ### that this is the build asked for, checked before dpkg replaces ### anything. if [ -e "/var/opt/${_mqDeb}" ]; then _mqVer=$(dpkg-deb -f "/var/opt/${_mqDeb}" Version 2>/dev/null) if [ "${_mqVer}" = "${_MYQUICK_VRN_TWO}~${_mqCode}" ]; then if _myquick_busy; then rm -rf /var/opt/mydumper_* return 0 fi dpkg -i "/var/opt/${_mqDeb}" &> /dev/null _mqRan=$? fi fi rm -rf /var/opt/mydumper_* _mqNow=$(/usr/bin/mydumper -V 2>&1 \ | grep -m1 "^mydumper v" \ | cut -d" " -f2 \ | tr -d "v,") if [ "${_mqNow}" != "${_MYQUICK_VRN_TWO}" ]; then ### dpkg swaps the binary before anything can be checked, so say which ### state the box is in. Nothing changed (no usable package from the ### mirror, the dpkg lock held): no marker, a later pass retries. The ### package went in but its binary does not run: the same package ### would fail the same way, so the marker records it and the ### barracuda pass takes over. if [ "${_mqRan}" = "NO" ]; then echo "OOPS: MyQuick ${_MYQUICK_VRN_TWO}: no ${_mqCode} package of that version came from the mirror; nothing changed, retrying on a later pass" elif [ "${_mqRan}" != "0" ] && [ "${_mqNow}" = "${_mqWas}" ]; then echo "OOPS: MyQuick ${_MYQUICK_VRN_TWO}: dpkg failed (rc ${_mqRan}) on the ${_mqCode} package; ${_mqWas} kept, retrying on a later pass" else echo "$(date) ${_mqCode} package: dpkg rc ${_mqRan} over ${_mqWas}, mydumper now reports: ${_mqNow}" > "${_mqMark}" echo "OOPS: MyQuick ${_MYQUICK_VRN_TWO}: after dpkg (rc ${_mqRan}) of the ${_mqCode} package over ${_mqWas} mydumper reports: ${_mqNow}; recorded in ${_mqMark}" fi return 0 fi ln -sfn /usr/bin/mydumper /usr/local/bin/mydumper ### The myloader path the callers resolve, derived as barracuda derives ### it for the packaged pair: 1.x removed --overwrite-tables, so older ### callers get a translating wrapper; a build taking both styles gets a ### plain symlink. A real file there is the leftover of a source build; ### barracuda's wrapper derivation removes it in this layout, and so ### does this, or the callers would pair the new mydumper with a ### myloader of another release. _mqHelp=$(/usr/bin/myloader --help 2>&1) if echo "${_mqHelp}" | grep -q "Usage"; then rm -f "${_mqLocal}" if ! echo "${_mqHelp}" \ | grep -qE "^[[:space:]]+(-o, )?--overwrite-tables[[:space:]]"; then cat > "${_mqLocal}" <= 1.0 removed --overwrite-tables; translate for older callers. # The target is pinned to the build the installed mydumper came from. _MYL_REAL=/usr/bin/myloader MYLOADERHDR cat >> "${_mqLocal}" <<'MYLOADEREOF' [[ -x "${_MYL_REAL}" ]] || _MYL_REAL=/usr/local/bin/myloader.bin [[ -x "${_MYL_REAL}" ]] || _MYL_REAL=/usr/bin/myloader _MYL_ARGS=() for _MYL_A in "$@"; do if [[ "${_MYL_A}" == "--overwrite-tables" ]]; then _MYL_ARGS+=("--drop-table=DROP") else _MYL_ARGS+=("${_MYL_A}") fi done exec "${_MYL_REAL}" "${_MYL_ARGS[@]}" MYLOADEREOF chmod 755 "${_mqLocal}" else ln -sfn /usr/bin/myloader "${_mqLocal}" fi fi ### Cron discards every line this pass prints, so the outcome lives in ### the marker. echo "$(date) ${_mqCode} package installed over ${_mqWas}: mydumper ${_mqNow}" > "${_mqMark}" echo "INFO: MyQuick ${_MYQUICK_VRN_TWO} installed from the ${_mqCode} package" } ### A Drupal 8 or newer core is created on Solr's stock configset and stays ### there until the site uploads its own files. That configset guesses the ### type of every unknown field from the first documents it takes, and the ### schema uploaded later can never write to an index typed that way. With ### update.autoCreateFields off, such a core refuses an unknown field with ### a 400 before anything is written, so the site sees at once that its ### configuration is not in place. Cores on uploaded or template configs ### name no such property and run as before. The stamp of the rewrite is a ### one-shot fact, so it lives under /etc/boa/control (0755, the stamp ### 0644, never the umask); a box carrying the earlier line rewrites it ### once and restarts Solr for it. The restart follows only a rewrite that ### changed the file and landed the flag: a stamp that cannot be written or ### a line that cannot be appended is logged, never a restart every tick. ### A replication standby and a box under the xmass Solr hold keep Solr ### down (manage_solr_config.sh and java.sh stand down on the same two ### markers), so neither is rewritten or restarted here. _fix_solr_defaults_line() { # ${1} = solr7 or solr9 ${2} = its stop port ${3} = its stop key local _svc="${1}" _port="${2}" _key="${3}" _file _stamp _sum _file="/etc/default/${_svc}.in.sh" _stamp="/etc/boa/control/${_svc}.in.005.fixed.pid" [ -x "/etc/init.d/${_svc}" ] && [ -e "${_file}" ] || return 0 if [ -e "${_stamp}" ] \ && grep -q "STOP\.PORT=${_port}" "${_file}" \ && grep -q "SOLR_START_WAIT=\"10\"" "${_file}" \ && grep -q "update\.autoCreateFields=false" "${_file}"; then return 0 fi _sum=$(md5sum "${_file}" 2> /dev/null | cut -d' ' -f1) sed -i "s/^SOLR_STOP_PORT.*//g" "${_file}" sed -i "s/^SOLR_STOP_KEY.*//g" "${_file}" sed -i "s/.*${_key}.*//g" "${_file}" sed -i "s/.*_WAIT.*//g" "${_file}" echo "SOLR_OPTS=\"\$SOLR_OPTS -DSTOP.PORT=${_port} -DSTOP.KEY=${_key} -Dupdate.autoCreateFields=false\"" >> "${_file}" echo "SOLR_START_WAIT=\"10\"" >> "${_file}" echo "SOLR_STOP_WAIT=\"10\"" >> "${_file}" echo "SOLR_WAIT_FOR_ZK=\"10\"" >> "${_file}" sed -i "/^$/d" "${_file}" if ! grep -q "update\.autoCreateFields=false" "${_file}"; then echo "SOLR-OPTS-ERROR: the ${_svc} line could not be written to ${_file} at $(date)" >> ${_pthLog}/_fix_start_stop_ports_solr.log return 0 fi mkdir -p /etc/boa/control 2> /dev/null chmod 0755 /etc/boa /etc/boa/control &> /dev/null touch "${_stamp}" 2> /dev/null chmod 0644 "${_stamp}" &> /dev/null [ -e "${_stamp}" ] \ || echo "SOLR-OPTS-ERROR: the stamp ${_stamp} could not be written at $(date)" >> ${_pthLog}/_fix_start_stop_ports_solr.log if [ "$(md5sum "${_file}" 2> /dev/null | cut -d' ' -f1)" != "${_sum}" ]; then echo "_restart${_svc^} at $(date)" >> ${_pthLog}/_fix_start_stop_ports_solr.log service "${_svc}" restart fi } _fix_start_stop_ports_solr() { if [ ! -e "/root/.standby.cnf" ] \ && [ ! -e "/var/log/boa/.xmass_solr_hold.pid" ]; then _fix_solr_defaults_line solr9 19099 mycustomkey9 _fix_solr_defaults_line solr7 19077 mycustomkey7 fi if [ -x "/etc/init.d/jetty9" ]; then _restartSolr4=FALSE ### Only jetty9's own entries count: any account can create names ### matching this glob in /tmp and would otherwise trip the kill below ### on every pass. _ctrl_jetty_nr=$(find /tmp -mindepth 1 -maxdepth 1 -user jetty9 \ -name 'jetty-0.0.0.0-8099-solr.war*' -exec ls -la {} + 2> /dev/null \ | wc -l 2>&1) if [[ ! "${_ctrl_jetty_nr}" =~ "No such file" ]] && [ "${_ctrl_jetty_nr}" -gt 8 ]; then _restartSolr4=TRUE fi if [ "${_restartSolr4}" = "TRUE" ]; then if [ ! -x "/etc/init.d/jenkins" ] && [ ! -e "/var/lib/jenkins" ]; then ### -execdir: removed from inside the directory holding each entry, ### so a directory an account owns on the way cannot be swapped for ### a link that points the removal elsewhere. env PATH=/usr/local/bin:/usr/bin:/bin find /tmp -mindepth 1 \ -user jetty9 -execdir rm -rf -- {} + 2>/dev/null pkill -9 -f '^[^ ]*java[0-9]* .*jetty9' echo "_restartSolr4 at $(date)" >> ${_pthLog}/_fix_start_stop_ports_solr.log fi fi fi } _fix_log4j_solr7() { _LOG4J_VRN=2.17.1 _DO_SOLR_RESTART= if [ -x "/etc/init.d/solr7" ] && [ -e "/etc/default/solr7.in.sh" ]; then if [ -e "/opt/solr-7.7.3" ] \ && [ ! -e "/opt/solr-7.7.3/server/lib/ext/log4j-core-${_LOG4J_VRN}.jar" ]; then cd /var/opt rm -rf apache-log4j* _get_dev_src "apache-log4j-${_LOG4J_VRN}-bin.tar.gz" if [ -e "/var/opt/apache-log4j-${_LOG4J_VRN}-bin/log4j-core-${_LOG4J_VRN}.jar" ]; then cd /var/opt/apache-log4j-${_LOG4J_VRN}-bin [ -d "/var/backups/log4j/solr-7.7.3" ] || mkdir -p /var/backups/log4j/solr-7.7.3 mv -f /opt/solr-7.7.3/server/lib/ext/log4j* /var/backups/log4j/solr-7.7.3/ rm -f /opt/solr-7.7.3/contrib/prometheus-exporter/lib/log4j* cp -af log4j-1.2-api-${_LOG4J_VRN}.jar /opt/solr-7.7.3/server/lib/ext/ cp -af log4j-core-${_LOG4J_VRN}.jar /opt/solr-7.7.3/server/lib/ext/ cp -af log4j-core-${_LOG4J_VRN}.jar /opt/solr-7.7.3/contrib/prometheus-exporter/lib/ cp -af log4j-slf4j-impl-${_LOG4J_VRN}.jar /opt/solr-7.7.3/server/lib/ext/ cp -af log4j-slf4j-impl-${_LOG4J_VRN}.jar /opt/solr-7.7.3/contrib/prometheus-exporter/lib/ cp -af log4j-api-${_LOG4J_VRN}.jar /opt/solr-7.7.3/server/lib/ext/ cp -af log4j-api-${_LOG4J_VRN}.jar /opt/solr-7.7.3/contrib/prometheus-exporter/lib/ chown root:root /opt/solr-7.7.3/server/lib/ext/log4j* chown root:root /opt/solr-7.7.3/contrib/prometheus-exporter/lib/log4j* _DO_SOLR_RESTART=YES fi fi if [ -e "/opt/solr-7.6.0" ] \ && [ ! -e "/opt/solr-7.6.0/server/lib/ext/log4j-core-${_LOG4J_VRN}.jar" ]; then cd /var/opt rm -rf apache-log4j* _get_dev_src "apache-log4j-${_LOG4J_VRN}-bin.tar.gz" if [ -e "/var/opt/apache-log4j-${_LOG4J_VRN}-bin/log4j-core-${_LOG4J_VRN}.jar" ]; then cd /var/opt/apache-log4j-${_LOG4J_VRN}-bin [ -d "/var/backups/log4j/solr-7.6.0" ] || mkdir -p /var/backups/log4j/solr-7.6.0 mv -f /opt/solr-7.6.0/server/lib/ext/log4j* /var/backups/log4j/solr-7.6.0/ rm -f /opt/solr-7.6.0/contrib/prometheus-exporter/lib/log4j* cp -af log4j-1.2-api-${_LOG4J_VRN}.jar /opt/solr-7.6.0/server/lib/ext/ cp -af log4j-core-${_LOG4J_VRN}.jar /opt/solr-7.6.0/server/lib/ext/ cp -af log4j-core-${_LOG4J_VRN}.jar /opt/solr-7.6.0/contrib/prometheus-exporter/lib/ cp -af log4j-slf4j-impl-${_LOG4J_VRN}.jar /opt/solr-7.6.0/server/lib/ext/ cp -af log4j-slf4j-impl-${_LOG4J_VRN}.jar /opt/solr-7.6.0/contrib/prometheus-exporter/lib/ cp -af log4j-api-${_LOG4J_VRN}.jar /opt/solr-7.6.0/server/lib/ext/ cp -af log4j-api-${_LOG4J_VRN}.jar /opt/solr-7.6.0/contrib/prometheus-exporter/lib/ chown root:root /opt/solr-7.6.0/server/lib/ext/log4j* chown root:root /opt/solr-7.6.0/contrib/prometheus-exporter/lib/log4j* _DO_SOLR_RESTART=YES fi fi _RESULT_LOG4J=$(grep "LOG4J_FORMAT_MSG_NO_LOOKUPS=true" /etc/default/solr7.in.sh 2>&1) if [[ ! "${_RESULT_LOG4J}" =~ "LOG4J" ]]; then echo "LOG4J_FORMAT_MSG_NO_LOOKUPS=true" >> /etc/default/solr7.in.sh fi if [[ ! "${_RESULT_LOG4J}" =~ "LOG4J" ]] || [ ! -z "${_DO_SOLR_RESTART}" ]; then #pkill -9 -f solr7 service solr7 restart &> /dev/null fi fi } # Each home belongs to its account, which can put a link at .ssh or at # authorized_keys (a chmod by name follows it anywhere on the box): the # modes are set only inside the pinned real .ssh, on the directory itself # and, through a handle, on a single-link regular authorized_keys. _ssh_modes_here() { perl -e "${_BOA_FCHMOD_PL}" 0600 authorized_keys chmod 0700 . } _fix_authorized_keys() { local _hOme if [ ! -e "${_pthLog}/_fix_authorized_keys.ctrl.${_tRee}.${_xSrl}.pid" ]; then for _hOme in /home/*; do [ -d "${_hOme}/.ssh" ] || continue _acct_in_real_dir "${_hOme}/.ssh" _ssh_modes_here &> /dev/null done touch ${_pthLog}/_fix_authorized_keys.ctrl.${_tRee}.${_xSrl}.pid fi } _fix_aio() { _AIO_FIX=$(grep "fs.aio-max-nr" /etc/sysctl.conf 2>&1) if [ -z "${_AIO_FIX}" ]; then echo "fs.aio-max-nr = 1048576" >> /etc/sysctl.conf fi } _fix_console_print() { _PRK_FIX=$(grep "kernel.printk" /etc/sysctl.conf 2>&1) if [ -z "${_PRK_FIX}" ]; then echo "kernel.printk = 4 1 1 7" >> /etc/sysctl.conf fi } _fix_java_symlinks() { if [ "${_OS_CODE}" = "jessie" ] && [ -x "/usr/lib/jvm/java-7-openjdk/jre/bin/java" ]; then if [ ! -e "/usr/bin/java" ] || [ ! -e "/etc/alternatives/java" ]; then ln -sfn /usr/lib/jvm/java-7-openjdk/jre/bin/java /etc/alternatives/java ln -sfn /etc/alternatives/java /usr/bin/java echo fixed java symlinks for ${_OS_CODE} fi fi if [ "${_OS_CODE}" = "stretch" ] && [ -x "/usr/lib/jvm/java-8-openjdk/jre/bin/java" ]; then if [ ! -e "/usr/bin/java" ] || [ ! -e "/etc/alternatives/java" ]; then ln -sfn /usr/lib/jvm/java-8-openjdk/jre/bin/java /etc/alternatives/java ln -sfn /etc/alternatives/java /usr/bin/java echo fixed java symlinks for ${_OS_CODE} fi fi if [ "${_OS_CODE}" = "excalibur" ] \ || [ "${_OS_CODE}" = "daedalus" ] \ || [ "${_OS_CODE}" = "chimaera" ]; then if [ ! -e "/usr/lib/jvm/java-21-openjdk" ] \ && [ -d "/usr/lib/jvm/java-21-openjdk-amd64" ]; then ln -sfn /usr/lib/jvm/java-21-openjdk-amd64 /usr/lib/jvm/java-21-openjdk fi if [ ! -e "/usr/bin/java21" ] \ && [ -e "/usr/lib/jvm/java-21-openjdk-amd64/bin/java" ]; then ln -sfn /usr/lib/jvm/java-21-openjdk-amd64/bin/java /usr/bin/java21 fi if [ ! -e "/usr/lib/jvm/java-17-openjdk" ] \ && [ -d "/usr/lib/jvm/java-17-openjdk-amd64" ]; then ln -sfn /usr/lib/jvm/java-17-openjdk-amd64 /usr/lib/jvm/java-17-openjdk fi if [ ! -e "/usr/bin/java17" ] \ && [ -e "/usr/lib/jvm/java-17-openjdk-amd64/bin/java" ]; then ln -sfn /usr/lib/jvm/java-17-openjdk-amd64/bin/java /usr/bin/java17 fi if [ ! -e "/usr/lib/jvm/java-11-openjdk" ] \ && [ -d "/usr/lib/jvm/java-11-openjdk-amd64" ]; then ln -sfn /usr/lib/jvm/java-11-openjdk-amd64 /usr/lib/jvm/java-11-openjdk fi if [ ! -e "/usr/bin/java11" ] \ && [ -e "/usr/lib/jvm/java-11-openjdk-amd64/bin/java" ]; then ln -sfn /usr/lib/jvm/java-11-openjdk-amd64/bin/java /usr/bin/java11 fi if [ -x "/etc/init.d/jenkins" ] && [ -e "/var/lib/jenkins" ]; then _LOOK_LIKE_JENKINS=TRUE elif [ -e "/etc/boa/.look.like.jenkins.cnf" ] \ || [ "${_FORCE_CI_BOX}" = "YES" ]; then _LOOK_LIKE_JENKINS=TRUE else _LOOK_LIKE_JENKINS=FALSE fi if [ "${_LOOK_LIKE_JENKINS}" = "TRUE" ] \ || [ "${_OS_CODE}" = "daedalus" ] \ || [ "${_OS_CODE}" = "excalibur" ]; then if [ -x "/usr/lib/jvm/java-17-openjdk/bin/java" ] \ && [ ! -e "/var/log/boa/.fixed-java17-symlinks.log" ]; then if [ -e "/usr/lib/jvm/java-17-openjdk-amd64" ]; then rm -f /usr/lib/jvm/default-java ln -sfn /usr/lib/jvm/java-17-openjdk-amd64 /usr/lib/jvm/default-java fi ln -sfn /usr/lib/jvm/java-17-openjdk/bin/java /etc/alternatives/java ln -sfn /etc/alternatives/java /usr/bin/java touch /var/log/boa/.fixed-java17-symlinks.log echo "Fixed Java 17 symlinks for ${_OS_CODE}" fi if [ -x "/usr/lib/jvm/java-21-openjdk/bin/java" ] \ && [ ! -e "/var/log/boa/.fixed-java21-symlinks.log" ]; then if [ -e "/usr/lib/jvm/java-21-openjdk-amd64" ]; then rm -f /usr/lib/jvm/default-java ln -sfn /usr/lib/jvm/java-21-openjdk-amd64 /usr/lib/jvm/default-java fi ln -sfn /usr/lib/jvm/java-21-openjdk/bin/java /etc/alternatives/java ln -sfn /etc/alternatives/java /usr/bin/java touch /var/log/boa/.fixed-java21-symlinks.log echo "Fixed Java 21 symlinks for ${_OS_CODE}" fi else if [ -x "/usr/lib/jvm/java-11-openjdk/bin/java" ]; then if [ -e "/usr/lib/jvm/java-11-openjdk-amd64" ]; then rm -f /usr/lib/jvm/default-java ln -sfn /usr/lib/jvm/java-11-openjdk-amd64 /usr/lib/jvm/default-java fi if [ ! -e "/usr/bin/java" ] || [ ! -e "/etc/alternatives/java" ]; then ln -sfn /usr/lib/jvm/java-11-openjdk/bin/java /etc/alternatives/java ln -sfn /etc/alternatives/java /usr/bin/java echo "Fixed Java 11 symlinks for ${_OS_CODE}" fi fi fi else if [ -x "/usr/lib/jvm/java-11-openjdk/bin/java" ]; then if [ ! -e "/usr/bin/java" ] || [ ! -e "/etc/alternatives/java" ]; then ln -sfn /usr/lib/jvm/java-11-openjdk/bin/java /etc/alternatives/java ln -sfn /etc/alternatives/java /usr/bin/java echo "Fixed Java 11 symlinks for ${_OS_CODE}" fi fi fi } _fix_composer_version() { _COMPOSER_VRN=2.10.3 if [ -x "/usr/local/bin/composer" ]; then _COMPOSER_IS=$(composer --no-interaction --version 2>&1 \ | tr -d "\n" \ | cut -d" " -f35 \ | awk '{ print $1}' 2>&1) if [ "${_COMPOSER_IS}" != "${_COMPOSER_VRN}" ]; then composer self-update ${_COMPOSER_VRN} &> /dev/null fi fi } _fix_sftp_server() { if [ -e "/etc/ssh/sshd_config" ]; then _SFTP_UMASK_TEST=$(grep "sftp-server -u 0002" /etc/ssh/sshd_config 2>&1) if [[ ! "${_SFTP_UMASK_TEST}" =~ "sftp-server -u 0002" ]]; then sed -i "s/^Subsystem.*//g" /etc/ssh/sshd_config echo "Subsystem sftp /usr/lib/openssh/sftp-server -u 0002" >> /etc/ssh/sshd_config sed -i "/^$/d" /etc/ssh/sshd_config service ssh restart 2> /dev/null fi fi } _fix_wkhtml_perms() { _WKHTML_ARRAY="/usr/local/bin/wkhtmltopdf \ /usr/bin/wkhtmltopdf \ /usr/bin/wkhtmltopdf-0.12.4 \ /usr/local/bin/wkhtmltoimage \ /usr/bin/wkhtmltoimage \ /usr/bin/wkhtmltoimage-0.12.4" for _WKHTML_ITEM in ${_WKHTML_ARRAY}; do if [ -x "${_WKHTML_ITEM}" ]; then _PERM_TEST=$(ls -la ${_WKHTML_ITEM} | grep rwxr-xr-x 2>&1) if [ -z "${_PERM_TEST}" ]; then chgrp root ${_WKHTML_ITEM} &> /dev/null chmod 755 ${_WKHTML_ITEM} &> /dev/null fi fi done } _fix_wkhtml() { if [ -x "/usr/local/bin/wkhtmltopdf" ] \ && [ -L "/usr/bin/wkhtmltopdf" ]; then rm -f /usr/bin/wkhtmltopdf cp -af /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf chgrp root /usr/bin/wkhtmltopdf &> /dev/null chmod 755 /usr/bin/wkhtmltopdf &> /dev/null fi if [ -x "/usr/local/bin/wkhtmltoimage" ] \ && [ -L "/usr/bin/wkhtmltoimage" ]; then rm -f /usr/bin/wkhtmltoimage cp -af /usr/local/bin/wkhtmltoimage /usr/bin/wkhtmltoimage chgrp root /usr/bin/wkhtmltoimage &> /dev/null chmod 755 /usr/bin/wkhtmltoimage &> /dev/null fi if [ -x "/usr/local/bin/wkhtmltopdf" ] \ && [ ! -e "/usr/bin/wkhtmltopdf" ]; then cp -af /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf chgrp root /usr/bin/wkhtmltopdf &> /dev/null chmod 755 /usr/bin/wkhtmltopdf &> /dev/null fi if [ -x "/usr/local/bin/wkhtmltoimage" ] \ && [ ! -e "/usr/bin/wkhtmltoimage" ]; then cp -af /usr/local/bin/wkhtmltoimage /usr/bin/wkhtmltoimage chgrp root /usr/bin/wkhtmltoimage &> /dev/null chmod 755 /usr/bin/wkhtmltoimage &> /dev/null fi if [ ! -x "/usr/local/bin/wkhtmltopdf" ] \ && [ -x "/usr/bin/wkhtmltopdf" ]; then rm -f /usr/local/bin/wkhtmltopdf cp -af /usr/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf chgrp root /usr/local/bin/wkhtmltopdf &> /dev/null chmod 755 /usr/local/bin/wkhtmltopdf &> /dev/null fi if [ ! -x "/usr/local/bin/wkhtmltoimage" ] \ && [ -x "/usr/bin/wkhtmltoimage" ]; then rm -f /usr/local/bin/wkhtmltoimage cp -af /usr/bin/wkhtmltoimage /usr/local/bin/wkhtmltoimage chgrp root /usr/local/bin/wkhtmltoimage &> /dev/null chmod 755 /usr/local/bin/wkhtmltoimage &> /dev/null fi } _fix_eldir() { if [ -e "/var/xdrago" ] \ && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ ! -e "${_eldirP}" ]; then mkdir -p /var/xdrago/conf curl ${_crlGet} "${_urlHmr}/patches/${_eldirF}" -o ${_eldirP} fi } # /data/conf (the current, pinned directory) kept as the Octopus upgrade # leaves it. A folder an account owns, or any account may write, is taken # back to root:root 0711: the writers there rename fresh files into place, and # a folder an earlier release left handed out would let the account swap a # name in between. The spider-protection switch, when neither of its names is # there (the root tools remove a link, FIFO or directory an account left at # one), is put back from the tools' own copy, so protection works again # before the next barracuda upgrade. _data_conf_keep_here() { local _o _o="$(stat -c '%u:%g:%a' . 2> /dev/null)" || return 1 if [ "${_o%:*}" != "0:0" ] || (( 8#${_o##*:} & 8#022 )); then chown root:root . && chmod 0711 . fi if [ ! -e ./nginx_high_load_off.conf ] && [ ! -L ./nginx_high_load_off.conf ] \ && [ ! -e ./nginx_high_load.conf ] && [ ! -L ./nginx_high_load.conf ] \ && [ -f /var/xdrago/conf/nginx_high_load_off.conf ]; then _acct_put_mode_here nginx_high_load_off.conf 0 0 644 \ < /var/xdrago/conf/nginx_high_load_off.conf fi } _data_conf_keep() { [ -d /data/conf ] && [ ! -L /data/conf ] || return 0 _acct_in_real_dir /data/conf _data_conf_keep_here &> /dev/null } # /data/conf/patches holds the patches every account's Drupal 10 and 11 # platforms are patched from. A run of an earlier release handed /data/conf # to the Octopus account it upgraded, so a name there can still be a link # that account put: the files are written and removed only inside the real # patches/, never through a link put there or at patches itself, and a FIFO # or a device put at a name is never opened (grep -D skip). _if_drupal_patches_update() { if [ -e "/var/xdrago" ]; then _BROKEN_UPDATE_TEST_A=$(grep -D skip "Under Construction" /data/conf/patches/* 2>&1) _BROKEN_UPDATE_TEST_B=$(grep -D skip "404 Not Found" /data/conf/patches/* 2>&1) if [ ! -z "${_BROKEN_UPDATE_TEST_A}" ] \ || [ ! -z "${_BROKEN_UPDATE_TEST_B}" ] \ || [ ! -e "/data/conf/patches/ctrl.f96.${_tRee}.${_xSrl}.pid" ]; then mkdir -p /data/conf/patches _acct_in_real_dir /data/conf/patches _acct_rm_all_here _acct_in_real_dir /data/conf/patches \ _acct_touch_here "ctrl.f96.${_tRee}.${_xSrl}.pid" fi fi } # Every name directly in the current (pinned) directory removed, as rm -f # dir/* removed them (the glob expands here). _acct_rm_all_here() { rm -f -- ./* } # The patch $1 fetched as /var/xdrago/conf/$1 (root's tree) through # _fetch_versioned with the serial $2, which holds a new serial while the # mirror still serves the bytes already there and revalidates within the # hour, and put as /data/conf/patches/$1, root's and 0644, when that is # missing or not a regular file, or when the fetched copy changed: through # one handle inside the real patches/ (_acct_put_mode_here). A patch fetched # only when missing kept, until the next release, whatever a mirror that # had not caught up handed over. _drupal_patch_get() { local _p="/var/xdrago/conf/${1}" _sum [ -d /var/xdrago/conf ] || mkdir -p /var/xdrago/conf _sum="$(md5sum "${_p}" 2> /dev/null | cut -d' ' -f1)" _fetch_versioned "${_p}" "${_urlHmr}/patches/${1}" "${2}" --mode 644 [ -s "${_p}" ] || return 0 if [ -f "/data/conf/patches/${1}" ] && [ ! -L "/data/conf/patches/${1}" ] \ && [ "$(md5sum "${_p}" 2> /dev/null | cut -d' ' -f1)" = "${_sum}" ]; then return 0 fi mkdir -p /data/conf/patches _acct_in_real_dir /data/conf/patches \ _acct_put_mode_here "${1}" 0 0 644 < "${_p}" } _fix_drupal_core_ten() { if [ -e "/var/xdrago" ]; then _drupal_patch_get "${_tenCorePatchFname}" f99 _drupal_patch_get "${_tenConsolePatchFname}" f99 fi } _fix_drupal_core_eleven() { if [ -e "/var/xdrago" ]; then _drupal_patch_get "${_elevenCorePatchFname}" f99 _drupal_patch_get "${_elevenConsolePatchFname}" f99 _drupal_patch_get "${_elevenValidatorPatchFname}" f99 fi } _fix_pure_ftpd() { if [ -e "/usr/local/etc/pure-ftpd.conf" ]; then _PAM_AUTH=$(grep "^PAMAuthentication" /usr/local/etc/pure-ftpd.conf 2>&1) if [ ! -z "${_PAM_AUTH}" ]; then sed -i "s/^PAMAuthentication/# PAMAuthentication/g" /usr/local/etc/pure-ftpd.conf killall -9 pure-ftpd &> /dev/null fi fi } # The Let's Encrypt helper comes through _fetch_versioned like every other # fetched file; the leftovers of earlier layouts are cleared once per release # and whenever one is back. The accounts get their copy from the # le_renewal_days_69 block of _update_agents, later in the same pass, once per # release: a helper that changes here drops that block's stamp, so the # accounts get the new bytes in this pass, not a release later. The hosting_le # module ships its own drush files: BOA no longer keeps copies to lay over it. _fix_hosting_le() { local _dhySum if [ -d "/var/xdrago/conf" ]; then if [ ! -e "${_pthLog}/dehydrated-up01.ctrl.${_tRee}.${_xSrl}.pid" ] \ || [ -e "/var/xdrago/${_provLeInc}" ] \ || [ -e "/var/xdrago/${_hoLeInc}" ] \ || [ -e "/var/xdrago/${_dehydName}" ] \ || [ -e "/root/${_provLeInc}" ] \ || [ -e "/root/hosting_le_vhost.drush.inc.ctrl.${_tRee}.${_xSrl}.pid" ] \ || [ -e "/root/${_hoLeInc}" ] \ || [ -e "${_legacyLeSh}" ]; then mkdir -p /var/xdrago/conf rm -f /var/xdrago/*.drush.inc* rm -f /root/*.drush.inc* rm -f ${_legacyLeSh} touch ${_pthLog}/dehydrated-up01.ctrl.${_tRee}.${_xSrl}.pid fi # The copies go with their fetch markers: a marker left behind would tell # a later fetch of a copy that it is already in place. rm -f "${_provLeIncFull}" "${_hoLeIncFull}" \ "${_pthLog}/${_provLeInc}".* "${_pthLog}/${_hoLeInc}".* _dhySum="$(md5sum "${_dehydSrcPath}" 2>/dev/null | cut -d' ' -f1)" _fetch_versioned "${_dehydSrcPath}" "${_urlHmr}/helpers/dehydrated" f99 --mode 644 if [ "$(md5sum "${_dehydSrcPath}" 2>/dev/null | cut -d' ' -f1)" != "${_dhySum}" ]; then rm -f "${_pthLog}/le_renewal_days_69.ctrl.${_tRee}.${_xSrl}.pid" fi fi } _fix_newrelic() { _PHP_EXT_DIR_84="/opt/php84/lib/php/extensions/no-debug-non-zts-20240924" _NR_SO="/usr/lib/newrelic-php5/agent/x64/newrelic-20240924.so" if [ -e "${_PHP_EXT_DIR_84}" ] \ && [ -e "${_NR_SO}" ] \ && [ ! -e "${_PHP_EXT_DIR_84}/newrelic.so" ]; then ln -sfn ${_NR_SO} ${_PHP_EXT_DIR_84}/newrelic.so service php84-fpm reload fi _PHP_EXT_DIR_74="/opt/php74/lib/php/extensions/no-debug-non-zts-20190902" _NR_SO="/usr/lib/newrelic-php5/agent/x64/newrelic-20190902.so" if [ -e "${_PHP_EXT_DIR_74}" ] \ && [ -e "${_NR_SO}" ] \ && [ ! -e "${_PHP_EXT_DIR_74}/newrelic.so" ]; then ln -sfn ${_NR_SO} ${_PHP_EXT_DIR_74}/newrelic.so service php74-fpm reload fi _PHP_EXT_DIR_71="/opt/php71/lib/php/extensions/no-debug-non-zts-20160303" _NR_SO="/usr/lib/newrelic-php5/agent/x64/newrelic-20160303.so" if [ -e "${_PHP_EXT_DIR_71}" ] \ && [ ! -e "${_NR_SO}" ] \ && [ -L "${_PHP_EXT_DIR_71}/newrelic.so" ]; then rm -f ${_PHP_EXT_DIR_71}/newrelic.so service php71-fpm reload fi _PHP_EXT_DIR_70="/opt/php70/lib/php/extensions/no-debug-non-zts-20151012" _NR_SO="/usr/lib/newrelic-php5/agent/x64/newrelic-20151012.so" if [ -e "${_PHP_EXT_DIR_70}" ] \ && [ ! -e "${_NR_SO}" ] \ && [ -L "${_PHP_EXT_DIR_70}/newrelic.so" ]; then rm -f ${_PHP_EXT_DIR_70}/newrelic.so service php70-fpm reload fi _PHP_EXT_DIR_56="/opt/php56/lib/php/extensions/no-debug-non-zts-20131226" _NR_SO="/usr/lib/newrelic-php5/agent/x64/newrelic-20131226.so" if [ -e "${_PHP_EXT_DIR_56}" ] \ && [ ! -e "${_NR_SO}" ] \ && [ -L "${_PHP_EXT_DIR_56}/newrelic.so" ]; then rm -f ${_PHP_EXT_DIR_56}/newrelic.so service php56-fpm reload fi } _fix_leftovers() { if [ -e "/data/disk/arch/static/control" ]; then rm -rf /data/disk/arch/static fi } # # The crawler-access fixes below edit nginx configs and templates that an # account owns (the master's too), in directories the account can write, so # no name there is trusted: each file is read once into the root-only work # directory of this run (_crwWrk, made by the orchestrator), bounded and # never through a link or from a FIFO, inside its pinned real directory; the # edit is made on that copy and put back as a fresh file with the owner, # group and mode the file had, renamed over the name (_acct_put_mode_here). # The file as it was is kept for the revert in the root-only _crwPrev, # never beside the file, with its owner, group and mode in a .own file. # _crwPrev="/var/backups/crawler-fix-prev" # $1 copied to ${_crwWrk}/src when it is a single-link regular file under # 1 MiB; prints its numeric owner, group and mode. _crawler_copy() { [ -n "${_crwWrk}" ] && [ -d "${_crwWrk}" ] || return 1 rm -f -- "${_crwWrk}/src" "${_crwWrk}/new" _acct_in_real_dir "${1%/*}" _crawler_copy_here "${1##*/}" "${_crwWrk}/src" } # ./$1 of the current (pinned) directory read into the root-only file $2, as # _crawler_copy describes; leftovers of the older in-place writer dropped. _crawler_copy_here() { local _st _typ _uid _gid _mod _sz _nl rm -f -- "./${1}".crawler-fix*.tmp 2> /dev/null _st=$(stat -c '%F|%u|%g|%a|%s|%h' -- "./${1}" 2> /dev/null) || return 1 IFS='|' read -r _typ _uid _gid _mod _sz _nl <<< "${_st}" case "${_typ}" in "regular file"|"regular empty file") ;; *) return 1 ;; esac [[ "${_uid}" =~ ^[0-9]+$ && "${_gid}" =~ ^[0-9]+$ \ && "${_mod}" =~ ^[0-7]+$ && "${_sz}" =~ ^[0-9]+$ ]] || return 1 [ "${_nl}" = "1" ] && [ "${_sz}" -lt 1048576 ] || return 1 _acct_read_here "${1}" > "${2}" || return 1 [ "$(stat -c %s -- "${2}" 2> /dev/null)" = "${_sz}" ] || return 1 echo "${_uid} ${_gid} ${_mod}" } # ${_crwWrk}/new put as $1 with the owner, group and mode $2 (as # _crawler_copy printed them). ${_crwWrk}/src, the file as it was, is kept # first in _crwPrev for the revert, unless kept there already (by this run, # or by one that stopped before its nginx test). An emptied result is put # back from the kept copy, as before. _crawler_store() { local _p="${_crwPrev}${1}" _kept=0 _u _g _m read -r _u _g _m <<< "${2}" if [ ! -f "${_p}" ]; then if ! ( umask 077 mkdir -p -- "${_p%/*}" && cp -- "${_crwWrk}/src" "${_p}" \ && echo "${2}" > "${_p}.own" ); then rm -f -- "${_p}" "${_p}.own" return 1 fi _kept=1 fi if ! _acct_in_real_dir "${1%/*}" _acct_put_mode_here "${1##*/}" \ "${_u}" "${_g}" "${_m}" < "${_crwWrk}/new"; then [ "${_kept}" = "1" ] && rm -f -- "${_p}" "${_p}.own" return 1 fi if ! _acct_in_real_dir "${1%/*}" test -s "./${1##*/}"; then read -r _u _g _m < "${_p}.own" _acct_in_real_dir "${1%/*}" _acct_put_mode_here "${1##*/}" \ "${_u}" "${_g}" "${_m}" < "${_p}" return 1 fi return 0 } # Every file kept in _crwPrev put back as it was, with the owner, group and # mode it had, inside its pinned real directory; the kept copies dropped. _crawler_revert() { local _p _f _u _g _m [ -d "${_crwPrev}" ] || return 0 while IFS= read -r -d '' _p; do _f="${_p#"${_crwPrev}"}" if read -r _u _g _m < "${_p}.own"; then _acct_in_real_dir "${_f%/*}" _acct_put_mode_here "${_f##*/}" \ "${_u}" "${_g}" "${_m}" < "${_p}" fi rm -f -- "${_p}" "${_p}.own" done < <(find "${_crwPrev}" -type f ! -name '*.own' -print0 2> /dev/null) } # # Strip the $is_bot 444 guard from crawler-essential nginx locations (sitemaps, # feeds, css/js/json, images, styles, public files): the $is_bot UA map matches # every legitimate search crawler, so the guard made Googlebot receive 444 # (Cloudflare 520, GSC "Sitemap could not be read") on assets it must fetch. # Guards on private/robots-disallowed locations are kept. The file is read # and put back as _crawler_copy and _crawler_store do it, from a root-only # work directory of this run: two cron schedules can start clear.sh in the # same second (:00), and a shared tmp path once let one instance validate a # tmp the other had just truncated, which emptied a live include. # _strip_isbot_xml_guards() { local _fIle="$1" local _sRc="${_crwWrk}/src" local _tMp="${_crwWrk}/new" local _own="" local _cntPre=0 local _cntPost=0 _own=$(_crawler_copy "${_fIle}") || return 1 grep -qF 'if ( $is_bot )' "${_sRc}" || return 1 _cntPre=$(grep -cF 'if ( $is_bot )' "${_sRc}") awk 'BEGIN { t["location ^~ /cdn/farfuture/ {"]=1 t["location ~* \\.r\\.(?:jpe?g|png|gif) {"]=1 t["location ~* /(?:.+)/files/(css|js|styles)/adaptive/(?:.+)$ {"]=1 t["location ~* /sites/.*/files/(css|js|styles)/(.*)$ {"]=1 t["location ~* /s3/files/(css|js|styles)/(.*)$ {"]=1 t["location ~* /sites/.*/files/imagecache/(.*)$ {"]=1 t["location ~* /(?:external|system)/ {"]=1 t["location ~* wysiwyg_fields/(?:plugins|scripts)/.*\\.(?:js|css) {"]=1 t["location ~* files/advagg_(?:css|js)/ {"]=1 t["location ~* \\.css$ {"]=1 t["location = /sw.js {"]=1 t["location ~* \\.(?:js|htc)$ {"]=1 t["location ~* \\.json$ {"]=1 t["location ~* ^/sites/.*/files/.*\\.json$ {"]=1 t["location @uncached {"]=1 t["location ^~ /files/ {"]=1 t["location ^~ /downloads/ {"]=1 t["location ~* ^.+\\.(?:pdf|jpe?g|gif|png|ico|webp|bmp|svg|swf|docx?|xlsx?|pptx?|tiff?|txt|rtf|vcard|vcf|bat|dll|class|otf|ttf|woff2?|eot|less|avi|mpe?g|mov|wmv|mp3|ogg|ogv|wav|midi|zip|tar|t?gz|rar|dmg|exe|apk|pxl|ipa|map)$ {"]=1 t["location ~* ^.+\\.(?:avi|mpe?g|mov|wmv|ogg|ogv|webm|zip|tar|t?gz|rar|dmg|exe|apk|pxl|ipa)$ {"]=1 t["location ~* ^/sites/.+/files/.+\\.(?:pdf|aspx?)$ {"]=1 t["location ~* ^.+\\.flv$ {"]=1 t["location ~* ^.+\\.(?:mp4|m4a)$ {"]=1 t["location ~* ^/sites/.*/files/ {"]=1 t["location ~* \\.xml$ {"]=1 } w == 1 { if ($0 == "") { print; next } if ($0 == " if ( $is_bot ) {") { if ((getline l2) > 0 && (getline l3) > 0) { if (l2 == " return 444;" && l3 == " }") { w = 0; next } print; print l2; print l3; w = 0; next } } w = 0 } { print } $0 in t { w = 1 }' "${_sRc}" > "${_tMp}" if [ "$?" -ne 0 ] || [ ! -s "${_tMp}" ]; then rm -f "${_tMp}" return 1 fi _cntPost=$(grep -cF 'if ( $is_bot )' "${_tMp}") ### Kept guards always survive a legitimate strip, so a zero count can ### only mean a corrupted read -- fail closed. if [ "${_cntPost}" -ge "${_cntPre}" ] || [ "${_cntPost}" -eq 0 ]; then rm -f "${_tMp}" return 1 fi _crawler_store "${_fIle}" "${_own}" } # # Remove legitimate preview/search bots (Pinterest, Sogou, TikTok) and the # generic Go-http-client library token from the $is_crawler hard-block roster; # SEO scrapers and bulk collectors stay blocked. Token edits are scoped to the # $is_crawler map block only and are no-ops once applied. # _relax_crawler_roster() { local _fIle="$1" local _sRc="${_crwWrk}/src" local _tMp="${_crwWrk}/new" local _own="" _own=$(_crawler_copy "${_fIle}") || return 1 grep -qF 'map $http_user_agent $is_crawler {' "${_sRc}" || return 1 sed '/map $http_user_agent $is_crawler {/,/^}/ { s/|TikTok//; s/Go-http-client|//; s/|Pinterest//; s/|Sogou//; }' \ "${_sRc}" > "${_tMp}" if [ "$?" -ne 0 ] || [ ! -s "${_tMp}" ] || cmp -s "${_sRc}" "${_tMp}"; then rm -f "${_tMp}" return 1 fi _crawler_store "${_fIle}" "${_own}" } # # Soften the Referer-less /print* guard from 444 to 404: search crawlers never # send a Referer, so 444 reached them as Cloudflare 520 / proxy 502 and filled # GSC with 5xx churn; a static 404 is equally php-fpm-free and the correct # crawl outcome for duplicate print pages. # _soften_print_guard() { local _fIle="$1" local _sRc="${_crwWrk}/src" local _tMp="${_crwWrk}/new" local _own="" _own=$(_crawler_copy "${_fIle}") || return 1 grep -qF 'block_print_no_referer' "${_sRc}" || return 1 sed '/^if ($block_print_no_referer) {$/,/^}$/ s/^ return 444;$/ return 404;/' \ "${_sRc}" > "${_tMp}" if [ "$?" -ne 0 ] || [ ! -s "${_tMp}" ] || cmp -s "${_sRc}" "${_tMp}"; then rm -f "${_tMp}" return 1 fi _crawler_store "${_fIle}" "${_own}" } # # Restore the bare 'bot' token in the $deny_on_high_load map. That roster is # byte-identical to $is_bot minus this one token, so a UA self-identifying only # as '...bot' matched $is_bot but never high-load shedding, leaving Spider # Protection a silent no-op against it: an observed distributed crawler drove # a hosted box to load 264 while being served zero 503s. Scoped to the map # block, fail-closed on a customised roster, and a no-op once applied. # _fix_high_load_bot_token() { local _fIle="$1" local _sRc="${_crwWrk}/src" local _tMp="${_crwWrk}/new" local _own="" _own=$(_crawler_copy "${_fIle}") || return 1 grep -qF 'map $http_user_agent $deny_on_high_load {' "${_sRc}" || return 1 sed '/map $http_user_agent $deny_on_high_load {/,/^}/ s/~\*crawl|spider|/~*crawl|bot|spider|/' \ "${_sRc}" > "${_tMp}" if [ "$?" -ne 0 ] || [ ! -s "${_tMp}" ] || cmp -s "${_sRc}" "${_tMp}"; then rm -f "${_tMp}" return 1 fi _crawler_store "${_fIle}" "${_own}" } # # Orchestrator: apply all four crawler-access fixes to the Aegir master and # every Octopus account -- provision templates (so verify tasks re-render the # fixed config) AND already-rendered includes (so the fix is live without a # verify). Reload nginx only when something changed and the config test # passes; on a failed test restore every file changed in this run. # The whole run holds the shared nginx-config lock: clear.sh is scheduled # twice (*/5 root crontab + the autoupboa /etc/crontab line) and both fire # at :00, so without the lock two instances race each other over the same # files -- that race emptied a rendered include on a hosted box (2026-07-23). # _log_crawler_fix() { echo "$1" echo "$(date 2> /dev/null) $1" >> "${_pthLog}/crawler-access-fix.log" } _fix_nginx_crawler_access() { local _bAse="" local _fIle="" local _chAnged=0 local _crwWrk="" local _tPl="/.drush/sys/provision/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php" local _sTpl="/.drush/sys/provision/http/Provision/Config/Nginx/server.tpl.php" [ -e "/var/aegir/config/includes/nginx_vhost_common.conf" ] || return 0 ( ### Boxes without util-linux flock still run, protected by the per-run ### root-only work directory and post-write verification in the helpers. { exec 9> /run/boa_nginx_config.lock; } 2> /dev/null if command -v flock > /dev/null 2>&1; then if ! flock -w 30 9; then _log_crawler_fix "NOTE: crawler-access fix: nginx-config lock busy, skipping this run" exit 0 fi fi if ! _crwWrk=$(mktemp -d /run/.crawler-fix.XXXXXX 2> /dev/null); then _log_crawler_fix "ERROR: crawler-access fix: no root-only work directory in /run, skipping this run" exit 0 fi for _bAse in /var/aegir /data/disk/*; do [ -d "${_bAse}" ] || continue for _fIle in "${_bAse}${_tPl}" "${_bAse}/config/includes/nginx_vhost_common.conf"; do if _strip_isbot_xml_guards "${_fIle}"; then _chAnged=1 _log_crawler_fix "NOTE: crawler-access fix: is_bot guards stripped in ${_fIle}" fi if _soften_print_guard "${_fIle}"; then _chAnged=1 _log_crawler_fix "NOTE: crawler-access fix: print guard softened in ${_fIle}" fi done for _fIle in "${_bAse}${_sTpl}" "${_bAse}/config/server_master/nginx.conf"; do if _relax_crawler_roster "${_fIle}"; then _chAnged=1 _log_crawler_fix "NOTE: crawler-access fix: is_crawler roster relaxed in ${_fIle}" fi if _fix_high_load_bot_token "${_fIle}"; then _chAnged=1 _log_crawler_fix "NOTE: crawler-access fix: high-load bot token restored in ${_fIle}" fi done done if [ "${_chAnged}" = "1" ]; then if nginx -t &> /dev/null; then rm -rf -- "${_crwPrev}" service nginx reload &> /dev/null _log_crawler_fix "NOTE: crawler-access fix applied, nginx reloaded" else _crawler_revert _log_crawler_fix "ERROR: crawler-access fix reverted -- nginx -t failed after patching" fi fi rm -rf -- "${_crwWrk}" ) } # # TRUE while any BOA install or upgrade may be consuming /opt/tmp/boa: the # chained install drops boa_run/boa_wait mid-chain by design and its octopus # leg runs as "bash /var/backups/OCTOPUS.sh.txt", so the lock pids alone are # not enough -- sweep the marker AND the live process forms. This gate is # self-contained on purpose: BOA.sh.txt reaches every box on the next cron # fetch, ahead of any fNN-pinned tool update. # _install_in_flight() { [ -e "/run/boa_run.pid" ] && return 0 [ -e "/run/boa_wait.pid" ] && return 0 [ -e "/run/octopus_install_run.pid" ] && return 0 # Anchored, not a bare substring: a command line that merely MENTIONS the # wrapper -- an editor, a checksum, an operator's ssh probe -- must not read # as an install in flight and defer the agent indefinitely. pgrep -f "^(/[^ ]*/)?bash (-c )?/(opt|usr)/local/bin/barracuda( |$)" > /dev/null 2>&1 && return 0 pgrep -f "^(/[^ ]*/)?bash (-c )?/(opt|usr)/local/bin/octopus( |$)" > /dev/null 2>&1 && return 0 # Anchored: a LOCAL install only -- "ssh root@box /opt/local/bin/boa in-" # (a remote install driven from this box) must not defer this box pgrep -f "^(/[^ ]*/)?bash (-c )?/(opt|usr)/local/bin/boa in-" > /dev/null 2>&1 && return 0 # Dual-path for the staging move: match both the old /var/backups home and # the root-only /var/opt/boa-dist one, because tools arrive per file and a # box runs whatever mix it has converged on. A watcher that knows only one # path would read a box as idle in the middle of an install. Anchored on # the executor for the same reason as the wrapper arms above; left open at # the tail because the octopus leg runs as "OCTOPUS.sh.txt.oN". pgrep -f "^(/[^ ]*/)?bash (-c )?/(var/backups|var/opt/boa-dist)/(BARRACUDA|OCTOPUS)\.sh\.txt" > /dev/null 2>&1 && return 0 # The Ægir setup children ("su - oN -c bash .../AegirSetupC.sh.txt") match # none of the forms above and are the phase that consumes /opt/tmp/boa the # longest. Anchored on the executor so a remotely driven install cannot # defer this box; the [ABC] class keeps it from matching its own pgrep pgrep -f "^(/[^ ]*/)?(bash|sh|su) .*/aegir/scripts/AegirSetup[ABC]\.sh\.txt" > /dev/null 2>&1 && return 0 return 1 } # # Self-heal for nginx configs wiped by the pre-hardening crawler-fix race # (or any other crashed writer): these files are never legitimately empty, # so an empty file older than 10 minutes means a crashed write, not a # render in progress. Recovery queues the account's own on-demand Octopus # upgrade via the documented tenant trigger consumed by run-xdrago, which # re-renders every config from fresh code. run-upgrade.pid is ignored # there unless platforms.info exists, so seed an inert list when the # account has none: it must be UPPERCASE to survive the [ 0-9A-Z] # sanitizer in the satellite code -- a lowercase word sanitizes to empty # and falls back to installing the full default platforms list. # _fix_wiped_nginx_config() { local _bAse="" local _uSr="" local _fIle="" local _wIped="" local _ctlDir="" local _bLog="" local _bcdaLaunched=0 local _rBoot="" local _rBootLogged=0 local _hName="" local _tPl="/.drush/sys/provision/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php" local _sTpl="/.drush/sys/provision/http/Provision/Config/Nginx/server.tpl.php" for _bAse in /data/disk/*; do [ -d "${_bAse}/config" ] || continue _wIped="" for _fIle in "${_bAse}/config/includes/nginx_vhost_common.conf" \ "${_bAse}/config/server_master/nginx.conf" \ "${_bAse}${_tPl}" "${_bAse}${_sTpl}"; do [ -f "${_fIle}" ] || continue if [ -n "$(find "${_fIle}" -maxdepth 0 -type f -size 0 -mmin +10 2> /dev/null)" ]; then _wIped="${_fIle}" break fi done [ -n "${_wIped}" ] || continue ### Replication standby: never arm the heal here. The trigger pair ### (run-upgrade.pid + a seeded platforms.info) lands in the SYNCED ### static/control tree, its only consumer (run-xdrago via runner.sh) ### is parked for the whole standby window, and nothing clears it at ### cutover -- so the first post-promotion runner tick would fire an ### unattended full Octopus upgrade on the fresh production box, and ### the inert platforms list would shadow the real one. The active ### heals its own wipe and the sync leg re-delivers the good config. if [ -e "/root/.standby.cnf" ]; then _log_crawler_fix "NOTE: wiped nginx config detected (${_wIped}); replication standby, arming skipped" continue fi ### The octopus installer refuses to run while barracuda lags the ### release (same "Barracuda ${_rLsn}-" test it applies), so bring the ### base system current first; the wipe is still present on the next ### battery pass, which then arms the account upgrade normally. _bLog=$(cat /var/log/barracuda_log.txt 2> /dev/null) if [[ ! "${_bLog}" =~ "Barracuda ${_rLsn}-" ]]; then ### The purge below empties the shared build staging, so it must ### never fire while an install may still be consuming it -- the ### old boa_run.pid-only test opened exactly mid-octopus-leg; the ### wipe persists, so a deferred heal simply arms on a later pass. if [ "${_bcdaLaunched}" = "0" ] && ! _install_in_flight; then _bcdaLaunched=1 _log_crawler_fix "NOTE: wiped nginx config detected (${_wIped}); barracuda ${_rLsn} upgrade launched first" rm -rf /opt/tmp/* nohup /opt/local/bin/barracuda up-${_tRee} system noscreen \ >> /var/log/boa/barracuda-selfheal.log 2>&1 & fi continue fi ### The barracuda run may stage a new kernel, and on auto-reboot boxes ### autoupboa fires a gated boa reboot that could land mid-octopus ### (its safeguards have been beaten before). That reboot only fires ### inside the 04:00-04:59 night window now, so an armed octopus run ### is at risk only when it starts shortly before or inside that ### window: defer arming during 02:00-04:59 while a kernel reboot is ### pending -- the wipe persists, so the next pass arms cleanly. At ### any other hour the reboot cannot fire and arming is safe. if [ -z "${_rBoot}" ]; then _rBoot=0 case "$(date +%H)" in 02|03|04) _hName="$(cat /etc/hostname 2> /dev/null | tr -d '\n')" [ -n "${_hName}" ] || _hName="$(hostname -f 2> /dev/null)" if [ -e "/root/.allow.auto.reboot.cnf" ] || [ -e "/root/.host8.cnf" ] \ || [[ "${_hName}" =~ ".aegir.cc"($) ]]; then if { [ -e "/run/reboot-required.pkgs" ] \ && grep -q linux /run/reboot-required.pkgs 2> /dev/null; } \ || [ "$(/opt/local/bin/boa info 2> /dev/null | grep -c Next)" -ge 1 ]; then _rBoot=1 fi fi ;; esac fi if [ "${_rBoot}" = "1" ]; then if [ "${_rBootLogged}" = "0" ]; then _rBootLogged=1 _log_crawler_fix "NOTE: wiped nginx config detected (${_wIped}); kernel reboot pending, arming deferred" fi continue fi _uSr=$(basename "${_bAse}") _acct_in_real_dir "${_bAse}/static" _acct_ctrl_dir_here if _acct_in_real_dir "${_bAse}/static/control" _ctrl_upgrade_here \ "${_uSr}.ftp:$(_acct_group "${_uSr}")"; then _log_crawler_fix "NOTE: wiped nginx config detected (${_wIped}); octopus upgrade queued for ${_uSr}" fi done } _force_rebuild() { if [ ! -e "${_pthLog}/forced.rebuild.glibc.txt" ]; then echo "_GIT_FORCE_REINSTALL=YES" >> ${_barCnf} echo "_NGX_FORCE_REINSTALL=YES" >> ${_barCnf} echo "_PHP_FORCE_REINSTALL=YES" >> ${_barCnf} echo "_SSH_FORCE_REINSTALL=YES" >> ${_barCnf} echo "_SSL_FORCE_REINSTALL=YES" >> ${_barCnf} rm -f ${_pthLog}/pure-ftpd-build* rm -f ${_pthLog}/mss-build* rm -f ${_pthLog}/lshell-build* rm -f ${_pthLog}/redis-* rm -f ${_pthLog}/valkey-* touch ${_pthLog}/forced.rebuild.glibc.txt fi } # # Detect, remove, and report broken symlinks _check_and_remove_broken_symlinks() { local _dir=$1 # Find broken symlinks in the directory _broken_symlinks=$(find "${_dir}" -maxdepth 1 -type l ! -exec test -e {} \; -print) if [ -n "${_broken_symlinks}" ]; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "CLNP: Removing the following broken symlinks from ${_dir}:" echo "CLNP: ${_broken_symlinks}" fi for _symlink in ${_broken_symlinks}; do rm "${_symlink}" if [ "${_DEBUG_MODE}" = "YES" ]; then echo "CLNP: Removed broken symlink: ${_symlink}" fi done # Set the _ifAnySymlinksCleaned variable to true since we removed broken symlinks _ifAnySymlinksCleaned=YES else if [ "${_DEBUG_MODE}" = "YES" ]; then echo "CLNP: No broken symlinks found in ${_dir}" fi fi } # # Check and move disallowed versions _check_and_move() { local _dir=$1 # Determine the name of the backup subdirectory based on the source directory local _backup_dir="${_backLegBase}$(echo "${_dir}" | tr '/' '_')" # Find any libcurl.so files in the directory, excluding the allowed version and those without a complete version number _found_versions=$(find "${_dir}" -maxdepth 1 -type f -name "libcurl.so.*" ! -name "${_allowedFile}" | grep -E "libcurl\.so\.[0-9]+\.[0-9]+\.[0-9]+$") if [ -n "${_found_versions}" ]; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "CLNP: Moving the following disallowed versions from ${_dir} to ${_backup_dir}:" echo "CLNP: ${_found_versions}" fi # Create the backup directory if it doesn't exist mkdir -p "${_backup_dir}" # Move each found version to the backup directory for _file in ${_found_versions}; do mv -f "${_file}" "${_backup_dir}/" if [ "${_DEBUG_MODE}" = "YES" ]; then echo "CLNP: Moved ${_file} to ${_backup_dir}/" fi done # Set the _ifAnyFilesCleaned variable to true since we moved files _ifAnyFilesCleaned=YES else if [ "${_DEBUG_MODE}" = "YES" ]; then echo "CLNP: Only the allowed version (${_allowedFile}) is present in ${_dir}" fi fi } _if_reinstall_curl() { _CURL_VRN=8.23.0 _CURL_INSTALL_REQUIRED=NO if ! command -v lsb_release &> /dev/null; then apt-get update -qq &> /dev/null apt-get install lsb-release ${_aptYesUnth} -qq 2> /dev/null fi _OS_CODE=$(lsb_release -ar 2>/dev/null | grep -i codename | cut -s -f2) [ "${_OS_CODE}" = "wheezy" ] && _CURL_VRN=7.50.1 [ "${_OS_CODE}" = "jessie" ] && _CURL_VRN=7.71.1 [ "${_OS_CODE}" = "stretch" ] && _CURL_VRN=8.2.1 if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ] \ && [ "${_OS_CODE}" != "jessie" ] \ && [ "${_OS_CODE}" != "stretch" ]; then # Target version _allowedFile="libcurl.so.4.8.0" # Directories to check _dirsToClean=("/usr/lib" "/usr/local/lib" "/usr/lib/x86_64-linux-gnu") # Backup base directory _backLegBase="/var/backups/legacy-libcurl-boa-${_NOW}" # Variable to track if any files were moved _ifAnyFilesCleaned=NO # Variable to track if any broken symlinks were found and removed _ifAnySymlinksCleaned=NO # Iterate over the directories and apply the _check_and_move function for _dir in "${_dirsToClean[@]}"; do _check_and_move "${_dir}" done # Iterate over the directories and apply the _check_and_remove_broken_symlinks function for _dir in "${_dirsToClean[@]}"; do _check_and_remove_broken_symlinks "${_dir}" done # Export the _ifAnyFilesCleaned variable for later use export _ifAnyFilesCleaned # Export the _ifAnySymlinksCleaned variable for later use export _ifAnySymlinksCleaned fi if [ "${_ifAnySymlinksCleaned}" = "YES" ] \ || [ "${_ifAnyFilesCleaned}" = "YES" ]; then ldconfig 2> /dev/null _CURL_INSTALL_REQUIRED=YES _bkLibcurlPre="/var/backups/legacy-libcurl-pre-${_CURL_VRN}-${_NOW}" mkdir -p ${_bkLibcurlPre} mv -f /usr/lib/x86_64-linux-gnu/libcurl.so* ${_bkLibcurlPre}/ &> /dev/null mv -f /usr/lib/x86_64-linux-gnu/libcurl.la ${_bkLibcurlPre}/ &> /dev/null mv -f /usr/lib/x86_64-linux-gnu/libcurl.a ${_bkLibcurlPre}/ &> /dev/null fi _isCurl=$(curl --version 2>&1) if [[ ! "${_isCurl}" =~ "OpenSSL" ]] \ || [[ "${_isCurl}" =~ "libcurl.so.4" ]] \ || [ -z "${_isCurl}" ] \ || [ "${_ifAnySymlinksCleaned}" = "YES" ] \ || [ "${_ifAnyFilesCleaned}" = "YES" ] \ || [ "${_CURL_INSTALL_REQUIRED}" = "YES" ]; then if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then echo "OOPS: cURL is broken! Re-installing.." fi if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi echo "curl install" | dpkg --set-selections 2> /dev/null _apt_clean_update # Check for libssl1.0-dev and remove conditionally if dpkg-query -W -f='${Status}' libssl1.0-dev 2>/dev/null | grep -q "install ok installed"; then apt-get remove libssl1.0-dev -y --purge --auto-remove -qq 2>/dev/null fi apt-get autoremove -y 2> /dev/null apt-get install libssl-dev ${_aptYesUnth} -qq 2> /dev/null apt-get build-dep curl ${_aptYesUnth} -qq 2> /dev/null if [ ! -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then apt-get install curl --reinstall ${_aptYesUnth} -qq 2> /dev/null fi if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then echo "INFO: Installing curl from sources..." mkdir -p /var/opt rm -rf /var/opt/curl* cd /var/opt wget ${_wgetGet} https://files.boa.io/dev/src/curl-${_CURL_VRN}.tar.gz &> /dev/null tar --no-same-owner -xzf curl-${_CURL_VRN}.tar.gz &> /dev/null if [ -e "/root/.install.modern.openssl.cnf" ] \ && [ -x "/usr/local/ssl3/bin/openssl" ]; then _SSL_BINARY=/usr/local/ssl3/bin/openssl else _SSL_BINARY=/usr/local/ssl/bin/openssl fi if [ -e "/usr/local/ssl3/lib64/libssl.so.3" ]; then _SSL_PATH="/usr/local/ssl3" _SSL_LIB_PATH="${_SSL_PATH}/lib64" else _SSL_PATH="/usr/local/ssl" _SSL_LIB_PATH="${_SSL_PATH}/lib" fi _PKG_CONFIG_PATH="${_SSL_LIB_PATH}/pkgconfig" if [ -e "${_PKG_CONFIG_PATH}" ] \ && [ -e "/var/opt/curl-${_CURL_VRN}" ]; then cd /var/opt/curl-${_CURL_VRN} LIBS="-ldl -lpthread" PKG_CONFIG_PATH="${_PKG_CONFIG_PATH}" ./configure \ --with-openssl \ --with-zlib=/usr \ --prefix=/usr/local &> /dev/null make -j $(nproc) --quiet &> /dev/null make --quiet install &> /dev/null ldconfig 2> /dev/null fi fi if [ -x "/usr/local/bin/curl" ] && [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then _CURL_ITD=$(/usr/local/bin/curl --version 2>&1 \ | tr -d "\n" \ | cut -d" " -f2 \ | awk '{ print $1}' 2>&1) if [[ ! "${_CURL_ITD}" =~ OpenSSL ]]; then echo "ERRR: /usr/local/bin/curl is broken" echo "ERRR: Please install cURL and debug manually" else echo "GOOD: /usr/local/bin/curl works" echo "curl hold" | dpkg --set-selections &> /dev/null if [ -x "/usr/local/bin/curl" ]; then if [ -x "/usr/bin/curl" ] && [ ! -L "/usr/bin/curl" ]; then mv -f /usr/bin/curl /usr/bin/old-curl-$(date +%y%m%d-%H%M%S) fi ln -sfn /usr/local/bin/curl /usr/bin/curl fi if [ ! -e "${_SSL_PATH}/certs/ca-certificates.crt" ]; then cp -af /etc/ssl/certs/* ${_SSL_PATH}/certs/ &> /dev/null fi if [ -e "/usr/local/lib/libcurl.so.4.8.0" ]; then ln -sfn /usr/local/lib/libcurl.so.4.8.0 /usr/lib/libcurl.so ln -sfn /usr/local/lib/libcurl.so.4.8.0 /usr/lib/libcurl.so.4 ln -sfn /usr/local/lib/libcurl.so.4.8.0 /usr/lib/libcurl.so.4.8.0 ln -sfn /usr/local/lib/libcurl.so.4.8.0 /usr/lib/x86_64-linux-gnu/libcurl.so ln -sfn /usr/local/lib/libcurl.so.4.8.0 /usr/lib/x86_64-linux-gnu/libcurl.so.4 ln -sfn /usr/local/lib/libcurl.so.4.8.0 /usr/lib/x86_64-linux-gnu/libcurl.so.4.8.0 fi if [ -e "/usr/local/lib/libcurl.a" ]; then ln -sfn /usr/local/lib/libcurl.a /usr/lib/x86_64-linux-gnu/libcurl.a ln -sfn /usr/local/lib/libcurl.a /usr/lib/libcurl.a fi if [ -e "/usr/local/lib/libcurl.la" ]; then ln -sfn /usr/local/lib/libcurl.la /usr/lib/x86_64-linux-gnu/libcurl.la ln -sfn /usr/local/lib/libcurl.la /usr/lib/libcurl.la fi ldconfig 2> /dev/null if [ -e "/usr/local/include/curl/curl.h" ] \ && [ -e "/usr/local/include/curl/easy.h" ] \ && [ -d "/usr/include/x86_64-linux-gnu/curl" ] \ && [ ! -L "/usr/include/x86_64-linux-gnu/curl" ]; then _apt_clean_update if dpkg-query -W -f='${Status}' libcurl4-openssl-dev 2>/dev/null | grep -q "install ok installed"; then apt-get remove libcurl4-openssl-dev -y --purge --auto-remove -qq 2> /dev/null fi ln -sfn /usr/local/include/curl /usr/include/x86_64-linux-gnu/curl ldconfig 2> /dev/null fi fi fi fi } _if_boa_key_tools_update_allowed() { if [ -e "/root/.run-to-excalibur.cnf" ] \ || [ -e "/root/.run-to-daedalus.cnf" ] \ || [ -e "/root/.run-to-chimaera.cnf" ] \ || [ -e "/root/.run-to-beowulf.cnf" ]; then _BOA_KEY_TOOLS_UPDATE_ALLOWED=NO else _BOA_KEY_TOOLS_UPDATE_ALLOWED=YES fi } # The Drupal core minors drupal.org supports, as the catalogue build recorded # them on the mirror beside the core tarballs (core/drupal-supported.txt); # eolreport reads the copy kept here. Taken from the mirror the box already # talks to, never from drupal.org. A fetch that fails, or that returns # anything but one line of minors, leaves the last good copy as it is; a # mirror box reads its own tree. _fetch_core_coverage() { local _dst="${_pthLog}/drupal-supported.txt" _tmp _l _tmp="${_dst}.new.$$" if [ -s "/var/www/static/core/drupal-supported.txt" ]; then cp -f "/var/www/static/core/drupal-supported.txt" "${_tmp}" 2> /dev/null else curl ${_crlGet} --max-time 60 "https://${_USE_MIR}/core/drupal-supported.txt" -o "${_tmp}" 2> /dev/null fi _l="$(head -n 1 "${_tmp}" 2> /dev/null | tr -d '\r')" if [[ "${_l}" =~ ^[0-9]+\.[0-9]+(\ [0-9]+\.[0-9]+)*$ ]]; then printf '%s\n' "${_l}" > "${_tmp}" \ && chmod 0644 "${_tmp}" \ && mv -f "${_tmp}" "${_dst}" fi rm -f "${_tmp}" } _update_boa_tools() { mkdir -p ${_usrBin} _FETCH_FAIL_CNT=0 # The ONE home for the core tool set every box carries. Runs on EVERY pass # (installer, Skynet-active, and _SKYNET_MODE=OFF key-tools paths alike) -- # the old once-per-release updateBOAtools latch is retired, so a tool update # now reaches the fleet as soon as its serial changes, with no release bump # needed. Cheap by construction: each _fetch_versioned line self-gates on its # own .ctrl... stamp plus a non-empty file, only # re-checks the mirror once the revalidation window expires, and re-stamps # ONLY on a successful (non-empty, non-HTML) fetch -- a mirror lag or a # transient curl failure is retried on the next pass instead of being latched # skipped, as the old single updateFx*.ctrl gate did. To push one tool: move # its serial to a value not currently deployed (the stamps are wildcard- # cleared, so direction does not matter; convention counts down). rm -f ${_pthLog}/updateBOAtools*.pid # Standby web-hold, firewall half (2026-08-25 ruling): csf -r flushes every # chain, so the hold must be re-added the moment csf finishes -- csfpost.sh # runs exactly then. The block itself checks the markers at csf-start time; # on a non-standby box it is a no-op. It lives HERE, in the one function # that runs on every pass in every context (installer, Skynet-active and # key-tools paths alike), so fleet delivery retrofits standing mirrors -- # the key-tools-only section would reach only _SKYNET_MODE=OFF boxes. # Creates the file when absent (seeding the same FTP conntrack helpers # the legacy key-tools creator writes -- that creator runs only on # _SKYNET_MODE=OFF boxes, so on the fleet THIS is the creator), then # appends the guarded block once. if [ -x "/usr/sbin/csf" ] \ && [ -e "/etc/csf/csf.deny" ] \ && [ ! -e "/etc/csf/csfpost.sh" ]; then echo "" > /etc/csf/csfpost.sh echo "iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp" >> /etc/csf/csfpost.sh echo "iptables -t raw -A OUTPUT -p tcp --dport 21 -j CT --helper ftp" >> /etc/csf/csfpost.sh chmod 700 /etc/csf/csfpost.sh fi if [ -x "/usr/sbin/csf" ] \ && [ -e "/etc/csf/csf.deny" ] \ && [ -e "/etc/csf/csfpost.sh" ] \ && ! grep -q "boa-standby-web-hold-v2" /etc/csf/csfpost.sh 2> /dev/null; then # Retrofit, not append-once: the first block keyed on the xmass in-flight # signal and sits on every box already, where a csf restart would # re-assert the chain on a rule the nginx watchdog no longer uses (a # promoted box still carrying its marker would get its web ports # dropped). Strip what sits between BOA's own two marker lines, then # write the current block; the version token inside is what the next # revision tests. The file is the operator's: the strip happens ONLY # when each marker line is there exactly once and in order -- a lone # opening line would make the range run to the end of the file. _csfStbO=$(grep -n '^# >>> boa-standby-web-hold >>>$' /etc/csf/csfpost.sh 2> /dev/null | cut -d: -f1) _csfStbC=$(grep -n '^# <<< boa-standby-web-hold <<<$' /etc/csf/csfpost.sh 2> /dev/null | cut -d: -f1) _csfStbW=NO if [ -z "${_csfStbO}" ] && [ -z "${_csfStbC}" ]; then _csfStbW=YES elif [[ "${_csfStbO}" =~ ^[0-9]+$ ]] && [[ "${_csfStbC}" =~ ^[0-9]+$ ]] \ && [ "${_csfStbO}" -lt "${_csfStbC}" ]; then sed -i "${_csfStbO},${_csfStbC}d" /etc/csf/csfpost.sh 2> /dev/null \ && _csfStbW=YES elif [ ! -e "${_pthLog}/csfpost.standby.retrofit.log" ]; then echo "$(date) csfpost.sh: the boa-standby-web-hold marker lines are not one ordered pair (open at '${_csfStbO}', close at '${_csfStbC}') -- block NOT rewritten; restore the two marker lines around BOA's block or remove the block, and the next refresh writes the current one" \ > ${_pthLog}/csfpost.standby.retrofit.log 2> /dev/null fi else _csfStbW=NO fi if [ "${_csfStbW}" = "YES" ]; then cat >> /etc/csf/csfpost.sh <<'CSFSTANDBY' # >>> boa-standby-web-hold >>> # boa-standby-web-hold-v2 # Passive replication standby: drop inbound web while the marker holds. # The promoted latch is the nginx watchdog's last PROVEN answer (no replica # config AND the database unlocked); absent means HELD, which is what a csf # restart on an unjudged box must do. Loopback exempt. if [ -e "/root/.standby.cnf" ] \ && [ ! -e "/root/.standby.serve.cnf" ] \ && [ ! -e "/var/log/boa/.standby_promoted.pid" ]; then for _ipt in iptables ip6tables; do command -v "${_ipt}" > /dev/null 2>&1 || continue "${_ipt}" -w 5 -nL BOA_STANDBY_WEB > /dev/null 2>&1 \ || "${_ipt}" -w 5 -N BOA_STANDBY_WEB > /dev/null 2>&1 "${_ipt}" -w 5 -C INPUT -j BOA_STANDBY_WEB > /dev/null 2>&1 \ || "${_ipt}" -w 5 -I INPUT -j BOA_STANDBY_WEB > /dev/null 2>&1 "${_ipt}" -w 5 -C BOA_STANDBY_WEB ! -i lo -p tcp -m multiport --dports 80,443 -j DROP > /dev/null 2>&1 \ || "${_ipt}" -w 5 -A BOA_STANDBY_WEB ! -i lo -p tcp -m multiport --dports 80,443 -j DROP > /dev/null 2>&1 done fi # <<< boa-standby-web-hold <<< CSFSTANDBY chmod 700 /etc/csf/csfpost.sh rm -f ${_pthLog}/csfpost.standby.retrofit.log fi rm -f ${_pthLog}/vmnetfix.sh.ctrl.* if [ -e "${_pthLog}" ]; then _fetch_versioned ${_usrBin}/fix-drupal-platform-permissions.sh "${_urlHmr}/${_tBn}/fix-drupal-platform-permissions.sh" f66 _fetch_versioned ${_usrBin}/fix-drupal-site-permissions.sh "${_urlHmr}/${_tBn}/fix-drupal-site-permissions.sh" f65 _fetch_versioned ${_usrBin}/fix-drupal-platform-ownership.sh "${_urlHmr}/${_tBn}/fix-drupal-platform-ownership.sh" f73 _fetch_versioned ${_usrBin}/fix-drupal-site-ownership.sh "${_urlHmr}/${_tBn}/fix-drupal-site-ownership.sh" f68 _fetch_versioned ${_usrBin}/lock-local-drush-permissions.sh "${_urlHmr}/${_tBn}/lock-local-drush-permissions.sh" f92 _fetch_versioned ${_usrBin}/fix-drupal-site-symlinks.sh "${_urlHmr}/${_tBn}/fix-drupal-site-symlinks.sh" f95 _fetch_versioned ${_usrBin}/boa-dbctl "${_urlHmr}/${_tBn}/boa-dbctl" f20 fi mkdir -p ${_optBin} # Per-file, serial-gated, retry-on-failure fetch of the core /opt/local/bin # tool set. Replaces the old bulk curl loop, which used [ -e ] not [ -s ] (an # empty --fail curl could overwrite a good tool AND delete its .prev backup) # and had no retry. Final permissions are still set by the explicit chmod # groups below. Every tool listed here is fetched HERE and nowhere else -- # the old second listing in _update_agents (a workaround for the retired # release latch) is gone, so there are no paired serials to keep in sync. # (_update_agents still fetches its own few Skynet-box-scoped bin tools; # a tool must have exactly ONE fetch line, wherever it lives.) if [ -e "${_pthLog}" ]; then # The aegir2boa toolset (vanilla Aegir -> BOA adoption, docs/AEGIR2BOA.md) # is fetched to every box so the BOA target end of a migration always # holds current bytes and the vanilla source takes its copies from it. # None of the three runs on its own and every source-side verb refuses a # BOA box. stage2 is dual-resident and run by name on the target, so the # documented /usr/local/bin path stays a symlink to the fleet copy. _fetch_versioned ${_optBin}/aegir2boa-preflight "${_urlHmr}/${_tBn}/aegir2boa-preflight" f94 --guard /local/bin/aegir2boa-preflight _fetch_versioned ${_optBin}/aegir2boa-stage1 "${_urlHmr}/${_tBn}/aegir2boa-stage1" f95 --guard /local/bin/aegir2boa-stage1 _fetch_versioned ${_optBin}/aegir2boa-stage2 "${_urlHmr}/${_tBn}/aegir2boa-stage2" f58 --symlink ${_usrBin}/aegir2boa-stage2 --guard /local/bin/aegir2boa-stage2 _fetch_versioned ${_optBin}/aptcleanup "${_urlHmr}/${_tBn}/aptcleanup" f91 --guard /local/bin/aptcleanup _fetch_versioned ${_optBin}/aptfast "${_urlHmr}/${_tBn}/aptfast" f95 --guard /local/bin/aptfast _fetch_versioned ${_optBin}/autobeowulf "${_urlHmr}/${_tBn}/autobeowulf" f83 --guard /local/bin/autobeowulf _fetch_versioned ${_optBin}/autoexcalibur "${_urlHmr}/${_tBn}/autoexcalibur" f82 --guard /local/bin/autoexcalibur _fetch_versioned ${_optBin}/autosymlink "${_urlHmr}/${_tBn}/autosymlink" f32 --guard /local/bin/autosymlink _fetch_versioned ${_optBin}/autoupboa "${_urlHmr}/${_tBn}/autoupboa" f19 --guard /local/bin/autoupboa # Backup family: guard on duplicity itself -- never replace a backup tool # while a backup run is in flight. _fetch_versioned ${_optBin}/backboa "${_urlHmr}/${_tBn}/backboa" f53 --guard duplicity _fetch_versioned ${_optBin}/barracuda "${_urlHmr}/${_tBn}/barracuda" f39 --guard /local/bin/barracuda _fetch_versioned ${_optBin}/boa "${_urlHmr}/${_tBn}/boa" f13 --guard /local/bin/boa _fetch_versioned ${_optBin}/dcysetup "${_urlHmr}/${_tBn}/dcysetup" f90 --guard duplicity _fetch_versioned ${_optBin}/dhcpfix "${_urlHmr}/${_tBn}/dhcpfix" f98 --guard /local/bin/dhcpfix _fetch_versioned ${_optBin}/duobackboa "${_urlHmr}/${_tBn}/duobackboa" f53 --guard duplicity # The remote-backups run scripts ride here with dcysetup, which drives # them from clear.sh every few minutes: on a _SKYNET_MODE=OFF box the # agents pass never runs, and a refreshed dcysetup calling a stale # installer (no --patch-only arm yet) ran the whole dependency install. if [ -d "/root/.remote_backups/run" ]; then _fetch_versioned /root/.remote_backups/run/install_dependencies.sh "${_urlHmr}/tools/backup/run/install_dependencies.sh" f31 _fetch_versioned /root/.remote_backups/run/create_credentials_templates.sh "${_urlHmr}/tools/backup/run/create_credentials_templates.sh" f45 _fetch_versioned /root/.remote_backups/run/create_global_paths_config.sh "${_urlHmr}/tools/backup/run/create_global_paths_config.sh" f43 _fetch_versioned /root/.remote_backups/run/create_user_paths_config.sh "${_urlHmr}/tools/backup/run/create_user_paths_config.sh" f40 _fetch_versioned /root/.remote_backups/run/create_cron_entries.sh "${_urlHmr}/tools/backup/run/create_cron_entries.sh" f41 _fetch_versioned /root/.remote_backups/run/create_readme.sh "${_urlHmr}/tools/backup/run/create_readme.sh" f43 _fetch_versioned /root/.remote_backups/run/create_config_readme.sh "${_urlHmr}/tools/backup/run/create_config_readme.sh" f41 fi _fetch_versioned ${_optBin}/ffdevuan "${_urlHmr}/${_tBn}/ffdevuan" f93 --guard /local/bin/ffdevuan _fetch_versioned ${_optBin}/ffmirror "${_urlHmr}/${_tBn}/ffmirror" f97 --guard /local/bin/ffmirror _fetch_versioned ${_optBin}/fixmounts "${_urlHmr}/${_tBn}/fixmounts" f98 --guard /local/bin/fixmounts _fetch_versioned ${_optBin}/fixrepo "${_urlHmr}/${_tBn}/fixrepo" f91 --guard /local/bin/fixrepo _fetch_versioned ${_optBin}/instgrp "${_urlHmr}/${_tBn}/instgrp" f04 --guard /local/bin/instgrp _fetch_versioned ${_optBin}/linkguard "${_urlHmr}/${_tBn}/linkguard" f88 --guard /local/bin/linkguard _fetch_versioned ${_optBin}/eolreport "${_urlHmr}/${_tBn}/eolreport" f99 --symlink ${_usrBin}/eolreport --guard /local/bin/eolreport _fetch_core_coverage _fetch_versioned ${_optBin}/floodreport "${_urlHmr}/${_tBn}/floodreport" f99 --symlink ${_usrBin}/floodreport --guard /local/bin/floodreport _fetch_versioned ${_optBin}/fpmreport "${_urlHmr}/${_tBn}/fpmreport" f97 --symlink ${_usrBin}/fpmreport --guard /local/bin/fpmreport _fetch_versioned ${_optBin}/loadreport "${_urlHmr}/${_tBn}/loadreport" f93 --symlink ${_usrBin}/loadreport --guard /local/bin/loadreport _fetch_versioned ${_optBin}/lock.inc "${_urlHmr}/${_tBn}/lock.inc" f85 --guard /local/bin/lock.inc _fetch_versioned ${_optBin}/multiback "${_urlHmr}/${_tBn}/multiback" f81 --guard duplicity _fetch_versioned ${_optBin}/mybackup "${_urlHmr}/${_tBn}/mybackup" f97 --mode 755 --guard duplicity _fetch_versioned ${_optBin}/octopus "${_urlHmr}/${_tBn}/octopus" f51 --guard /local/bin/octopus _fetch_versioned ${_optBin}/perftest "${_urlHmr}/${_tBn}/perftest" f93 --symlink ${_usrBin}/perftest --guard /local/bin/perftest _fetch_versioned ${_optBin}/renameaegirhost "${_urlHmr}/${_tBn}/renameaegirhost" f42 --guard /local/bin/renameaegirhost _fetch_versioned ${_optBin}/screenfetch "${_urlHmr}/${_tBn}/screenfetch" f99 --guard /local/bin/screenfetch _fetch_versioned ${_optBin}/updatesymlinks "${_urlHmr}/${_tBn}/updatesymlinks" f77 --guard /local/bin/updatesymlinks _fetch_versioned ${_optBin}/symlinkinfo "${_urlHmr}/${_tBn}/symlinkinfo" f96 --guard /local/bin/symlinkinfo _fetch_versioned ${_optBin}/migratefs "${_urlHmr}/${_tBn}/migratefs" f79 --guard /local/bin/migratefs _fetch_versioned ${_optBin}/synproxy "${_urlHmr}/${_tBn}/synproxy" f93 --guard /local/bin/synproxy _fetch_versioned ${_optBin}/synproxy_hook_fix "${_urlHmr}/${_tBn}/synproxy_hook_fix" f99 --guard /local/bin/synproxy_hook_fix _fetch_versioned ${_optBin}/synproxy_monitor "${_urlHmr}/${_tBn}/synproxy_monitor" f98 --guard /local/bin/synproxy_monitor _fetch_versioned ${_optBin}/synproxy_reassert "${_urlHmr}/${_tBn}/synproxy_reassert" f98 --guard /local/bin/synproxy_reassert _fetch_versioned ${_optBin}/synproxy_rollback "${_urlHmr}/${_tBn}/synproxy_rollback" f94 --guard /local/bin/synproxy_rollback _fetch_versioned ${_optBin}/synproxy_snapshot "${_urlHmr}/${_tBn}/synproxy_snapshot" f99 --guard /local/bin/synproxy_snapshot _fetch_versioned ${_optBin}/synproxy_status "${_urlHmr}/${_tBn}/synproxy_status" f99 --guard /local/bin/synproxy_status _fetch_versioned ${_optBin}/webserver "${_urlHmr}/${_tBn}/webserver" f93 --guard /local/bin/webserver _fetch_versioned ${_optBin}/xcopy "${_urlHmr}/${_tBn}/xcopy" f79 --guard /local/bin/xcopy _fetch_versioned ${_optBin}/xmass "${_urlHmr}/${_tBn}/xmass" f25 --guard /local/bin/xmass _fetch_versioned ${_optBin}/xoct "${_urlHmr}/${_tBn}/xoct" f89 --guard /local/bin/xoct _fetch_versioned ${_optBin}/xtrim "${_urlHmr}/${_tBn}/xtrim" f62 --guard /local/bin/xtrim _fetch_versioned ${_optBin}/solrtrim "${_urlHmr}/${_tBn}/solrtrim" f91 --guard /local/bin/solrtrim _fetch_versioned ${_optBin}/autochimaera "${_urlHmr}/${_tBn}/autochimaera" f84 --guard /local/bin/autochimaera _fetch_versioned ${_optBin}/autodaedalus "${_urlHmr}/${_tBn}/autodaedalus" f84 --guard /local/bin/autodaedalus _fetch_versioned ${_optBin}/autoinit "${_urlHmr}/${_tBn}/autoinit" f65 --guard /local/bin/autoinit # automini is retired: a reduced copy of autoinit made for custom # testing, never started by BOA. Markers go with the file. rm -f ${_optBin}/automini ${_usrBin}/automini \ ${_pthLog}/automini.ctrl.* ${_pthLog}/automini.stale.* \ ${_pthLog}/automini.mvfail.* ${_pthLog}/automini.compared.pid &> /dev/null _fetch_versioned ${_optBin}/backchain "${_urlHmr}/${_tBn}/backchain" f93 --mode 755 --guard /local/bin/backchain _fetch_versioned ${_optBin}/checkmyperms "${_urlHmr}/${_tBn}/checkmyperms" f99 --mode 755 --guard /local/bin/checkmyperms _fetch_versioned ${_optBin}/codebasecheck "${_urlHmr}/${_tBn}/codebasecheck" f90 --guard /local/bin/codebasecheck _fetch_versioned ${_optBin}/copydbackup "${_urlHmr}/${_tBn}/copydbackup" f82 --guard /local/bin/copydbackup _fetch_versioned ${_optBin}/fancynow "${_urlHmr}/${_tBn}/fancynow" f99 --guard /local/bin/fancynow _fetch_versioned ${_optBin}/killer "${_urlHmr}/${_tBn}/killer" f99 --guard /local/bin/killer _fetch_versioned ${_optBin}/memorytuner "${_urlHmr}/${_tBn}/memorytuner" f87 --guard /local/bin/memorytuner _fetch_versioned ${_optBin}/mergecsf "${_urlHmr}/${_tBn}/mergecsf" f99 --guard /local/bin/mergecsf # mycnfup is retired: a Debian-only tuner that only the discontinued # cluster tooling ever called. Markers go with the file. rm -f ${_optBin}/mycnfup ${_usrBin}/mycnfup \ ${_pthLog}/mycnfup.ctrl.* ${_pthLog}/mycnfup.stale.* &> /dev/null _fetch_versioned ${_optBin}/randpass "${_urlHmr}/${_tBn}/randpass" f99 --mode 755 --guard /local/bin/randpass _fetch_versioned ${_optBin}/setprio "${_urlHmr}/${_tBn}/setprio" f96 --guard /local/bin/setprio _fetch_versioned ${_optBin}/smtpgapps "${_urlHmr}/${_tBn}/smtpgapps" f98 --guard /local/bin/smtpgapps _fetch_versioned ${_optBin}/sqlclean "${_urlHmr}/${_tBn}/sqlclean" f90 --guard /local/bin/sqlclean _fetch_versioned ${_optBin}/sqlmagic "${_urlHmr}/${_tBn}/sqlmagic" f98 --mode 755 --guard /local/bin/sqlmagic _fetch_versioned ${_optBin}/syncpass "${_urlHmr}/${_tBn}/syncpass" f84 --guard /local/bin/syncpass _fetch_versioned ${_optBin}/thinkdifferent "${_urlHmr}/${_tBn}/thinkdifferent" f99 --mode 755 --guard /local/bin/thinkdifferent _fetch_versioned ${_optBin}/verifyvhostsdns "${_urlHmr}/${_tBn}/verifyvhostsdns" f98 --guard /local/bin/verifyvhostsdns _fetch_versioned ${_optBin}/vhostcheck "${_urlHmr}/${_tBn}/vhostcheck" f98 --guard /local/bin/vhostcheck _fetch_versioned ${_optBin}/vmnetfix "${_urlHmr}/${_tBn}/vmnetfix" f94 --guard /local/bin/vmnetfix # weblogx: skip while owl.sh runs (preserves the old loop guard) _wlOwlCnt=$(pgrep -fc /var/xdrago/owl.sh 2>/dev/null) _wlOwlCnt=${_wlOwlCnt:-0} if (( _wlOwlCnt == 0 )); then _fetch_versioned ${_optBin}/weblogx "${_urlHmr}/${_tBn}/weblogx" f92 --guard /local/bin/weblogx else echo "The weblogx and owl.sh is running!" fi # websh doubles as /bin/sh on hosted boxes: world-executable, and the # tail-anchored sentinel refuses a wrong OR truncated payload before it # can reach client shells -- websh invokes its dash forwarder near EOF, # so the token must keep living in websh's last 4 KiB. _fetch_versioned ${_optBin}/websh "${_urlHmr}/${_tBn}/websh" f58 --mode 755 --require-token _forward_to_dash --guard /local/bin/websh _fetch_versioned ${_optBin}/xdr9000 "${_urlHmr}/${_tBn}/xdr9000" f98 --symlink ${_usrBin}/xdr9000 --guard /local/bin/xdr9000 fi if [ -e "${_optBin}/fixmounts" ] && [ ! -e "${_usrBin}/fixmounts" ]; then rm -f ${_usrBin}/{aptcleanup*,autoinit*,backchain*,barracuda*,boa*,dhcpfix*,ffdevuan*,ffmirror*,fixmounts*} rm -f ${_usrBin}/{aptfast*,killer*,perftest*,octopus*,screenfetch*,vmnetfix*,webserver*,websh*} ln -sfn ${_optBin}/aptcleanup ${_usrBin}/aptcleanup ln -sfn ${_optBin}/autoinit ${_usrBin}/autoinit ln -sfn ${_optBin}/backchain ${_usrBin}/backchain ln -sfn ${_optBin}/barracuda ${_usrBin}/barracuda ln -sfn ${_optBin}/boa ${_usrBin}/boa ln -sfn ${_optBin}/dhcpfix ${_usrBin}/dhcpfix ln -sfn ${_optBin}/ffdevuan ${_usrBin}/ffdevuan ln -sfn ${_optBin}/ffmirror ${_usrBin}/ffmirror ln -sfn ${_optBin}/fixmounts ${_usrBin}/fixmounts ln -sfn ${_optBin}/killer ${_usrBin}/killer ln -sfn ${_optBin}/octopus ${_usrBin}/octopus ln -sfn ${_optBin}/perftest ${_usrBin}/perftest ln -sfn ${_optBin}/screenfetch ${_usrBin}/screenfetch ln -sfn ${_optBin}/vmnetfix ${_usrBin}/vmnetfix ln -sfn ${_optBin}/webserver ${_usrBin}/webserver ln -sfn ${_optBin}/websh ${_usrBin}/websh fi # A plain file at one of these names (a hand copy) comes first in PATH and # keeps cron on its bytes while every update lands in /opt/local/bin, so # anything that is not a link is replaced, as --symlink does for the rest. if [ -e "/data/u" ]; then if [ ! -L "${_usrBin}/dcysetup" ] && [ -e "${_optBin}/dcysetup" ]; then ln -sfn ${_optBin}/dcysetup ${_usrBin}/dcysetup fi if [ ! -L "${_usrBin}/multiback" ] && [ -e "${_optBin}/multiback" ]; then ln -sfn ${_optBin}/multiback ${_usrBin}/multiback fi if [ ! -L "${_usrBin}/mybackup" ] && [ -e "${_optBin}/mybackup" ]; then ln -sfn ${_optBin}/mybackup ${_usrBin}/mybackup fi fi # Permission self-heal, grouped by function. Runs every pass now, so no # banner output; a missing tool (gated or not-yet-fetched) is not an error. # _AUTO (700): automatic install/upgrade helpers chmod 700 ${_optBin}/{autobeowulf,autochimaera,autodaedalus,autoexcalibur,autoinit,autoupboa} &> /dev/null # _BACKUP (700): backup helpers chmod 700 ${_optBin}/{backboa,copydbackup,dcysetup,duobackboa,multiback} &> /dev/null # _CORE (700): core BOA tools chmod 700 ${_optBin}/{barracuda,boa,ffdevuan,ffmirror,killer,octopus,webserver} &> /dev/null chmod 700 ${_optBin}/{aegir2boa-preflight,aegir2boa-stage1,aegir2boa-stage2,lock.inc,renameaegirhost,solrtrim,syncpass,weblogx,xcopy,xmass,xoct,xtrim} &> /dev/null # _DB (700): performance and DB tuners chmod 700 ${_optBin}/{eolreport,floodreport,fpmreport,loadreport,memorytuner,perftest,xdr9000} &> /dev/null # _MAIL (700): mail + priority tools chmod 700 ${_optBin}/{setprio,smtpgapps} &> /dev/null # _NET (700): network protection chmod 700 ${_optBin}/synproxy* &> /dev/null # _SYS (700): system utilities chmod 700 ${_optBin}/{aptfast,codebasecheck,dhcpfix,fancynow,fixmounts,fixrepo,instgrp,linkguard,mergecsf,screenfetch,vmnetfix} &> /dev/null # _SYS (700): cleanup tools chmod 700 ${_optBin}/{aptcleanup,autosymlink,sqlclean,updatesymlinks,verifyvhostsdns,vhostcheck} &> /dev/null # _MISC (755): misc user-space utilities chmod 755 ${_optBin}/{backchain,checkmyperms,mybackup,randpass,sqlmagic,thinkdifferent,websh} &> /dev/null _fetch_failed_report "the tools pass" } # Ensure /usr/sbin/ipset and /sbin/ipset both resolve to the actual ipset binary. _ensure_ipset_symlinks() { _IPSET_REAL="$(command -v ipset 2>/dev/null || true)" if [ -z "${_IPSET_REAL}" ]; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "ipset not installed; skipping symlink fixes" fi return 0 fi # Resolve through any intermediate symlinks. if [ -L "${_IPSET_REAL}" ]; then _IPSET_REAL="$(readlink -f "${_IPSET_REAL}")" fi for _CAND in /usr/sbin/ipset /sbin/ipset; do _PARENT="$(dirname "${_CAND}")" [ -d "${_PARENT}" ] || mkdir -p "${_PARENT}" # If the candidate *is* the real file, nothing to do. if [ "${_CAND}" = "${_IPSET_REAL}" ]; then continue fi # If it exists, check whether it already resolves to the right target. if [ -e "${_CAND}" ] || [ -L "${_CAND}" ]; then _TARGET="$(readlink -f "${_CAND}" 2>/dev/null || true)" if [ "${_TARGET}" = "${_IPSET_REAL}" ]; then continue fi fi ln -sfn "${_IPSET_REAL}" "${_CAND}" if [ "${_DEBUG_MODE}" = "YES" ]; then echo "Linked ${_CAND} -> ${_IPSET_REAL}" fi done } _if_update_boa_key_tools_only() { _check_dns_settings _if_reinstall_curl _CURL_TEST=$(curl -L -s \ --max-redirs 10 \ --retry 3 \ --retry-delay 10 \ -I "https://${_USE_MIR}" 2> /dev/null) if [[ ! "${_CURL_TEST}" =~ "HTTP/2 200" ]]; then if [[ "${_CURL_TEST}" =~ "unknown option was passed in to libcurl" ]]; then echo "ERROR: cURL libs are out of sync! Re-installing.." _if_reinstall_curl fi echo "ERROR: ${_USE_MIR} is not available, please try later" exit 1 else _use_mirror_serving_tree "key tools only" _urlHmr="https://${_USE_MIR}/versions/${_tRee}/boa/aegir" fi _LSB_TEST="$(which lsb_release)" if [ ! -x "${_LSB_TEST}" ]; then if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _apt_clean_update apt-get install lsb-release ${_aptYesUnth} -qq 2> /dev/null fi _IPSET_TEST="$(which ipset)" if [ ! -x "${_IPSET_TEST}" ]; then if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _apt_clean_update if [ -L "/sbin/ipset" ]; then rm -f /sbin/ipset fi if [ -L "/usr/sbin/ipset" ]; then rm -f /usr/sbin/ipset fi apt-get install ipset ${_aptYesUnth} -qq 2> /dev/null fi _ensure_ipset_symlinks if [ -x "/usr/sbin/csf" ] \ && [ -e "/etc/csf/csf.deny" ] \ && [ ! -x "/etc/csf/csfpost.sh" ]; then echo "" > /etc/csf/csfpost.sh echo "iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp" >> /etc/csf/csfpost.sh echo "iptables -t raw -A OUTPUT -p tcp --dport 21 -j CT --helper ftp" >> /etc/csf/csfpost.sh chmod 700 /etc/csf/csfpost.sh _CSF_TEST="$(which csf)" if [ -x "${_CSF_TEST}" ]; then service clean-boa-env start &> /dev/null _if_fix_iptables_symlinks ### csf -uf ### wait _NFTABLES_TEST=$(iptables -V) if [[ "${_NFTABLES_TEST}" =~ "nf_tables" ]]; then if [ -e "/usr/sbin/iptables-legacy" ]; then update-alternatives --set iptables /usr/sbin/iptables-legacy &> /dev/null fi if [ -e "/usr/sbin/ip6tables-legacy" ]; then update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy &> /dev/null fi if [ -e "/usr/sbin/arptables-legacy" ]; then update-alternatives --set arptables /usr/sbin/arptables-legacy &> /dev/null fi if [ -e "/usr/sbin/ebtables-legacy" ]; then update-alternatives --set ebtables /usr/sbin/ebtables-legacy &> /dev/null fi fi sed -i "s/.*DHCP.*//g" /etc/csf/csf.allow wait sed -i "/^$/d" /etc/csf/csf.allow if [ -e "/var/log/daemon.log" ]; then _DHCP_LOG="/var/log/daemon.log" else _DHCP_LOG="/var/log/syslog" fi grep DHCPREQUEST "${_DHCP_LOG}" | awk '{print $12}' | sort -u | while read -r _IP; do if [[ ${_IP} =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then IFS='.' read -r oct1 oct2 oct3 oct4 <<< "${_IP}" if (( oct1 <= 255 && oct2 <= 255 && oct3 <= 255 && oct4 <= 255 )); then echo "udp|out|d=67|d=${_IP} # Local DHCP out" >> /etc/csf/csf.allow fi fi done if [ -e "/etc/csf/csfpost.d/synproxy.sh" ]; then csf -ra &> /dev/null synproxy_reassert -p "443 80" --no-quic -q &> /dev/null else csf -r &> /dev/null fi ### Linux kernel TCP SACK CVEs mitigation ### CVE-2019-11477 SACK Panic ### CVE-2019-11478 SACK Slowness ### CVE-2019-11479 Excess Resource Consumption Due to Low MSS Values if [ -x "/usr/sbin/csf" ] && [ -e "/etc/csf/csf.deny" ]; then ### Persist the rules via csfpost.sh so csf -r cannot flush them; the ### appended lines use POSIX redirects only (csfpost.sh runs under ### sh). Gated on -x so the fresh-install csfpost.sh seeding block ### above (gated on ! -x) is never preempted. if [ -x "/etc/csf/csfpost.sh" ] \ && ! grep -q "BOA-SACK-MITIGATION" /etc/csf/csfpost.sh; then echo "### BOA-SACK-MITIGATION begin (CVE-2019-11477/78/79) do not edit" >> /etc/csf/csfpost.sh echo "sysctl net.ipv4.tcp_mtu_probing=0 >/dev/null 2>&1" >> /etc/csf/csfpost.sh echo "iptables -C INPUT -p tcp -m tcpmss --mss 1:500 -j DROP >/dev/null 2>&1 || iptables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP" >> /etc/csf/csfpost.sh echo "ip6tables -C INPUT -p tcp -m tcpmss --mss 1:500 -j DROP >/dev/null 2>&1 || ip6tables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP" >> /etc/csf/csfpost.sh echo "### BOA-SACK-MITIGATION end" >> /etc/csf/csfpost.sh fi _SACK_TEST=$(ip6tables --list | grep tcpmss) if [[ ! "${_SACK_TEST}" =~ "tcpmss" ]]; then sysctl net.ipv4.tcp_mtu_probing=0 &> /dev/null iptables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP &> /dev/null ip6tables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP &> /dev/null [ -e "/etc/csf/csfpost.d/synproxy.sh" ] && synproxy_reassert -p "443 80" --no-quic -q &> /dev/null fi fi fi fi if [ -x "/usr/sbin/csf" ] && [ -e "/etc/csf/csf.conf" ]; then _CC_SRC_TEST=$(grep 'CC_SRC\ =' /etc/csf/csf.conf 2>&1) ### echo _CC_SRC_TEST 1 is "${_CC_SRC_TEST}" if [[ ! ${_CC_SRC_TEST} =~ CC_SRC\ =\ \"2\" ]]; then echo _CC_SRC_TEST 2 is "${_CC_SRC_TEST}" service clean-boa-env start &> /dev/null _if_fix_iptables_symlinks ### csf -uf ### wait _NFTABLES_TEST=$(iptables -V) if [[ "${_NFTABLES_TEST}" =~ "nf_tables" ]]; then if [ -e "/usr/sbin/iptables-legacy" ]; then update-alternatives --set iptables /usr/sbin/iptables-legacy &> /dev/null fi if [ -e "/usr/sbin/ip6tables-legacy" ]; then update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy &> /dev/null fi if [ -e "/usr/sbin/arptables-legacy" ]; then update-alternatives --set arptables /usr/sbin/arptables-legacy &> /dev/null fi if [ -e "/usr/sbin/ebtables-legacy" ]; then update-alternatives --set ebtables /usr/sbin/ebtables-legacy &> /dev/null fi fi sed -i "s/^CC_SRC .*/CC_SRC = \"2\"/g" /etc/csf/csf.conf wait sed -i "s/^AUTO_UPDATES .*/AUTO_UPDATES = \"0\"/g" /etc/csf/csf.conf if [ -e "/etc/csf/csfpost.d/synproxy.sh" ]; then csf -ra &> /dev/null synproxy_reassert -p "443 80" --no-quic -q &> /dev/null else csf -r &> /dev/null fi fi fi # Unconditional: every tool line self-gates on its own serial stamp, so the # old once-per-release updateBOAtools latch is retired and a bumped serial # reaches Skynet-OFF boxes on their next pass too. _update_boa_tools if [ ! -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then if [ "${1}" = "verbose" ] || [ -z "${1}" ]; then echo echo "BOA Meta Installers setup completed" echo "Please check INSTALL.md and UPGRADE.md at https://github.com/omega8cc/boa" echo "Bye" echo fi fi } _boa_setup() { _BENG_VS=NO _VMFAMILY=NO _RANDOMIZE=NO _VM_TEST="$(uname -a)" if [[ "${_VM_TEST}" =~ "-beng" ]]; then _BENG_VS=YES _RANDOMIZE=YES fi _if_hosted_sys if [ "${_hostedSys}" = "YES" ]; then _VMFAMILY=HOSTED fi _check_dns_settings if [ -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then [ -d /run/unbound ] || mkdir -p /run/unbound [ -d /run/unbound ] && chown -R unbound:unbound /run/unbound fi if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _APT_CONFIG_FILE="/etc/apt/apt.conf.d/99ignorestrict" # The Allow* pair is a belt for a box that still carries an index APT cannot # verify -- it was never meant to be permanent, and while it is on nothing can # tell a verified archive from an unverifiable one (that is what hid a missing # devuan-keyring until unattended-upgrades had been inert for weeks). So probe # for real, at most once a day -- a strict update is the only proof that also # covers a repo added since the last check -- and carry the pair only while # this box actually needs it. The stamp is shared with the barracuda-side copy # of this block, so the two never probe twice. # # A box that needs it can never lose it: every failure direction -- probe # error, unreachable mirror, any untrusted index -- writes the pair back. The # file itself always stays; its Fix-Broken line is load-bearing for aptitude # and has nothing to do with trust. _APT_TRUST_STAMP="${_pthLog}/.apt-trust-probe.stamp" if [ -e "${_APT_CONFIG_FILE}" ] \ && ! grep -q "AllowInsecureRepositories" "${_APT_CONFIG_FILE}" 2>/dev/null; then _APT_TRUST_MODE=STRICT else _APT_TRUST_MODE=BYPASS fi if [ ! -e "${_APT_TRUST_STAMP}" ] \ || [ -n "$(find "${_APT_TRUST_STAMP}" -mmin +1440 2>/dev/null)" ]; then _APT_TRUST_OUT=$(LC_ALL=C apt-get update \ -o Acquire::AllowInsecureRepositories=false \ -o Acquire::AllowDowngradeToInsecureRepositories=false \ -o Acquire::AllowWeakRepositories=false 2>&1) _APT_TRUST_RET=$? _APT_TRUST_BAD=$(LC_ALL=C apt-get indextargets 2>/dev/null | grep -c "^Trusted: no") _APT_TRUST_BAD=${_APT_TRUST_BAD:-0} if [ "${_APT_TRUST_RET}" = "0" ] \ && [ "${_APT_TRUST_BAD}" -eq 0 ] \ && ! echo "${_APT_TRUST_OUT}" | grep -qiE "NO_PUBKEY|is not signed|EXPKEYSIG|KEYEXPIRED|REVKEYSIG"; then _APT_TRUST_MODE=STRICT else _APT_TRUST_MODE=BYPASS fi touch "${_APT_TRUST_STAMP}" fi # Desired configuration content if [ "${_APT_TRUST_MODE}" = "STRICT" ]; then _DESIRED_APT_CONFIG='Aptitude::CmdLine::Fix-Broken "true";' else _DESIRED_APT_CONFIG='Acquire::AllowInsecureRepositories "true"; APT::Get::AllowUnauthenticated "true"; Aptitude::CmdLine::Fix-Broken "true";' fi # Remove leading whitespace from each line _CLEANED_DESIRED_APT_CONFIG=$(echo "${_DESIRED_APT_CONFIG}" | sed 's/^[[:space:]]\+//') # Normalize the existing file content if [[ -f "${_APT_CONFIG_FILE}" ]]; then _CURRENT_APT_CONFIG=$(tr -d '[:space:]' < "${_APT_CONFIG_FILE}") else _CURRENT_APT_CONFIG="" fi # Normalize the cleaned desired configuration content _NORMALIZED_DESIRED_APT_CONFIG=$(echo "${_CLEANED_DESIRED_APT_CONFIG}" | tr -d '[:space:]') # Compare normalized contents and update if necessary if [[ "${_CURRENT_APT_CONFIG}" != "${_NORMALIZED_DESIRED_APT_CONFIG}" ]]; then echo "${_CLEANED_DESIRED_APT_CONFIG}" | tee "${_APT_CONFIG_FILE}" > /dev/null fi if [ ! -e "/var/aegir/.drush/hm.alias.drushrc.php" ] && [ ! -e "/var/xdrago/manage_solr_config.sh" ]; then # apt-get remove unscd -y --purge --auto-remove -qq &> /dev/null # apt-get remove dbus -y --purge --auto-remove -qq &> /dev/null # if [ -e "/usr/share/dbus-1" ]; then # rm -f /usr/share/dbus-1/*/*freedesktop* # fi userdel -r debian &> /dev/null sed -i "s/^#startup_message off/startup_message off/g" /etc/screenrc &> /dev/null fi _isScreen=$(screen --version 2>&1) if [[ ! "${_isScreen}" =~ "GNU" ]] || [ -z "${_isScreen}" ]; then apt-get install screen ${_aptYesUnth} -qq 2> /dev/null apt-get install net-tools ${_aptYesUnth} -qq 2> /dev/null apt-get install hostname ${_aptYesUnth} -qq 2> /dev/null apt-get install ntpsec-ntpdate ${_aptYesUnth} -qq 2> /dev/null fi _if_reinstall_curl _CURL_TEST=$(curl -L -s \ --max-redirs 10 \ --retry 3 \ --retry-delay 10 \ -I "https://${_USE_MIR}" 2> /dev/null) if [[ ! "${_CURL_TEST}" =~ "HTTP/2 200" ]]; then if [[ "${_CURL_TEST}" =~ "unknown option was passed in to libcurl" ]]; then echo "ERROR: cURL libs are out of sync! Re-installing.." _if_reinstall_curl fi echo "ERROR: ${_USE_MIR} is not available, please try later" exit 1 else _use_mirror_serving_tree "setup" _urlHmr="https://${_USE_MIR}/versions/${_tRee}/boa/aegir" fi _if_clean_boa_env _LSB_TEST="$(which lsb_release)" if [ ! -x "${_LSB_TEST}" ]; then if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _apt_clean_update apt-get install lsb-release ${_aptYesUnth} -qq 2> /dev/null fi # jq is a base dependency: multiback/mybackup URL-encode b2 credentials # with it, but only the PRO-only backup chain installed it, so a box # could fetch the tools yet miss the encoder they depend on. _JQ_TEST="$(which jq)" if [ ! -x "${_JQ_TEST}" ]; then if [ ! -e "/etc/apt/apt.conf.d/00sandboxoff" ] \ && [ -e "/etc/apt/apt.conf.d" ]; then echo "APT::Sandbox::User \"root\";" > /etc/apt/apt.conf.d/00sandboxoff fi _apt_clean_update apt-get install jq ${_aptYesUnth} -qq 2> /dev/null fi ### Fix or install VM system detection _check_virt # Unconditional: every tool line self-gates on its own serial stamp; the # old once-per-release updateBOAtools latch is retired. The completion # message stays meaningful only on a box BOA is not installed on yet. _update_boa_tools if [ ! -e "/var/aegir/.drush/hm.alias.drushrc.php" ]; then echo echo "BOA Meta Installers setup completed" echo "Please check INSTALL.md and UPGRADE.md at https://github.com/omega8cc/boa" echo "Bye" echo fi } _count_cpu() { _CPU_INFO="$(grep -c processor /proc/cpuinfo)" _CPU_INFO=${_CPU_INFO//[^0-9]/} _NPROC_TEST="$(which nproc)" if [ -z "${_NPROC_TEST}" ]; then _CPU_NR="${_CPU_INFO}" else _CPU_NR=$(nproc 2>&1) fi _CPU_NR=${_CPU_NR//[^0-9]/} if [ ! -z "${_CPU_NR}" ] \ && [ ! -z "${_CPU_INFO}" ] \ && [ "${_CPU_NR}" -gt "${_CPU_INFO}" ] \ && [ "${_CPU_INFO}" -gt 0 ]; then _CPU_NR="${_CPU_INFO}" fi if [ -z "${_CPU_NR}" ] || [ "${_CPU_NR}" -lt 1 ]; then _CPU_NR=1 fi mkdir -p /data/all # the store as root works in it (a link of root's there leads through # root's own directories only to /data/disk/all, _ROOT_REAL_PATH_PL and # _SAT_STORE_TARGET_PL; a pin refuses any other link), set 0755 and # given a fresh cpuinfo (0644) renamed over the name, so a link or a # hard link left at the name is replaced, never written through local _st _st="$(perl -e "${_ROOT_REAL_PATH_PL}" -- /data/all 2> /dev/null)" perl -e "${_SAT_STORE_TARGET_PL}" -- "${_st}" &> /dev/null || _st="" _acct_in_real_dir "${_st:-/data/all}" chmod 755 . _acct_in_real_dir "${_st:-/data/all}" _acct_put_here cpuinfo "${_CPU_NR}" } # sysctl.conf is fetched as /var/backups/sysctl.conf through # _fetch_versioned, which holds a new serial while the mirror still serves # the bytes already there and revalidates within the hour; the refresh below # runs once per release and whenever that copy changes. A bare fetch stamped # the release even when it failed. _sysctl_update() { local _sysSum _sysDue=NO if [ "${_NO_SYSCTL_UPDATE}" != "YES" ] \ && [ ! -e "/root/.no.sysctl.update.cnf" ]; then [ -d "/var/backups" ] || mkdir -p /var/backups _sysSum="$(md5sum /var/backups/sysctl.conf 2>/dev/null | cut -d' ' -f1)" _fetch_versioned /var/backups/sysctl.conf "${_urlHmr}/conf/var/sysctl.conf" f99 --mode 644 _source_due /var/backups/sysctl.conf "${_sysSum}" \ "/var/backups/.sysctl.conf.mod-disable-ipv6-${_xSrl}.log" && _sysDue=YES fi if [ "${_sysDue}" = "YES" ]; then cd /var/backups if [ -e "/var/backups/sysctl.conf" ]; then cp -af /var/backups/sysctl.conf /etc/sysctl.conf fi if [ -e "/etc/security/limits.conf" ]; then _IF_NF=$(grep '2097152' /etc/security/limits.conf 2>&1) if [ ! -z "${_IF_NF}" ]; then sed -i "s/.*2097152.*//g" /etc/security/limits.conf wait fi _IF_NF=$(grep '524288' /etc/security/limits.conf 2>&1) if [ -z "${_IF_NF}" ]; then echo "* hard nofile 524288" >> /etc/security/limits.conf echo "* soft nofile 524288" >> /etc/security/limits.conf echo "root hard nofile 1048576" >> /etc/security/limits.conf echo "root soft nofile 1048576" >> /etc/security/limits.conf fi _IF_NF=$(grep '65556' /etc/security/limits.conf 2>&1) if [ -z "${_IF_NF}" ]; then echo "* hard nproc 65556" >> /etc/security/limits.conf echo "* soft nproc 65556" >> /etc/security/limits.conf fi fi if [ -e "/boot/grub/grub.cfg" ] || [ -e "/boot/grub/menu.lst" ]; then #echo never > /sys/kernel/mm/transparent_hugepage/enabled if [ -e "/etc/sysctl.conf" ]; then sysctl -p /etc/sysctl.conf &> /dev/null fi else if [ -e "/etc/sysctl.conf" ]; then sysctl -p /etc/sysctl.conf &> /dev/null fi fi if [ -e "/etc/default/nginx" ]; then _IF_ULNX=$(grep '524288' /etc/default/nginx 2>&1) if [ -z "${_IF_ULNX}" ]; then sed -i "s/^ULIMIT=.*//gi" /etc/default/nginx wait echo ULIMIT=\"-n 524288\" >> /etc/default/nginx ulimit -n 524288 &> /dev/null service nginx restart &> /dev/null fi fi if [ -e "/etc/security/limits.d" ] \ && [ ! -e "/etc/security/limits.d/solr9.conf" ]; then echo "sshd soft nofile 524288" > /etc/security/limits.d/sshd.conf echo "sshd hard nofile 999999" >> /etc/security/limits.d/sshd.conf echo "nginx soft nofile 524288" > /etc/security/limits.d/nginx.conf echo "nginx hard nofile 999999" >> /etc/security/limits.d/nginx.conf echo "jetty9 soft nofile 65535" > /etc/security/limits.d/jetty9.conf echo "jetty9 hard nofile 524288" >> /etc/security/limits.d/jetty9.conf echo "solr7 soft nofile 65535" > /etc/security/limits.d/solr7.conf echo "solr7 hard nofile 524288" >> /etc/security/limits.d/solr7.conf echo "solr9 soft nofile 65535" > /etc/security/limits.d/solr9.conf echo "solr9 hard nofile 524288" >> /etc/security/limits.d/solr9.conf echo "@www-data soft nofile 65535" > /etc/security/limits.d/www.conf echo "@www-data hard nofile 524288" >> /etc/security/limits.d/www.conf if [ -e "/etc/init.d/valkey-server" ]; then service valkey-server restart &> /dev/null fi service nginx restart &> /dev/null service ssh restart &> /dev/null _PHP_V="86 85 84 83 82 81 80 74 73 72 71 70 56" for e in ${_PHP_V}; do if [ -e "/etc/init.d/php${e}-fpm" ]; then service "php${e}-fpm" reload &> /dev/null fi done fi touch /var/backups/.sysctl.conf.mod-disable-ipv6-${_xSrl}.log fi } ### ### Load + normalize _INCIDENT_REPORT ### ### Legacy values: ### NO becomes OFF (see below) ### YES becomes MINI (see below) ### ### Current values: ### OFF == Total silence, no email alerts ### ALL == Very noisy, good for debugging ### MINI == Only the most important alerts (default) ### CRIT == Only critical if _lvl=ALERT ### _normalize_incident_report() { : "${_INCIDENT_REPORT:=MINI}" _INCIDENT_REPORT="${_INCIDENT_REPORT^^}" _INCIDENT_REPORT="${_INCIDENT_REPORT//[^A-Z]/}" ### ### Map legacy + validate ### case "${_INCIDENT_REPORT}" in NO) _INCIDENT_REPORT="OFF" ;; YES) _INCIDENT_REPORT="MINI" ;; OFF|ALL|MINI|CRIT) : ;; *) _INCIDENT_REPORT="MINI" ;; esac } # Function to notify about still running backup _backup_waiting_notify() { _hName="$(cat /etc/hostname 2>/dev/null | tr -d '\n' || hostname -f 2>/dev/null)" _templog="${_bLogB}" cat /root/.remote_backups/schedule/backup_schedule.txt > ${_templog} ps axf | grep multiback >> ${_templog} ps axf | grep duplicity >> ${_templog} ls -la /tmp/duplicity-*-tempdir >> ${_templog} tree /root/.cache/duplicity >> ${_templog} ls -laR /root/.cache/duplicity >> ${_templog} grep "Out of memory: Killed process.*duplicity" /var/log/iptables.log >> ${_templog} boa info >> ${_templog} if [ -n "${_MY_EMAIL}" ] && [ "${_INCIDENT_REPORT}" != "OFF" ]; then s-nail -s "Multiback Waiting Report for [${_hName}] on $(date)" ${_MY_EMAIL} < ${_templog} fi } # On an image that leaves recommended packages on, every install brings whole # service stacks along: the distro wkhtmltopdf brought polkitd and a logind # whose login-session cgroups filled a box's kernel memory. BOA's own apt # settings files turn them off for the calls that read them; this file does it # for every other apt call on the box. A later file in apt.conf.d still wins, # so an operator who wants them back can say so there. _apt_no_recommends() { local _anrFile="/etc/apt/apt.conf.d/00boa-no-recommends" local _anrWant _anrWant="APT::Install-Recommends \"false\";"$'\n'"APT::Install-Suggests \"false\";" [ -d "/etc/apt/apt.conf.d" ] || return 0 if [ "$(cat "${_anrFile}" 2> /dev/null)" != "${_anrWant}" ]; then printf '%s\n' "${_anrWant}" > "${_anrFile}.new" \ && mv -f "${_anrFile}.new" "${_anrFile}" fi return 0 } # Load kTLS module only if it is not already loaded. _load_ktls_module() { if ! lsmod 2>/dev/null | awk '{print $1}' | grep -qx "tls"; then if [ -e "/lib/modules/$(uname -r)/kernel/net/tls/tls.ko" ] \ || modinfo tls >/dev/null 2>&1; then modprobe -q tls >/dev/null 2>&1 || true grep -qxF "tls" /etc/modules 2>/dev/null || printf '%s\n' "tls" >> /etc/modules fi fi } # Fix CSF config only if Quic HTTP3 support is not enabled yet _csf_allow_quic_udp_443() { _CSF_CONF="/etc/csf/csf.conf" _CSF_CHANGED="NO" if ! command -v csf >/dev/null 2>&1; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: csf not found - skipping UDP/443 (QUIC) firewall check" fi return 0 fi if [ ! -s "${_CSF_CONF}" ]; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "WARN: ${_CSF_CONF} not found or empty - skipping" fi return 0 fi # An operator who keeps csf.conf under _CUSTOM_CONFIG_CSF=YES owns its port # lists, as barracuda's template pass already honours; until this check did # too, every root run of this library re-opened UDP/443 on such a box and # reloaded csf, undoing a closure the operator keeps every five minutes. if grep -qE "^(export[[:space:]]+)?_CUSTOM_CONFIG_CSF=[\"']?YES" "${_barCnf:-/root/.barracuda.cnf}" 2> /dev/null; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: _CUSTOM_CONFIG_CSF=YES - leaving the csf.conf port lists alone" fi return 0 fi _csf_add_port_to_list_var() { # $1 = VAR (UDP_IN / UDP6_IN), $2 = PORT (443) _VAR="${1}" _PORT="${2}" # Extract current value between quotes _CUR="$(grep -E "^${_VAR}[[:space:]]*=" "${_CSF_CONF}" 2>/dev/null | head -n1 | sed -E 's/^[^"]*"([^"]*)".*$/\1/')" # If var not present, do nothing if ! grep -q -E "^${_VAR}[[:space:]]*=" "${_CSF_CONF}" 2>/dev/null; then return 0 fi # If already present as a whole list item, do nothing if echo ",${_CUR}," | grep -q ",${_PORT},"; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: CSF ${_VAR} already includes ${_PORT}" fi return 0 fi # Append port (handle empty list) if [ -n "${_CUR}" ]; then _NEW="${_CUR},${_PORT}" else _NEW="${_PORT}" fi # Replace only the FIRST matching line for this var, keep surrounding quotes # Make a backup once (csf.conf.bak) if not already present if [ ! -e "${_CSF_CONF}.bak" ]; then cp -af "${_CSF_CONF}" "${_CSF_CONF}.bak" fi sed -i -E "0,/^${_VAR}[[:space:]]*=/{s|^(${_VAR}[[:space:]]*=[[:space:]]*\").*(\".*)|\1${_NEW}\2|}" "${_CSF_CONF}" _CSF_CHANGED="YES" if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: CSF updated: ${_VAR}=\"${_NEW}\"" fi } # QUIC needs inbound UDP/443 _csf_add_port_to_list_var "UDP_IN" "443" # If your CSF has separate IPv6 var, update it too if grep -q -E '^UDP6_IN[[:space:]]*=' "${_CSF_CONF}" 2>/dev/null; then _csf_add_port_to_list_var "UDP6_IN" "443" fi if [ "${_CSF_CHANGED}" = "YES" ]; then if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: Reloading CSF to apply UDP/443 change" fi csf -r >/dev/null 2>&1 || csf -ra >/dev/null 2>&1 else if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: CSF config unchanged" fi fi return 0 } # Raise the csf temporary ban ceiling on boxes still carrying the stock 300. # csf evicts the OLDEST temp bans once the list is full and re-allows those # hosts regardless of time remaining, so at 300 the web IDS unblocks addresses # that are still attacking and a distributed flood never converges (98 source # addresses in one observed scraper flood, 72 in an earlier auth-probe # flood). LF_IPSET # is on, so entries are an O(1) ipset lookup, which is the condition csf itself # recommends raising these limits under. Only ever raises: an operator who # chose a higher value, or 0 for unlimited, keeps it. _csf_raise_temp_ban_limit() { local _CSF_CNF="/etc/csf/csf.conf" local _WANT="2000" local _CUR="" if ! command -v csf > /dev/null 2>&1; then return 0 fi if [ ! -s "${_CSF_CNF}" ]; then return 0 fi _CUR=$(grep -E "^DENY_TEMP_IP_LIMIT[[:space:]]*=" "${_CSF_CNF}" 2> /dev/null \ | head -n1 | sed -E 's/^[^"]*"([^"]*)".*$/\1/') ### Absent, non-numeric, or 0 (csf's "unlimited") -- never touch it. if [ -z "${_CUR}" ] || [ "${_CUR}" = "0" ]; then return 0 fi case "${_CUR}" in *[!0-9]*) return 0 ;; esac if [ "${_CUR}" -ge "${_WANT}" ]; then return 0 fi if [ ! -e "${_CSF_CNF}.bak" ]; then cp -af "${_CSF_CNF}" "${_CSF_CNF}.bak" fi sed -i -E "0,/^DENY_TEMP_IP_LIMIT[[:space:]]*=/{s|^(DENY_TEMP_IP_LIMIT[[:space:]]*=[[:space:]]*\").*(\".*)|\1${_WANT}\2|}" \ "${_CSF_CNF}" if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: CSF DENY_TEMP_IP_LIMIT raised ${_CUR} -> ${_WANT}" fi csf -r > /dev/null 2>&1 || csf -ra > /dev/null 2>&1 } # Keep session.upload_progress.enabled Off in the FPM and CLI ini of every PHP # version on the box, and fatal_error_backtraces Off in those of an 8.5 or 8.6 # build older than the first release with PHP's October 2026 security fix # (8.5.12, 8.6.0RC4). The upload progress handler runs on every multipart POST # and no hosted CMS reads it (Drupal and Backdrop use the uploadprogress # extension). An older 8.5 or 8.6 is one the security floor has not upgraded # yet (a box it holds back); PHP's advisory gives backtraces Off for a build # without the fix, and a build with it is left as it is. Checked on every run, # never stamped: each barracuda pass copies the ini templates over these files, # and a pass from a tree older than the templates' own Off line brings PHP's # default back. A changed FPM ini is applied by a reload after the master's own # config test. A master failing that test keeps serving, and the reload is # retried on every run until it passes; the marker lives in /run, since a # master started after a reboot reads the changed file anyway. _php_upload_progress_off() { local _e="" local _fIle="" local _fpmCh="" local _pId="" local _pNd="" local _rO="" local _lIne="" local _kEy="" local _aCt="" local _cMt="" local _fBt="" local _fFx="" for _e in 86 85 84 83 82 81 80 74 73 72 71 70 56; do _fpmCh=NO _fBt=NO ### The first release of each version that carries the fix. case "${_e}" in 86) _fFx="8.6.0RC4" ;; 85) _fFx="8.5.12" ;; *) _fFx="" ;; esac if [ -n "${_fFx}" ] && [ -x "/opt/php${_e}/bin/php" ] \ && "/opt/php${_e}/bin/php" -n -r "exit(version_compare(PHP_VERSION, '${_fFx}', '<') ? 0 : 1);" 2> /dev/null; then _fBt=YES fi for _fIle in "/opt/php${_e}/etc/php${_e}.ini" "/opt/php${_e}/lib/php.ini"; do if [ ! -f "${_fIle}" ] || [ -L "${_fIle}" ]; then continue fi for _lIne in "session.upload_progress.enabled = Off" "fatal_error_backtraces = Off"; do _kEy="${_lIne%% *}" if [ "${_kEy}" = "fatal_error_backtraces" ] && [ "${_fBt}" != "YES" ]; then continue fi _aCt="^[[:space:]]*${_kEy//./\\.}[[:space:]]*=" _cMt="^;[[:space:]]*${_kEy//./\\.}" if [ "$(grep -E "${_aCt}" "${_fIle}" 2> /dev/null)" = "${_lIne}" ]; then continue fi ### Every active line goes; the line comes back in place of the stock ### commented default, or at the end of a file that has none. ### Once per file while it stays so, not every five minutes (an ### immutable file): a marker in /run per file. _rO="/run/php-upload-progress.$(echo "${_fIle}" | cksum | cut -d' ' -f1).ro" if ! sed -i -E "/${_aCt}/d" "${_fIle}" 2> /dev/null; then if [ ! -e "${_rO}" ]; then [ -d "${_pthLog}" ] && echo "$(date 2> /dev/null) ERROR: php${_e}: ${_fIle} not writable" \ >> "${_pthLog}/php-upload-progress.log" touch "${_rO}" fi continue 2 fi rm -f "${_rO}" if grep -qE "${_cMt}" "${_fIle}" 2> /dev/null; then sed -i -E "0,/${_cMt}/{s/${_cMt}.*/${_lIne}/}" "${_fIle}" else echo "${_lIne}" >> "${_fIle}" fi [ -d "${_pthLog}" ] && echo "$(date 2> /dev/null) php${_e}: ${_lIne} in ${_fIle}" \ >> "${_pthLog}/php-upload-progress.log" if [ "${_DEBUG_MODE}" = "YES" ]; then echo "INFO: php${_e}: ${_lIne} in ${_fIle}" fi [ "${_fIle}" = "/opt/php${_e}/etc/php${_e}.ini" ] && _fpmCh=YES done done _pNd="/run/php${_e}-fpm.ini-reload" [ "${_fpmCh}" = "YES" ] && touch "${_pNd}" [ -e "${_pNd}" ] || continue _pId=$(cat "/run/php${_e}-fpm.pid" 2> /dev/null) case "${_pId}" in ''|*[!0-9]*) _pId="" ;; esac ### No init script or no live master: the next start reads the file. if [ ! -e "/etc/init.d/php${_e}-fpm" ] || [ -z "${_pId}" ] \ || ! kill -0 "${_pId}" 2> /dev/null; then rm -f "${_pNd}" continue fi if [ -x "/opt/php${_e}/sbin/php-fpm" ] \ && "/opt/php${_e}/sbin/php-fpm" -t --fpm-config "/opt/php${_e}/etc/php${_e}-fpm.conf" \ -c "/opt/php${_e}/etc/php${_e}.ini" &> /dev/null; then if service "php${_e}-fpm" reload &> /dev/null; then rm -f "${_pNd}" [ -d "${_pthLog}" ] && echo "$(date 2> /dev/null) php${_e}-fpm reloaded" \ >> "${_pthLog}/php-upload-progress.log" elif [ "${_fpmCh}" = "YES" ]; then [ -d "${_pthLog}" ] && echo "$(date 2> /dev/null) ERROR: php${_e}-fpm reload failed, retried every run" \ >> "${_pthLog}/php-upload-progress.log" fi elif [ "${_fpmCh}" = "YES" ]; then [ -d "${_pthLog}" ] && echo "$(date 2> /dev/null) ERROR: php${_e}-fpm config test failed, reload retried every run" \ >> "${_pthLog}/php-upload-progress.log" fi done } # PHP security floor: the first point release of each minor version that # carries a security fix no box may wait for, e.g. "8.2.35 8.3.36"; empty # means off. A release PHP no longer fixes takes BOA's own security patch # as "+", e.g. "8.1.34+": a build of that release counts # as below until /opt/phpNN/lib/boa-sec-patches lists the id (see # _php_sec_patches in lib/functions/php.sh.inc). A box running an older # build of an installed version listed here gets a barracuda system pass, # which rebuilds PHP to the pins of the box's tree, with the patches. # SKYNET-off boxes get it too, through the launcher's override # for this pass alone: a pass that may break an old Octopus instance until # that is upgraded as well is still safer than a known remote code execution # hole, and a system pass leaves the master Ægir instance as it is. _PHP_SEC_FLOOR="8.2.35 8.3.36 8.4.27 8.5.12 8.6.0RC4 7.4.33+cve-2026-109618-109619 8.0.30+cve-2026-109618-109619 8.1.34+cve-2026-109618-109619 5.6.40+sury-105 5.6.40+openssl3-pkey-export 7.0.33+sury-93 7.0.33+openssl3-pkey-export 7.1.33+sury-81 7.2.34+sury-66 7.3.33+sury-35 7.4.33+sury-31 8.0.30+sury-25 8.1.34+sury-9 5.6.40+xlts-1 7.0.33+xlts-1 7.1.33+xlts-1 7.2.34+xlts-1 7.3.33+xlts-1 7.4.33+xlts-1 8.0.30+xlts-1" # One line per state: a wait repeated on every run is logged once, until # something else is logged. TRUE only when it wrote the line, so a mail can # follow a new state alone. _php_floor_log() { [ -d "${_pthLog}" ] || return 1 tail -n 1 "${_pthLog}/php-sec-floor.log" 2> /dev/null | grep -qF -- "$1" && return 1 echo "$(date 2> /dev/null) $1" >> "${_pthLog}/php-sec-floor.log" } # TRUE when version $2 is older than $3 by PHP's own rules, asked of the PHP # binary $1: sort -V puts 8.6.0 before 8.6.0RC3. _php_floor_older() { # shellcheck disable=SC2016 # $argv is PHP's, not the shell's "$1" -n -r 'exit(version_compare($argv[1], $argv[2], "<") ? 0 : 1);' "$2" "$3" 2> /dev/null } # TRUE while no pass may start: an install or upgrade, the nightly dump, an # xoct or xcopy verb, SQL maintenance, mydumper or myloader, or an xmass or # standby seed in its first two days. _php_floor_busy() { _myquick_busy && return 0 [ -n "$(find /run/boa_xmass_init.pid /root/.standby.init.pid -mmin -2880 2> /dev/null)" ] && return 0 return 1 } # Mailed to the operator unless _INCIDENT_REPORT=OFF: $1 subject, $2 body. _php_floor_mail() { [ -n "${_MY_EMAIL}" ] || return 0 [ "${_INCIDENT_REPORT}" = "OFF" ] && return 0 echo "$2" | s-nail -s "$1" "${_MY_EMAIL}" &> /dev/null } # Decides whether this box needs the floor's pass. _php_security_floor_launch # starts it at the end of the run, after the tools refresh: the run's own apt # and service steps never overlap the pass, and a SKYNET-off box already has # the launcher that honours the override. The state, one line in # /etc/boa/control, is the time of the next allowed start, the passes started # and the versions they were for. Each box starts at its own spread of up to # 30 minutes, also when a wait for the release ends, so the fleet does not # fetch from the mirrors at once. A version waits while the pin of the box's # tree is below its floor or the pin's source is not on the mirror. A pass # that leaves a version below is tried again 12 hours later; after three, or # when a major OS upgrade is staged in the cnf, the operator is mailed once # and the box is left to them. _php_security_floor() { local _fLr="" _e="" _iV="" _pIn="" _bElow="" _lIst="" _sTp="" _nOw="" local _dUe="" _tRies="" _lAst="" _sUm="" _hN="" _sPr="" _pIns="" _sZ="" local _tR="" _wAit="" _eNt="" _fV="" _fId="" _hOst="" _PHP_FLOOR_GO=NO [ -n "${_PHP_SEC_FLOOR}" ] || return 0 [ -x "/opt/local/bin/barracuda" ] || return 0 [ -e "/var/log/barracuda_log.txt" ] || return 0 ### A major OS upgrade in progress rebuilds every PHP version itself. ls /root/.run-to-*.cnf > /dev/null 2>&1 && return 0 for _fLr in ${_PHP_SEC_FLOOR}; do _fV="${_fLr%%+*}" _fId="" [ "${_fV}" = "${_fLr}" ] || _fId="${_fLr#*+}" _e=$(echo "${_fV}" | cut -d. -f1-2 | tr -d .) [ -x "/opt/php${_e}/bin/php" ] || continue ### -n: no ini, so no extension load warning can reach the output _iV=$("/opt/php${_e}/bin/php" -n -r 'echo PHP_VERSION;' 2> /dev/null) case "${_iV}" in ''|*[!0-9A-Za-z.]*) continue ;; esac if _php_floor_older "/opt/php${_e}/bin/php" "${_iV}" "${_fV}" \ || { [ -n "${_fId}" ] && [ "${_iV}" = "${_fV}" ] \ && ! grep -qx -- "${_fId}" "/opt/php${_e}/lib/boa-sec-patches" 2> /dev/null; }; then _bElow="${_bElow} ${_e}:${_fLr}" _lIst="${_lIst} ${_iV}${_fId:+ (without ${_fId})}" fi done _sTp="/etc/boa/control/php-sec-floor.$(echo "${_PHP_SEC_FLOOR}" | cksum | cut -d' ' -f1).txt" find /etc/boa/control -maxdepth 1 -name 'php-sec-floor.*.txt' ! -name "${_sTp##*/}" \ -delete 2> /dev/null if [ -z "${_bElow}" ]; then rm -f "${_sTp}" return 0 fi _sUm=$(echo "${_bElow}" | cksum | cut -d' ' -f1) _hN=$(cat /etc/hostname 2> /dev/null) _sPr=$(( $(echo "${_hN}" | cksum | cut -d' ' -f1) % 1800 )) _nOw=$(date +%s) [ -s "${_sTp}" ] && read -r _dUe _tRies _lAst < "${_sTp}" case "${_dUe}" in ''|*[!0-9]*) _dUe=$(( _nOw + _sPr )) mkdir -p /etc/boa/control echo "${_dUe} 0 ${_sUm}" > "${_sTp}" _php_floor_log "PHP${_lIst} below the security floor, barracuda system pass due $(date -d "@${_dUe}" 2> /dev/null)" if [ "${_SKYNET_MODE}" = "OFF" ]; then _php_floor_mail "PHP security upgrade scheduled for [${_hN}]" \ "PHP${_lIst} on ${_hN} is older than the security release BOA requires. A barracuda system pass rebuilds PHP from $(date -d "@${_dUe}" 2> /dev/null), once the release is out for this box. It runs although _SKYNET_MODE=OFF is set: the release fixes remote code execution bugs. The pass does not touch the master Aegir instance. Log: ${_pthLog}/php-sec-floor.log" fi return 0 ;; esac case "${_tRies}" in ''|*[!0-9]*) _tRies=0 ;; esac ### Other versions below than the passes so far were for: count afresh. [ "${_lAst}" = "${_sUm}" ] || _tRies=0 [ "${_nOw}" -ge "${_dUe}" ] || return 0 [ "${_tRies}" -ge 4 ] && return 0 if [ "${_tRies}" -eq 3 ]; then echo "${_dUe} 4 ${_sUm}" > "${_sTp}" _php_floor_log "PHP${_lIst} below the security floor after three barracuda system passes; no forced pass from here on" _php_floor_mail "PHP security upgrade needs you on [${_hN}]" \ "PHP${_lIst} on ${_hN} is older than the security release BOA requires, and still is after three barracuda system passes. BOA starts no more passes for it. Run 'barracuda up- system' by hand and read its report in /var/backups/reports/up/barracuda/. Log: ${_pthLog}/php-sec-floor.log" return 0 fi _php_floor_busy && return 0 ### The box's own tree, as its last barracuda pass recorded it. _tR=$(grep -oE "Barracuda BOA-[0-9.]+-(dev|lts|pro)" /var/log/barracuda_log.txt 2> /dev/null \ | tail -n 1 | sed -E 's/.*-//') [ -n "${_tR}" ] || _tR="${_tRee}" ### The pass builds the pins of that tree, from a package or else from the ### mirror's source: wait until every pin reaches its floor and its source ### is there (a missing file is served as a small page, so only a real ### size counts), then start at the box's own spread. Pins and patches are ### read where the pass's launcher reads its tree, files.boa.io, and from ### this box's mirror when that gives nothing. for _hOst in files.boa.io "${_USE_MIR}"; do _pIns=$(curl -sL --max-time 60 -A iCab "https://${_hOst}/versions/${_tR}/boa/BARRACUDA.sh.txt" \ 2> /dev/null | grep -E '^_PHP[0-9]{2}_VRN=') [ -n "${_pIns}" ] && break done for _eNt in ${_bElow}; do _e="${_eNt%%:*}" _fLr="${_eNt#*:}" _fV="${_fLr%%+*}" _fId="" [ "${_fV}" = "${_fLr}" ] || _fId="${_fLr#*+}" _pIn=$(echo "${_pIns}" | grep "^_PHP${_e}_VRN=" | head -n 1 | cut -d= -f2 | tr -cd '0-9A-Za-z.') if [ -z "${_pIn}" ] || _php_floor_older "/opt/php${_e}/bin/php" "${_pIn}" "${_fV}"; then _php_floor_log "waiting for the ${_tR} tree to pin PHP ${_fV} or newer (it pins ${_pIn:-nothing readable})" _wAit=YES break fi ### A patched release needs its patch in the box's tree: every patch has ### hunks, the mirror's page for a missing file has none. if [ -n "${_fId}" ] && [ "${_pIn}" = "${_fV}" ]; then _sZ="" for _hOst in files.boa.io "${_USE_MIR}"; do if curl -sL --max-time 30 -A iCab \ "https://${_hOst}/versions/${_tR}/boa/aegir/patches/php-sec/php-${_pIn}-${_fId}.patch" \ 2> /dev/null | grep -q '^@@'; then _sZ=YES break fi done if [ "${_sZ}" != "YES" ]; then _php_floor_log "waiting for the ${_tR} tree to carry the PHP ${_pIn} patch ${_fId}" _wAit=YES break fi fi _sZ=$(curl -sIL --max-time 30 -A iCab "${_urlDev}/src/php-${_pIn}.tar.bz2" 2> /dev/null \ | grep -i "^content-length:" | tail -n 1 | tr -dc '0-9') if [ -z "${_sZ}" ] || [ "${_sZ}" -lt 1000000 ]; then _php_floor_log "waiting for php-${_pIn}.tar.bz2 on ${_USE_MIR}" _wAit=YES break fi done ### A staged major OS upgrade would run inside the pass, unattended. The ### flags as barracuda reads them (the cnf is sourced, so exported or ### quoted ones count) and named as in _barracuda_cnf_cleanup: hold until ### the operator runs or clears it, with one mail. if [ "${_wAit}" != "YES" ]; then for _eNt in _DAEDALUS_TO_EXCALIBUR _TRIXIE_TO_EXCALIBUR _BOOKWORM_TO_DAEDALUS \ _CHIMAERA_TO_DAEDALUS _BULLSEYE_TO_CHIMAERA _BOOKWORM_TO_TRIXIE _BULLSEYE_TO_BOOKWORM \ _BEOWULF_TO_CHIMAERA _BUSTER_TO_BEOWULF _STRETCH_TO_BEOWULF _JESSIE_TO_BEOWULF \ _BUSTER_TO_BULLSEYE _STRETCH_TO_BUSTER _JESSIE_TO_STRETCH _WHEEZY_TO_JESSIE \ _SQUEEZE_TO_WHEEZY _LENNY_TO_SQUEEZE; do [ "${!_eNt}" = "YES" ] || continue if _php_floor_log "PHP${_lIst} below the security floor, held: ${_eNt}=YES is staged in /root/.barracuda.cnf"; then _php_floor_mail "PHP security upgrade needs you on [${_hN}]" \ "PHP${_lIst} on ${_hN} is older than the security release BOA requires. The barracuda system pass that rebuilds it would also run the major OS upgrade staged with ${_eNt}=YES in /root/.barracuda.cnf. BOA starts it once that upgrade has been run or the line is removed. Log: ${_pthLog}/php-sec-floor.log" fi _wAit=YES break done fi if [ "${_wAit}" = "YES" ]; then echo "$(( _nOw + 300 + _sPr )) ${_tRies} ${_lAst}" > "${_sTp}" return 0 fi _PHP_FLOOR_GO=YES _PHP_FLOOR_TREE="${_tR}" _PHP_FLOOR_STATE="${_sTp}" _PHP_FLOOR_NEXT="$(( _nOw + 43200 )) $(( _tRies + 1 )) ${_sUm}" _PHP_FLOOR_LIST="${_lIst}" } # Starts the pass _php_security_floor decided on, detached from this run: # a new session, so neither this script's nor clear.sh's waits block on it. _php_security_floor_launch() { [ "${_PHP_FLOOR_GO}" = "YES" ] || return 0 _PHP_FLOOR_GO=NO _php_floor_busy && return 0 ### With _SKYNET_MODE=OFF only a launcher that honours the floor's override ### runs the pass; an older one, its refresh failed, would refuse in silence. if [ "${_SKYNET_MODE}" = "OFF" ] \ && ! grep -q "_PHP_SEC_FLOOR_PASS" /opt/local/bin/barracuda 2> /dev/null; then _php_floor_log "waiting for a barracuda launcher that runs the floor's pass with _SKYNET_MODE=OFF" return 0 fi echo "${_PHP_FLOOR_NEXT}" > "${_PHP_FLOOR_STATE}" [ -d "${_pthLog}" ] && echo "$(date 2> /dev/null) PHP${_PHP_FLOOR_LIST} below the security floor, barracuda up-${_PHP_FLOOR_TREE} system launched" \ >> "${_pthLog}/php-sec-floor.log" ### Nothing of an earlier run may stay in /opt/tmp, hidden entries included ### (a stale build tree or its refetch marker): the pass starts clean. find /opt/tmp/ -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + 2> /dev/null ( _PHP_SEC_FLOOR_PASS=YES nohup setsid /opt/local/bin/barracuda "up-${_PHP_FLOOR_TREE}" system noscreen \ >> /var/log/boa/barracuda-php-floor.log 2>&1 < /dev/null & ) } ###--------------------### if [ "$(id -u)" -eq 0 ]; then # Before any install below: no recommended packages, box-wide _apt_no_recommends # Load kTLS module only if it is not already loaded _load_ktls_module # Fix CSF config only if Quic HTTP3 support is not enabled yet _csf_allow_quic_udp_443 # Raise the csf temp ban ceiling if it is still at the stock 300 _csf_raise_temp_ban_limit # PHP session upload progress Off in every ini, re-asserted every run _php_upload_progress_off if ! command -v bc &> /dev/null; then apt-get update -qq &> /dev/null ${_INITINS} bc &> /dev/null fi if ! command -v curl &> /dev/null; then apt-get update -qq &> /dev/null ${_INITINS} curl &> /dev/null fi _find_correct_ip _find_server_city [ ! -e "/var/aegir/.drush/hm.alias.drushrc.php" ] && _locales_check_fix_early _os_detection_minimal _find_fast_mirror_early _verify_boa_keys ### Prefer Devuan apt sources if [ -d "/var/aegir" ] && [ ! -e "/etc/apt/preferences.d/99-prefer-devuan" ]; then if grep -qi 'ID=devuan' /etc/os-release 2>/dev/null; then _prefer_devuan_repositories fi fi ### Fix VM system detection _check_virt if [ -e "${_barCnf}" ]; then source ${_barCnf} _normalize_incident_report fi ### Notify if multiback backups seem to run for too long # Genuine executions only, the same anchoring the rest of this engine # and the backup tools use: bare substrings counted any command line # that merely mentioned the word, so this notice could fire off an # operator's editor or log tail rather than a real run. _DCY=$(pgrep -fc "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*duplicity( |$)" 2>/dev/null) _DCYD=$(pgrep -fc "^[^ ]*duplicity( |$)" 2>/dev/null) _DCY=$(( ${_DCY:-0} + ${_DCYD:-0} )) _MLT=$(pgrep -fc "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*multiback( |$)" 2>/dev/null) _MLT=${_MLT:-0} if (( _DCY > 0 )) && (( _MLT > 0 )); then _bLogA="/var/backups/multiback_waiting_queue.log" _bLogB="/var/backups/tmp_multiback_waiting_queue.log" if [ ! -e "${_bLogA}" ] && [ ! -e "${_bLogB}" ]; then _backup_waiting_notify fi fi ### Backup freshness notice: a box whose scheduled off-site backups have ### quietly stopped (dead credentials, dead cron, wedged wrapper) fails ### one run at a time into per-run mail that is easy to miss; this raises ### ONE distinct, daily-throttled alert per service once no successful ### run has landed for too long. Fail-quiet by design: no schedule means ### not configured, standby mirrors hold their chain on purpose, a live ### run defers judgement, and a service with neither an OK nor an ERR ### sentinel is treated as never armed rather than broken. A fresh ERR ### with a stale OK means runs happen and fail (alert early); nothing ### fresh at all means runs stopped entirely (alert on a longer floor ### that clears a monthly full spanning several cycles). _bupSched="/root/.remote_backups/schedule/backup_schedule.txt" if [ -s "${_bupSched}" ] && [ ! -e "/root/.standby.cnf" ]; then _bupIntM=${_BACKUP_INTERVAL//[^0-9]/} _bupIntM=${_bupIntM:-360} _bupIntH=$(( _bupIntM / 60 )) [ "${_bupIntH}" -lt 1 ] && _bupIntH=1 _bupFailH=$(( _bupIntH * 5 )) [ "${_bupFailH}" -lt 30 ] && _bupFailH=30 _bupDeadH=$(( _bupIntH * 8 )) [ "${_bupDeadH}" -lt 48 ] && _bupDeadH=48 _bupHstN="$(cat /etc/hostname 2>/dev/null | tr -d '[:space:]')" if [ -z "${_bupHstN}" ]; then _bupHstN="$(hostname -f 2>/dev/null | tr -d '[:space:]')" case "${_bupHstN}" in localhost|localhost.*) _bupHstN="" ;; esac fi # The sentinel name must be derived exactly as its WRITER derives it. # multiback builds the bucket name -- and so the OK/ERR log filename -- # from the raw hostname with no case folding, so folding here made the # two disagree on any box carrying an uppercase character in # /etc/hostname: neither sentinel was ever found, every scheduled # service read as never armed, and this notice went permanently silent # on precisely the boxes it exists to catch. The fold is kept for the # display name in the alert subject and body only. _bupHstD=$(echo -n "${_bupHstN}" | tr . - 2>&1) _bupHstN=$(echo -n "${_bupHstN}" | tr A-Z a-z 2>&1) _bupNowT=$(date +%s) # A pass in flight is not a stalled service. The wrapper walks the # schedule strictly sequentially, so an entry whose turn has not come # yet is PENDING, not broken -- judging it by its own idle pid file # alone alerted three boxes whose tail entries were queued behind a # sibling still recovering from a long retention pass, and every one # of them completed within the hour. So freshness is judged only # while the box is idle; a box whose backups run but never finish is # the waiting-notify's business above, not this notice's. # Suppression is bounded: past this ceiling a stream is not busy, it # is stuck, and silence would defeat the whole notice. Four cycles # clears the monthly full that legitimately spans several of them. _bupStuckH=$(( _bupIntH * 4 )) [ "${_bupStuckH}" -lt 24 ] && _bupStuckH=24 _bupLiveAny=NO _bupStuckKey="" _bupStuckWhat="" _bupStuckAgeH=0 for _bupPidF in /run/duplicity_*.pid; do [ -e "${_bupPidF}" ] || continue _bupPid=$(tr -dc '0-9' 2>/dev/null < "${_bupPidF}") [ -n "${_bupPid}" ] && [ -d "/proc/${_bupPid}" ] || continue # The recorded owner's command line must still look like this # tooling: bare pid liveness would let a leftover file whose pid has # been recycled by an unrelated long-lived process silence the box, # and a pid file for a service no longer in the schedule is never # healed by a later run, so that silence would never lift. _bupCmd=$(tr '\0' ' ' 2>/dev/null < "/proc/${_bupPid}/cmdline") case "${_bupCmd}" in *duplicity*|*multiback*|*sequential_backups*) ;; *) continue ;; esac _bupLiveAny=YES # The pid file is written once when the lock is taken, so its mtime # is that run's start; no ps call needed to age it. _bupHeldT=$(stat -c %Y "${_bupPidF}" 2>/dev/null) [ -n "${_bupHeldT}" ] || _bupHeldT="${_bupNowT}" _bupHeldH=$(( (_bupNowT - _bupHeldT) / 3600 )) if [ "${_bupHeldH}" -ge "${_bupStuckH}" ] && [ -z "${_bupStuckKey}" ]; then _bupStuckKey="${_bupPidF##*/}" _bupStuckKey="${_bupStuckKey#duplicity_}" _bupStuckKey="${_bupStuckKey%.pid}" _bupStuckKey="${_bupStuckKey%_sequential}" _bupStuckWhat="${_bupStuckKey} (lock ${_bupPidF})" _bupStuckAgeH="${_bupHeldH}" fi done for _bupPat in \ "^([^ ]*/)?((ba|da)?sh|python[0-9.]*) (-[^ ]+ )*[^ ]*(sequential_backups\.sh|multiback|duplicity)( |$)" \ "^[^ ]*(sequential_backups\.sh|multiback|duplicity)( |$)"; do # Age these the same way, from the process itself: liveness found # here often owns NO /run/duplicity_*.pid file at all -- the daily # backboa/duobackboa chains lock under their own names, and the # wrapper is briefly live between entries -- so pid-file mtimes # alone would leave this path with no ceiling and suppress for as # long as such a run hangs. for _bupProcP in $(pgrep -f "${_bupPat}" 2>/dev/null); do _bupLiveAny=YES _bupProcS=$(ps -o etimes= -p "${_bupProcP}" 2>/dev/null | tr -dc '0-9') _bupProcH=$(( ${_bupProcS:-0} / 3600 )) if [ "${_bupProcH}" -ge "${_bupStuckH}" ] && [ -z "${_bupStuckKey}" ]; then _bupStuckKey="pid-${_bupProcP}" _bupStuckWhat="a backup process (pid ${_bupProcP})" _bupStuckAgeH="${_bupProcH}" fi done done # A stream past the ceiling gets ONE distinct daily notice naming it, # and the pending entries behind it stay quiet: the operator should # read one true cause, not a list of its symptoms. This is deliberately # self-contained -- the older waiting-queue notice above cannot serve # as the backstop here, since it fires at most once per box until its # queue logs are cleared. if [ -n "${_bupStuckKey}" ]; then _bupStuckKey="${_bupStuckKey//[^a-zA-Z0-9_-]/_}" _bupStamp="/var/log/boa/backup-freshness-stalled-${_bupStuckKey}.alerted" if [ ! -e "${_bupStamp}" ] \ || [ "$(( _bupNowT - $(stat -c %Y "${_bupStamp}" 2>/dev/null || echo 0) ))" -ge 86400 ]; then touch "${_bupStamp}" echo "$(date) STALLED: ${_bupStuckWhat} has held the schedule for ${_bupStuckAgeH} hours" \ >> /var/log/boa/backup-freshness.log if [ -n "${_MY_EMAIL}" ] && [ "${_INCIDENT_REPORT}" != "OFF" ]; then { echo "One off-site backup stream has been running for ${_bupStuckAgeH} hours on ${_bupHstN}" echo echo "Stuck stream: ${_bupStuckWhat}" echo echo "The schedule is sequential, so every entry behind this one is" echo "waiting and no freshness alert is raised for them. Check the" echo "running duplicity process and its bucket log under /var/log/boa" echo "before killing anything -- a monthly full legitimately runs for" echo "many hours, and killing a full costs the whole chain a cycle." echo echo "This notice repeats daily until the stream finishes." } | s-nail -s "Backup STALLED Alert for [${_bupHstN}]: ${_bupStuckKey} running ${_bupStuckAgeH}h" ${_MY_EMAIL} fi fi else rm -f /var/log/boa/backup-freshness-stalled-*.alerted fi while read -r _bupSvc _bupUsr _bupRest; do case "${_bupSvc}" in ""|\#*) continue ;; esac [ -n "${_bupUsr}" ] || continue _bupSvcD=${_bupSvc//_/-} if [ "${_bupUsr}" = "global" ] \ || [ "${_bupUsr}" = "data" ] \ || [ "${_bupUsr}" = "custom" ]; then _bupLogD="/root/.remote_backups/logs" else _bupLogD="/data/disk/${_bupUsr}/static/control/remote_backups/logs" fi _bupOkF="${_bupLogD}/OK-back-to-${_bupUsr}-${_bupHstD}-${_bupSvcD}.log" _bupErF="${_bupLogD}/ERR-back-to-${_bupUsr}-${_bupHstD}-${_bupSvcD}.log" if [ ! -e "${_bupOkF}" ] && [ ! -e "${_bupErF}" ]; then continue fi # Box-wide liveness, computed once above and covering this service's # own pid files too: a per-service check alone called a queued entry # stalled while its pass was demonstrably running [ "${_bupLiveAny}" = "YES" ] && continue _bupOkT=$(stat -c %Y "${_bupOkF}" 2>/dev/null) _bupOkT=${_bupOkT:-0} _bupErT=$(stat -c %Y "${_bupErF}" 2>/dev/null) _bupErT=${_bupErT:-0} _bupAgeH=$(( (_bupNowT - _bupOkT) / 3600 )) _bupKind="" if [ "${_bupErT}" -gt "${_bupOkT}" ] && [ "${_bupAgeH}" -ge "${_bupFailH}" ]; then _bupKind="FAILING" elif [ "${_bupAgeH}" -ge "${_bupDeadH}" ]; then _bupKind="NOT RUNNING" fi _bupStamp="/var/log/boa/backup-freshness-${_bupSvc}-${_bupUsr}.alerted" if [ -z "${_bupKind}" ]; then rm -f "${_bupStamp}" continue fi if [ -e "${_bupStamp}" ] \ && [ "$(( _bupNowT - $(stat -c %Y "${_bupStamp}" 2>/dev/null || echo 0) ))" -lt 86400 ]; then continue fi touch "${_bupStamp}" _bupOkD="never" [ "${_bupOkT}" -gt 0 ] && _bupOkD="$(date -d @${_bupOkT} 2>/dev/null)" _bupErD="never" [ "${_bupErT}" -gt 0 ] && _bupErD="$(date -d @${_bupErT} 2>/dev/null)" echo "$(date) ${_bupKind}: ${_bupSvc} ${_bupUsr} last OK ${_bupOkD}; last ERR ${_bupErD}" \ >> /var/log/boa/backup-freshness.log if [ -n "${_MY_EMAIL}" ] && [ "${_INCIDENT_REPORT}" != "OFF" ]; then { echo "Off-site backups ${_bupKind} for ${_bupSvc} (${_bupUsr}) on ${_bupHstN}" echo echo "No successful run for ${_bupAgeH} hours." echo "Last OK: ${_bupOkD}" echo "Last ERR: ${_bupErD}" echo echo "Every scheduled run mails its own report, but this box has had" echo "no completed backup past the alert floor. Check first:" echo " ${_bupErF}" echo " /var/log/boa/*conn-test-err.log (credentials/bucket problems)" echo " /etc/cron.d/duplicity_backup and /var/log/backup_runtime.log" echo echo "This notice repeats daily until a run completes." } | s-nail -s "Backup FRESHNESS Alert for [${_bupHstN}]: ${_bupSvc} ${_bupUsr} ${_bupKind}" ${_MY_EMAIL} fi done < "${_bupSched}" fi ### Make local OpenSSL new/legacy ssl/certs symlinked to system ssl/certs if [ -d "/var/aegir" ]; then _fix_sync_system_ssl_certs fi ### Fix Solr 4/7/9 conflicting ports if [ -d "/var/aegir" ]; then _fix_start_stop_ports_solr fi ### CVE-2021-44228 Log4j 2 Vulnerability ### CVE-2021-45046 Log4j 2 Vulnerability ### CVE-2021-45105 Log4j 2 Vulnerability _fix_log4j_solr7 ### Linux kernel TCP SACK CVEs mitigation ### CVE-2019-11477 SACK Panic ### CVE-2019-11478 SACK Slowness ### CVE-2019-11479 Excess Resource Consumption Due to Low MSS Values if [ -x "/usr/sbin/csf" ] && [ -e "/etc/csf/csf.deny" ]; then ### Persist the rules via csfpost.sh so csf -r cannot flush them; the ### appended lines use POSIX redirects only (csfpost.sh runs under sh). ### Gated on -x so the fresh-install csfpost.sh seeding block (gated ### on ! -x) is never preempted. if [ -x "/etc/csf/csfpost.sh" ] \ && ! grep -q "BOA-SACK-MITIGATION" /etc/csf/csfpost.sh; then echo "### BOA-SACK-MITIGATION begin (CVE-2019-11477/78/79) do not edit" >> /etc/csf/csfpost.sh echo "sysctl net.ipv4.tcp_mtu_probing=0 >/dev/null 2>&1" >> /etc/csf/csfpost.sh echo "iptables -C INPUT -p tcp -m tcpmss --mss 1:500 -j DROP >/dev/null 2>&1 || iptables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP" >> /etc/csf/csfpost.sh echo "ip6tables -C INPUT -p tcp -m tcpmss --mss 1:500 -j DROP >/dev/null 2>&1 || ip6tables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP" >> /etc/csf/csfpost.sh echo "### BOA-SACK-MITIGATION end" >> /etc/csf/csfpost.sh fi _SACK_TEST=$(ip6tables --list | grep tcpmss) if [[ ! "${_SACK_TEST}" =~ "tcpmss" ]]; then sysctl net.ipv4.tcp_mtu_probing=0 &> /dev/null iptables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP &> /dev/null ip6tables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP &> /dev/null [ -e "/etc/csf/csfpost.d/synproxy.sh" ] && synproxy_reassert -p "443 80" --no-quic -q &> /dev/null fi fi ### More aggressive mitigation affecting network performance # if [ -e "/proc/sys/net/ipv4/tcp_sack" ]; then # _SACK_TEST=$(cat /proc/sys/net/ipv4/tcp_sack 2>&1) # _SACK_TEST=$(echo -n ${_SACK_TEST} | tr -d "\n" 2>&1) # if [[ "${_SACK_TEST}" =~ "1" ]]; then # echo "0" > /proc/sys/net/ipv4/tcp_sack # fi # fi ### Block known attackers IPs _CSF_TEST="$(which csf)" if [ -x "${_CSF_TEST}" ]; then _IP_BLOCK="47.82.0.0/16 47.79.0.0/16 2.57.121.0/24 2.57.122.0/24 45.148.10.0/24 80.94.92.0/24 92.118.39.0/24 185.177.72.0/24 185.194.178.0/24 142.248.80.0/24" for _IP in ${_IP_BLOCK}; do _FW_TEST=$(csf -g ${_IP} 2>&1) if [[ "${_FW_TEST}" =~ "DENY Match:${_IP} Setting" ]] \ && [[ "${_FW_TEST}" =~ "csf.deny: ${_IP}" ]]; then echo "${_IP} already denied for Brute force SSH/Web Server attacks" else csf -d ${_IP} do not delete Brute force SSH/Web Server attacks [ -e "/etc/csf/csfpost.d/synproxy.sh" ] && synproxy_reassert -p "443 80" --no-quic -q &> /dev/null fi done fi ### Linux kernel CVE-2017-2636 hotfix if [ -e "/etc/modprobe.d" ] \ && [ ! -e "/etc/modprobe.d/blacklist-n_hdlc.conf" ]; then echo "install n_hdlc /bin/true" > /etc/modprobe.d/blacklist-n_hdlc.conf rmmod n_hdlc &> /dev/null fi ### Linux kernel CVE-2017-6074 hotfix if [ -e "/etc/modprobe.d" ] \ && [ ! -e "/etc/modprobe.d/blacklist-dccp-all.conf" ]; then echo "install dccp /bin/true" > /etc/modprobe.d/blacklist-dccp-all.conf echo "install dccp_diag /bin/true" >> /etc/modprobe.d/blacklist-dccp-all.conf echo "install dccp_ipv4 /bin/true" >> /etc/modprobe.d/blacklist-dccp-all.conf echo "install dccp_ipv6 /bin/true" >> /etc/modprobe.d/blacklist-dccp-all.conf echo "install dccp_probe /bin/true" >> /etc/modprobe.d/blacklist-dccp-all.conf rmmod dccp &> /dev/null rmmod dccp_diag &> /dev/null rmmod dccp_ipv4 &> /dev/null rmmod dccp_ipv6 &> /dev/null rmmod dccp_probe &> /dev/null fi if [ ! -e "/data/all/cpuinfo" ]; then _count_cpu fi # Below the PHP security floor: decide on a barracuda system pass, # SKYNET-off too; it starts at the end of whichever branch runs below _php_security_floor _if_boa_key_tools_update_allowed if [ "${_BOA_KEY_TOOLS_UPDATE_ALLOWED}" = "YES" ] \ && [ -e "/opt/etc/fpm/fpm-pool-common.conf" ] \ && [ -e "/var/xdrago" ]; then if [ ! -z "${_SKYNET_MODE}" ] && [ "${_SKYNET_MODE}" = "OFF" ]; then if [ -n "${SSH_TTY+x}" ]; then echo echo "STATUS: BOA Skynet Agent is Inactive!" echo echo "HINT: Please remove the _SKYNET_MODE=OFF line from" echo "HINT: ${_barCnf} to enable me again." echo echo "NOTE: Critically important BOA tools will be still updated" echo _if_update_boa_key_tools_only verbose _php_security_floor_launch exit 0 else _if_update_boa_key_tools_only silent _php_security_floor_launch exit 0 fi else if [ -n "${SSH_TTY+x}" ]; then echo echo "STATUS: BOA Skynet Agent is Active, OK!" echo echo "HINT: You can add the _SKYNET_MODE=OFF line in" echo "HINT: ${_barCnf} to disable me, if needed." echo fi fi else if [ -z "$STY" ]; then _SCREEN_INIT=YES fi fi if [ -d "/.newrelic" ]; then rm -rf /.newrelic fi chmod a+w /dev/null if [ ! -e "/dev/fd" ]; then if [ -e "/proc/self/fd" ]; then rm -rf /dev/fd ln -sfn /proc/self/fd /dev/fd fi fi if [ "${_BOA_KEY_TOOLS_UPDATE_ALLOWED}" = "YES" ]; then _boa_setup fi if [ "${_BOA_KEY_TOOLS_UPDATE_ALLOWED}" = "YES" ] \ && [ -e "/var/log/barracuda_log.txt" ]; then _fix_sftp_server _fix_ping_perms _fix_tmp_perms _fix_stray_drush _fix_fpm_process_max _if_fix_lshell _if_fix_myquick _fix_node_in_lshell_access # _fix_php_in_lshell_access _fix_authorized_keys _fix_aio _fix_console_print _fix_java_symlinks _fix_composer_version _fix_wkhtml _fix_wkhtml_perms _fix_eldir _data_conf_keep _if_drupal_patches_update _fix_drupal_core_ten _fix_drupal_core_eleven _fix_pure_ftpd _fix_hosting_le _fix_newrelic _fix_leftovers _fix_nginx_crawler_access _fix_wiped_nginx_config _update_agents _sysctl_update # _saCoreN="SA-CORE-2018-002" # _fix_core_dgd # sleep 3 # _saCoreN="SA-CORE-2018-004" # _fix_core_dgd # sleep 3 # _saCoreN="SA-CORE-2018-006" # _fix_core_dgd # sleep 3 # _saCoreN="SA-CORE-2019-004" # _fix_core_dgd # sleep 3 # _saCoreN="3143016-83" # _fix_core_dgd fi # The PHP security floor's pass, decided above, after this run's own work _php_security_floor_launch if [ ! -e "/etc/ssl/private/4096.dhp" ] && [ -d "/var/xdrago" ]; then echo "Generating 4096.dhp -- it may take a very long time..." openssl dhparam -out /etc/ssl/private/4096.dhp 4096 > /dev/null 2>&1 & fi if [ -e "/etc/ssl/private/4096.dhp" ]; then chown -R root:ssl-cert /etc/ssl/private chmod 640 /etc/ssl/private/* chmod 710 /etc/ssl/private fi if [ ! -e "/root/.upstart.cnf" ] \ && [ "${_NO_CRON_AUTOSTART}" != "YES" ]; then service cron reload &> /dev/null fi if [ "${_SCREEN_INIT}" = "YES" ]; then if [ "${_DEBUG_MODE}" != "YES" ]; then clear fi echo echo "The system is ready for BOA installation!" echo echo "We will start screen session for you automatically" echo "to avoid problems with dropped SSH connections" echo "during BOA stack installation, which may take up to" echo "45-60 minutes, depending on your server speed." echo echo "If your connection will drop, simply log in again" echo "and re-attach your session with 'screen -R' command." echo echo "Enjoy!" echo if [ -x "/usr/sbin/aa-teardown" ]; then aa-teardown &> /dev/null fi else exit 0 fi else echo "ERROR: This script should be run as a root user" exit 1 fi