Corp/ops/deploy/install-deploy-hardening
Игорь Надеин 4f6244ab1f
Some checks failed
Deploy production / deploy (push) Failing after 2s
Fix CORP runtime errors and harden deployment and media access
2026-09-10 16:40:08 +03:00

115 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Transactional tooling-only update. Never restarts the active application.
set -Eeuo pipefail
[[ ${EUID} -eq 0 ]] || { echo 'Run this installer with sudo.' >&2; exit 1; }
SOURCE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
exec 9>/run/lock/eusrr-deploy.lock
flock -n 9 || { echo 'A deployment is running. Retry after it finishes.' >&2; exit 3; }
[[ -d /srv/eusrr-slots && ! -L /srv/eusrr-slots ]] || {
echo 'Expected a real /srv/eusrr-slots directory.' >&2; exit 1;
}
slot_owner="$(stat -c '%u:%g' /srv/eusrr-slots)"
slot_mode="$(stat -c '%a' /srv/eusrr-slots)"
backup="/var/lib/eusrr-deploy/tooling-backup-$(date -u +%Y%m%dT%H%M%SZ)-$$"
install -d -o root -g root -m 0700 "${backup}" "${backup}/before" "${backup}/staged"
staged="${backup}/staged"
installed=()
complete=0
mutated=0
cleanup() {
local status=$? target name recovered=1
trap - EXIT HUP INT TERM
if [[ ${complete} -eq 0 && ${mutated} -eq 1 ]]; then
set +e
echo 'Tooling validation failed; restoring the previous files.' >&2
for target in "${installed[@]}"; do
name="${target//\//_}"
if [[ -f ${backup}/before/${name} ]]; then
cp -a -- "${backup}/before/${name}" "${target}" || recovered=0
else
rm -f -- "${target}" || recovered=0
fi
done
chown "${slot_owner}" /srv/eusrr-slots || recovered=0
chmod "0${slot_mode}" /srv/eusrr-slots || recovered=0
systemctl daemon-reload || recovered=0
if [[ ${recovered} -eq 1 ]]; then
echo "Previous tooling restored. Review backup: ${backup}" >&2
else
echo "ERROR: tooling recovery incomplete. Manual recovery required from ${backup}" >&2
fi
(( status != 0 )) || status=1
fi
exit "${status}"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
# Copy into a root-only directory before checking or installing anything.
for file in eusrr-deploy eusrr-deploy-ssh eusrr-deploy-healthcheck logrotate.conf journald.conf gitea_known_hosts; do
install -o root -g root -m 0644 "${SOURCE_DIR}/${file}" "${staged}/${file}"
done
for unit in backend frontend celery-worker celery-beat; do
install -o root -g root -m 0644 "${SOURCE_DIR}/systemd/eusrr-${unit}@.service" \
"${staged}/eusrr-${unit}@.service"
done
bash -n "${staged}/eusrr-deploy"
bash -n "${staged}/eusrr-deploy-ssh"
python3 -m py_compile "${staged}/eusrr-deploy-healthcheck"
[[ $(ssh-keygen -lf "${staged}/gitea_known_hosts" | awk '{print $2}') == \
'SHA256:341ON+FsVZMVMsduFUZjevfxjagbFG9yPeZ0edTVSOk' ]] || {
echo 'Pinned Gitea host key fingerprint does not match.' >&2; exit 1;
}
if ! LC_ALL=C logrotate --debug --state /dev/null "${staged}/logrotate.conf" \
>"${staged}/logrotate-validation.log" 2>&1 ||
grep -Eq '^error:|Ignoring .* because' "${staged}/logrotate-validation.log"; then
cat "${staged}/logrotate-validation.log" >&2
exit 1
fi
# Ubuntu 24.04 sudo supports anchored argument regexes. The runtime account
# cannot invoke maintenance, self-test, or extra arguments through sudo.
printf '%s\n' 'deploy ALL=(root) NOPASSWD: /usr/local/sbin/eusrr-deploy ^[0-9a-f]{40}$' \
>"${staged}/sudoers"
visudo -cf "${staged}/sudoers" >/dev/null
install_file() {
local source="$1" target="$2" mode="$3" name
name="${target//\//_}"
[[ ! -L ${target} ]] || { echo "Refusing symlink target: ${target}" >&2; return 1; }
[[ ! -e ${target} ]] || cp -a -- "${target}" "${backup}/before/${name}"
installed+=("${target}")
mutated=1
install -o root -g root -m "${mode}" "${source}" "${target}.new"
mv -Tf -- "${target}.new" "${target}"
}
install -d -o root -g root -m 0755 /usr/local/libexec /etc/systemd/journald.conf.d
install_file "${staged}/eusrr-deploy" /usr/local/sbin/eusrr-deploy 0755
install_file "${staged}/eusrr-deploy-ssh" /usr/local/sbin/eusrr-deploy-ssh 0755
install_file "${staged}/eusrr-deploy-healthcheck" /usr/local/libexec/eusrr-deploy-healthcheck 0755
install_file "${staged}/logrotate.conf" /etc/logrotate.d/eusrr-deploy 0644
install_file "${staged}/journald.conf" /etc/systemd/journald.conf.d/60-eusrr-retention.conf 0644
install_file "${staged}/sudoers" /etc/sudoers.d/eusrr-deploy 0440
install_file "${staged}/gitea_known_hosts" /etc/eusrr/gitea_known_hosts 0644
for unit in backend frontend celery-worker celery-beat; do
install_file "${staged}/eusrr-${unit}@.service" "/etc/systemd/system/eusrr-${unit}@.service" 0644
done
chown root:root /srv/eusrr-slots
# A four-digit chmod retains existing setgid on GNU directory targets.
chmod 00755 /srv/eusrr-slots
systemctl daemon-reload
/usr/local/sbin/eusrr-deploy --self-test
visudo -c >/dev/null
slot="$(cat /var/lib/eusrr-deploy/active-slot)"
[[ ${slot} == blue || ${slot} == green ]] || {
echo 'Expected a native active slot before updating deployment tooling.' >&2; exit 1;
}
systemctl is-active "eusrr-backend@${slot}.service" "eusrr-frontend@${slot}.service" \
"eusrr-celery-worker@${slot}.service" "eusrr-celery-beat@${slot}.service"
complete=1
printf 'Deployment tooling and unit templates updated; active application was not restarted. Backup: %s\n' "${backup}"
echo 'Journald limits take effect on its next restart. No logs were vacuumed or releases removed.'