From c1723502006f1aab475c901bac5e98aa0e91c4e0 Mon Sep 17 00:00:00 2001 From: Qiufeng Date: Wed, 5 Aug 2026 06:23:39 +0800 Subject: [PATCH] fix: keep release downloads on configured origin --- README.md | 2 +- distribution/bin/erp-update | 22 ++++++++++----- docs/online-install-and-update.md | 2 +- install.sh | 23 ++++++++++----- .../kaidi/oa/service/SystemUpdateService.java | 18 ++++++++---- .../oa/service/SystemUpdateServiceTest.java | 24 +++++++++------- r | 2 +- reinstall-centos9.sh | 2 +- tests/release-scripts.test.sh | 28 ++++++++++--------- 9 files changed, 76 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 43797d0..61017f6 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ curl -fsSL https://git.awaioi.com/awaioi/ERP/raw/branch/main/install.sh | bash tmp="$(mktemp)" trap 'rm -f -- "$tmp"' EXIT curl -fsSL https://git.awaioi.com/awaioi/ERP/raw/tag/v0.3.8/install.sh -o "$tmp" - printf '%s %s\n' '716f6a9ea75659be3c7162d031f1b1fe5e8aa0e82e0101296153842dff9b35d6' "$tmp" | sha256sum -c - + printf '%s %s\n' 'ae6a6613d4abe37ba24b41e1901eaa3b29cc32939583302c7f62277922fbbe9b' "$tmp" | sha256sum -c - sudo -E bash "$tmp" --version 0.3.8 ) ``` diff --git a/distribution/bin/erp-update b/distribution/bin/erp-update index b450f8e..1fc9488 100755 --- a/distribution/bin/erp-update +++ b/distribution/bin/erp-update @@ -220,12 +220,15 @@ if channel.lower() == "stable" and release.get("prerelease"): raise SystemExit("prerelease rejected on stable channel") if channel.lower() == "stable" and "-" in version.split("+", 1)[0]: raise SystemExit("prerelease tag rejected on stable channel") -assets = {str(item.get("name")): str(item.get("browser_download_url") or "") for item in release.get("assets", [])} +assets = { + str(item.get("name") or "") + for item in release.get("assets", []) + if isinstance(item, dict) +} names = [f"kaidi-erp-{version}.tar.gz", "SHA256SUMS", "SHA256SUMS.sig"] -urls = [assets.get(name, "") for name in names] -if any(not value or any(ch.isspace() for ch in value) for value in urls): +if any(name not in assets for name in names): raise SystemExit("release assets are incomplete") -print("\t".join([version, *urls])) +print("\t".join([version, tag])) PY } @@ -371,7 +374,8 @@ install_release() { || fail "健康检查超时时间配置无效" [[ "$HEALTH_POLL_SECONDS" =~ ^[1-9][0-9]*$ ]] \ || fail "健康检查间隔配置无效" - [[ "$REPOSITORY" =~ ^[^/[:space:]]+/[^/[:space:]]+$ ]] || fail "更新仓库配置无效" + [[ "$REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,99}/[A-Za-z0-9][A-Za-z0-9._-]{0,99}$ ]] \ + || fail "更新仓库配置无效" require_command curl require_command python3 require_command tar @@ -386,9 +390,13 @@ install_release() { local selection="$TMP_DIR/selection" select_release "$release_json" "$requested" "$selection" || fail "Release 元数据验证失败" 10 - local archive_url sums_url signature_url - IFS=$'\t' read -r TARGET_VERSION archive_url sums_url signature_url < "$selection" + local release_tag release_base archive_url sums_url signature_url + IFS=$'\t' read -r TARGET_VERSION release_tag < "$selection" local archive_name="kaidi-erp-${TARGET_VERSION}.tar.gz" + release_base="${GITEA_BASE_URL%/}/$owner/$repo/releases/download/$release_tag" + archive_url="$release_base/$archive_name" + sums_url="$release_base/SHA256SUMS" + signature_url="$release_base/SHA256SUMS.sig" local archive="$TMP_DIR/$archive_name" sums="$TMP_DIR/SHA256SUMS" signature="$TMP_DIR/SHA256SUMS.sig" write_state DOWNLOADING 20 "正在下载版本 ${TARGET_VERSION}" "$TARGET_VERSION" diff --git a/docs/online-install-and-update.md b/docs/online-install-and-update.md index 27f6462..d62d97e 100644 --- a/docs/online-install-and-update.md +++ b/docs/online-install-and-update.md @@ -59,7 +59,7 @@ Linux 生产服务要求主机使用 systemd;没有 systemd 的容器、WSL tmp="$(mktemp)" trap 'rm -f -- "$tmp"' EXIT curl -fsSL https://git.awaioi.com/awaioi/ERP/raw/tag/v0.3.8/install.sh -o "$tmp" - printf '%s %s\n' '716f6a9ea75659be3c7162d031f1b1fe5e8aa0e82e0101296153842dff9b35d6' "$tmp" | sha256sum -c - + printf '%s %s\n' 'ae6a6613d4abe37ba24b41e1901eaa3b29cc32939583302c7f62277922fbbe9b' "$tmp" | sha256sum -c - sudo -E bash "$tmp" --version 0.3.8 ) ``` diff --git a/install.sh b/install.sh index a76bfa4..31ec89e 100755 --- a/install.sh +++ b/install.sh @@ -200,7 +200,8 @@ download() { } download_release() { - [[ "$REPOSITORY" =~ ^[^/[:space:]]+/[^/[:space:]]+$ ]] || fail 'invalid Gitea repository' + [[ "$REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,99}/[A-Za-z0-9][A-Za-z0-9._-]{0,99}$ ]] \ + || fail 'invalid Gitea repository' local owner="${REPOSITORY%%/*}" repo="${REPOSITORY#*/}" local release_json="$TMP_DIR/release.json" download "${GITEA_BASE_URL%/}/api/v1/repos/$owner/$repo/releases/latest" "$release_json" \ @@ -223,17 +224,20 @@ if "-" in version.split("+", 1)[0]: requested = sys.argv[2].removeprefix("v") if requested and requested != version: raise SystemExit("requested version does not match latest release") -assets = {str(a.get("name")): str(a.get("browser_download_url") or "") for a in release.get("assets", [])} +assets = { + str(a.get("name") or "") + for a in release.get("assets", []) + if isinstance(a, dict) +} names = [ f"kaidi-erp-{version}.tar.gz", f"kaidi-erp-installer-{version}.jar", "SHA256SUMS", "SHA256SUMS.sig", ] -urls = [assets.get(name, "") for name in names] -if any(not url or any(ch.isspace() for ch in url) for url in urls): +if any(name not in assets for name in names): raise SystemExit("release assets missing") -print("\t".join([version, *urls])) +print("\t".join([version, tag])) PY then local reason="invalid Release metadata" @@ -241,10 +245,15 @@ PY fail "$reason" fi - local archive_url installer_url sums_url signature_url - IFS=$'\t' read -r VERSION archive_url installer_url sums_url signature_url < "$selection" + local release_tag release_base archive_url installer_url sums_url signature_url + IFS=$'\t' read -r VERSION release_tag < "$selection" ARCHIVE_NAME="kaidi-erp-${VERSION}.tar.gz" INSTALLER_NAME="kaidi-erp-installer-${VERSION}.jar" + release_base="${GITEA_BASE_URL%/}/$owner/$repo/releases/download/$release_tag" + archive_url="$release_base/$ARCHIVE_NAME" + installer_url="$release_base/$INSTALLER_NAME" + sums_url="$release_base/SHA256SUMS" + signature_url="$release_base/SHA256SUMS.sig" ARCHIVE_PATH="$TMP_DIR/$ARCHIVE_NAME" INSTALLER_PATH="$TMP_DIR/$INSTALLER_NAME" download "$archive_url" "$ARCHIVE_PATH" diff --git a/oa-backend/src/main/java/com/kaidi/oa/service/SystemUpdateService.java b/oa-backend/src/main/java/com/kaidi/oa/service/SystemUpdateService.java index e7c9221..1473d21 100644 --- a/oa-backend/src/main/java/com/kaidi/oa/service/SystemUpdateService.java +++ b/oa-backend/src/main/java/com/kaidi/oa/service/SystemUpdateService.java @@ -50,7 +50,6 @@ public class SystemUpdateService { private static final int MAX_RELEASE_HISTORY = 20; private static final int MAX_RELEASE_ASSETS = 32; private static final int MAX_ASSET_NAME_CHARS = 255; - private static final int MAX_ASSET_URL_CHARS = 4_096; private final UpdateProperties properties; private final ObjectMapper objectMapper; @@ -266,6 +265,16 @@ public class SystemUpdateService { + encode(repository[1]) + "/releases" + suffix); } + private URI releaseAssetUri(String tag, String name) { + String[] repository = properties.getRepository().split("/", 2); + if (repository.length != 2 || repository[0].isBlank() || repository[1].isBlank()) { + throw new ApiException(500, "更新仓库配置无效"); + } + String base = properties.getGiteaBaseUrl().replaceAll("/+$", ""); + return URI.create(base + "/" + encode(repository[0]) + "/" + encode(repository[1]) + + "/releases/download/" + encode(tag) + "/" + encode(name)); + } + private JsonNode fetchReleaseJson(URI uri) { HttpRequest.Builder builder = HttpRequest.newBuilder(uri) .timeout(Duration.ofSeconds(Math.max(1, properties.getRequestTimeoutSeconds()))) @@ -327,15 +336,12 @@ public class SystemUpdateService { List assets = new ArrayList<>(); for (JsonNode node : root.path("assets")) { String name = text(node, "name"); - String downloadUrl = text(node, "browser_download_url"); - if (name.isBlank() || name.length() > MAX_ASSET_NAME_CHARS - || downloadUrl.isBlank() || downloadUrl.length() > MAX_ASSET_URL_CHARS) { + if (name.isBlank() || name.length() > MAX_ASSET_NAME_CHARS) { throw new ApiException(502, "Release 文件信息无效"); } - validateAssetUrl(downloadUrl); assets.add(new ReleaseAsset( name, - downloadUrl, + releaseAssetUri(tag, name).toString(), node.path("size").asLong(0) )); } diff --git a/oa-backend/src/test/java/com/kaidi/oa/service/SystemUpdateServiceTest.java b/oa-backend/src/test/java/com/kaidi/oa/service/SystemUpdateServiceTest.java index 1399159..e86c054 100644 --- a/oa-backend/src/test/java/com/kaidi/oa/service/SystemUpdateServiceTest.java +++ b/oa-backend/src/test/java/com/kaidi/oa/service/SystemUpdateServiceTest.java @@ -43,8 +43,7 @@ class SystemUpdateServiceTest { void checksLatestGiteaReleaseAndRequiresSignedAssetSet() throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/api/v1/repos/awaioi/ERP/releases/latest", exchange -> { - String origin = "http://127.0.0.1:" + exchange.getLocalAddress().getPort(); - byte[] body = (""" + byte[] body = """ { "tag_name": "v0.2.0", "body": "PostgreSQL production release", @@ -52,12 +51,12 @@ class SystemUpdateServiceTest { "prerelease": false, "published_at": "2026-08-03T10:00:00Z", "assets": [ - {"name":"kaidi-erp-0.2.0.tar.gz","browser_download_url":"%s/app","size":123}, - {"name":"SHA256SUMS","browser_download_url":"%s/sums","size":64}, - {"name":"SHA256SUMS.sig","browser_download_url":"%s/sig","size":64} + {"name":"kaidi-erp-0.2.0.tar.gz","browser_download_url":"http://legacy.invalid/app","size":123}, + {"name":"SHA256SUMS","browser_download_url":"http://legacy.invalid/sums","size":64}, + {"name":"SHA256SUMS.sig","browser_download_url":"http://legacy.invalid/sig","size":64} ] } - """).formatted(origin, origin, origin).getBytes(StandardCharsets.UTF_8); + """.getBytes(StandardCharsets.UTF_8); exchange.getResponseHeaders().add("Content-Type", "application/json"); exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); @@ -75,6 +74,9 @@ class SystemUpdateServiceTest { assertThat(status.latestVersion()).isEqualTo("0.2.0"); assertThat(status.assets()).extracting(SystemUpdateService.ReleaseAsset::name) .containsExactly("kaidi-erp-0.2.0.tar.gz", "SHA256SUMS", "SHA256SUMS.sig"); + assertThat(status.assets()).extracting(SystemUpdateService.ReleaseAsset::downloadUrl) + .allMatch(url -> url.startsWith("http://127.0.0.1:" + server.getAddress().getPort() + + "/awaioi/ERP/releases/download/v0.2.0/")); } finally { server.stop(0); } @@ -198,7 +200,7 @@ class SystemUpdateServiceTest { } @Test - void rejectsReleaseAssetsFromAnotherOrigin() throws Exception { + void ignoresReleaseAssetOriginsAndBuildsTrustedDownloadUrls() throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/api/v1/repos/awaioi/ERP/releases/latest", exchange -> { byte[] body = """ @@ -214,9 +216,11 @@ class SystemUpdateServiceTest { server.start(); try { SystemUpdateService service = service(configuredProperties(server.getAddress().getPort()), "0.3.0"); - assertThatThrownBy(service::check) - .isInstanceOf(ApiException.class) - .hasMessageContaining("未受信任"); + SystemUpdateService.UpdateStatus status = service.check(); + + assertThat(status.assets()).extracting(SystemUpdateService.ReleaseAsset::downloadUrl) + .allMatch(url -> url.startsWith("http://127.0.0.1:" + server.getAddress().getPort() + + "/awaioi/ERP/releases/download/v0.4.0/")); } finally { server.stop(0); } diff --git a/r b/r index fc5c682..1b29b6b 100755 --- a/r +++ b/r @@ -3,7 +3,7 @@ set -Eeuo pipefail TARGET=/root/kaidi-erp-reinstall-v038.sh URL=https://git.awaioi.com/awaioi/ERP/raw/tag/v0.3.8/reinstall-centos9.sh -SHA256=77c21df88b23596ce0b0ffd40c1eb341c7a0653871cdd359ef13af22c6ffe8c4 +SHA256=a8496345fa5aaf6fb55180805098fb0e05bdc68b21c455b1c0b1901bba57fedd curl -fsSL --connect-timeout 15 --max-time 300 "$URL" -o "$TARGET" printf '%s %s\n' "$SHA256" "$TARGET" | sha256sum -c - diff --git a/reinstall-centos9.sh b/reinstall-centos9.sh index 479945f..414f3ea 100755 --- a/reinstall-centos9.sh +++ b/reinstall-centos9.sh @@ -5,7 +5,7 @@ GITEA_BASE_URL="https://git.awaioi.com" REPOSITORY="awaioi/ERP" VERSION="0.3.8" TAG="v${VERSION}" -INSTALL_SHA256="716f6a9ea75659be3c7162d031f1b1fe5e8aa0e82e0101296153842dff9b35d6" +INSTALL_SHA256="ae6a6613d4abe37ba24b41e1901eaa3b29cc32939583302c7f62277922fbbe9b" UNINSTALL_SHA256="98c56fed2fd4d01874e4ab5a1a4f3ec42ec3e29b315ffd87385a95488d587546" PG_ROOT="${ERP_RECOVERY_PG_ROOT:-/www/server/pgsql}" PG_DATA="${ERP_RECOVERY_PG_DATA:-/www/server/pgsql/data}" diff --git a/tests/release-scripts.test.sh b/tests/release-scripts.test.sh index 042bd8e..44fdef0 100755 --- a/tests/release-scripts.test.sh +++ b/tests/release-scripts.test.sh @@ -323,22 +323,23 @@ class Handler(BaseHTTPRequestHandler): def do_GET(self): if self.path == "/api/v1/repos/awaioi/ERP/releases/latest": - base = f"http://127.0.0.1:{self.server.server_port}/assets" + legacy = "http://legacy.invalid/assets" payload = { "tag_name": "v" + version, "draft": False, "prerelease": False, "assets": [ - {"name": archive, "browser_download_url": base + "/" + archive}, - {"name": f"kaidi-erp-installer-{version}.jar", "browser_download_url": base + f"/kaidi-erp-installer-{version}.jar"}, - {"name": "SHA256SUMS", "browser_download_url": base + "/SHA256SUMS"}, - {"name": "SHA256SUMS.sig", "browser_download_url": base + "/SHA256SUMS.sig"}, + {"name": archive, "browser_download_url": legacy + "/" + archive}, + {"name": f"kaidi-erp-installer-{version}.jar", "browser_download_url": legacy + f"/kaidi-erp-installer-{version}.jar"}, + {"name": "SHA256SUMS", "browser_download_url": legacy + "/SHA256SUMS"}, + {"name": "SHA256SUMS.sig", "browser_download_url": legacy + "/SHA256SUMS.sig"}, ], } self.send_bytes(200, json.dumps(payload).encode(), "application/json") return - if self.path.startswith("/assets/"): - name = self.path.removeprefix("/assets/") + release_prefix = f"/awaioi/ERP/releases/download/v{version}/" + if self.path.startswith(release_prefix): + name = self.path.removeprefix(release_prefix) if name not in {archive, f"kaidi-erp-installer-{version}.jar", "SHA256SUMS", "SHA256SUMS.sig"}: self.send_bytes(404, b"not found", "text/plain") return @@ -437,15 +438,15 @@ class Handler(BaseHTTPRequestHandler): def do_GET(self): if self.path == "/api/v1/repos/awaioi/ERP/releases/latest": - base = f"http://127.0.0.1:{self.server.server_port}" + legacy = "http://legacy.invalid" payload = { "tag_name": "v2.0.0", "draft": False, "prerelease": False, "assets": [ - {"name": "kaidi-erp-2.0.0.tar.gz", "browser_download_url": base + "/assets/kaidi-erp-2.0.0.tar.gz"}, - {"name": "SHA256SUMS", "browser_download_url": base + "/assets/SHA256SUMS"}, - {"name": "SHA256SUMS.sig", "browser_download_url": base + "/assets/SHA256SUMS.sig"}, + {"name": "kaidi-erp-2.0.0.tar.gz", "browser_download_url": legacy + "/assets/kaidi-erp-2.0.0.tar.gz"}, + {"name": "SHA256SUMS", "browser_download_url": legacy + "/assets/SHA256SUMS"}, + {"name": "SHA256SUMS.sig", "browser_download_url": legacy + "/assets/SHA256SUMS.sig"}, ], } self.send_bytes(200, json.dumps(payload).encode(), "application/json") @@ -455,8 +456,9 @@ class Handler(BaseHTTPRequestHandler): healthy = current == "2.0.0" if mode == "success" else current == "1.0.0" self.send_bytes(200 if healthy else 503, b'{"status":"UP"}' if healthy else b'{"status":"DOWN"}', "application/json") return - if self.path.startswith("/assets/"): - name = self.path.removeprefix("/assets/") + release_prefix = "/awaioi/ERP/releases/download/v2.0.0/" + if self.path.startswith(release_prefix): + name = self.path.removeprefix(release_prefix) if name not in {"kaidi-erp-2.0.0.tar.gz", "SHA256SUMS", "SHA256SUMS.sig"}: self.send_bytes(404, b"not found", "text/plain") return