文件
hyb-oa/scripts/refresh-deployment-contract.sh
T

202 行
5.9 KiB
Bash
可执行文件

#!/usr/bin/env bash
set -euo pipefail
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
umask 077
unset BASH_ENV ENV CDPATH GLOBIGNORE LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH NODE_OPTIONS
SERVICES=(
hyb-oa-automatic-backup-daily.service
hyb-oa-automatic-backup-watchdog.service
hyb-oa-automatic-backup-drill-reminder.service
hyb-oa-warch3-ops.service
)
TIMERS=(
hyb-oa-automatic-backup-daily.timer
hyb-oa-automatic-backup-watchdog.timer
hyb-oa-automatic-backup-drill-reminder.timer
hyb-oa-warch3-ops.timer
)
CONTRACT_SERVICE="hyb-oa-automatic-backup-daily.service"
DROPIN_NAME="20-active-build.conf"
ROOT_PREFIX=""
TEST_MODE=0
fail() {
local code="$1"
local category="$2"
echo "[deployment-contract-refresh][FAIL] category=${category}" >&2
exit "$code"
}
# Exit 255 is reserved for the SSH client transport layer. Do not use it for
# helper failures, because callers classify 255 as root_identity_unavailable.
if [[ "$(id -u)" != "0" ]]; then
test_root="${HYB_CONTRACT_REFRESH_TEST_ROOT:-}"
if [[ "$test_root" =~ ^/tmp/[A-Za-z0-9._/-]+$ && "$test_root" != */ \
&& "$test_root" != *"//"* && "/$test_root/" != *"/../"* ]]; then
TEST_MODE=1
ROOT_PREFIX="$test_root"
PATH="$test_root/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
else
fail 70 root_required
fi
fi
UNIT_ROOT="${ROOT_PREFIX}/etc/systemd/system"
LOCK_FILE="${ROOT_PREFIX}/run/lock/hyb-oa-deployment-contract-refresh.lock"
EXPECTED_TARGET="${ROOT_PREFIX}/usr/local/sbin/hyb-oa-deployment-contract-refresh"
need_command() {
command -v "$1" >/dev/null 2>&1 || fail 70 runtime_prerequisite_missing
}
for required_command in systemctl docker install mktemp mv mkdir chmod flock stat; do
need_command "$required_command"
done
validate_self() {
if [[ "$TEST_MODE" == "0" ]]; then
[[ "$0" == "$EXPECTED_TARGET" && -f "$EXPECTED_TARGET" && ! -L "$EXPECTED_TARGET" \
&& "$(stat -c %u:%g:%a "$EXPECTED_TARGET")" == "0:0:755" ]] \
|| fail 70 refresh_program_integrity_invalid
else
[[ -f "$0" && ! -L "$0" ]] || fail 70 refresh_program_integrity_invalid
fi
}
systemctl_value() {
local unit="$1"
local property="$2"
systemctl show "$unit" -p "$property" --value
}
unit_environment_value() {
local unit="$1"
local wanted="$2"
local entry value=""
for entry in $(systemctl_value "$unit" Environment); do
if [[ "${entry%%=*}" == "$wanted" ]]; then
value="${entry#*=}"
fi
done
[[ -n "$value" ]] || return 1
printf '%s' "$value"
}
validate_fixed_units() {
local unit fragment owner
for unit in "${SERVICES[@]}"; do
[[ "$(systemctl_value "$unit" LoadState)" == "loaded" ]] \
|| fail 70 service_not_loaded
fragment="$(systemctl_value "$unit" FragmentPath)"
[[ -n "$fragment" ]] || fail 70 service_fragment_missing
if [[ "$TEST_MODE" == "0" ]]; then
[[ "$fragment" == /etc/systemd/system/* && -f "$fragment" && ! -L "$fragment" ]] \
|| fail 70 service_fragment_invalid
owner="$(stat -c %u "$fragment")"
[[ "$owner" == "0" ]] || fail 70 service_fragment_not_root_owned
fi
done
}
resolve_active_build() {
local project
local -a servers=()
project="$(unit_environment_value "$CONTRACT_SERVICE" COMPOSE_PROJECT_NAME)" \
|| fail 70 compose_project_missing
[[ "$project" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || fail 70 compose_project_invalid
mapfile -t servers < <(docker ps \
--filter "label=com.docker.compose.project=$project" \
--filter "label=com.docker.compose.service=server" \
--format '{{.ID}}')
[[ "${#servers[@]}" -eq 1 && -n "${servers[0]}" ]] \
|| fail 70 active_server_not_unique
ACTIVE_BUILD="$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "${servers[0]}" \
| sed -n 's/^BUILD_SHA=//p' | tail -n 1)"
ACTIVE_BUILD="${ACTIVE_BUILD,,}"
[[ "$ACTIVE_BUILD" =~ ^[0-9a-f]{40,64}$ ]] || fail 70 active_build_invalid
ACTIVE_FRONTEND_BUILD="${ACTIVE_BUILD:0:12}"
}
write_build_dropin() {
local service="$1"
local dropin_dir="$UNIT_ROOT/${service}.d"
local target="$dropin_dir/$DROPIN_NAME"
local temporary
if [[ "$TEST_MODE" == "0" ]]; then
install -d -o root -g root -m 0755 "$dropin_dir"
else
mkdir -p "$dropin_dir"
chmod 0755 "$dropin_dir"
fi
temporary="$(mktemp "$dropin_dir/.${DROPIN_NAME}.XXXXXX")"
{
printf '[Service]\n'
printf 'Environment=BUILD_SHA=%s\n' "$ACTIVE_BUILD"
printf 'Environment=VITE_BUILD_SHA=%s\n' "$ACTIVE_FRONTEND_BUILD"
} >"$temporary"
chmod 0644 "$temporary"
if [[ "$TEST_MODE" == "0" ]]; then
chown root:root "$temporary"
fi
mv -f -- "$temporary" "$target"
}
verify_builds() {
local service service_build service_frontend_build
for service in "${SERVICES[@]}"; do
service_build="$(unit_environment_value "$service" BUILD_SHA)" \
|| fail 42 unit_build_not_refreshed
service_frontend_build="$(unit_environment_value "$service" VITE_BUILD_SHA)" \
|| fail 42 unit_build_not_refreshed
[[ "$service_build" == "$ACTIVE_BUILD" \
&& "$service_frontend_build" == "$ACTIVE_FRONTEND_BUILD" ]] \
|| fail 42 unit_build_not_refreshed
done
}
verify_timers() {
local timer
for timer in "${TIMERS[@]}"; do
[[ "$(systemctl is-enabled "$timer")" == "enabled" \
&& "$(systemctl is-active "$timer")" == "active" ]] \
|| fail 43 timer_not_active
done
}
probe() {
validate_self
validate_fixed_units
resolve_active_build
echo "[deployment-contract-refresh][PASS] probe"
}
refresh() {
validate_self
validate_fixed_units
resolve_active_build
mkdir -p "$(dirname "$LOCK_FILE")"
exec 9>"$LOCK_FILE"
flock -n 9 || fail 42 refresh_already_running
local service
for service in "${SERVICES[@]}"; do
write_build_dropin "$service"
done
systemctl daemon-reload
verify_builds
verify_timers
echo "[deployment-contract-refresh][PASS] refreshed"
}
[[ "$#" -eq 0 ]] || fail 64 unsupported_forced_command
case "${SSH_ORIGINAL_COMMAND:-}" in
probe) probe ;;
refresh) refresh ;;
*) fail 64 unsupported_forced_command ;;
esac