115 lines
5.1 KiB
Bash
Executable File
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.'
|