fix: make Linux installation permissions deterministic
Release / release (push) Successful in 3s

This commit is contained in:
Qiufeng
2026-08-17 20:16:04 +08:00
parent dc35a48b44
commit 4f626da9db
7 changed files with 381 additions and 70 deletions
+57 -1
View File
@@ -11,6 +11,10 @@ fail() {
exit 1
}
mode_of() {
stat -c '%a' "$1" 2>/dev/null || stat -f '%Lp' "$1"
}
sed -n '/^is_semver()/,/^}/p' "$ROOT/deploy/update.sh" > "$WORK/update-semver.sh"
# shellcheck disable=SC1090,SC1091
source "$WORK/update-semver.sh"
@@ -21,6 +25,20 @@ for version in 01.2.3 1.02.3 1.2.03 1.2.3-01 1.2.3-alpha..1; do
! is_semver "$version" || fail "updater accepted invalid SemVer $version"
done
missing_service_user="kaidi-fixture-missing-$$"
if KAIDI_APP_ROOT="$WORK/bootstrap/app" \
KAIDI_UPDATE_STATE_ROOT="$WORK/bootstrap/state" \
KAIDI_LOG_ROOT="$WORK/bootstrap/log" \
KAIDI_SERVICE_USER="$missing_service_user" \
KAIDI_SERVICE_GROUP="$missing_service_user" \
sh "$ROOT/deploy/update.sh" > "$WORK/bootstrap.log" 2>&1; then
fail 'updater accepted a missing service identity during bootstrap'
fi
grep -Fq "Service user $missing_service_user is missing" "$WORK/bootstrap.log" \
|| fail 'updater bootstrap failure did not preserve its diagnostic'
! grep -Eq 'No such file|nonexistent directory|cannot create' "$WORK/bootstrap.log" \
|| fail 'updater bootstrap failure was masked by an unavailable status directory'
write_mock_commands() {
local mock_bin=$1
mkdir -p "$mock_bin"
@@ -99,6 +117,33 @@ SH
cat > "$mock_bin/chown" <<'SH'
#!/bin/sh
exit 0
SH
cat > "$mock_bin/runuser" <<'SH'
#!/bin/sh
[ "${1:-}" = -u ] || exit 2
shift 2
[ "${1:-}" = -- ] && shift
exec "$@"
SH
cat > "$mock_bin/install" <<'SH'
#!/usr/bin/env bash
if [[ " $* " != *" -d "* ]]; then
exec /usr/bin/install "$@"
fi
mode=0755
paths=()
while [ "$#" -gt 0 ]; do
case "$1" in
-d) shift ;;
-o|-g) shift 2 ;;
-m) mode=$2; shift 2 ;;
*) paths+=("$1"); shift ;;
esac
done
mkdir -p "${paths[@]}"
chmod "$mode" "${paths[@]}"
SH
cat > "$mock_bin/mv" <<'SH'
@@ -170,13 +215,18 @@ prepare_installation() {
local fixture=$1
local version=$2
mkdir -p "$fixture/app/releases/1.0.0-preview.1/public" "$fixture/app/bin" \
"$fixture/state/inbox" "$fixture/systemd" "$fixture/log"
"$fixture/app/runtime/java/bin" "$fixture/state/inbox" "$fixture/systemd" "$fixture/log"
printf 'old application\n' > "$fixture/app/releases/1.0.0-preview.1/app.jar"
printf '<!doctype html><title>old</title>\n' > "$fixture/app/releases/1.0.0-preview.1/public/index.html"
printf '1.0.0-preview.1\n' > "$fixture/app/releases/1.0.0-preview.1/VERSION"
ln -s "$fixture/app/releases/1.0.0-preview.1" "$fixture/app/current"
printf 'old update.sh\n' > "$fixture/app/bin/update.sh"
chmod 0755 "$fixture/app/bin/update.sh"
cat > "$fixture/app/runtime/java/bin/java" <<'SH'
#!/bin/sh
exit 0
SH
chmod 0755 "$fixture/app/runtime/java/bin/java"
for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do
printf 'old %s\n' "$name" > "$fixture/systemd/$name"
done
@@ -222,6 +272,8 @@ run_update() {
MOCK_RELEASE_ORIGIN="${FIXTURE_RELEASE_ORIGIN:-https://release.fixture.invalid}" \
MOCK_EXPECT_RELEASE_TOKEN="${FIXTURE_RELEASE_TOKEN-}" \
MOCK_SYSTEMCTL_LOG="$fixture/systemctl.log" \
KAIDI_SERVICE_USER="$(id -un)" \
KAIDI_SERVICE_GROUP="$(id -gn)" \
KAIDI_APP_ROOT="$fixture/app" \
KAIDI_UPDATE_STATE_ROOT="$fixture/state" \
KAIDI_LOG_ROOT="$fixture/log" \
@@ -310,6 +362,10 @@ assert_success_case() {
|| fail 'success case left a claimed request behind'
grep -qx 'daemon-reload' "$fixture/systemctl.log" || fail 'systemd units were not reloaded'
! grep -qi nginx "$fixture/systemctl.log" || fail 'updater unexpectedly managed Nginx'
[ "$(mode_of "$fixture/app")" = 750 ] || fail 'application root is not traversable by the service group'
[ "$(mode_of "$fixture/app/releases/$version")" = 750 ] || fail 'release root mode is not 0750'
[ "$(mode_of "$fixture/app/current/app.jar")" = 640 ] || fail 'release file mode is not 0640'
[ "$(mode_of "$fixture/app/current/ops/update.sh")" = 750 ] || fail 'release updater mode is not 0750'
}
assert_rollback_case() {