From 21393864b30fae405320784b27b7d2516ab2c87f Mon Sep 17 00:00:00 2001 From: JGZYES Date: Sun, 30 Aug 2026 21:54:15 +0800 Subject: [PATCH] mirror: read-only PHP package browser (TUNA-like); serve index + downloads --- README.md | 3 +- mirror/index.php | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 mirror/index.php diff --git a/README.md b/README.md index 08921da..98ca3b1 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,8 @@ priority = 20 ```sh cd mirror -python -m http.server 8080 # 或 nginx/apache 直接指向 mirror/ 目录 +php -S 0.0.0.0:8080 # 浏览页 + 静态下载(PHP,无 router 参数) +# 或 nginx/apache:DirectoryIndex index.php,.pdm 走静态文件 ``` - 目录约定:`packages/<包>.json`(latest,含 `variants`)、 diff --git a/mirror/index.php b/mirror/index.php new file mode 100644 index 0000000..77055a1 --- /dev/null +++ b/mirror/index.php @@ -0,0 +1,110 @@ + package index + * GET /?pkg=nodejs -> nodejs variants + download links + * packages//.pdm -> served as a static file by the web server + * + * Deploy: php -S 0.0.0.0:80 (or Apache/Nginx) serving this directory. + */ +$root = __DIR__; +$pkg = $root . '/packages'; +$base = getenv('PMM_BASE_URL'); +if (!$base) $base = 'https://pmm.parlz.com/mirror'; +$base = rtrim($base, '/'); + +function esc($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } + +function human_size($bytes) { + $bytes = (int)$bytes; + $u = array('B','KB','MB','GB'); + $i = 0; + while ($bytes >= 1024 && $i < 3) { $bytes /= 1024; $i++; } + return round($bytes, 2) . ' ' . $u[$i]; +} + +function pkg_list($dir) { + $out = array(); + foreach (glob($dir . '/*.json') as $f) { + $m = json_decode(file_get_contents($f), true); + if (!is_array($m) || empty($m['variants'])) continue; + $out[] = $m; + } + usort($out, function ($a, $b) { return strcmp($a['name'], $b['name']); }); + return $out; +} + +function pkg_meta($dir, $name) { + $f = $dir . '/' . $name . '.json'; + if (!is_file($f)) return null; + $m = json_decode(file_get_contents($f), true); + return (is_array($m) && !empty($m['variants'])) ? $m : null; +} + +function pkg_size($dir, $file) { $p = $dir . '/' . $file; return is_file($p) ? filesize($p) : 0; } + +function page_head($title) { + echo "" + . "" + . "" . esc($title) . " · PMM 镜像"; + echo "

📦 PMM 镜像 — ".esc($base)."

"; +} + +function page_foot() { + echo "
ParlzPackageManger · mirrors · pmm install <pkg>
"; +} + +// ---- router ---- +header('Content-Type: text/html; charset=utf-8'); +header('Access-Control-Allow-Origin: *'); + +$pkgName = isset($_GET['pkg']) ? trim((string)$_GET['pkg']) : ''; + +if ($pkgName !== '') { + $m = pkg_meta($pkg, $pkgName); + page_head($pkgName); + if ($m === null) { echo "

没有找到包:".esc($pkgName)."

"; page_foot(); exit; } + echo "
← 返回全部包  /  ".esc($m['name'])." ".(isset($m['description'])?esc($m['description']):'')."
"; + echo ""; + foreach ($m['variants'] as $v) { + $file = isset($v['file']) ? $v['file'] : ($v['version'].'-'.(isset($v['os'])?$v['os']:'any').'.pdm'); + $size = pkg_size($pkg, $file); + $sha = isset($v['sha256']) ? substr($v['sha256'], 0, 16).'…' : '-'; + $href = 'packages/' . rawurlencode($m['name']) . '/' . rawurlencode($file); + echo "" + . "" + . ""; + } + echo "
版本平台架构大小sha256
" . esc($v['version']) . "" . esc($v['os']) . "" . esc(isset($v['arch'])?$v['arch']:'-') . "" . human_size($size) . "".esc($sha)."下载 .pdm
"; + page_foot(); + exit; +} + +// ---- index ---- +page_head('软件包索引'); +echo "
Index of /mirror · 共 ".count(pkg_list($pkg))." 个包。" . + " 客户端: pmm install <pkg>,镜像 ".esc('registry='.$base.'/packages')."
"; +echo ""; +foreach (pkg_list($pkg) as $m) { + $name = $m['name']; + $tags = ''; + foreach ($m['variants'] as $v) { + $tags .= "" . esc($v['os']) . (isset($v['arch']) ? '/' . esc($v['arch']) : '') . " "; + } + echo "" + . "" + . ""; +} +echo "
包名最新版本平台/架构安装
" . esc($name) . "" . esc($m['version']) . "$tagspmm install ".esc($name)."
"; +page_foot();