feat: v9.0.0 重大更新 — 镜像源重构、CoolerControl集成、垃圾清理系统、磁盘直通增强、Web文档站移除#86
Conversation
- 镜像源系统重构为并行数组架构,新增智能镜像选择与通用下载函数 - 新增 CoolerControl 风扇控制工具一键管理(安装/更新/卸载) - 新增完整垃圾清理系统(基础清理、备份修剪、快照修剪、孤立磁盘扫描) - 磁盘直通能力大幅增强(整盘发现、存储控制器直通、NVMe直通、系统盘保护) - 新增存储挂载向导,支持 ext4/xfs 分区一键挂载 - 新增安全中心菜单,整合安全相关功能 - 主菜单重组:新增「安全中心」「第三方工具」入口,移除已修复CVE模块 - 虚拟机高级操作拆分为聚焦型子菜单 - 移除内嵌 Web 文档站点,仓库更精简 - VERSION 升级至 9.0.0
Reviewer's GuideRefactors mirror source handling into a parallel-array registry with smart selection, introduces shared download utilities and smarter update URL selection, significantly extends disk/NVMe/PCI passthrough tooling and adds a CoolerControl manager, garbage-cleanup system, storage mount wizard, security center menu, reorganized menus, and removes the embedded Web docs while bumping VERSION to 9.0.0. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughAdds ChangesMirror Test CLI Tool, Shell LF Enforcement, and Version Bump
Web Directory Removal
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
PVE-Tools.shscript is getting very large and now mixes concerns like mirror management, SSH hardening, garbage cleanup, VM tooling, etc.; consider extracting major subsystems (e.g. mirrors, garbage cleanup, security, networking) into separate sourced files or shell modules to keep each file smaller and easier to reason about. - Several helper functions are duplicated or redefined in slightly different forms (e.g. storage path calculation, archive discovery, and some network helpers are reintroduced later in the file); it would be safer to centralize these utilities in a single section and reuse them to avoid diverging behavior over time.
- The SSH hardening logic directly edits
/etc/ssh/sshd_configand writes drop-in configs while also manipulating fail2ban; to reduce the risk of locking users out, consider adding a dry‑run mode and an explicit check that key-based root login works (or that a non-root admin user exists) before applying changes, and ensure rollback paths are clearly logged for operators.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `PVE-Tools.sh` script is getting very large and now mixes concerns like mirror management, SSH hardening, garbage cleanup, VM tooling, etc.; consider extracting major subsystems (e.g. mirrors, garbage cleanup, security, networking) into separate sourced files or shell modules to keep each file smaller and easier to reason about.
- Several helper functions are duplicated or redefined in slightly different forms (e.g. storage path calculation, archive discovery, and some network helpers are reintroduced later in the file); it would be safer to centralize these utilities in a single section and reuse them to avoid diverging behavior over time.
- The SSH hardening logic directly edits `/etc/ssh/sshd_config` and writes drop-in configs while also manipulating fail2ban; to reduce the risk of locking users out, consider adding a dry‑run mode and an explicit check that key-based root login works (or that a non-root admin user exists) before applying changes, and ensure rollback paths are clearly logged for operators.
## Individual Comments
### Comment 1
<location path="PVE-Tools.sh" line_range="505-511" />
<code_context>
+ return 1
+ fi
+
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout "$timeout" --max-time "$timeout" "$url" 2>/dev/null
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q -T "$timeout" -O - "$url" 2>/dev/null
+ else
+ return 1
+ fi
+}
+
+pve_tools_download_file() {
+ local url="$1"
+ local output_file="$2"
+ local timeout="${3:-60}"
+
+ if [[ -z "$url" || -z "$output_file" ]]; then
+ return 1
+ fi
+
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 15 --max-time "$timeout" -o "$output_file" "$url" 2>/dev/null
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q -T "$timeout" -O "$output_file" "$url" 2>/dev/null
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The download timeout parameter is partially ignored for curl, which could cause unexpected behavior.
In `pve_tools_download_file`, `wget` honors `timeout` via `-T "$timeout"`, but the `curl` path hardcodes `--connect-timeout 15` while only `--max-time` uses the caller value, so callers cannot control connect timeout and behavior differs between the two tools.
To make the API consistent and predictable, consider either using `--connect-timeout "$timeout"` as well, or adding a separate connect-timeout parameter (defaulting to 15s). Aligning this with `pve_tools_download_url` (which uses `--connect-timeout "$timeout" --max-time "$timeout"`) would avoid subtle differences in timeout behavior.
```suggestion
if command -v curl >/dev/null 2>&1; then
curl -fsSL --connect-timeout "$timeout" --max-time "$timeout" -o "$output_file" "$url" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then
wget -q -T "$timeout" -O "$output_file" "$url" 2>/dev/null
else
return 1
fi
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| if command -v curl >/dev/null 2>&1; then | ||
| curl -fsSL --connect-timeout 15 --max-time "$timeout" -o "$output_file" "$url" 2>/dev/null | ||
| elif command -v wget >/dev/null 2>&1; then | ||
| wget -q -T "$timeout" -O "$output_file" "$url" 2>/dev/null | ||
| else | ||
| return 1 | ||
| fi |
There was a problem hiding this comment.
suggestion (bug_risk): The download timeout parameter is partially ignored for curl, which could cause unexpected behavior.
In pve_tools_download_file, wget honors timeout via -T "$timeout", but the curl path hardcodes --connect-timeout 15 while only --max-time uses the caller value, so callers cannot control connect timeout and behavior differs between the two tools.
To make the API consistent and predictable, consider either using --connect-timeout "$timeout" as well, or adding a separate connect-timeout parameter (defaulting to 15s). Aligning this with pve_tools_download_url (which uses --connect-timeout "$timeout" --max-time "$timeout") would avoid subtle differences in timeout behavior.
| if command -v curl >/dev/null 2>&1; then | |
| curl -fsSL --connect-timeout 15 --max-time "$timeout" -o "$output_file" "$url" 2>/dev/null | |
| elif command -v wget >/dev/null 2>&1; then | |
| wget -q -T "$timeout" -O "$output_file" "$url" 2>/dev/null | |
| else | |
| return 1 | |
| fi | |
| if command -v curl >/dev/null 2>&1; then | |
| curl -fsSL --connect-timeout "$timeout" --max-time "$timeout" -o "$output_file" "$url" 2>/dev/null | |
| elif command -v wget >/dev/null 2>&1; then | |
| wget -q -T "$timeout" -O "$output_file" "$url" 2>/dev/null | |
| else | |
| return 1 | |
| fi |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Tools/mirror-test.mjs`:
- Around line 113-124: The CLI argument parsing in the switch statement lacks
validation for option values, allowing invalid inputs to be silently accepted or
the next token to be incorrectly consumed. For each case (--timeout,
--concurrency, and --output/-o), add validation to check that the next argument
exists (to prevent out-of-bounds access), validate that the parsed value is
appropriate for that option (for example, ensure timeout and concurrency are
positive numbers), and throw or exit with a clear error message if validation
fails. This prevents negative concurrency values, missing output filenames, and
other malformed inputs from being silently accepted.
- Around line 169-181: The probeWithRetry function discards the actual HTTP
status code from the final probe attempt by always returning a hardcoded {
status: 0, ok: false } object when all retries are exhausted. Instead of
returning the hardcoded error object at the end of the function, preserve and
return the last result object obtained from the final probeUrl call (whether it
was from the HEAD request or the GET fallback), so that the actual HTTP status
codes like 404 or 500 are preserved in the final report for better debugging.
In `@VERSION`:
- Line 1: The VERSION file has been updated to 9.0.0 but the CURRENT_VERSION
variable in PVE-Tools.sh is still set to 8.8.8, causing a version mismatch that
will fail CI checks. Locate the CURRENT_VERSION variable in PVE-Tools.sh and
update its value from 8.8.8 to 9.0.0 to match the VERSION file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 24b59f09-c48e-4692-8646-54893f6e78ab
⛔ Files ignored due to path filters (24)
Web/assets/WeChat.jpgis excluded by!**/*.jpgWeb/assets/images/Proxmox-Corporate-Brandguideline.pdfis excluded by!**/*.pdfWeb/assets/images/Proxmox_logos_full_lockup_SVG/proxmox-full-lockup-color.svgis excluded by!**/*.svgWeb/assets/images/Proxmox_logos_full_lockup_SVG/proxmox-full-lockup-inverted-color.svgis excluded by!**/*.svgWeb/assets/images/Proxmox_logos_full_lockup_SVG/proxmox-logo-stacked-color.svgis excluded by!**/*.svgWeb/assets/images/Proxmox_logos_full_lockup_SVG/proxmox-logo-stacked-inverted-color.svgis excluded by!**/*.svgWeb/bun.lockis excluded by!**/*.lockWeb/home-full.pngis excluded by!**/*.pngWeb/public/icons/clock-outline.svgis excluded by!**/*.svgWeb/public/icons/dots-horizontal.svgis excluded by!**/*.svgWeb/public/icons/harddisk.svgis excluded by!**/*.svgWeb/public/icons/rocket-launch-outline.svgis excluded by!**/*.svgWeb/public/icons/server-network.svgis excluded by!**/*.svgWeb/public/icons/shield-check-outline.svgis excluded by!**/*.svgWeb/public/icons/sparkles.svgis excluded by!**/*.svgWeb/public/icons/video-card.svgis excluded by!**/*.svgWeb/public/images/PixPin_2026-05-15_19-56-27.pngis excluded by!**/*.pngWeb/public/images/PixPin_2026-05-15_19-58-55.pngis excluded by!**/*.pngWeb/public/images/PixPin_2026-05-15_20-05-01.pngis excluded by!**/*.pngWeb/public/logo-dark.svgis excluded by!**/*.svgWeb/public/logo-horizontal-dark.svgis excluded by!**/*.svgWeb/public/logo-horizontal.svgis excluded by!**/*.svgWeb/public/logo.svgis excluded by!**/*.svgWeb/public/og-image.pngis excluded by!**/*.png
📒 Files selected for processing (45)
.gitattributesPVE-Tools.shTools/mirror-test.mjsVERSIONWeb/.vitepress/config.mtsWeb/.vitepress/theme/components/Announcement.vueWeb/.vitepress/theme/components/CookieConsent.vueWeb/.vitepress/theme/components/CopyCodeBox.vueWeb/.vitepress/theme/components/Giscus.vueWeb/.vitepress/theme/components/HomeFeaturesWithTimeline.vueWeb/.vitepress/theme/components/SponsorWall.vueWeb/.vitepress/theme/components/TodoList.vueWeb/.vitepress/theme/custom.cssWeb/.vitepress/theme/index.tsWeb/CLAUDE.mdWeb/advanced/cpu-optimization.mdWeb/advanced/data-recovery-after-mistake.mdWeb/advanced/gpu-passthrough.mdWeb/advanced/gpu-virtualization.mdWeb/advanced/host-network-firewall-ipv6.mdWeb/advanced/how-to-connect-ssh.mdWeb/advanced/index.mdWeb/advanced/nvidia-vgpu-driver-notes.mdWeb/advanced/pve-upgrade.mdWeb/advanced/storage-management.mdWeb/advanced/vm-backup-migration-cloudinit.mdWeb/eol.mdWeb/faq.mdWeb/features.mdWeb/guide.mdWeb/index.jsWeb/index.mdWeb/package.jsonWeb/pay.mdWeb/public/_headersWeb/public/robots.txtWeb/sponsor-data.jsonWeb/sponsor.mdWeb/submit-plugin.mdWeb/todo-data.jsonWeb/todo.mdWeb/tos.mdWeb/ula.mdWeb/update.mdWeb/wrangler.jsonc
💤 Files with no reviewable changes (41)
- Web/ula.md
- Web/eol.md
- Web/advanced/data-recovery-after-mistake.md
- Web/advanced/host-network-firewall-ipv6.md
- Web/advanced/index.md
- Web/wrangler.jsonc
- Web/advanced/storage-management.md
- Web/guide.md
- Web/.vitepress/theme/components/Announcement.vue
- Web/todo.md
- Web/faq.md
- Web/submit-plugin.md
- Web/.vitepress/theme/components/Giscus.vue
- Web/sponsor-data.json
- Web/.vitepress/theme/components/TodoList.vue
- Web/features.md
- Web/pay.md
- Web/advanced/nvidia-vgpu-driver-notes.md
- Web/advanced/pve-upgrade.md
- Web/.vitepress/theme/components/CookieConsent.vue
- Web/tos.md
- Web/package.json
- Web/advanced/how-to-connect-ssh.md
- Web/advanced/gpu-virtualization.md
- Web/public/_headers
- Web/advanced/cpu-optimization.md
- Web/.vitepress/config.mts
- Web/.vitepress/theme/custom.css
- Web/advanced/vm-backup-migration-cloudinit.md
- Web/.vitepress/theme/components/CopyCodeBox.vue
- Web/public/robots.txt
- Web/sponsor.md
- Web/advanced/gpu-passthrough.md
- Web/index.js
- Web/todo-data.json
- Web/index.md
- Web/.vitepress/theme/components/HomeFeaturesWithTimeline.vue
- Web/update.md
- Web/.vitepress/theme/index.ts
- Web/.vitepress/theme/components/SponsorWall.vue
- Web/CLAUDE.md
| for (let i = 0; i < args.length; i++) { | ||
| switch (args[i]) { | ||
| case "--timeout": | ||
| opts.timeout = Number(args[++i]) || 8000; | ||
| break; | ||
| case "--concurrency": | ||
| opts.concurrency = Number(args[++i]) || 6; | ||
| break; | ||
| case "--output": | ||
| case "-o": | ||
| opts.output = args[++i]; | ||
| break; |
There was a problem hiding this comment.
Fail fast on invalid CLI option values instead of silently coercing.
Line 116/119/123 currently consume the next token without validation. This can silently break execution (e.g., --concurrency -1 yields zero workers and an empty report; missing --output value can consume the next flag as a filename).
Suggested fix
function parseArgs() {
const args = process.argv.slice(2);
@@
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case "--timeout":
- opts.timeout = Number(args[++i]) || 8000;
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
+ throw new Error("--timeout requires a numeric value");
+ }
+ opts.timeout = Number(args[++i]);
+ if (!Number.isFinite(opts.timeout) || opts.timeout <= 0) {
+ throw new Error("--timeout must be > 0");
+ }
break;
case "--concurrency":
- opts.concurrency = Number(args[++i]) || 6;
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
+ throw new Error("--concurrency requires an integer value");
+ }
+ opts.concurrency = Number(args[++i]);
+ if (!Number.isInteger(opts.concurrency) || opts.concurrency <= 0) {
+ throw new Error("--concurrency must be a positive integer");
+ }
break;
case "--output":
case "-o":
- opts.output = args[++i];
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
+ throw new Error("--output requires a file path");
+ }
+ opts.output = args[++i];
break;Also applies to: 189-199, 396-401
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Tools/mirror-test.mjs` around lines 113 - 124, The CLI argument parsing in
the switch statement lacks validation for option values, allowing invalid inputs
to be silently accepted or the next token to be incorrectly consumed. For each
case (--timeout, --concurrency, and --output/-o), add validation to check that
the next argument exists (to prevent out-of-bounds access), validate that the
parsed value is appropriate for that option (for example, ensure timeout and
concurrency are positive numbers), and throw or exit with a clear error message
if validation fails. This prevents negative concurrency values, missing output
filenames, and other malformed inputs from being silently accepted.
| async function probeWithRetry(url, timeoutMs, { retries = 1, fallbackGet = false } = {}) { | ||
| for (let attempt = 0; attempt <= retries; attempt++) { | ||
| let result = await probeUrl(url, timeoutMs, "HEAD"); | ||
| if (result.ok) return result; | ||
| // HEAD 失败且启用 GET 回退时,用 GET 再试一次 | ||
| if (fallbackGet && attempt === retries) { | ||
| result = await probeUrl(url, timeoutMs, "GET"); | ||
| if (result.ok) return result; | ||
| } | ||
| if (attempt < retries) await sleep(300); | ||
| } | ||
| return { status: 0, ok: false }; | ||
| } |
There was a problem hiding this comment.
Preserve final HTTP status from retries for accurate reporting.
Line 180 always returns { status: 0 } after retries, which drops real failure status (e.g., 404/500) and makes the report less actionable.
Suggested fix
async function probeWithRetry(url, timeoutMs, { retries = 1, fallbackGet = false } = {}) {
+ let last = { status: 0, ok: false };
for (let attempt = 0; attempt <= retries; attempt++) {
- let result = await probeUrl(url, timeoutMs, "HEAD");
+ let result = await probeUrl(url, timeoutMs, "HEAD");
+ last = result;
if (result.ok) return result;
@@
if (fallbackGet && attempt === retries) {
result = await probeUrl(url, timeoutMs, "GET");
+ last = result;
if (result.ok) return result;
}
if (attempt < retries) await sleep(300);
}
- return { status: 0, ok: false };
+ return last;
}Also applies to: 217-236
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Tools/mirror-test.mjs` around lines 169 - 181, The probeWithRetry function
discards the actual HTTP status code from the final probe attempt by always
returning a hardcoded { status: 0, ok: false } object when all retries are
exhausted. Instead of returning the hardcoded error object at the end of the
function, preserve and return the last result object obtained from the final
probeUrl call (whether it was from the HEAD request or the GET fallback), so
that the actual HTTP status codes like 404 or 500 are preserved in the final
report for better debugging.
| @@ -1 +1 @@ | |||
| 8.8.8 | |||
| 9.0.0 | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
SCRIPT_VERSION=$(rg -n 'CURRENT_VERSION=' PVE-Tools.sh | sed -E 's/.*"([^"]+)".*/\1/' | head -n1)
FILE_VERSION=$(tr -d '\r\n' < VERSION)
echo "PVE-Tools.sh CURRENT_VERSION: ${SCRIPT_VERSION}"
echo "VERSION file: ${FILE_VERSION}"
if [[ "${SCRIPT_VERSION}" != "${FILE_VERSION}" ]]; then
echo "Mismatch detected"
exit 1
fi
echo "Versions are consistent"Repository: PVE-Tools/PVE-Tools-9
Length of output: 139
Update CURRENT_VERSION in PVE-Tools.sh to match the VERSION file.
VERSION is set to 9.0.0, but PVE-Tools.sh still has CURRENT_VERSION=8.8.8. This mismatch will cause CI to fail. Update CURRENT_VERSION to 9.0.0 in PVE-Tools.sh before merging.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@VERSION` at line 1, The VERSION file has been updated to 9.0.0 but the
CURRENT_VERSION variable in PVE-Tools.sh is still set to 8.8.8, causing a
version mismatch that will fail CI checks. Locate the CURRENT_VERSION variable
in PVE-Tools.sh and update its value from 8.8.8 to 9.0.0 to match the VERSION
file.
Summary by Sourcery
Release PVE-Tools 9.0.0 with a reworked mirror registry, new third-party cooler and security tooling, expanded disk passthrough utilities, a comprehensive garbage cleanup system, and removal of the embedded web documentation site.
Enhancements:
Build:
Documentation:
Summary by CodeRabbit
New Features
Chores