Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c35ef3d383 | ||
|
|
cea46b494d | ||
|
|
b679381946 |
@@ -163,18 +163,19 @@ GRANT ALL PRIVILEGES ON kaidi_finance.* TO 'kaidi'@'KAIDI_SERVER_IP';
|
|||||||
管理员数据和运维人员新增的环境变量:
|
管理员数据和运维人员新增的环境变量:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.19/install.sh \
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.22/install.sh \
|
||||||
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash
|
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash
|
||||||
```
|
```
|
||||||
|
|
||||||
没有显式设置 `KAIDI_APP_PORT` 时,修复性重装会沿用 `/etc/kaidi/kaidi.env` 中的原端口。重装失败会恢复原应用链接、Java 运行时、环境文件和 systemd 单元;脚本会明确报告回滚不完整,
|
没有显式设置 `KAIDI_APP_PORT` 时,修复性重装会沿用 `/etc/kaidi/kaidi.env` 中的原端口。重装失败会恢复原应用链接、Java 运行时、环境文件和 systemd 单元;脚本会明确报告回滚不完整,
|
||||||
不会把恢复失败吞掉。
|
不会把恢复失败吞掉。新版本安装器在 `KAIDI_REINSTALL=true` 时默认使用生产修复模式,显式的
|
||||||
|
`KAIDI_SETUP_WIZARD=false` 仅用于兼容旧版本安装器。
|
||||||
|
|
||||||
如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
|
如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.19/install.sh \
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.22/install.sh \
|
||||||
| sudo env KAIDI_REINSTALL=true bash
|
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=true bash
|
||||||
```
|
```
|
||||||
|
|
||||||
### 停用并移除程序
|
### 停用并移除程序
|
||||||
|
|||||||
+7
-1
@@ -18,7 +18,13 @@ SERVER_ADDRESS=${KAIDI_SERVER_ADDRESS:-127.0.0.1}
|
|||||||
PROXY_TARGET=
|
PROXY_TARGET=
|
||||||
TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}
|
TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}
|
||||||
REINSTALL=${KAIDI_REINSTALL:-false}
|
REINSTALL=${KAIDI_REINSTALL:-false}
|
||||||
SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-true}
|
if [ "${KAIDI_SETUP_WIZARD+x}" = x ]; then
|
||||||
|
SETUP_WIZARD=$KAIDI_SETUP_WIZARD
|
||||||
|
elif [ "$REINSTALL" = true ]; then
|
||||||
|
SETUP_WIZARD=false
|
||||||
|
else
|
||||||
|
SETUP_WIZARD=true
|
||||||
|
fi
|
||||||
WORK_DIR=
|
WORK_DIR=
|
||||||
RELEASE_AUTH_HEADER_FILE=
|
RELEASE_AUTH_HEADER_FILE=
|
||||||
INSTALL_TRANSACTION_ARMED=false
|
INSTALL_TRANSACTION_ARMED=false
|
||||||
|
|||||||
+48
-19
@@ -18,6 +18,7 @@ RELEASE_API_URL=${UPDATE_RELEASE_API_URL:-}
|
|||||||
RELEASE_TOKEN=${UPDATE_RELEASE_TOKEN:-}
|
RELEASE_TOKEN=${UPDATE_RELEASE_TOKEN:-}
|
||||||
CACHE_ROOT=${KAIDI_UPDATE_CACHE_ROOT:-$STATE_ROOT/cache}
|
CACHE_ROOT=${KAIDI_UPDATE_CACHE_ROOT:-$STATE_ROOT/cache}
|
||||||
SERVICE_NAME=${KAIDI_SERVICE_NAME:-kaidi-finance.service}
|
SERVICE_NAME=${KAIDI_SERVICE_NAME:-kaidi-finance.service}
|
||||||
|
UPDATE_PATH_NAME=${KAIDI_UPDATE_PATH_NAME:-kaidi-update.path}
|
||||||
SERVICE_USER=${KAIDI_SERVICE_USER:-kaidi}
|
SERVICE_USER=${KAIDI_SERVICE_USER:-kaidi}
|
||||||
SERVICE_GROUP=${KAIDI_SERVICE_GROUP:-kaidi}
|
SERVICE_GROUP=${KAIDI_SERVICE_GROUP:-kaidi}
|
||||||
HEALTH_URL=${KAIDI_HEALTH_URL:-http://127.0.0.1:18080/actuator/health}
|
HEALTH_URL=${KAIDI_HEALTH_URL:-http://127.0.0.1:18080/actuator/health}
|
||||||
@@ -272,6 +273,22 @@ atomic_install() {
|
|||||||
mv -f "$install_temporary" "$install_destination"
|
mv -f "$install_temporary" "$install_destination"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_operation() {
|
||||||
|
operation_source=$1
|
||||||
|
operation_destination=$2
|
||||||
|
operation_mode=$3
|
||||||
|
operation_is_systemd=$4
|
||||||
|
if [ -f "$operation_destination" ] && [ ! -L "$operation_destination" ] \
|
||||||
|
&& cmp -s "$operation_source" "$operation_destination"; then
|
||||||
|
chmod "$operation_mode" "$operation_destination"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
atomic_install "$operation_source" "$operation_destination" "$operation_mode" || return 1
|
||||||
|
if [ "$operation_is_systemd" = true ]; then
|
||||||
|
SYSTEMD_OPERATIONS_CHANGED=true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
backup_managed_file() {
|
backup_managed_file() {
|
||||||
backup_source=$1
|
backup_source=$1
|
||||||
backup_name=$2
|
backup_name=$2
|
||||||
@@ -311,14 +328,16 @@ backup_operations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply_operations() {
|
apply_operations() {
|
||||||
atomic_install "$RELEASE_DIR/ops/update.sh" "$UPDATER_PATH" 0755 \
|
SYSTEMD_OPERATIONS_CHANGED=false
|
||||||
&& atomic_install "$RELEASE_DIR/ops/kaidi-finance.service" \
|
install_operation "$RELEASE_DIR/ops/update.sh" "$UPDATER_PATH" 0755 false \
|
||||||
"$SYSTEMD_ROOT/kaidi-finance.service" 0644 \
|
&& install_operation "$RELEASE_DIR/ops/kaidi-finance.service" \
|
||||||
&& atomic_install "$RELEASE_DIR/ops/kaidi-update.service" \
|
"$SYSTEMD_ROOT/kaidi-finance.service" 0644 true \
|
||||||
"$SYSTEMD_ROOT/kaidi-update.service" 0644 \
|
&& install_operation "$RELEASE_DIR/ops/kaidi-update.service" \
|
||||||
&& atomic_install "$RELEASE_DIR/ops/kaidi-update.path" \
|
"$SYSTEMD_ROOT/kaidi-update.service" 0644 true \
|
||||||
"$SYSTEMD_ROOT/kaidi-update.path" 0644 \
|
&& install_operation "$RELEASE_DIR/ops/kaidi-update.path" \
|
||||||
&& systemctl daemon-reload
|
"$SYSTEMD_ROOT/kaidi-update.path" 0644 true \
|
||||||
|
|| return 1
|
||||||
|
[ "$SYSTEMD_OPERATIONS_CHANGED" = false ] || systemctl daemon-reload
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_operations() {
|
restore_operations() {
|
||||||
@@ -357,25 +376,29 @@ rollback_active_transaction() {
|
|||||||
previous_target=$(transaction_value previous-target)
|
previous_target=$(transaction_value previous-target)
|
||||||
failed_release=$(transaction_value release-dir)
|
failed_release=$(transaction_value release-dir)
|
||||||
OPS_BACKUP="$ACTIVE_TRANSACTION/operations-backup"
|
OPS_BACKUP="$ACTIVE_TRANSACTION/operations-backup"
|
||||||
rollback_ok=true
|
link_restored=true
|
||||||
|
operations_restored=true
|
||||||
|
|
||||||
systemctl stop "$SERVICE_NAME" || true
|
systemctl stop "$SERVICE_NAME" || true
|
||||||
if [ -n "$previous_target" ] && [ -d "$previous_target" ]; then
|
if [ -n "$previous_target" ] && [ -d "$previous_target" ]; then
|
||||||
if ! ln -sfn "$previous_target" "$APP_ROOT/current.next" \
|
if ! ln -sfn "$previous_target" "$APP_ROOT/current.next" \
|
||||||
|| ! mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"; then
|
|| ! mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"; then
|
||||||
rollback_ok=false
|
link_restored=false
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
rollback_ok=false
|
link_restored=false
|
||||||
fi
|
fi
|
||||||
restore_operations || rollback_ok=false
|
restore_operations || operations_restored=false
|
||||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||||
systemctl start "$SERVICE_NAME" || rollback_ok=false
|
systemctl start "$SERVICE_NAME" || true
|
||||||
if [ "$rollback_ok" = true ] && verify_app_surface; then
|
if [ "$link_restored" = true ] && verify_app_surface; then
|
||||||
remove_failed_release "$failed_release"
|
remove_failed_release "$failed_release"
|
||||||
rm -rf "$ACTIVE_TRANSACTION"
|
rm -rf "$ACTIVE_TRANSACTION"
|
||||||
|
if [ "$operations_restored" = true ]; then
|
||||||
fail "$reason; previous release was restored and verified"
|
fail "$reason; previous release was restored and verified"
|
||||||
fi
|
fi
|
||||||
|
fail "$reason; previous release is running, but operations restoration requires manual review"
|
||||||
|
fi
|
||||||
TERMINAL_STATUS_WRITTEN=true
|
TERMINAL_STATUS_WRITTEN=true
|
||||||
status FAILED "$reason; rollback is incomplete and will be retried" "${TARGET_VERSION:-}"
|
status FAILED "$reason; rollback is incomplete and will be retried" "${TARGET_VERSION:-}"
|
||||||
printf '%s\n' "$reason; rollback is incomplete and will be retried" >&2
|
printf '%s\n' "$reason; rollback is incomplete and will be retried" >&2
|
||||||
@@ -600,15 +623,21 @@ write_transaction_value phase APP_SWITCHED
|
|||||||
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION"
|
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION"
|
||||||
write_transaction_value phase HEALTH_CHECKING
|
write_transaction_value phase HEALTH_CHECKING
|
||||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||||
if systemctl start "$SERVICE_NAME" \
|
systemctl start "$SERVICE_NAME" || true
|
||||||
&& systemctl is-active --quiet kaidi-update.path \
|
if verify_app_surface; then
|
||||||
&& verify_app_surface; then
|
success_message="Release $TARGET_VERSION is running"
|
||||||
|
if ! systemctl start "$UPDATE_PATH_NAME" >/dev/null 2>&1; then
|
||||||
|
success_message="$success_message; automatic update watcher could not be started"
|
||||||
|
fi
|
||||||
write_transaction_value phase COMMITTED
|
write_transaction_value phase COMMITTED
|
||||||
status SUCCEEDED "Release $TARGET_VERSION is running" "$TARGET_VERSION"
|
status SUCCEEDED "$success_message" "$TARGET_VERSION"
|
||||||
TERMINAL_STATUS_WRITTEN=true
|
TERMINAL_STATUS_WRITTEN=true
|
||||||
complete_request
|
complete_request
|
||||||
rm -rf "$ACTIVE_TRANSACTION"
|
rm -rf "$ACTIVE_TRANSACTION"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rollback_active_transaction "Release health or application-surface verification failed"
|
service_state=$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)
|
||||||
|
[ -n "$service_state" ] || service_state=unknown
|
||||||
|
rollback_active_transaction \
|
||||||
|
"Release health or application-surface verification failed (service state: $service_state)"
|
||||||
|
|||||||
@@ -178,6 +178,16 @@ test('system administrator checks and queues a signed online update', async ({ p
|
|||||||
expect(installBodies).toEqual([{ version: '1.0.0-preview.2', reason: '管理员确认立即重启' }]);
|
expect(installBodies).toEqual([{ version: '1.0.0-preview.2', reason: '管理员确认立即重启' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('brand logo returns the active identity to its workbench', async ({ page }) => {
|
||||||
|
await installFixture(page, [], []);
|
||||||
|
await page.goto('/governance/update');
|
||||||
|
|
||||||
|
await page.locator('.brand-logo').click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/governance\/settings$/);
|
||||||
|
await expect(page.getByText('选择工作身份', { exact: true })).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('disabled online updates expose status without an executable check action', async ({ page }) => {
|
test('disabled online updates expose status without an executable check action', async ({ page }) => {
|
||||||
await installFixture(page, [], [], false);
|
await installFixture(page, [], [], false);
|
||||||
await page.goto('/governance/update');
|
await page.goto('/governance/update');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div :class="layoutCls">
|
<div :class="layoutCls">
|
||||||
<t-head-menu :class="menuCls" :theme="menuTheme" expand-type="popup" :value="active">
|
<t-head-menu :class="menuCls" :theme="menuTheme" expand-type="popup" :value="active">
|
||||||
<template #logo>
|
<template #logo>
|
||||||
<span v-if="showLogo" class="header-logo-container" @click="handleNav('/role-select')">
|
<span v-if="showLogo" class="header-logo-container" @click="handleNav(user.workbenchRoute)">
|
||||||
<span class="header-brand">财务系统</span>
|
<span class="header-brand">财务系统</span>
|
||||||
</span>
|
</span>
|
||||||
<div v-else class="header-operate-left">
|
<div v-else class="header-operate-left">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import { useRouter } from 'vue-router';
|
|||||||
import { prefix } from '@/config/global';
|
import { prefix } from '@/config/global';
|
||||||
import { t } from '@/locales';
|
import { t } from '@/locales';
|
||||||
import { getActive } from '@/router';
|
import { getActive } from '@/router';
|
||||||
import { useSettingStore } from '@/store';
|
import { useSettingStore, useUserStore } from '@/store';
|
||||||
import type { MenuRoute, ModeType } from '@/types/interface';
|
import type { MenuRoute, ModeType } from '@/types/interface';
|
||||||
|
|
||||||
import pgk from '../../../package.json';
|
import pgk from '../../../package.json';
|
||||||
@@ -155,6 +155,7 @@ const menuCls = computed(() => {
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const autoCollapsed = () => {
|
const autoCollapsed = () => {
|
||||||
const isCompact = window.innerWidth <= MIN_POINT;
|
const isCompact = window.innerWidth <= MIN_POINT;
|
||||||
@@ -175,7 +176,7 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const goHome = () => {
|
const goHome = () => {
|
||||||
router.push('/role-select');
|
router.push(userStore.workbenchRoute);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -73,16 +73,7 @@ const choose = async (roleCode: string) => {
|
|||||||
try {
|
try {
|
||||||
await userStore.selectRole(roleCode);
|
await userStore.selectRole(roleCode);
|
||||||
permissionStore.initRoutes();
|
permissionStore.initRoutes();
|
||||||
const target = userStore.roles.find((role) => role.code === roleCode)?.workbenchRoute;
|
await router.replace(userStore.workbenchRoute);
|
||||||
const fallback =
|
|
||||||
roleCode === 'PROJECT_MANAGER'
|
|
||||||
? '/workbench/project'
|
|
||||||
: roleCode === 'ARCHIVE_MANAGER'
|
|
||||||
? '/workbench/archive'
|
|
||||||
: roleCode === 'SYSTEM_ADMIN'
|
|
||||||
? '/governance/settings'
|
|
||||||
: '/workbench/finance';
|
|
||||||
await router.replace(target || fallback);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
MessagePlugin.error((error as Error).message || '身份选择失败,请重试');
|
MessagePlugin.error((error as Error).message || '身份选择失败,请重试');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as authApi from '@/api/auth';
|
|||||||
import { usePermissionStore } from '@/store';
|
import { usePermissionStore } from '@/store';
|
||||||
|
|
||||||
import { hasPermissionAccess } from './menu-access';
|
import { hasPermissionAccess } from './menu-access';
|
||||||
|
import { resolveWorkbenchRoute } from './workbench-route';
|
||||||
|
|
||||||
const emptyUser: UserView = {
|
const emptyUser: UserView = {
|
||||||
publicId: '',
|
publicId: '',
|
||||||
@@ -28,6 +29,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
getters: {
|
getters: {
|
||||||
displayName: (state) => state.user.displayName || state.user.username,
|
displayName: (state) => state.user.displayName || state.user.username,
|
||||||
roleName: (state) => state.roles.find((role) => role.code === state.currentRole)?.name || '',
|
roleName: (state) => state.roles.find((role) => role.code === state.currentRole)?.name || '',
|
||||||
|
workbenchRoute: (state) => resolveWorkbenchRoute(state.currentRole, state.roles),
|
||||||
hasRole: (state) => (roleCode: string) => state.roles.some((role) => role.code === roleCode),
|
hasRole: (state) => (roleCode: string) => state.roles.some((role) => role.code === roleCode),
|
||||||
hasPermission: (state) => (permission: string) =>
|
hasPermission: (state) => (permission: string) =>
|
||||||
hasPermissionAccess(state.currentRole, state.permissions, permission),
|
hasPermissionAccess(state.currentRole, state.permissions, permission),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { RoleView } from '@/api/auth';
|
||||||
|
|
||||||
|
const fallbackWorkbenchRoutes: Record<string, string> = {
|
||||||
|
PROJECT_MANAGER: '/workbench/project',
|
||||||
|
FINANCE_MANAGER: '/workbench/finance',
|
||||||
|
ARCHIVE_MANAGER: '/workbench/archive',
|
||||||
|
SYSTEM_ADMIN: '/governance/settings',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveWorkbenchRoute(currentRole: string | null, roles: RoleView[]): string {
|
||||||
|
const configured = roles.find((role) => role.code === currentRole)?.workbenchRoute?.trim();
|
||||||
|
return configured || fallbackWorkbenchRoutes[currentRole || ''] || '/role-select';
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import type { RoleView } from '../src/api/auth';
|
||||||
|
import { resolveWorkbenchRoute } from '../src/store/modules/workbench-route';
|
||||||
|
|
||||||
|
const roles: RoleView[] = [
|
||||||
|
{ code: 'SYSTEM_ADMIN', name: '系统管理员', workbenchRoute: '/governance/settings' },
|
||||||
|
{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('active identity workbench route', () => {
|
||||||
|
it('uses the route supplied for the active role', () => {
|
||||||
|
expect(resolveWorkbenchRoute('SYSTEM_ADMIN', roles)).toBe('/governance/settings');
|
||||||
|
expect(resolveWorkbenchRoute('FINANCE_MANAGER', roles)).toBe('/workbench/finance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to the built-in role workbench when the server route is absent', () => {
|
||||||
|
expect(resolveWorkbenchRoute('PROJECT_MANAGER', [])).toBe('/workbench/project');
|
||||||
|
expect(resolveWorkbenchRoute('ARCHIVE_MANAGER', [])).toBe('/workbench/archive');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps role selection for sessions without an active identity', () => {
|
||||||
|
expect(resolveWorkbenchRoute(null, roles)).toBe('/role-select');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -325,8 +325,11 @@ grep -Fq 'https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest' "$
|
|||||||
grep -Fq 'TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}' "$ROOT/deploy/install.sh" \
|
grep -Fq 'TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}' "$ROOT/deploy/install.sh" \
|
||||||
|| fail 'installer does not pin the default release public-key fingerprint'
|
|| fail 'installer does not pin the default release public-key fingerprint'
|
||||||
# shellcheck disable=SC2016 # Match literal installer source.
|
# shellcheck disable=SC2016 # Match literal installer source.
|
||||||
grep -Fq 'SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-true}' "$ROOT/deploy/install.sh" \
|
grep -Fq 'if [ "${KAIDI_SETUP_WIZARD+x}" = x ]; then' "$ROOT/deploy/install.sh" \
|
||||||
|| fail 'installer does not enable the first-run setup wizard by default'
|
|| fail 'installer no longer distinguishes an explicit setup-wizard selection'
|
||||||
|
# shellcheck disable=SC2016 # Match literal installer source.
|
||||||
|
grep -Fq 'elif [ "$REINSTALL" = true ]; then' "$ROOT/deploy/install.sh" \
|
||||||
|
|| fail 'repair reinstall no longer defaults to production mode'
|
||||||
! grep -Fq 'docker run' "$ROOT/deploy/install.sh" \
|
! grep -Fq 'docker run' "$ROOT/deploy/install.sh" \
|
||||||
|| fail 'installer must not create a MySQL container'
|
|| fail 'installer must not create a MySQL container'
|
||||||
! grep -Fq 'install_docker' "$ROOT/deploy/install.sh" \
|
! grep -Fq 'install_docker' "$ROOT/deploy/install.sh" \
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ SH
|
|||||||
cat > "$mock_bin/systemctl" <<'SH'
|
cat > "$mock_bin/systemctl" <<'SH'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
printf '%s\n' "$*" >> "$MOCK_SYSTEMCTL_LOG"
|
printf '%s\n' "$*" >> "$MOCK_SYSTEMCTL_LOG"
|
||||||
|
[ "${MOCK_UPDATE_PATH_START:-success}:$*" != 'fail:start kaidi-update.path' ] || exit 1
|
||||||
exit 0
|
exit 0
|
||||||
SH
|
SH
|
||||||
|
|
||||||
@@ -170,6 +171,7 @@ SH
|
|||||||
build_release() {
|
build_release() {
|
||||||
local fixture=$1
|
local fixture=$1
|
||||||
local version=$2
|
local version=$2
|
||||||
|
local unit_prefix=${3:-new}
|
||||||
local stage="$fixture/stage"
|
local stage="$fixture/stage"
|
||||||
mkdir -p "$fixture/release" "$stage/public" "$stage/ops"
|
mkdir -p "$fixture/release" "$stage/public" "$stage/ops"
|
||||||
printf 'new application\n' > "$stage/app.jar"
|
printf 'new application\n' > "$stage/app.jar"
|
||||||
@@ -178,7 +180,7 @@ build_release() {
|
|||||||
printf '#!/bin/sh\nprintf "new updater\\n"\n' > "$stage/ops/update.sh"
|
printf '#!/bin/sh\nprintf "new updater\\n"\n' > "$stage/ops/update.sh"
|
||||||
chmod 0755 "$stage/ops/update.sh"
|
chmod 0755 "$stage/ops/update.sh"
|
||||||
for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do
|
for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do
|
||||||
printf 'new %s\n' "$name" > "$stage/ops/$name"
|
printf '%s %s\n' "$unit_prefix" "$name" > "$stage/ops/$name"
|
||||||
done
|
done
|
||||||
|
|
||||||
local artifact="kaidi-finance-$version.tar.gz"
|
local artifact="kaidi-finance-$version.tar.gz"
|
||||||
@@ -272,6 +274,7 @@ run_update() {
|
|||||||
MOCK_RELEASE_ORIGIN="${FIXTURE_RELEASE_ORIGIN:-https://release.fixture.invalid}" \
|
MOCK_RELEASE_ORIGIN="${FIXTURE_RELEASE_ORIGIN:-https://release.fixture.invalid}" \
|
||||||
MOCK_EXPECT_RELEASE_TOKEN="${FIXTURE_RELEASE_TOKEN-}" \
|
MOCK_EXPECT_RELEASE_TOKEN="${FIXTURE_RELEASE_TOKEN-}" \
|
||||||
MOCK_SYSTEMCTL_LOG="$fixture/systemctl.log" \
|
MOCK_SYSTEMCTL_LOG="$fixture/systemctl.log" \
|
||||||
|
MOCK_UPDATE_PATH_START="${MOCK_UPDATE_PATH_START:-success}" \
|
||||||
KAIDI_SERVICE_USER="$(id -un)" \
|
KAIDI_SERVICE_USER="$(id -un)" \
|
||||||
KAIDI_SERVICE_GROUP="$(id -gn)" \
|
KAIDI_SERVICE_GROUP="$(id -gn)" \
|
||||||
KAIDI_APP_ROOT="$fixture/app" \
|
KAIDI_APP_ROOT="$fixture/app" \
|
||||||
@@ -368,6 +371,41 @@ assert_success_case() {
|
|||||||
[ "$(mode_of "$fixture/app/current/ops/update.sh")" = 750 ] || fail 'release updater mode is not 0750'
|
[ "$(mode_of "$fixture/app/current/ops/update.sh")" = 750 ] || fail 'release updater mode is not 0750'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_identical_systemd_operations_case() {
|
||||||
|
local fixture="$WORK/identical-systemd"
|
||||||
|
local version='1.0.0-preview.2'
|
||||||
|
mkdir -p "$fixture"
|
||||||
|
write_mock_commands "$fixture/mock-bin"
|
||||||
|
build_release "$fixture" "$version" old
|
||||||
|
prepare_installation "$fixture" "$version"
|
||||||
|
|
||||||
|
download_and_prepare_install "$fixture" "$version"
|
||||||
|
: > "$fixture/systemctl.log"
|
||||||
|
run_update "$fixture" success
|
||||||
|
[ "$(readlink "$fixture/app/current")" = "$fixture/app/releases/$version" ] \
|
||||||
|
|| fail 'identical systemd case did not activate the new application'
|
||||||
|
! grep -qx 'daemon-reload' "$fixture/systemctl.log" \
|
||||||
|
|| fail 'identical systemd units triggered an unnecessary daemon reload'
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_update_path_failure_keeps_application_case() {
|
||||||
|
local fixture="$WORK/path-watcher-failure"
|
||||||
|
local version='1.0.0-preview.2'
|
||||||
|
mkdir -p "$fixture"
|
||||||
|
write_mock_commands "$fixture/mock-bin"
|
||||||
|
build_release "$fixture" "$version"
|
||||||
|
prepare_installation "$fixture" "$version"
|
||||||
|
|
||||||
|
download_and_prepare_install "$fixture" "$version"
|
||||||
|
MOCK_UPDATE_PATH_START=fail run_update "$fixture" success
|
||||||
|
[ "$(readlink "$fixture/app/current")" = "$fixture/app/releases/$version" ] \
|
||||||
|
|| fail 'path watcher failure rolled back a healthy application'
|
||||||
|
[ "$(jq -r '.state' "$fixture/state/status.json")" = SUCCEEDED ] \
|
||||||
|
|| fail 'path watcher failure did not preserve successful application state'
|
||||||
|
grep -Fq 'automatic update watcher could not be started' "$fixture/state/status.json" \
|
||||||
|
|| fail 'path watcher failure did not preserve its diagnostic'
|
||||||
|
}
|
||||||
|
|
||||||
assert_rollback_case() {
|
assert_rollback_case() {
|
||||||
local fixture="$WORK/rollback"
|
local fixture="$WORK/rollback"
|
||||||
local version='1.0.0-preview.2'
|
local version='1.0.0-preview.2'
|
||||||
@@ -480,6 +518,8 @@ assert_install_without_verified_cache_rejected() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert_success_case
|
assert_success_case
|
||||||
|
assert_identical_systemd_operations_case
|
||||||
|
assert_update_path_failure_keeps_application_case
|
||||||
assert_rollback_case
|
assert_rollback_case
|
||||||
assert_download_failure_case
|
assert_download_failure_case
|
||||||
assert_database_failure_case
|
assert_database_failure_case
|
||||||
|
|||||||
Reference in New Issue
Block a user