diff --git a/README.md b/README.md index ad267e3..99551c9 100644 --- a/README.md +++ b/README.md @@ -47,24 +47,32 @@ npm run build `https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest`,生产机不执行 `git pull` 或现场编译。 安装脚本内置固定的发布公钥指纹,下载的应用包、清单、SBOM 和更新脚本仍须通过 RSA 签名与 SHA-256 校验。 本版只支持由运维人员预先准备的外部 MySQL 8.4.x;安装器不会安装 MySQL、创建数据库容器或修改现有 -PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview.9 发布范围。 +PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview.10 发布范围。 -执行命令的机器需预装 `bash`、`sudo`、`curl`、`mktemp` 和 `sha256sum`,并能访问目标 Gitea;`jq`、Java、 -Nginx 和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、 -`/var/lib/kaidi-update` 和 `/etc/kaidi`。目标机应为专用主机,或确认现有 Nginx 默认站点可以被替换且 -80 端口可用;安装器会接管默认 HTTP 站点。 +执行命令的机器需预装 `bash`、`sudo`、`curl`、`mktemp` 和 `sha256sum`,并能访问目标 Gitea;`jq`、Java +和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、 +`/var/lib/kaidi-update` 和 `/etc/kaidi`。安装器不安装、不启动也不修改 Nginx、Caddy、宝塔或其他反向代理。 ### 直接 curl 安装 ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh | sudo bash +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh | sudo bash ``` -这条命令会安装最新签名 Release,并默认进入 `/setup` 安装向导。需要在执行前独立校验安装脚本时使用: +这条命令会提示填写 Java 应用端口,直接回车使用 `18080`;随后安装最新签名 Release,并默认进入 `/setup` +安装向导。Java 默认只监听 `127.0.0.1:所选端口`,前端页面、API 和健康检查均由同一端口提供。无人值守安装可直接指定: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh -o /tmp/kaidi-install.sh -printf '%s %s\n' faf52cc902abbc2bd48571caee3f4207de50ce339b82ce88fe23f9c4ce8f89ef /tmp/kaidi-install.sh | sha256sum -c - +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh \ + | sudo env KAIDI_APP_PORT=19090 bash +``` + +端口必须在 `1024-65535` 范围且未被其他程序监听;安装器会在安装前检查冲突。只有明确需要让其他主机直连 Java +时才设置 `KAIDI_SERVER_ADDRESS=0.0.0.0`,通常应保持默认回环绑定并由本机反向代理访问。需要在执行前独立校验安装脚本时使用: + +```bash +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh -o /tmp/kaidi-install.sh +printf '%s %s\n' cc9ae48fc3e36fbeb13b0660b0d91e59bd099a1386d49df699f214285c1afc0a /tmp/kaidi-install.sh | sha256sum -c - sudo bash /tmp/kaidi-install.sh rm -f /tmp/kaidi-install.sh ``` @@ -75,7 +83,7 @@ rm -f /tmp/kaidi-install.sh 包装器会在 `sudo` 前校验 `deploy/install.sh` 的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。 ```bash -git clone --branch v1.0.0-preview.9 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview +git clone --branch v1.0.0-preview.10 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview cd kaidi-preview ./deploy/install-from-git.sh ``` @@ -91,19 +99,35 @@ cd kaidi-preview - 使用安装器内置的 SHA-256 指纹校验 Release 公钥,再用该公钥验证发布清单 RSA 签名。 - 首次安装默认启用 `/setup` 向导,不在命令行保存数据库密码;向导只接受 MySQL 8.4.x,并在提交前验证 DDL/DML 权限。 - 安装签名 Release 到 `/opt/kaidi/releases/`,以 `/opt/kaidi/current` 原子切换当前版本。 -- 安装 Nginx、`kaidi-finance.service`、更新监听服务和健康检查。 +- 安装 `kaidi-finance.service`、更新监听服务和健康检查;Java 同时托管 TDesign 前端静态资源及 Vue 路由回退。 - 向导只初始化一个由操作者填写的 `SYSTEM_ADMIN` 管理员,不创建项目、财务、资料或演示账号。 - 安装器把一次性安装码写入仅 root 可读的 `/root/kaidi-first-login.txt`;完成向导后写入锁定标记并切换正式应用。 -安装完成后先执行 `sudo cat /root/kaidi-first-login.txt`,访问其中的 `/setup` 地址完成数据库和管理员配置;完成后再访问 -`http://SERVER_IP/` 登录。Preview 使用 HTTP 时安装器默认设置 +安装完成后先执行 `sudo cat /root/kaidi-first-login.txt`,其中会显示准确的反向代理目标和一次性安装码。配置反向代理后, +通过域名的 `/setup` 完成数据库和管理员配置,再访问域名根路径登录。Preview 使用 HTTP 时安装器默认设置 `SESSION_COOKIE_SECURE=false`;配置 HTTPS 反向代理后,应在 `/etc/kaidi/kaidi.env` 改为 `SESSION_COOKIE_SECURE=true` 并执行 `sudo systemctl restart kaidi-finance`。 -安装后执行以下命令确认应用、反向代理和首次登录信息: +以安装端口 `19090` 为例,Nginx 只需代理到 Java,不需要单独托管前端文件: + +```nginx +location / { + proxy_pass http://127.0.0.1:19090; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 10s; + proxy_read_timeout 120s; + client_max_body_size 500m; +} +``` + +宝塔、Caddy 或云网关使用同一个上游地址 `http://127.0.0.1:所选端口`。安装后执行以下命令确认应用和首次登录信息: ```bash -curl -fsS http://127.0.0.1/actuator/health | jq -e '.status == "UP"' +curl -fsS http://127.0.0.1:19090/actuator/health | jq -e '.status == "UP"' sudo systemctl --no-pager --full status kaidi-finance kaidi-update.path sudo cat /root/kaidi-first-login.txt ``` @@ -114,7 +138,7 @@ sudo cat /root/kaidi-first-login.txt 32 位服务端镜像,因此 32 位主机需要预先连接一台 MySQL 8.4 数据库,之后仍然只执行一个安装命令: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh | sudo bash +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh | sudo bash ``` 32 位主机不能运行安装器自动创建的 MySQL 容器,因此在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的 @@ -134,17 +158,17 @@ GRANT ALL PRIVILEGES ON kaidi_finance.* TO 'kaidi'@'KAIDI_SERVER_IP'; 管理员数据和运维人员新增的环境变量: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh \ +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh \ | sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash ``` -重装失败会恢复原应用链接、Java 运行时、环境文件、systemd 单元和 Nginx 配置;脚本会明确报告回滚不完整, +没有显式设置 `KAIDI_APP_PORT` 时,修复性重装会沿用 `/etc/kaidi/kaidi.env` 中的原端口。重装失败会恢复原应用链接、Java 运行时、环境文件和 systemd 单元;脚本会明确报告回滚不完整, 不会把恢复失败吞掉。 如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。 ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh \ +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh \ | sudo env KAIDI_REINSTALL=true bash ``` @@ -152,14 +176,12 @@ curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-previe 以下命令移除应用程序和服务,但保留 `/var/lib/kaidi`、`/var/lib/kaidi-update`、`/etc/kaidi` 以及数据库, 便于审计、备份或重新安装。确认数据备份前不要删除这些保留目录或 MySQL 数据卷。 -安装器已经删除原 Nginx 默认站点;停用后需按该主机原有配置恢复或另行创建默认站点。 +反向代理由运维人员独立管理,停用程序不会修改其配置。 ```bash sudo systemctl disable --now kaidi-update.path kaidi-update.service kaidi-finance.service sudo rm -f /etc/systemd/system/kaidi-finance.service /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path -sudo rm -f /etc/nginx/conf.d/kaidi-finance.conf sudo systemctl daemon-reload -sudo nginx -t && sudo systemctl reload nginx sudo rm -rf /opt/kaidi ``` @@ -182,8 +204,8 @@ act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前 Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布: ```bash -git tag v1.0.0-preview.9 -git push origin v1.0.0-preview.9 +git tag v1.0.0-preview.10 +git push origin v1.0.0-preview.10 ``` 在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。 @@ -196,8 +218,8 @@ URL、Token、脚本或命令。更新流程固定为: 的包不能进入安装。 4. 安装请求持久领取到 `/var/lib/kaidi-update/processing`;进程或主机中断后由 systemd 恢复未完成事务。 5. root 更新器重新验签和验哈希,确认 `mysqldump` 成功并生成权限为 `0600` 的备份,默认保留最近 5 份。 -6. 校验更新脚本和 systemd 单元后,原子切换 updater、systemd、Nginx 和应用版本。 -7. 同时检查后端直连、Nginx 健康端点、更新 path unit 和静态首页。全部通过后页面显示 10 秒倒计时并自动 +6. 校验更新脚本和 systemd 单元后,原子切换 updater、systemd 和应用版本,不修改反向代理。 +7. 同时检查 Java 健康端点、更新 path unit 和 Java 托管的静态首页。全部通过后页面显示 10 秒倒计时并自动 刷新;刷新或短暂断线发生在安装中时,页面会恢复 3 秒轮询。任一检查失败则恢复并验证上一版本。 忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;这就是更新执行冷却。10 秒只用于成功后的页面刷新, @@ -217,7 +239,7 @@ cat /var/lib/kaidi-update/status.json 如果 `status.json` 显示 `FAILED` 且日志提示回滚未完成,不要删除 `/var/lib/kaidi-update/processing/request.json` 或活动事务目录。systemd 在 300 秒内连续失败 3 次后会停止自动重试, -修复日志所示的磁盘、权限、Nginx 或旧版本健康问题后执行: +修复日志所示的磁盘、权限或旧版本健康问题后执行: ```bash sudo systemctl reset-failed kaidi-update.service kaidi-update.path @@ -234,7 +256,7 @@ cat /var/lib/kaidi-update/status.json ```bash KAIDI_RELEASE_SIGNING_KEY=/secure/release-signing-private.pem \ KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \ - ./scripts/package-release.sh 1.0.0-preview.9 + ./scripts/package-release.sh 1.0.0-preview.10 KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \ ./scripts/verify-release.sh dist/release ``` diff --git a/backend/src/main/java/com/kaidi/finance/setup/SetupSecurityConfig.java b/backend/src/main/java/com/kaidi/finance/setup/SetupSecurityConfig.java index 0ac923b..5095161 100644 --- a/backend/src/main/java/com/kaidi/finance/setup/SetupSecurityConfig.java +++ b/backend/src/main/java/com/kaidi/finance/setup/SetupSecurityConfig.java @@ -3,9 +3,11 @@ package com.kaidi.finance.setup; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.RequestMatcher; @Configuration @ConditionalOnProperty(name = "finance.setup.enabled", havingValue = "true") @@ -19,8 +21,20 @@ public class SetupSecurityConfig { .formLogin(AbstractHttpConfigurer::disable) .logout(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/api/v1/setup/**", "/actuator/health/**", "/error").permitAll() + .requestMatchers("/api/v1/setup/**", "/actuator/health/**", "/error", + "/", "/index.html", "/favicon.ico", "/assets/**").permitAll() + .requestMatchers((RequestMatcher) request -> isSpaRoute(request)).permitAll() .anyRequest().denyAll()); return http.build(); } + + private boolean isSpaRoute(jakarta.servlet.http.HttpServletRequest request) { + if (!HttpMethod.GET.matches(request.getMethod()) && !HttpMethod.HEAD.matches(request.getMethod())) { + return false; + } + String path = request.getRequestURI(); + return !path.contains(".") && !path.equals("/api") && !path.startsWith("/api/") + && !path.equals("/actuator") && !path.startsWith("/actuator/") + && !path.equals("/error"); + } } diff --git a/backend/src/main/java/com/kaidi/finance/shared/config/SecurityConfig.java b/backend/src/main/java/com/kaidi/finance/shared/config/SecurityConfig.java index c0ab708..5ea5868 100644 --- a/backend/src/main/java/com/kaidi/finance/shared/config/SecurityConfig.java +++ b/backend/src/main/java/com/kaidi/finance/shared/config/SecurityConfig.java @@ -14,6 +14,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; @Configuration @EnableMethodSecurity @@ -37,7 +38,9 @@ public class SecurityConfig { .authorizeHttpRequests(authorize -> authorize .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/api/v1/auth/csrf", "/api/v1/auth/login", "/actuator/health/**", - "/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/error").permitAll() + "/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/error", + "/", "/index.html", "/favicon.ico", "/assets/**").permitAll() + .requestMatchers((RequestMatcher) request -> isSpaRoute(request)).permitAll() .requestMatchers("/api/v1/**").permitAll() .anyRequest().authenticated()) .exceptionHandling(exceptions -> exceptions @@ -48,6 +51,18 @@ public class SecurityConfig { return http.build(); } + private boolean isSpaRoute(jakarta.servlet.http.HttpServletRequest request) { + if (!HttpMethod.GET.matches(request.getMethod()) && !HttpMethod.HEAD.matches(request.getMethod())) { + return false; + } + String path = request.getRequestURI(); + return !path.contains(".") && !path.equals("/api") && !path.startsWith("/api/") + && !path.equals("/actuator") && !path.startsWith("/actuator/") + && !path.equals("/api-docs") && !path.startsWith("/api-docs/") + && !path.equals("/swagger-ui.html") && !path.startsWith("/swagger-ui/") + && !path.equals("/error"); + } + @Bean PasswordEncoder passwordEncoder() { return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); diff --git a/backend/src/main/java/com/kaidi/finance/shared/web/SpaForwardFilter.java b/backend/src/main/java/com/kaidi/finance/shared/web/SpaForwardFilter.java new file mode 100644 index 0000000..62ceaeb --- /dev/null +++ b/backend/src/main/java/com/kaidi/finance/shared/web/SpaForwardFilter.java @@ -0,0 +1,51 @@ +package com.kaidi.finance.shared.web; + +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +/** Forwards extensionless browser routes to the bundled Vue entry point. */ +@Component +public class SpaForwardFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (isBrowserRoute(request)) { + request.getRequestDispatcher("/index.html").forward(request, response); + return; + } + filterChain.doFilter(request, response); + } + + private boolean isBrowserRoute(HttpServletRequest request) { + if (!"GET".equalsIgnoreCase(request.getMethod()) && !"HEAD".equalsIgnoreCase(request.getMethod())) { + return false; + } + String path = request.getRequestURI(); + String contextPath = request.getContextPath(); + if (contextPath != null && !contextPath.isEmpty() && path.startsWith(contextPath)) { + path = path.substring(contextPath.length()); + } + if (path.isEmpty()) { + path = "/"; + } + if (isReserved(path) || path.contains(".")) { + return false; + } + return DispatcherType.REQUEST.equals(request.getDispatcherType()); + } + + private boolean isReserved(String path) { + return path.equals("/api") || path.startsWith("/api/") + || path.equals("/actuator") || path.startsWith("/actuator/") + || path.equals("/api-docs") || path.startsWith("/api-docs/") + || path.equals("/swagger-ui.html") || path.startsWith("/swagger-ui/") + || path.equals("/error"); + } +} diff --git a/backend/src/main/java/com/kaidi/setup/SetupApplication.java b/backend/src/main/java/com/kaidi/setup/SetupApplication.java index 5171310..57de276 100644 --- a/backend/src/main/java/com/kaidi/setup/SetupApplication.java +++ b/backend/src/main/java/com/kaidi/setup/SetupApplication.java @@ -1,5 +1,7 @@ package com.kaidi.setup; +import com.kaidi.finance.setup.SetupProperties; +import com.kaidi.finance.shared.web.SpaForwardFilter; import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -9,7 +11,7 @@ import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import com.kaidi.finance.setup.SetupProperties; +import org.springframework.context.annotation.Import; /** * Minimal first-run context. It deliberately does not create a business DataSource or run @@ -28,6 +30,7 @@ import com.kaidi.finance.setup.SetupProperties; } ) @EnableConfigurationProperties(SetupProperties.class) +@Import(SpaForwardFilter.class) public class SetupApplication { public static void main(String[] args) { diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index fa58885..1bed27f 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -1,5 +1,6 @@ server: port: ${SERVER_PORT:18080} + address: ${SERVER_ADDRESS:127.0.0.1} shutdown: graceful servlet: session: @@ -12,6 +13,9 @@ server: spring: application: name: kaidi-finance + web: + resources: + static-locations: ${FINANCE_STATIC_LOCATIONS:file:./public/} profiles: default: local datasource: diff --git a/backend/src/test/java/com/kaidi/finance/setup/SetupContextSmokeTest.java b/backend/src/test/java/com/kaidi/finance/setup/SetupContextSmokeTest.java index b57e68d..0f33d25 100644 --- a/backend/src/test/java/com/kaidi/finance/setup/SetupContextSmokeTest.java +++ b/backend/src/test/java/com/kaidi/finance/setup/SetupContextSmokeTest.java @@ -31,6 +31,7 @@ class SetupContextSmokeTest { registry.add("finance.setup.env-file", () -> STATE_ROOT.resolve("application.env").toString()); registry.add("finance.setup.marker-file", () -> STATE_ROOT.resolve("locked").toString()); registry.add("finance.setup.restart-after-complete", () -> false); + registry.add("spring.web.resources.static-locations", () -> "classpath:/spa-fixture/"); } @Autowired @@ -49,6 +50,14 @@ class SetupContextSmokeTest { assertThat(status.getBody().path("data").path("supportedDatabaseTypes").toString()) .isEqualTo("[\"MYSQL\"]"); + ResponseEntity setupPage = rest.getForEntity(url("/setup"), String.class); + assertThat(setupPage.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(setupPage.getBody()).contains("kaidi-spa-fixture"); + + ResponseEntity asset = rest.getForEntity(url("/assets/app.js"), String.class); + assertThat(asset.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(asset.getBody()).contains("kaidi-spa-fixture"); + ResponseEntity business = rest.getForEntity(url("/api/v1/auth/session"), JsonNode.class); assertThat(business.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); } diff --git a/backend/src/test/java/com/kaidi/finance/shared/web/SpaForwardFilterTest.java b/backend/src/test/java/com/kaidi/finance/shared/web/SpaForwardFilterTest.java new file mode 100644 index 0000000..9da4d8c --- /dev/null +++ b/backend/src/test/java/com/kaidi/finance/shared/web/SpaForwardFilterTest.java @@ -0,0 +1,43 @@ +package com.kaidi.finance.shared.web; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class SpaForwardFilterTest { + + private final SpaForwardFilter filter = new SpaForwardFilter(); + + @Test + void forwardsExtensionlessBrowserRouteToVueEntryPoint() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/finance/workbench"); + MockHttpServletResponse response = new MockHttpServletResponse(); + AtomicBoolean continued = new AtomicBoolean(); + + filter.doFilter(request, response, (ignoredRequest, ignoredResponse) -> continued.set(true)); + + assertThat(response.getForwardedUrl()).isEqualTo("/index.html"); + assertThat(continued).isFalse(); + } + + @Test + void leavesApiAndStaticAssetRequestsToSpringMvc() throws Exception { + assertContinues("/api/v1/unknown-resource"); + assertContinues("/actuator/health"); + assertContinues("/assets/index-a1b2c3.js"); + } + + private void assertContinues(String uri) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", uri); + MockHttpServletResponse response = new MockHttpServletResponse(); + AtomicBoolean continued = new AtomicBoolean(); + + filter.doFilter(request, response, (ignoredRequest, ignoredResponse) -> continued.set(true)); + + assertThat(continued).isTrue(); + assertThat(response.getForwardedUrl()).isNull(); + } +} diff --git a/backend/src/test/resources/spa-fixture/assets/app.js b/backend/src/test/resources/spa-fixture/assets/app.js new file mode 100644 index 0000000..0fc9a6f --- /dev/null +++ b/backend/src/test/resources/spa-fixture/assets/app.js @@ -0,0 +1 @@ +window.__KAIDI_SPA_FIXTURE__ = 'kaidi-spa-fixture'; diff --git a/backend/src/test/resources/spa-fixture/index.html b/backend/src/test/resources/spa-fixture/index.html new file mode 100644 index 0000000..c762446 --- /dev/null +++ b/backend/src/test/resources/spa-fixture/index.html @@ -0,0 +1,5 @@ + + + kaidi-spa-fixture +
kaidi-spa-fixture
+ diff --git a/deploy/env.production.example b/deploy/env.production.example index 8adcc94..fc5a7c5 100644 --- a/deploy/env.production.example +++ b/deploy/env.production.example @@ -1,5 +1,6 @@ SPRING_PROFILES_ACTIVE=production SERVER_PORT=18080 +SERVER_ADDRESS=127.0.0.1 SESSION_COOKIE_SECURE=false DB_URL=jdbc:mysql://127.0.0.1:3307/kaidi_finance?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&serverTimezone=UTC diff --git a/deploy/install-from-git.sh b/deploy/install-from-git.sh index e4c9c9f..7e95555 100755 --- a/deploy/install-from-git.sh +++ b/deploy/install-from-git.sh @@ -5,7 +5,7 @@ umask 077 ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) INSTALLER="$ROOT/deploy/install.sh" -INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-faf52cc902abbc2bd48571caee3f4207de50ce339b82ce88fe23f9c4ce8f89ef} +INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-cc9ae48fc3e36fbeb13b0660b0d91e59bd099a1386d49df699f214285c1afc0a} RELEASE_API_URL=${KAIDI_RELEASE_API_URL:-https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest} PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9} TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-} @@ -44,6 +44,8 @@ fi KAIDI_REINSTALL=${KAIDI_REINSTALL:-} KAIDI_SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-} +KAIDI_APP_PORT=${KAIDI_APP_PORT:-} +KAIDI_SERVER_ADDRESS=${KAIDI_SERVER_ADDRESS:-} KAIDI_DB_URL=${KAIDI_DB_URL:-} KAIDI_DB_USERNAME=${KAIDI_DB_USERNAME:-} KAIDI_DB_PASSWORD=${KAIDI_DB_PASSWORD:-} @@ -54,7 +56,8 @@ KAIDI_DB_CONTAINER=${KAIDI_DB_CONTAINER:-} KAIDI_SESSION_COOKIE_SECURE=${KAIDI_SESSION_COOKIE_SECURE:-} KAIDI_FILE_SCANNER_ENABLED=${KAIDI_FILE_SCANNER_ENABLED:-} -for name in KAIDI_REINSTALL KAIDI_SETUP_WIZARD KAIDI_DB_URL KAIDI_DB_USERNAME KAIDI_DB_PASSWORD \ +for name in KAIDI_REINSTALL KAIDI_SETUP_WIZARD KAIDI_APP_PORT KAIDI_SERVER_ADDRESS \ + KAIDI_DB_URL KAIDI_DB_USERNAME KAIDI_DB_PASSWORD \ KAIDI_DB_HOST KAIDI_DB_PORT KAIDI_DB_NAME KAIDI_DB_CONTAINER \ KAIDI_SESSION_COOKIE_SECURE KAIDI_FILE_SCANNER_ENABLED; do value=${!name} diff --git a/deploy/install.sh b/deploy/install.sh index 1345dd4..45dae3a 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -11,7 +11,11 @@ RELEASE_BASE_URL=${KAIDI_RELEASE_BASE_URL:-} RELEASE_API_URL=${KAIDI_RELEASE_API_URL:-https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest} RELEASE_TOKEN=${KAIDI_RELEASE_TOKEN:-} RELEASE_TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-} -HEALTH_URL=${KAIDI_HEALTH_URL:-http://127.0.0.1:18080/actuator/health} +HEALTH_URL=${KAIDI_HEALTH_URL:-} +APP_INDEX_URL=${KAIDI_APP_INDEX_URL:-} +APP_PORT=${KAIDI_APP_PORT:-} +SERVER_ADDRESS=${KAIDI_SERVER_ADDRESS:-127.0.0.1} +PROXY_TARGET= TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9} REINSTALL=${KAIDI_REINSTALL:-false} SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-true} @@ -36,7 +40,7 @@ preflight_host() { [ "$(uname -s)" = Linux ] || die "The installer only supports Linux" [ "$APP_ROOT" = /opt/kaidi ] && [ "$STATE_ROOT" = /var/lib/kaidi ] \ && [ "$UPDATE_STATE_ROOT" = /var/lib/kaidi-update ] && [ "$CONFIG_ROOT" = /etc/kaidi ] \ - || die "Custom installation roots are not supported by the packaged systemd and Nginx configuration" + || die "Custom installation roots are not supported by the packaged systemd configuration" command -v systemctl >/dev/null 2>&1 || die "systemd is required" [ -d /run/systemd/system ] || die "systemd is not running as PID 1" [ -d /etc/systemd/system ] || die "/etc/systemd/system is missing" @@ -148,11 +152,11 @@ install_packages() { if command -v apt-get >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq - apt-get install -y -qq ca-certificates coreutils curl findutils gzip jq openssl tar nginx util-linux default-mysql-client + apt-get install -y -qq ca-certificates coreutils curl findutils gzip jq openssl tar util-linux default-mysql-client elif command -v dnf >/dev/null 2>&1; then - dnf install -y ca-certificates coreutils curl findutils gzip jq openssl tar nginx util-linux mysql + dnf install -y ca-certificates coreutils curl findutils gzip jq openssl tar util-linux mysql elif command -v yum >/dev/null 2>&1; then - yum install -y ca-certificates coreutils curl findutils gzip jq openssl tar nginx util-linux mysql + yum install -y ca-certificates coreutils curl findutils gzip jq openssl tar util-linux mysql else die "Supported package managers are apt, dnf, and yum" fi @@ -264,6 +268,72 @@ read_reinstall_env() { fi } +port_is_listening() { + local port_hex files=(/proc/net/tcp) + port_hex=$(printf '%04X' "$APP_PORT") + [ -r /proc/net/tcp ] || return 1 + [ ! -r /proc/net/tcp6 ] || files+=(/proc/net/tcp6) + awk -v port="$port_hex" ' + toupper($2) ~ (":" port "$") && $4 == "0A" { found=1 } + END { exit(found ? 0 : 1) } + ' "${files[@]}" 2>/dev/null +} + +valid_app_port() { + [[ "$1" =~ ^[0-9]{1,5}$ ]] && [ "$1" -ge 1024 ] && [ "$1" -le 65535 ] +} + +configure_app_port() { + local default_port=18080 existing_port entered probe_host probe_authority + if [ -z "$APP_PORT" ]; then + default_port=$(read_reinstall_env SERVER_PORT || true) + [[ "$default_port" =~ ^[0-9]{1,5}$ ]] || default_port=18080 + if [ -t 1 ] && [ -r /dev/tty ]; then + printf '[kaidi-install] Application port [%s]: ' "$default_port" > /dev/tty + if IFS= read -r entered < /dev/tty; then + APP_PORT=${entered:-$default_port} + else + APP_PORT=$default_port + fi + else + APP_PORT=$default_port + log "No interactive terminal detected; using application port $APP_PORT" + fi + fi + valid_app_port "$APP_PORT" \ + || die "KAIDI_APP_PORT must be an integer between 1024 and 65535" + case "$SERVER_ADDRESS" in + ''|*[!A-Za-z0-9_.:-]*) die "KAIDI_SERVER_ADDRESS contains unsupported characters" ;; + esac + if port_is_listening; then + existing_port=$(read_existing_env SERVER_PORT || true) + if [ "$REINSTALL" = true ] && [ "$existing_port" = "$APP_PORT" ] \ + && systemctl is-active --quiet kaidi-finance.service; then + log "Application port $APP_PORT is already held by the existing Kaidi service" + else + die "Application port $APP_PORT is already in use; choose another port with KAIDI_APP_PORT" + fi + fi + probe_host=${KAIDI_HEALTH_HOST:-$SERVER_ADDRESS} + case "$probe_host" in + 0.0.0.0) probe_host=127.0.0.1 ;; + ::) probe_host=::1 ;; + esac + case "$probe_host" in + *:*) probe_authority="[$probe_host]" ;; + *) probe_authority="$probe_host" ;; + esac + if [ -z "$HEALTH_URL" ]; then + HEALTH_URL="http://${probe_authority}:${APP_PORT}/actuator/health" + fi + if [ -z "$APP_INDEX_URL" ]; then + APP_INDEX_URL="http://${probe_authority}:${APP_PORT}/" + fi + PROXY_TARGET="http://${probe_authority}:${APP_PORT}" + log "Application will bind ${SERVER_ADDRESS}:${APP_PORT}" + log "Reverse proxy target: $PROXY_TARGET" +} + is_semver() { [ "${#1}" -le 128 ] \ && LC_ALL=C grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' \ @@ -454,8 +524,6 @@ backup_managed_state() { /etc/systemd/system/kaidi-finance.service /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path - /etc/nginx/conf.d/kaidi-finance.conf - /etc/nginx/sites-enabled/default ) BACKUP_DIR="$WORK_DIR/rollback" mkdir -p "$BACKUP_DIR/files" @@ -470,8 +538,6 @@ backup_managed_state() { systemctl is-enabled --quiet kaidi-finance.service && PREVIOUS_APP_ENABLED=true || PREVIOUS_APP_ENABLED=false systemctl is-active --quiet kaidi-update.path && PREVIOUS_UPDATE_ACTIVE=true || PREVIOUS_UPDATE_ACTIVE=false systemctl is-enabled --quiet kaidi-update.path && PREVIOUS_UPDATE_ENABLED=true || PREVIOUS_UPDATE_ENABLED=false - systemctl is-active --quiet nginx && PREVIOUS_NGINX_ACTIVE=true || PREVIOUS_NGINX_ACTIVE=false - systemctl is-enabled --quiet nginx && PREVIOUS_NGINX_ENABLED=true || PREVIOUS_NGINX_ENABLED=false INSTALL_TRANSACTION_ARMED=true } @@ -529,10 +595,6 @@ rollback_install() { || rollback_failed=true restore_unit_state kaidi-update.path "$PREVIOUS_UPDATE_ENABLED" "$PREVIOUS_UPDATE_ACTIVE" \ || rollback_failed=true - restore_unit_state nginx "$PREVIOUS_NGINX_ENABLED" "$PREVIOUS_NGINX_ACTIVE" || rollback_failed=true - if [ "$PREVIOUS_NGINX_ACTIVE" = true ]; then - nginx -t >/dev/null 2>&1 && systemctl reload nginx >/dev/null 2>&1 || rollback_failed=true - fi set -e [ "$rollback_failed" = false ] } @@ -542,7 +604,7 @@ cleanup() { trap - EXIT if [ "$result" -ne 0 ] && [ "$INSTALL_TRANSACTION_ARMED" = true ] \ && [ "$INSTALL_COMMITTED" != true ]; then - rollback_install || log "ERROR: rollback was incomplete; inspect systemd and Nginx state" + rollback_install || log "ERROR: rollback was incomplete; inspect the managed systemd state" fi [ -z "$WORK_DIR" ] || rm -rf "$WORK_DIR" exit "$result" @@ -550,6 +612,7 @@ cleanup() { trap cleanup EXIT +configure_app_port preflight_host install_packages @@ -655,7 +718,8 @@ if [ "$SETUP_WIZARD" = true ]; then fi write_env_file_preserving_unknown "$CONFIG_ROOT/kaidi.env" \ SPRING_PROFILES_ACTIVE production \ - SERVER_PORT 18080 \ + SERVER_PORT "$APP_PORT" \ + SERVER_ADDRESS "$SERVER_ADDRESS" \ SESSION_COOKIE_SECURE "$SESSION_COOKIE_SECURE" \ DB_URL "$DB_URL" \ DB_USERNAME "$DB_USERNAME" \ @@ -692,6 +756,7 @@ write_env_file "$CONFIG_ROOT/update.env" \ KAIDI_UPDATE_STATE_ROOT "$UPDATE_STATE_ROOT" \ KAIDI_SERVICE_NAME kaidi-finance.service \ KAIDI_HEALTH_URL "$HEALTH_URL" \ + KAIDI_APP_INDEX_URL "$APP_INDEX_URL" \ KAIDI_DB_CONTAINER "${KAIDI_DB_CONTAINER:-}" \ KAIDI_DB_HOST "$(database_host)" \ KAIDI_DB_PORT "$(database_port)" \ @@ -703,17 +768,12 @@ chmod 0600 "$CONFIG_ROOT/update.env" install -m 0644 "$RELEASE_DIR/ops/kaidi-finance.service" /etc/systemd/system/kaidi-finance.service install -m 0644 "$RELEASE_DIR/ops/kaidi-update.service" /etc/systemd/system/kaidi-update.service install -m 0644 "$RELEASE_DIR/ops/kaidi-update.path" /etc/systemd/system/kaidi-update.path -install -m 0644 "$RELEASE_DIR/ops/kaidi-finance.conf" /etc/nginx/conf.d/kaidi-finance.conf -rm -f /etc/nginx/sites-enabled/default -nginx -t systemctl daemon-reload systemctl enable kaidi-finance.service systemctl restart kaidi-finance.service wait_for_health -systemctl enable --now nginx -systemctl reload nginx -curl -fsS http://127.0.0.1/actuator/health | jq -e '.status == "UP"' >/dev/null \ - || die "Nginx proxy health check failed" +curl -fsS "$APP_INDEX_URL" | grep -Eiq ' /root/kaidi-first-login.txt < /root/kaidi-first-login.txt < "$STAGE/VERSION" cp "$ROOT/deploy/update.sh" "$STAGE/ops/update.sh" +# Kept in the archive so Preview.9's updater can complete the one-time transition. cp "$ROOT/deploy/nginx/kaidi-finance.conf" "$STAGE/ops/kaidi-finance.conf" cp "$ROOT/deploy/systemd/kaidi-finance.service" "$STAGE/ops/kaidi-finance.service" cp "$ROOT/deploy/systemd/kaidi-update.service" "$STAGE/ops/kaidi-update.service" diff --git a/scripts/test-git-install-fixture.sh b/scripts/test-git-install-fixture.sh index f2fb48f..965574a 100755 --- a/scripts/test-git-install-fixture.sh +++ b/scripts/test-git-install-fixture.sh @@ -23,6 +23,10 @@ set -Eeuo pipefail [ "$KAIDI_RELEASE_API_URL" = 'https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest' ] [ "$KAIDI_RELEASE_PUBLIC_KEY_SHA256" = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ] +if [ "${EXPECT_PORT:-false}" = true ]; then + [ "$KAIDI_APP_PORT" = '19090' ] + [ "$KAIDI_SERVER_ADDRESS" = '127.0.0.1' ] +fi if [ "${KAIDI_SETUP_WIZARD:-false}" = true ]; then [ -z "${KAIDI_DB_URL:-}${KAIDI_DB_USERNAME:-}${KAIDI_DB_PASSWORD:-}" ] else @@ -44,9 +48,12 @@ chmod 0600 "$FIXTURE/token" installer_sha256=$(sha256sum "$FIXTURE/repo/deploy/install.sh" | awk '{print $1}') PATH="$FIXTURE/bin:$PATH" \ +EXPECT_PORT=true \ KAIDI_INSTALLER_SHA256="$installer_sha256" \ KAIDI_RELEASE_API_URL=https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest \ KAIDI_RELEASE_PUBLIC_KEY_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ +KAIDI_APP_PORT=19090 \ +KAIDI_SERVER_ADDRESS=127.0.0.1 \ KAIDI_DB_URL=jdbc:mysql://DB_HOST:3306/kaidi_finance \ KAIDI_DB_USERNAME=kaidi \ KAIDI_DB_PASSWORD=fixture-password \ diff --git a/scripts/test-install-fixture.sh b/scripts/test-install-fixture.sh index b14c90e..2ba9eb1 100755 --- a/scripts/test-install-fixture.sh +++ b/scripts/test-install-fixture.sh @@ -17,6 +17,9 @@ fail() { sed -n '/^read_existing_env()/,/^}/p' "$ROOT/deploy/install.sh" sed -n '/^read_setup_env()/,/^}/p' "$ROOT/deploy/install.sh" sed -n '/^read_reinstall_env()/,/^}/p' "$ROOT/deploy/install.sh" + sed -n '/^port_is_listening()/,/^}/p' "$ROOT/deploy/install.sh" + sed -n '/^valid_app_port()/,/^}/p' "$ROOT/deploy/install.sh" + sed -n '/^configure_app_port()/,/^}/p' "$ROOT/deploy/install.sh" sed -n '/^is_semver()/,/^}/p' "$ROOT/deploy/install.sh" sed -n '/^write_env_file_preserving_unknown()/,/^}/p' "$ROOT/deploy/install.sh" sed -n '/^azul_arch()/,/^}/p' "$ROOT/deploy/install.sh" @@ -78,6 +81,31 @@ grep -qx 'jdbc:mysql://runtime/kaidi_finance' <(read_reinstall_env DB_URL) \ grep -qx 'runtime-user' <(read_reinstall_env DB_USERNAME) \ || fail 'reinstall did not prefer the completed setup runtime database user' +for port in 1024 18080 65535; do + valid_app_port "$port" || fail "installer rejected valid application port $port" +done +for port in 0 80 1023 65536 invalid 18080.0; do + ! valid_app_port "$port" || fail "installer accepted invalid application port $port" +done +port_is_listening() { return 1; } +log() { printf '%s\n' "$*" >/dev/null; } +# shellcheck disable=SC2329 # Invoked by the extracted installer helper. +die() { printf '%s\n' "$*" >&2; return 1; } +export APP_PORT=19090 SERVER_ADDRESS=127.0.0.1 HEALTH_URL='' APP_INDEX_URL='' REINSTALL=false +configure_app_port +[ "$HEALTH_URL" = 'http://127.0.0.1:19090/actuator/health' ] \ + || fail 'selected application port did not reach the health URL' +[ "$APP_INDEX_URL" = 'http://127.0.0.1:19090/' ] \ + || fail 'selected application port did not reach the frontend URL' +export APP_PORT=19091 SERVER_ADDRESS=::1 HEALTH_URL='' APP_INDEX_URL='' +configure_app_port +[ "$HEALTH_URL" = 'http://[::1]:19091/actuator/health' ] \ + || fail 'IPv6 bind address did not produce a bracketed health URL' +[ "$APP_INDEX_URL" = 'http://[::1]:19091/' ] \ + || fail 'IPv6 bind address did not produce a bracketed frontend URL' +[ "$PROXY_TARGET" = 'http://[::1]:19091' ] \ + || fail 'IPv6 bind address did not produce a bracketed reverse-proxy target' + for version in 0.0.0 1.2.3-alpha- 1.2.3--alpha 1.2.3-alpha+build.07; do is_semver "$version" || fail "installer rejected valid SemVer $version" "$ROOT/scripts/check-semver.sh" "$version" || fail "release workflow rejected valid SemVer $version" @@ -218,6 +246,14 @@ grep -Fq 'KAIDI_SETUP_WIZARD' "$ROOT/deploy/install.sh" \ || fail 'installer no longer supports first-run setup mode' grep -Fq 'FINANCE_SETUP_TOKEN_SHA256' "$ROOT/deploy/install.sh" \ || fail 'installer no longer writes the one-time setup-code hash' +# shellcheck disable=SC2016 # Match literal installer source. +grep -Fq 'SERVER_PORT "$APP_PORT"' "$ROOT/deploy/install.sh" \ + || fail 'installer does not persist the selected application port' +# shellcheck disable=SC2016 # Match literal installer source. +grep -Fq 'SERVER_ADDRESS "$SERVER_ADDRESS"' "$ROOT/deploy/install.sh" \ + || fail 'installer does not persist the selected bind address' +! grep -Eqi 'nginx|/etc/nginx/' "$ROOT/deploy/install.sh" \ + || fail 'installer must not install, start, or modify Nginx' grep -Fq 'EnvironmentFile=-/var/lib/kaidi/setup/application.env' \ "$ROOT/deploy/systemd/kaidi-finance.service" \ || fail 'application service no longer loads the setup-completion environment' @@ -230,4 +266,4 @@ grep -Fq 'chown root:kaidi "$UPDATE_STATE_ROOT"' "$ROOT/deploy/install.sh" \ grep -Fq 'install.sh | sudo bash' "$ROOT/README.md" \ || fail 'README does not document the public one-line setup-wizard install path' -printf 'Install configuration, public Gitea, optional private token, i686, setup wizard, and MySQL 8.4 fixtures passed\n' +printf 'Install configuration, custom port, public Gitea, optional private token, i686, setup wizard, and MySQL 8.4 fixtures passed\n' diff --git a/scripts/test-update-fixture.sh b/scripts/test-update-fixture.sh index e3f9d9c..5fb316b 100755 --- a/scripts/test-update-fixture.sh +++ b/scripts/test-update-fixture.sh @@ -55,7 +55,7 @@ esac if [ -n "$output" ]; then if [ -n "${MOCK_RELEASE_API_URL:-}" ] && [ "$url" = "$MOCK_RELEASE_API_URL" ]; then cp "$FIXTURE_RELEASE_ROOT/release-api.json" "$output" - elif [ "$url" = "${KAIDI_PUBLIC_INDEX_URL:-http://127.0.0.1/}" ]; then + elif [ "$url" = "${KAIDI_APP_INDEX_URL:-http://127.0.0.1:18080/}" ]; then cp "$MOCK_APP_ROOT/current/public/index.html" "$output" else [ "${MOCK_DOWNLOAD_FAILURE:-}" != "${url##*/}" ] || exit 22 @@ -75,12 +75,6 @@ SH #!/bin/sh printf '%s\n' "$*" >> "$MOCK_SYSTEMCTL_LOG" exit 0 -SH - - cat > "$mock_bin/nginx" <<'SH' -#!/bin/sh -printf '%s\n' "$*" >> "$MOCK_NGINX_LOG" -exit 0 SH cat > "$mock_bin/flock" <<'SH' @@ -138,7 +132,7 @@ build_release() { printf '%s\n' "$version" > "$stage/VERSION" printf '#!/bin/sh\nprintf "new updater\\n"\n' > "$stage/ops/update.sh" chmod 0755 "$stage/ops/update.sh" - for name in kaidi-finance.service kaidi-update.service kaidi-update.path kaidi-finance.conf; do + for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do printf 'new %s\n' "$name" > "$stage/ops/$name" done @@ -174,7 +168,7 @@ 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/nginx" "$fixture/log" + "$fixture/state/inbox" "$fixture/systemd" "$fixture/log" printf 'old application\n' > "$fixture/app/releases/1.0.0-preview.1/app.jar" printf 'old\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" @@ -184,7 +178,6 @@ prepare_installation() { for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do printf 'old %s\n' "$name" > "$fixture/systemd/$name" done - printf 'old kaidi-finance.conf\n' > "$fixture/nginx/kaidi-finance.conf" write_request "$fixture" "$version" DOWNLOAD } @@ -227,12 +220,10 @@ 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" \ - MOCK_NGINX_LOG="$fixture/nginx.log" \ KAIDI_APP_ROOT="$fixture/app" \ KAIDI_UPDATE_STATE_ROOT="$fixture/state" \ KAIDI_LOG_ROOT="$fixture/log" \ KAIDI_SYSTEMD_ROOT="$fixture/systemd" \ - KAIDI_NGINX_CONFIG="$fixture/nginx/kaidi-finance.conf" \ KAIDI_UPDATER_PATH="$fixture/app/bin/update.sh" \ KAIDI_SKIP_DB_BACKUP="$skip_backup" \ KAIDI_UPDATE_HEALTH_ATTEMPTS=1 \ @@ -243,9 +234,8 @@ run_update() { UPDATE_PUBLIC_KEY="$fixture/release-public.pem" \ UPDATE_REQUEST_FILE="$fixture/state/inbox/request.json" \ UPDATE_STATUS_FILE="$fixture/state/status.json" \ - KAIDI_HEALTH_URL=http://127.0.0.1:18080/actuator/health \ - KAIDI_PUBLIC_HEALTH_URL=http://127.0.0.1/actuator/health \ - KAIDI_PUBLIC_INDEX_URL=http://127.0.0.1/ \ + KAIDI_HEALTH_URL=http://127.0.0.1:19090/actuator/health \ + KAIDI_APP_INDEX_URL=http://127.0.0.1:19090/ \ "$ROOT/deploy/update.sh" } @@ -319,7 +309,7 @@ assert_success_case() { [ ! -e "$fixture/state/processing/request.json" ] \ || fail 'success case left a claimed request behind' grep -qx 'daemon-reload' "$fixture/systemctl.log" || fail 'systemd units were not reloaded' - grep -qx 'reload nginx' "$fixture/systemctl.log" || fail 'Nginx was not reloaded' + ! grep -qi nginx "$fixture/systemctl.log" || fail 'updater unexpectedly managed Nginx' } assert_rollback_case() {