Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d2e2f0d1 | ||
|
|
bb5e63aaf0 | ||
|
|
2cf29c030a |
@@ -56,7 +56,7 @@ PostgreSQL 18 兼容工作继续冻结。
|
|||||||
先在宝塔面板停止并删除当前错误的 Java 项目,再执行:
|
先在宝塔面板停止并删除当前错误的 Java 项目,再执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.28/install.sh | sudo env KAIDI_APP_PORT=18080 bash
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.30/install.sh | sudo env KAIDI_APP_PORT=18080 bash
|
||||||
```
|
```
|
||||||
|
|
||||||
该命令只安装程序运行所需的 systemd 单元,自动创建 `kaidi` 用户并检测现有 Java 17(包括
|
该命令只安装程序运行所需的 systemd 单元,自动创建 `kaidi` 用户并检测现有 Java 17(包括
|
||||||
|
|||||||
+77
-8
@@ -67,8 +67,61 @@ related_pids() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
managed_service_account() {
|
||||||
|
local entry home shell
|
||||||
|
entry=$(getent passwd "$SERVICE_USER" 2>/dev/null || true)
|
||||||
|
[ -n "$entry" ] || return 1
|
||||||
|
home=$(printf '%s\n' "$entry" | awk -F: '{print $6}')
|
||||||
|
shell=$(printf '%s\n' "$entry" | awk -F: '{print $7}')
|
||||||
|
case "$home:$shell" in
|
||||||
|
"$STATE_ROOT:"*/nologin|"$STATE_ROOT:"*/false) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
service_user_pids() {
|
||||||
|
local uid=$1 proc pid owner_uid
|
||||||
|
for proc in /proc/[0-9]*; do
|
||||||
|
[ -d "$proc" ] || continue
|
||||||
|
pid=${proc##*/}
|
||||||
|
owner_uid=$(stat -c '%u' "$proc" 2>/dev/null || true)
|
||||||
|
[ "$owner_uid" = "$uid" ] && printf '%s\n' "$pid"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate_uid_processes() {
|
||||||
|
local uid=$1 deadline
|
||||||
|
local -a pids=()
|
||||||
|
mapfile -t pids < <(service_user_pids "$uid")
|
||||||
|
if [ "${#pids[@]}" -gt 0 ]; then
|
||||||
|
log "Stopping processes owned by the dedicated $SERVICE_USER account: ${pids[*]}"
|
||||||
|
kill -TERM "${pids[@]}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
deadline=$((SECONDS + 5))
|
||||||
|
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||||
|
mapfile -t pids < <(service_user_pids "$uid")
|
||||||
|
[ "${#pids[@]}" -gt 0 ] || return 0
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
kill -KILL "${pids[@]}" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
mapfile -t pids < <(service_user_pids "$uid")
|
||||||
|
[ "${#pids[@]}" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
all_pids_owned_by_service_user() {
|
||||||
|
local service_uid pid owner_uid
|
||||||
|
managed_service_account || return 1
|
||||||
|
service_uid=$(id -u "$SERVICE_USER")
|
||||||
|
for pid in "$@"; do
|
||||||
|
[ -d "/proc/$pid" ] || continue
|
||||||
|
owner_uid=$(stat -c '%u' "/proc/$pid" 2>/dev/null || true)
|
||||||
|
[ "$owner_uid" = "$service_uid" ] || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
stop_managed_processes() {
|
stop_managed_processes() {
|
||||||
local deadline
|
local allow_service_restart=${1:-false} deadline
|
||||||
local -a pids=()
|
local -a pids=()
|
||||||
mapfile -t pids < <(related_pids)
|
mapfile -t pids < <(related_pids)
|
||||||
if [ "${#pids[@]}" -gt 0 ]; then
|
if [ "${#pids[@]}" -gt 0 ]; then
|
||||||
@@ -84,8 +137,12 @@ stop_managed_processes() {
|
|||||||
kill -KILL "${pids[@]}" 2>/dev/null || true
|
kill -KILL "${pids[@]}" 2>/dev/null || true
|
||||||
sleep 1
|
sleep 1
|
||||||
mapfile -t pids < <(related_pids)
|
mapfile -t pids < <(related_pids)
|
||||||
[ "${#pids[@]}" -eq 0 ] \
|
[ "${#pids[@]}" -eq 0 ] && return 0
|
||||||
|| die "A process manager is restarting Kaidi; remove the Kaidi project from Baota and run this command again"
|
if [ "$allow_service_restart" = true ] && all_pids_owned_by_service_user "${pids[@]}"; then
|
||||||
|
log "A process manager restarted the dedicated $SERVICE_USER account; forced account cleanup will stop it"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
die "A process manager is restarting Kaidi; remove the Kaidi project from Baota and run this command again"
|
||||||
}
|
}
|
||||||
|
|
||||||
create_recovery_backup() {
|
create_recovery_backup() {
|
||||||
@@ -120,6 +177,10 @@ create_recovery_backup() {
|
|||||||
|
|
||||||
remove_systemd_units() {
|
remove_systemd_units() {
|
||||||
local unit
|
local unit
|
||||||
|
rm -rf \
|
||||||
|
/etc/systemd/system/kaidi-finance.service.d \
|
||||||
|
/etc/systemd/system/kaidi-update.service.d \
|
||||||
|
/etc/systemd/system/kaidi-update.path.d
|
||||||
rm -f \
|
rm -f \
|
||||||
/etc/systemd/system/kaidi-finance.service \
|
/etc/systemd/system/kaidi-finance.service \
|
||||||
/etc/systemd/system/kaidi-update.service \
|
/etc/systemd/system/kaidi-update.service \
|
||||||
@@ -149,15 +210,23 @@ remove_download_archives() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_service_identity() {
|
remove_service_identity() {
|
||||||
local entry home shell group_entry gid members primary_users
|
local entry home shell service_uid group_entry gid members primary_users
|
||||||
entry=$(getent passwd "$SERVICE_USER" 2>/dev/null || true)
|
entry=$(getent passwd "$SERVICE_USER" 2>/dev/null || true)
|
||||||
if [ -n "$entry" ]; then
|
if [ -n "$entry" ]; then
|
||||||
home=$(printf '%s\n' "$entry" | awk -F: '{print $6}')
|
home=$(printf '%s\n' "$entry" | awk -F: '{print $6}')
|
||||||
shell=$(printf '%s\n' "$entry" | awk -F: '{print $7}')
|
shell=$(printf '%s\n' "$entry" | awk -F: '{print $7}')
|
||||||
case "$home:$shell" in
|
case "$home:$shell" in
|
||||||
"$STATE_ROOT:"*/nologin|"$STATE_ROOT:"*/false)
|
"$STATE_ROOT:"*/nologin|"$STATE_ROOT:"*/false)
|
||||||
userdel "$SERVICE_USER" \
|
service_uid=$(id -u "$SERVICE_USER")
|
||||||
|| die "Failed to remove the dedicated $SERVICE_USER service account"
|
terminate_uid_processes "$service_uid" || true
|
||||||
|
if ! userdel --force "$SERVICE_USER"; then
|
||||||
|
terminate_uid_processes "$service_uid" || true
|
||||||
|
userdel --force "$SERVICE_USER" \
|
||||||
|
|| die "Failed to remove the dedicated $SERVICE_USER service account"
|
||||||
|
fi
|
||||||
|
terminate_uid_processes "$service_uid" \
|
||||||
|
|| die "Processes owned by the removed $SERVICE_USER account are still running"
|
||||||
|
rm -rf "/run/user/$service_uid"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log "Keeping pre-existing user $SERVICE_USER because its home or shell is not Kaidi-managed"
|
log "Keeping pre-existing user $SERVICE_USER because its home or shell is not Kaidi-managed"
|
||||||
@@ -182,13 +251,13 @@ main() {
|
|||||||
validate_inputs
|
validate_inputs
|
||||||
log "External MySQL data and reverse-proxy configuration will not be modified"
|
log "External MySQL data and reverse-proxy configuration will not be modified"
|
||||||
stop_systemd_units
|
stop_systemd_units
|
||||||
stop_managed_processes
|
stop_managed_processes true
|
||||||
create_recovery_backup
|
create_recovery_backup
|
||||||
remove_systemd_units
|
remove_systemd_units
|
||||||
remove_managed_paths
|
remove_managed_paths
|
||||||
remove_download_archives
|
remove_download_archives
|
||||||
remove_service_identity
|
remove_service_identity
|
||||||
stop_managed_processes
|
stop_managed_processes false
|
||||||
log "Local Kaidi installation state has been removed"
|
log "Local Kaidi installation state has been removed"
|
||||||
if [ -n "$BACKUP_ARCHIVE" ]; then
|
if [ -n "$BACKUP_ARCHIVE" ]; then
|
||||||
log "Recovery backup: $BACKUP_ARCHIVE"
|
log "Recovery backup: $BACKUP_ARCHIVE"
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ IOSchedulingPriority=6
|
|||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
ReadWritePaths=/opt/kaidi /www/wwwroot/kaidi /var/lib/kaidi /var/lib/kaidi-update /var/log/kaidi /etc/systemd/system
|
ReadWritePaths=/opt/kaidi -/www/wwwroot/kaidi /var/lib/kaidi /var/lib/kaidi-update /var/log/kaidi /etc/systemd/system
|
||||||
UMask=0077
|
UMask=0077
|
||||||
|
|||||||
+15
-3
@@ -62,6 +62,9 @@ case "$RUNTIME_ENV_FILE" in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
bootstrap_die() {
|
bootstrap_die() {
|
||||||
|
if [ -f "$PROCESSING_FILE" ] && [ ! -L "$PROCESSING_FILE" ]; then
|
||||||
|
fail "$1"
|
||||||
|
fi
|
||||||
printf '%s\n' "$1" >&2
|
printf '%s\n' "$1" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -174,6 +177,15 @@ prepare_update_layout() {
|
|||||||
|| bootstrap_die "Private update directories could not be prepared"
|
|| bootstrap_die "Private update directories could not be prepared"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepare_queue_layout() {
|
||||||
|
if [ -d "$PROCESSING_DIR" ] && [ ! -L "$PROCESSING_DIR" ] \
|
||||||
|
&& [ -d "$FAILED_REQUEST_ROOT" ] && [ ! -L "$FAILED_REQUEST_ROOT" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
install -d -o root -g root -m 0700 "$PROCESSING_DIR" "$FAILED_REQUEST_ROOT" \
|
||||||
|
|| bootstrap_die "Private update queue directories could not be prepared"
|
||||||
|
}
|
||||||
|
|
||||||
secure_release_tree() {
|
secure_release_tree() {
|
||||||
release_dir=$1
|
release_dir=$1
|
||||||
chown -R root:"$SERVICE_GROUP" "$release_dir" || return 1
|
chown -R root:"$SERVICE_GROUP" "$release_dir" || return 1
|
||||||
@@ -197,9 +209,6 @@ verify_release_access() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_update_layout
|
|
||||||
load_runtime_database_env
|
|
||||||
|
|
||||||
# shellcheck disable=SC2329 # Invoked by the EXIT trap below.
|
# shellcheck disable=SC2329 # Invoked by the EXIT trap below.
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rc=$?
|
rc=$?
|
||||||
@@ -616,6 +625,7 @@ validate_release_operations() {
|
|||||||
"$WORK_DIR/extracted/ops/kaidi-update.path" >/dev/null || return 1
|
"$WORK_DIR/extracted/ops/kaidi-update.path" >/dev/null || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepare_queue_layout
|
||||||
exec 9>"$LOCK_FILE"
|
exec 9>"$LOCK_FILE"
|
||||||
flock -n 9 || exit 0
|
flock -n 9 || exit 0
|
||||||
recover_interrupted_transaction
|
recover_interrupted_transaction
|
||||||
@@ -650,6 +660,8 @@ TARGET_VERSION=$REQUESTED_VERSION
|
|||||||
REQUEST_ACTION=$(jq -er '(.action // "INSTALL") | strings | ascii_upcase
|
REQUEST_ACTION=$(jq -er '(.action // "INSTALL") | strings | ascii_upcase
|
||||||
| select(. == "DOWNLOAD" or . == "INSTALL")' "$PROCESSING_FILE") \
|
| select(. == "DOWNLOAD" or . == "INSTALL")' "$PROCESSING_FILE") \
|
||||||
|| fail "Update request action is invalid"
|
|| fail "Update request action is invalid"
|
||||||
|
prepare_update_layout
|
||||||
|
load_runtime_database_env
|
||||||
[ -z "$RELEASE_TOKEN" ] || { [ "${#RELEASE_TOKEN}" -le 512 ] \
|
[ -z "$RELEASE_TOKEN" ] || { [ "${#RELEASE_TOKEN}" -le 512 ] \
|
||||||
&& ! printf '%s' "$RELEASE_TOKEN" | grep -q '[[:cntrl:]]'; } \
|
&& ! printf '%s' "$RELEASE_TOKEN" | grep -q '[[:cntrl:]]'; } \
|
||||||
|| fail "UPDATE_RELEASE_TOKEN is invalid"
|
|| fail "UPDATE_RELEASE_TOKEN is invalid"
|
||||||
|
|||||||
@@ -137,3 +137,69 @@ test('confirms completion after the setup response is interrupted by a service r
|
|||||||
await expect(page.getByText('网络请求失败')).toHaveCount(0);
|
await expect(page.getByText('网络请求失败')).toHaveCount(0);
|
||||||
await expect(page).toHaveURL(/\/login$/, { timeout: 15_000 });
|
await expect(page).toHaveURL(/\/login$/, { timeout: 15_000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('leaves the completed wizard when post-completion status polling is unavailable', async ({ page }) => {
|
||||||
|
let completionStarted = false;
|
||||||
|
await page.route('**/api/v1/setup/**', async (route) => {
|
||||||
|
const request = route.request();
|
||||||
|
const pathname = new URL(request.url()).pathname;
|
||||||
|
if (pathname === '/api/v1/setup/status') {
|
||||||
|
if (completionStarted) {
|
||||||
|
await route.abort('connectionreset');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await route.fulfill({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
required: true,
|
||||||
|
locked: false,
|
||||||
|
ready: false,
|
||||||
|
supportedDatabaseTypes: ['MYSQL'],
|
||||||
|
message: '请完成安装',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const body = request.postDataJSON() as Record<string, unknown>;
|
||||||
|
if (pathname.endsWith('/test-connection')) {
|
||||||
|
await route.fulfill({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
successful: true,
|
||||||
|
databaseType: 'MYSQL',
|
||||||
|
serverVersion: '8.4.6',
|
||||||
|
schemaReady: true,
|
||||||
|
message: 'MySQL 8.4 连接、排序规则和完整迁移权限验证通过',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(body.adminUsername).toBe('admin');
|
||||||
|
completionStarted = true;
|
||||||
|
await route.fulfill({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
completed: true,
|
||||||
|
username: 'admin',
|
||||||
|
message: '安装完成,系统正在切换到正式模式',
|
||||||
|
restartAfterSeconds: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/setup');
|
||||||
|
await page.getByLabel('数据库密码').fill('fixture-db-password');
|
||||||
|
await page.getByLabel('安装码').fill('fixture-setup-code');
|
||||||
|
await page.getByRole('button', { name: '测试连接' }).click();
|
||||||
|
await page.getByRole('button', { name: '下一步' }).click();
|
||||||
|
await page.getByLabel('管理员密码').fill('SetupAdmin@2026Strong');
|
||||||
|
await page.getByLabel('确认密码').fill('SetupAdmin@2026Strong');
|
||||||
|
await page.getByRole('button', { name: '完成安装' }).click();
|
||||||
|
await page.getByRole('button', { name: '确定', exact: true }).click();
|
||||||
|
|
||||||
|
await expect(page.getByText('安装完成', { exact: true })).toBeVisible();
|
||||||
|
await expect(page).toHaveURL(/\/login$/, { timeout: 5_000 });
|
||||||
|
});
|
||||||
|
|||||||
@@ -378,19 +378,18 @@ function confirmSetup() {
|
|||||||
function startCountdown() {
|
function startCountdown() {
|
||||||
window.clearInterval(countdownTimer);
|
window.clearInterval(countdownTimer);
|
||||||
countdownTimer = window.setInterval(() => {
|
countdownTimer = window.setInterval(() => {
|
||||||
countdown.value -= 1;
|
countdown.value = Math.max(0, countdown.value - 1);
|
||||||
if (countdown.value <= 0) void openLogin();
|
if (countdown.value === 0) openLogin();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openLogin() {
|
function openLogin() {
|
||||||
window.clearInterval(countdownTimer);
|
window.clearInterval(countdownTimer);
|
||||||
completionMessage.value = '系统正在启动,请稍候。';
|
countdown.value = 0;
|
||||||
if (await waitForSetupCompletion()) {
|
completionMessage.value = '正在进入登录页。';
|
||||||
await router.replace('/login');
|
// A full navigation clears the setup-mode SPA state retained across the
|
||||||
return;
|
// service restart. The login route guard performs the final setup check.
|
||||||
}
|
window.location.replace('/login');
|
||||||
completionMessage.value = '系统启动时间较长,请稍后刷新页面。';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -319,6 +319,8 @@ grep -Fqx 'StartLimitBurst=3' "$ROOT/deploy/systemd/kaidi-finance.service" \
|
|||||||
|| fail 'application service no longer has a bounded restart burst'
|
|| fail 'application service no longer has a bounded restart burst'
|
||||||
grep -Fqx 'Restart=no' "$ROOT/deploy/systemd/kaidi-update.service" \
|
grep -Fqx 'Restart=no' "$ROOT/deploy/systemd/kaidi-update.service" \
|
||||||
|| fail 'update service can automatically repeat a failed switching transaction'
|
|| fail 'update service can automatically repeat a failed switching transaction'
|
||||||
|
grep -Fq -- '-/www/wwwroot/kaidi' "$ROOT/deploy/systemd/kaidi-update.service" \
|
||||||
|
|| fail 'update service requires the Baota application path on a systemd-only installation'
|
||||||
! grep -Fq '/var/lib/kaidi-update/processing' "$ROOT/deploy/systemd/kaidi-update.path" \
|
! grep -Fq '/var/lib/kaidi-update/processing' "$ROOT/deploy/systemd/kaidi-update.path" \
|
||||||
|| fail 'update path can automatically repeat a claimed switching transaction'
|
|| fail 'update path can automatically repeat a claimed switching transaction'
|
||||||
# shellcheck disable=SC2016 # Match literal installer source.
|
# shellcheck disable=SC2016 # Match literal installer source.
|
||||||
|
|||||||
@@ -83,5 +83,13 @@ grep -Fq 'KAIDI_PURGE_CONFIRM=$REQUIRED_CONFIRMATION' "$ROOT/deploy/purge.sh" \
|
|||||||
|| fail 'destructive removal no longer requires explicit confirmation'
|
|| fail 'destructive removal no longer requires explicit confirmation'
|
||||||
grep -Fq 'External MySQL data and reverse-proxy configuration will not be modified' "$ROOT/deploy/purge.sh" \
|
grep -Fq 'External MySQL data and reverse-proxy configuration will not be modified' "$ROOT/deploy/purge.sh" \
|
||||||
|| fail 'purge boundary is no longer explicit'
|
|| fail 'purge boundary is no longer explicit'
|
||||||
|
# shellcheck disable=SC2016 # Match literal service-account cleanup calls.
|
||||||
|
grep -Fq 'terminate_uid_processes "$service_uid"' "$ROOT/deploy/purge.sh" \
|
||||||
|
|| fail 'dedicated service-account processes are not terminated before account removal'
|
||||||
|
# shellcheck disable=SC2016 # Match literal forced account removal.
|
||||||
|
grep -Fq 'userdel --force "$SERVICE_USER"' "$ROOT/deploy/purge.sh" \
|
||||||
|
|| fail 'dedicated service-account removal is not resilient to a restarting panel process'
|
||||||
|
grep -Fq '/etc/systemd/system/kaidi-update.service.d' "$ROOT/deploy/purge.sh" \
|
||||||
|
|| fail 'updater systemd drop-ins are not removed'
|
||||||
|
|
||||||
printf 'Local purge and recovery fixture passed\n'
|
printf 'Local purge and recovery fixture passed\n'
|
||||||
|
|||||||
@@ -26,18 +26,35 @@ for version in 01.2.3 1.02.3 1.2.03 1.2.3-01 1.2.3-alpha..1; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
missing_service_user="kaidi-fixture-missing-$$"
|
missing_service_user="kaidi-fixture-missing-$$"
|
||||||
|
mkdir -p "$WORK/bootstrap/app" "$WORK/bootstrap/state/inbox" \
|
||||||
|
"$WORK/bootstrap/state/processing" "$WORK/bootstrap/state/failed" "$WORK/bootstrap/log" \
|
||||||
|
"$WORK/bootstrap/bin"
|
||||||
|
cat > "$WORK/bootstrap/bin/flock" <<'SH'
|
||||||
|
#!/bin/sh
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod 0755 "$WORK/bootstrap/bin/flock"
|
||||||
|
jq -n \
|
||||||
|
'{action:"DOWNLOAD",version:"1.0.0-preview.2",reason:"bootstrap fixture"}' \
|
||||||
|
> "$WORK/bootstrap/state/inbox/request.json"
|
||||||
if KAIDI_APP_ROOT="$WORK/bootstrap/app" \
|
if KAIDI_APP_ROOT="$WORK/bootstrap/app" \
|
||||||
KAIDI_UPDATE_STATE_ROOT="$WORK/bootstrap/state" \
|
KAIDI_UPDATE_STATE_ROOT="$WORK/bootstrap/state" \
|
||||||
KAIDI_LOG_ROOT="$WORK/bootstrap/log" \
|
KAIDI_LOG_ROOT="$WORK/bootstrap/log" \
|
||||||
KAIDI_SERVICE_USER="$missing_service_user" \
|
KAIDI_SERVICE_USER="$missing_service_user" \
|
||||||
KAIDI_SERVICE_GROUP="$missing_service_user" \
|
KAIDI_SERVICE_GROUP="$missing_service_user" \
|
||||||
|
PATH="$WORK/bootstrap/bin:$PATH" \
|
||||||
sh "$ROOT/deploy/update.sh" > "$WORK/bootstrap.log" 2>&1; then
|
sh "$ROOT/deploy/update.sh" > "$WORK/bootstrap.log" 2>&1; then
|
||||||
fail 'updater accepted a missing service identity during bootstrap'
|
fail 'updater accepted a missing service identity during bootstrap'
|
||||||
fi
|
fi
|
||||||
grep -Fq "Service user $missing_service_user is missing" "$WORK/bootstrap.log" \
|
grep -Fq "Service user $missing_service_user is missing" "$WORK/bootstrap.log" \
|
||||||
|| fail 'updater bootstrap failure did not preserve its diagnostic'
|
|| fail 'updater bootstrap failure did not preserve its diagnostic'
|
||||||
! grep -Eq 'No such file|nonexistent directory|cannot create' "$WORK/bootstrap.log" \
|
[ "$(jq -r '.state' "$WORK/bootstrap/state/status.json")" = FAILED ] \
|
||||||
|| fail 'updater bootstrap failure was masked by an unavailable status directory'
|
|| fail 'updater bootstrap failure did not persist FAILED'
|
||||||
|
[ ! -e "$WORK/bootstrap/state/inbox/request.json" ] \
|
||||||
|
&& [ ! -e "$WORK/bootstrap/state/processing/request.json" ] \
|
||||||
|
|| fail 'updater bootstrap failure left a request permanently queued'
|
||||||
|
find "$WORK/bootstrap/state/failed" -type f -name 'request-*.json' -print -quit | grep -q . \
|
||||||
|
|| fail 'updater bootstrap failure did not archive the claimed request'
|
||||||
|
|
||||||
write_mock_commands() {
|
write_mock_commands() {
|
||||||
local mock_bin=$1
|
local mock_bin=$1
|
||||||
|
|||||||
Reference in New Issue
Block a user