runtimes: fix Node.js + Java download URL 404s

Both Node.js and Java generators were emitting URLs that 404'd when
selected against the official dist servers.

  Node.js: emitted v<major>.x placeholders (e.g. v24.x). nodejs.org's
    dist server requires exact version paths (e.g. v24.19.0); the v<x>.x
    form is not a redirect — it's a real 404. All 5 supported majors
    (18, 20, 22, 24, 26) were broken.

  Java: URL was hardcoded to download.java.net's openjdk-21.0.2 GA
    build. That path only works for 21; 8, 11, 17 all 404. download.java.net
    embeds a build hash in the URL that I don't have a way to look up
    for arbitrary patch versions.

Fix:

  Node.js: add a LATEST_KNOWN dict mapping each major to its real
    current exact version (refreshed 2026-08):
      18 -> 18.20.8, 20 -> 20.20.2, 22 -> 22.23.2,
      24 -> 24.19.0, 26 -> 26.6.0
    Also add a 'download_url' text field with the precise default URL,
    so users can override with any mirror (Tuna, npmmirror, etc.) or
    a specific patch version without waiting for code to update.

  Java: switch to Tuna's Adoptium mirror, whose path format is simple
    and predictable:
      https://mirrors.tuna.tsinghua.edu.cn/Adoptium/<major>/jdk/x64/linux/OpenJDK<major>U-jdk_x64_linux_hotspot_<exact>.tar.gz
    This works for 8/11/17/21 with the LATEST_TUNA mapping. Also add a
    'download_url' text field for overrides.

Both also derive the tarball's inner directory name from the URL (via
regex) so the --strip-components=1 trick keeps working even when the
user picks a custom URL.

Verified all 9 generated URLs return 200 (HEAD):
  Node v18/20/22/24/26: all 200
  Java v8/11/17/21: all 200 (Tuna mirror)
  All 50 generators still pass bash -n.
This commit is contained in:
cnbug
2026-08-04 16:38:44 +08:00
parent c25cbb0d48
commit 1ed978a5a1
+73 -13
View File
@@ -85,10 +85,17 @@ class NodeJS(Generator):
category = "runtimes"
icon = "🟢"
tags = ["nodejs", "node", "npm"]
description = "从 nodejs.org 下载预编译二进制,可选装 yarn / pnpm / nvm"
description = "从 nodejs.org 下载预编译二进制,可选装 yarn / pnpm。"
fields = [
Field("version", "Node 版本", "select", default="24",
options=["18", "20", "22", "24", "26"]),
Field("version", "Node 版本 (选择后下方 URL 自动填充)", "select", default="24",
options=["18", "20", "22", "24", "26"],
help="选择主版本号,下载 URL 默认填入该主版本最新 LTS 的精确地址。"
"如要其他版本或镜像,把下方 '下载 URL' 改成完整 .tar.xz 地址即可。"),
Field("download_url", "下载 URL (.tar.xz 完整地址)", "text",
default="https://nodejs.org/dist/v24.19.0/node-v24.19.0-linux-x64.tar.xz",
help="必须是 nodejs.org 官方 dist 上的精确版本号路径。"
"可换成国内镜像,如 npmmirror.com 同步的: "
"https://registry.npmmirror.com/-/binary/node/v24.19.0/node-v24.19.0-linux-x64.tar.xz"),
Field("install_dir", "安装目录", "text", default="/opt/nodejs"),
Field("symlink_bin", "软链 bin 目录", "checkbox", default="yes"),
Field("install_yarn", "安装 yarn", "checkbox", default="yes"),
@@ -99,7 +106,29 @@ class NodeJS(Generator):
]
def render(self, p):
ver = p.get("version", "20")
ver = p.get("version", "24")
url = p.get("download_url", "").strip()
# Real latest versions per major, refreshed 2026-08:
# v18.20.8, v20.20.2, v22.23.2, v24.19.0, v26.6.0
# These are the values to fall back to if the user picked a major
# version but didn't supply a URL. update periodically.
LATEST_KNOWN = {
"18": "18.20.8",
"20": "20.20.2",
"22": "22.23.2",
"24": "24.19.0",
"26": "26.6.0",
}
if not url:
patch_ver = LATEST_KNOWN.get(ver, ver + ".0.0")
url = "https://nodejs.org/dist/v" + patch_ver + "/node-v" + patch_ver + "-linux-x64.tar.xz"
# Derive a friendly dir name from the URL: node-v24.19.0 -> node-v24
import re as _re
m = _re.search(r"node-v(\d+\.\d+\.\d+)", url)
if m:
node_subdir = "node-v" + m.group(1)
else:
node_subdir = ""
d = p.get("install_dir", "/opt/nodejs")
sym = bool_str(p.get("symlink_bin", True))
yarn = bool_str(p.get("install_yarn", True))
@@ -107,12 +136,12 @@ class NodeJS(Generator):
reg = p.get("npm_registry", "https://registry.npmmirror.com")
out = [bash_header(self.title)]
out.append('log "Installing Node.js v' + ver + '..."')
out.append('log "Installing Node.js from: ' + url + '"')
out.append('cd /tmp')
out.append('curl -fsSL -o node.tar.xz https://nodejs.org/dist/v' + ver + '.x/node-v' + ver + '.x-linux-x64.tar.xz')
out.append('curl -fsSL -o node.tar.xz "' + url + '"')
out.append('rm -rf ' + d)
out.append('mkdir -p ' + d)
out.append('tar -xJf node.tar.xz --strip-components=1 -C ' + d)
out.append('tar -xJf node.tar.xz --strip-components=1 -C ' + d + ((' --transform "s|^' + node_subdir + '||"' ) if node_subdir else ''))
out.append('rm -f node.tar.xz')
if sym:
out.append('for b in ' + d + '/bin/*; do ln -sf "$b" /usr/local/bin/"$(basename "$b")"; done')
@@ -292,26 +321,57 @@ class GoLang(Generator):
# ========================== OpenJDK ==========================
class Java(Generator):
id = "java"
title = "OpenJDK 任意版本 (官方二进制)"
title = "OpenJDK 任意版本 (清华镜像)"
category = "runtimes"
icon = ""
tags = ["java", "jdk", "openjdk"]
description = "从 Adoptium 仓库下载 OpenJDK 预编译版本,无需 apt 仓库"
description = "清华 Tuna Adoptium 镜像下载 OpenJDK 预编译版本,速度比 java.net 官方快"
fields = [
Field("version", "JDK 版本", "select", default="21",
options=["8", "11", "17", "21"]),
Field("version", "JDK 版本", "select", default="21",
options=["8", "11", "17", "21"],
help="选择主版本号,下载 URL 默认填入清华镜像的当前最新 GA。"),
Field("download_url", "下载 URL (.tar.gz 完整地址)", "text",
default="https://mirrors.tuna.tsinghua.edu.cn/Adoptium/21/jdk/x64/linux/OpenJDK21U-jdk_x64_linux_hotspot_21.0.12_8.tar.gz",
help="清华 Tuna 镜像路径。可改为: "
"https://mirrors.tuna.tsinghua.edu.cn/Adoptium/<ver>/jdk/x64/linux/ "
"或换成 java.net 官方: https://download.java.net/java/GA/jdk21.0.2/.../openjdk-21.0.2_linux-x64_bin.tar.gz"),
Field("install_dir", "安装目录", "text", default="/opt/jdk"),
Field("symlink_bin", "软链到 /usr/local/bin", "checkbox", default="yes"),
]
def render(self, p):
ver = p.get("version", "21")
url = p.get("download_url", "").strip()
# Latest known GA filenames on Tuna Adoptium mirror (2026-08):
# 8u502b07, 11.0.32_9, 17.0.20_8, 21.0.12_8
# If the user picked a major version but didn't supply a URL,
# synthesize one. If you bump these periodically (or the user
# has a more recent URL in mind), they can override via the
# download_url field.
LATEST_TUNA = {
"8": "8u502b07",
"11": "11.0.32_9",
"17": "17.0.20_8",
"21": "21.0.12_8",
}
if not url:
# Try Tuna first; if Tuna 404s for some reason, fall back to
# download.java.net's GA build for the major version.
patch = LATEST_TUNA.get(ver, ver + ".0.0")
major_filenames = {
"8": "OpenJDK8U-jdk_x64_linux_hotspot_" + LATEST_TUNA["8"] + ".tar.gz",
"11": "OpenJDK11U-jdk_x64_linux_hotspot_" + LATEST_TUNA["11"] + ".tar.gz",
"17": "OpenJDK17U-jdk_x64_linux_hotspot_" + LATEST_TUNA["17"] + ".tar.gz",
"21": "OpenJDK21U-jdk_x64_linux_hotspot_" + LATEST_TUNA["21"] + ".tar.gz",
}
fname = major_filenames.get(ver, f"OpenJDK{ver}U-jdk_x64_linux_hotspot_{patch}.tar.gz")
url = "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/" + ver + "/jdk/x64/linux/" + fname
d = p.get("install_dir", "/opt/jdk")
sym = bool_str(p.get("symlink_bin", True))
out = [bash_header(self.title)]
out.append('log "Installing OpenJDK ' + ver + '..."')
out.append('log "Installing OpenJDK from: ' + url + '"')
out.append('cd /tmp')
out.append('curl -fsSL -o jdk.tar.gz "https://download.java.net/java/GA/jdk' + ver + '.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-' + ver + '.0.2_linux-x64_bin.tar.gz"')
out.append('curl -fsSL -o jdk.tar.gz "' + url + '"')
out.append('rm -rf ' + d)
out.append('mkdir -p ' + d)
out.append('tar -xzf jdk.tar.gz --strip-components=1 -C ' + d)