web: split into home + 4 pages (features/install/download/servers-status); detailed multi-endpoint status API (server header/latency/timestamp); shared _common.php layout

这个提交包含在:
JGZYES
2026-09-02 19:41:11 +08:00
父节点 060ba69949
当前提交 745cc6ffc8
修改 9 个文件,包含 382 行新增232 行删除
+68
查看文件
@@ -0,0 +1,68 @@
<?php
/* Shared layout + constants for the PMM site (4 pages + home).
* Pure grayscale dark theme. Each page: define('PMM_SITE',1); require _common.php;
* then pmm_header($active,$title); ... pmm_footer(); */
if (!defined('PMM_SITE')) { http_response_code(403); exit('forbidden'); }
define('PMM_VERSION', '0.2.9');
function pmm_nav_links(string $active): string {
$items = [
'home' => ['index.php', '首页'],
'features' => ['features.php', '特性'],
'install' => ['install.php', '安装'],
'download' => ['download.php', '下载'],
'status' => ['servers.php', '服务状态'],
];
$html = '';
foreach ($items as $key => [$href, $label]) {
$cls = $key === $active ? ' class="active"' : '';
$html .= '<li><a' . $cls . ' href="' . $href . '">' . $label . '</a></li>';
}
return $html;
}
function pmm_header(string $active, string $title): void {
$t = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
echo '<!DOCTYPE html>' . "\n" .
'<html lang="zh-CN">' . "\n" .
'<head>' . "\n" .
'<meta charset="UTF-8">' . "\n" .
'<meta name="viewport" content="width=device-width, initial-scale=1.0">' . "\n" .
'<title>' . $t . ' · ParlzPackageManager</title>' . "\n" .
'<meta name="description" content="ParlzPackageManager (PMM) — 用 C 语言编写、跨 Windows/Linux/macOS 的包管理器。">' . "\n" .
'<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 32 32\'%3E%3Crect width=\'32\' height=\'32\' rx=\'7\' fill=\'%23050505\'/%3E%3Ctext x=\'16\' y=\'23\' font-size=\'17\' font-family=\'monospace\' fill=\'%23cfcfcf\' text-anchor=\'middle\'%3Epmm%3C/text%3E%3C/svg%3E">' . "\n" .
'<link rel="stylesheet" href="assets/style.css">' . "\n" .
'</head>' . "\n" .
'<body>' . "\n" .
'<nav class="nav">' . "\n" .
' <a class="brand" href="index.php"><span class="brand-mark">pmm</span><span class="brand-name">ParlzPackageManager</span></a>' . "\n" .
' <ul class="nav-links">' . "\n" .
' ' . pmm_nav_links($active) . "\n" .
' <li><a class="btn btn-sm" href="https://github.com/JGZYES/ParlzPackageManger" target="_blank" rel="noopener">GitHub ↗</a></li>' . "\n" .
' </ul>' . "\n" .
'</nav>' . "\n";
}
function pmm_page_open(string $kicker, string $title): void {
$k = htmlspecialchars($kicker, ENT_QUOTES, 'UTF-8');
$t = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
echo '<header class="page-hero"><div class="wrap">' . "\n" .
' <div class="page-kicker">' . $k . '</div>' . "\n" .
' <h1>' . $t . '</h1>' . "\n" .
'</div></header>' . "\n";
}
function pmm_footer(): void {
echo '<footer class="footer"><div class="wrap">' . "\n" .
' <div class="footer-top"><span class="brand-name">ParlzPackageManager</span><span>GNU GPL-3.0 · C11 · 跨平台</span></div>' . "\n" .
' <div class="footer-links">' . "\n" .
' <a href="https://github.com/JGZYES/ParlzPackageManger" target="_blank" rel="noopener">GitHub</a>' . "\n" .
' <a href="https://github.com/JGZYES/ParlzPackageManger/releases" target="_blank" rel="noopener">Releases</a>' . "\n" .
' <a href="index.php">首页</a>' . "\n" .
' </div>' . "\n" .
' <p class="footer-note">© 2026 ParlzPackageManager Project · 深圳 sz.pmm.parlz.com · 香港 pmm.parlz.com</p>' . "\n" .
'</div></footer>' . "\n" .
'<script src="assets/main.js"></script>' . "\n" .
'</body>' . "\n" . '</html>';
}
+51 -33
查看文件
@@ -1,4 +1,4 @@
/* PMM site — rotating dotted Earth globe + live status + tabs + copy */ /* PMM site — rotating dotted Earth globe + detailed live status + tabs + copy */
(function () { (function () {
"use strict"; "use strict";
@@ -48,14 +48,9 @@
var ctx = cv.getContext("2d"); var ctx = cv.getContext("2d");
var R = window.PMM_DIMS.rows, C = window.PMM_DIMS.cols; var R = window.PMM_DIMS.rows, C = window.PMM_DIMS.cols;
var DG = Math.PI / 180; var DG = Math.PI / 180;
// map cell -> lat/lon
var pts = window.PMM_MAP.map(function (p) { var pts = window.PMM_MAP.map(function (p) {
return { return { lat: (76 - p[0] * (152 / R)) * DG, lon: (-180 + p[1] * (360 / C)) * DG };
lat: (76 - p[0] * (152 / R)) * DG,
lon: (-180 + p[1] * (360 / C)) * DG
};
}); });
// server locations (Shenzhen ~22.5N/114E, HK ~22.3N/114.2E)
var servers = [ var servers = [
{ lat: 22.54 * DG, lon: 114.06 * DG }, { lat: 22.54 * DG, lon: 114.06 * DG },
{ lat: 22.32 * DG, lon: 114.17 * DG } { lat: 22.32 * DG, lon: 114.17 * DG }
@@ -74,14 +69,12 @@
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
var theta = 0, rafId = null; var theta = 0, rafId = null;
var ACC = "#d8d8d8", OKC = "#ffffff"; /* grayscale palette */
function draw(t) { function draw(t) {
theta += 0.0035; theta += 0.0035;
var cos = Math.cos, sin = Math.sin; var cos = Math.cos, sin = Math.sin;
ctx.clearRect(0, 0, W, H); ctx.clearRect(0, 0, W, H);
// sphere disc
ctx.beginPath(); ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI); ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255,255,255,0.045)"; ctx.fillStyle = "rgba(255,255,255,0.045)";
@@ -90,19 +83,14 @@
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.stroke(); ctx.stroke();
// faint equator + meridian rings for a globe feel
ctx.strokeStyle = "rgba(255,255,255,0.11)"; ctx.strokeStyle = "rgba(255,255,255,0.11)";
ctx.beginPath(); ctx.ellipse(cx, cy, radius, radius * 0.98, 0, 0, 2 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.ellipse(cx, cy, radius, radius * 0.98, 0, 0, 2 * Math.PI); ctx.stroke();
function proj(lat, lon) { function proj(lat, lon) {
var r = lon + theta; var r = lon + theta;
var x = cos(lat) * sin(r); return { x: cos(lat) * sin(r), y: sin(lat), z: cos(lat) * cos(r) };
var y = sin(lat);
var z = cos(lat) * cos(r);
return { x: x, y: y, z: z };
} }
// land dot cloud — back pass (z<0) dim, front pass (z>0) bright
for (var pass = 0; pass < 2; pass++) { for (var pass = 0; pass < 2; pass++) {
var front = pass === 1; var front = pass === 1;
for (var i = 0; i < pts.length; i++) { for (var i = 0; i < pts.length; i++) {
@@ -113,7 +101,6 @@
} }
} }
// server location markers (pulse) — only if on the visible hemisphere
servers.forEach(function (s) { servers.forEach(function (s) {
var q = proj(s.lat, s.lon); var q = proj(s.lat, s.lon);
if (q.z <= 0) return; if (q.z <= 0) return;
@@ -121,7 +108,7 @@
var pulse = 0.6 + 0.4 * sin(t * 0.006); var pulse = 0.6 + 0.4 * sin(t * 0.006);
ctx.beginPath(); ctx.beginPath();
ctx.arc(sx, sy, 3, 0, 2 * Math.PI); ctx.arc(sx, sy, 3, 0, 2 * Math.PI);
ctx.fillStyle = OKC; ctx.fillStyle = "#ffffff";
ctx.fill(); ctx.fill();
ctx.beginPath(); ctx.beginPath();
ctx.arc(sx, sy, 4 + (1 - pulse) * 4, 0, 2 * Math.PI); ctx.arc(sx, sy, 4 + (1 - pulse) * 4, 0, 2 * Math.PI);
@@ -133,7 +120,6 @@
rafId = requestAnimationFrame(draw); rafId = requestAnimationFrame(draw);
} }
// only animate while visible
if ("IntersectionObserver" in window) { if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (en) { var io = new IntersectionObserver(function (en) {
en.forEach(function (e) { en.forEach(function (e) {
@@ -147,24 +133,56 @@
} }
} }
/* ---------- Live mirror status ---------- */ /* ---------- Detailed live mirror status (servers.php) ---------- */
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function ms(v) { return (v == null ? "—" : v + " ms"); }
function renderStatus(data) {
if (!Array.isArray(data)) return;
var detail = document.getElementById("svc-detail");
var html = "";
data.forEach(function (s, i) {
var dot = document.getElementById("dot-" + i);
var meta = document.getElementById("meta-" + i);
if (dot) dot.className = "status-dot " + (s.online ? "up" : "down");
if (meta)
meta.innerHTML = s.online
? "在线 · 平均 <b>" + esc(s.avg_ms) + "</b> ms"
: "离线 / 不可达";
html += '<div class="svc-card">'
+ '<h3><span class="status-dot ' + (s.online ? "up" : "down") + '"></span>'
+ esc(s.name) + ' · <span style="color:#9a9a9a;font-weight:400">' + esc(s.region) + '</span></h3>'
+ '<div class="svc-host">' + esc(s.host) + ' · 检测时间 ' + esc(s.checked_at) + '</div>'
+ '<table class="svc-table"><tr><th>检测项</th><th>状态</th><th>HTTP</th><th>延迟</th><th>Server</th></tr>';
(s.checks || []).forEach(function (c) {
var r = c.result || {};
html += '<tr><td>' + esc(c.item) + '<br><code>' + esc(c.url) + '</code></td>'
+ '<td><span class="svc-badge ' + (r.ok ? "up" : "down") + '">' + (r.ok ? "正常" : "异常") + '</span></td>'
+ '<td class="' + (r.ok ? "svc-ok" : "svc-bad") + '">' + (r.code || "—") + '</td>'
+ '<td>' + ms(r.ms) + '</td>'
+ '<td><code>' + esc(r.server || "—") + '</code></td></tr>';
});
html += '</table></div>';
});
if (detail) detail.innerHTML = html;
var upd = document.getElementById("status-updated");
if (upd && data[0] && data[0].checked_at) upd.textContent = "最近检测:" + data[0].checked_at + "UTC";
}
function loadStatus() { function loadStatus() {
fetch("status.php", { cache: "no-store" }) fetch("status.php", { cache: "no-store" })
.then(function (r) { return r.json(); }) .then(function (r) { return r.json(); })
.then(function (data) { .then(renderStatus)
if (!Array.isArray(data)) return;
data.forEach(function (s, i) {
var dot = document.getElementById("dot-" + i);
var meta = document.getElementById("meta-" + i);
if (!dot || !meta) return;
dot.className = "status-dot " + (s.online ? "up" : "down");
meta.innerHTML = s.online
? "在线 · <b>" + s.ms + "</b> ms · HTTP " + s.code
: "离线 / 不可达";
});
})
.catch(function () {}); .catch(function () {});
} }
loadStatus(); var refreshBtn = document.getElementById("status-refresh");
setInterval(loadStatus, 12000); if (refreshBtn) refreshBtn.addEventListener("click", loadStatus);
if (document.getElementById("svc-detail")) {
loadStatus();
setInterval(loadStatus, 15000);
}
})(); })();
+37
查看文件
@@ -136,3 +136,40 @@ code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Co
.hero-globe{max-width:280px} .hero-globe{max-width:280px}
.nav-links{display:none} .nav-links{display:none}
} }
/* ---------- multi-page layout ---------- */
.nav-links a.active{color:#fff;font-weight:700}
.page-hero{border-bottom:1px solid var(--line);background:var(--bg2)}
.page-hero .wrap{padding:46px 22px 34px}
.page-kicker{font-size:12px;letter-spacing:4px;color:var(--dim);text-transform:uppercase;margin-bottom:10px}
.page-hero h1{font-size:clamp(28px,4vw,40px);font-weight:800;letter-spacing:-.5px}
/* home entry grid */
.entry-section{padding-top:34px}
.entry-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px}
.entry-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px;
display:flex;flex-direction:column;gap:8px;transition:transform .2s,border-color .2s}
.entry-card:hover{transform:translateY(-3px);border-color:#fff}
.entry-icon{font-size:22px;color:#fff}
.entry-title{font-size:17px;font-weight:800}
.entry-desc{font-size:13.5px;color:var(--muted)}
/* status page */
.status-toolbar{display:flex;align-items:center;justify-content:space-between;gap:14px;
max-width:760px;margin:0 auto 18px;flex-wrap:wrap}
.status-updated{font-size:13px;color:var(--dim);font-family:ui-monospace,monospace}
.svc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;max-width:760px;margin:0 auto}
.svc-detail-wrap{max-width:760px;margin:34px auto 0;display:grid;gap:22px}
.svc-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:22px}
.svc-card h3{display:flex;align-items:center;gap:10px;font-size:16px;margin-bottom:4px}
.svc-host{font-family:ui-monospace,monospace;font-size:12.5px;color:var(--dim);margin-bottom:14px}
.svc-table{width:100%;border-collapse:collapse;font-size:13.5px}
.svc-table th{ text-align:left;color:var(--dim);font-weight:600;padding:8px 10px;border-bottom:1px solid var(--line)}
.svc-table td{padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top;color:var(--muted)}
.svc-table tr:last-child td{border-bottom:0}
.svc-table code{font-size:12px;color:#bdbdbd;word-break:break-all}
.svc-badge{display:inline-block;font-size:11.5px;border-radius:999px;padding:2px 10px;border:1px solid var(--line);color:var(--muted);white-space:nowrap}
.svc-badge.up{color:#fff;border-color:#fff}
.svc-badge.down{color:#bbb;border-style:dashed}
.svc-ok{color:#fff;font-weight:700}
.svc-bad{color:#9a9a9a}
+38
查看文件
@@ -0,0 +1,38 @@
<?php
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
pmm_header('download', '下载');
pmm_page_open('DOWNLOAD', '下载 PMM');
?>
<section class="section">
<div class="wrap">
<p class="section-sub">可从官方镜像站或 GitHub Release 获取。镜像地址不变。</p>
<div class="dl-grid">
<a class="dl-card" href="downloads/pmm-<?php echo PMM_VERSION; ?>-windows-amd64.zip" download>
<span class="dl-os">Windows</span><span class="dl-file">pmm-<?php echo PMM_VERSION; ?>-windows-amd64.zip · 免安装</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="downloads/pmm_<?php echo PMM_VERSION; ?>_amd64.deb" download>
<span class="dl-os">Linux</span><span class="dl-file">pmm_<?php echo PMM_VERSION; ?>_amd64.deb</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="install.sh" download>
<span class="dl-os">Linux/macOS</span><span class="dl-file">install.sh · 一键安装</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="https://github.com/JGZYES/ParlzPackageManger/releases/latest" target="_blank" rel="noopener">
<span class="dl-os">全部</span><span class="dl-file">GitHub Releases / 源码</span><span class="dl-arrow">↗</span>
</a>
</div>
<p class="tips">Linux 也可 <code>curl -sSL https://pmm.parlz.com/install.sh | bash</code>;<code>.pdm</code> 包可直接 <code>pmm install pmm</code> 安装。</p>
<h2 class="section-title" style="margin-top:56px">镜像 <span class="accent">源</span></h2>
<p class="section-sub">PMM 默认镜像(apt 式,按优先级回退),地址与 <code>mirror.ini</code> 一致。</p>
<div class="mirror-list">
<div class="mirror-item"><span class="mirror-tag default">sz · 默认</span><code>https://sz.pmm.parlz.com/mirror/packages</code></div>
<div class="mirror-item"><span class="mirror-tag">main · 备份</span><code>https://pmm.parlz.com/mirror/packages</code></div>
</div>
<div style="text-align:center"><a class="btn btn-ghost" href="mirror/" target="_blank" rel="noopener">浏览镜像目录 ↗</a></div>
<p class="tips">全部历史版本的 <code>.pdm</code> 都在镜像 <code>mirror/packages/pmm/</code> 下。</p>
</div>
</section>
<?php pmm_footer(); ?>
+24
查看文件
@@ -0,0 +1,24 @@
<?php
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
pmm_header('features', '特性');
pmm_page_open('FEATURES', '核心特性');
?>
<section class="section">
<div class="wrap">
<div class="grid">
<div class="card"><h3>apt 式多镜像源</h3><p>支持 <code>mirror.ini</code> / <code>mirror.conf</code>,按优先级自动回退;默认自带 <code>sz</code>(深圳)与 <code>main</code>(香港)两个镜像,缺失时自动生成。</p></div>
<div class="card"><h3>Git Release 安装</h3><p><code>pmm install --git repo</code> 自动识别 GitHub / GitLab / Gitea / Forgejo 及私有部署;支持 <code>repo@tag</code> / <code>-b branch</code> 安装指定版本。</p></div>
<div class="card"><h3>.pdm 打包格式</h3><p><code>pmm pack &lt;dir&gt;</code> 生成含 <code>control.tar.gz</code> / <code>data.tar.gz</code> / <code>sha256sums</code> 的归档,思路同 .deb。</p></div>
<div class="card"><h3>依赖解析</h3><p><code>pdm-control</code> 声明 <code>Depends:</code>,registry 包声明 <code>"depends": [...]</code>;安装时自动递归解析并按版本约束(==/≥/≤)安装,带防环保护。</p></div>
<div class="card"><h3>并发下载 + 断点续传</h3><p>大文件(≥8MB)自动 4 路 Range 分片并行下载、逐片续传、拼接后校验;失败自动回退单流。进度条含已用/剩余时间与实时速度。</p></div>
<div class="card"><h3>哈希校验</h3><p>内置 SHA-256 与 SHA-1FIPS 180-4),下载后强校验;<code>pmm verify &lt;file&gt;</code> 随时验证任意安装包。</p></div>
<div class="card"><h3>本地包安装</h3><p><code>pmm install -dpkg x.deb</code>Linux)、<code>pmm install -msi x.msi</code>Windows),也支持 <code>.rpm/.zip/.tar.gz/.exe/.dmg</code> 等,直接装文件或 URL。</p></div>
<div class="card"><h3>搜索与信息</h3><p><code>pmm search &lt;关键词&gt;</code> 发现镜像里的包;<code>pmm info &lt;pkg&gt;</code> 查看版本与平台变体;<code>pmm list</code> 列出已装包。</p></div>
<div class="card"><h3>缓存管理</h3><p>下载缓存统一放在 <code>&lt;base&gt;/cache</code>;<code>pmm cache clean</code> 一键清空,<code>--no-cache</code> 强制重新下载。</p></div>
</div>
</div>
</section>
<?php pmm_footer(); ?>
+30 -151
查看文件
@@ -1,13 +1,9 @@
<?php <?php
/* /* PMM home — hero + rotating dotted Earth + entry cards to the 4 pages. */
* ParlzPackageManager (PMM) — official site (dark, pure flat colors). define('PMM_SITE', 1);
* Hero: left = PMM intro, right = rotating dotted Earth globe (canvas). require __DIR__ . '/_common.php';
* A status panel monitors the SZ + HK mirror servers via status.php.
* Static PHP, no dependencies.
*/
$VERSION = '0.2.9';
/* Coarse world map, '#'=land, ' '=ocean. Reused as the dot cloud on the globe. */ /* Coarse world map, '#'=land — reused as the dot cloud on the globe. */
$map = [ $map = [
" ", " ",
" #################### ", " #################### ",
@@ -45,56 +41,29 @@ $map = [
" ", " ",
]; ];
$ROWS = count($map); $ROWS = count($map);
/* Normalize every row to the same width so $map[$r][$c] never overflows
* (a shorter row otherwise triggers "Uninitialized string offset" warnings). */
$COLS = 0; $COLS = 0;
foreach ($map as $row) $COLS = max($COLS, strlen($row)); foreach ($map as $row) $COLS = max($COLS, strlen($row));
for ($r = 0; $r < $ROWS; $r++) $map[$r] = str_pad($map[$r], $COLS); for ($r = 0; $r < $ROWS; $r++) $map[$r] = str_pad($map[$r], $COLS);
if ($COLS === 0) $COLS = 1; if ($COLS === 0) $COLS = 1;
/* Land cell coordinates for the globe dot cloud */
$dots = array(); $dots = array();
for ($r = 0; $r < $ROWS; $r++) for ($r = 0; $r < $ROWS; $r++)
for ($c = 0; $c < $COLS; $c++) for ($c = 0; $c < $COLS; $c++)
if ($map[$r][$c] === '#') $dots[] = array($r, $c); if ($map[$r][$c] === '#') $dots[] = array($r, $c);
pmm_header('home', '跨平台 C 语言包管理器');
?> ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ParlzPackageManager · 跨平台 C 语言包管理器</title>
<meta name="description" content="ParlzPackageManager (PMM) — 用 C 语言编写、跨 Windows/Linux/macOS 的包管理器,apt 式多镜像源、git release 安装、.pdm 打包格式、SHA-256 校验。">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23050505'/%3E%3Ctext x='16' y='23' font-size='17' font-family='monospace' fill='%23cfcfcf' text-anchor='middle'%3Epmm%3C/text%3E%3C/svg%3E">
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<nav class="nav">
<a class="brand" href="#top"><span class="brand-mark">pmm</span><span class="brand-name">ParlzPackageManager</span></a>
<ul class="nav-links">
<li><a href="#features">特性</a></li>
<li><a href="#install">安装</a></li>
<li><a href="#download">下载</a></li>
<li><a href="#status">服务状态</a></li>
<li><a class="btn btn-sm" href="https://github.com/JGZYES/ParlzPackageManger" target="_blank" rel="noopener">GitHub ↗</a></li>
</ul>
</nav>
<!-- Two-column hero: left intro / right dotted Earth globe -->
<header id="top" class="hero"> <header id="top" class="hero">
<div class="hero-inner"> <div class="hero-inner">
<div class="hero-copy"> <div class="hero-copy">
<div class="hero-badge">v<?php echo $VERSION; ?> · C11 · Windows / Linux / macOS</div> <div class="hero-badge">v<?php echo PMM_VERSION; ?> · C11 · Windows / Linux / macOS</div>
<h1>ParlzPackageManager <span class="accent">(PMM)</span></h1> <h1>ParlzPackageManager <span class="accent">(PMM)</span></h1>
<p class="hero-tag">一个用 <strong>C 语言</strong>从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 <strong>GitHub / GitLab / Gitea / Forgejo</strong> Release 装包。</p> <p class="hero-tag">一个用 <strong>C 语言</strong>从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 <strong>GitHub / GitLab / Gitea / Forgejo</strong> Release 装包。</p>
<div class="hero-cta"> <div class="hero-cta">
<a class="btn btn-primary" href="#install">快速开始</a> <a class="btn btn-primary" href="install.php">快速安装</a>
<a class="btn btn-ghost" href="https://github.com/JGZYES/ParlzPackageManger/releases/latest" target="_blank" rel="noopener">最新版 ↓</a> <a class="btn btn-ghost" href="download.php">下载 v<?php echo PMM_VERSION; ?> ↓</a>
</div>
<div class="hero-note">
<span class="dot ok"></span> 双镜像 · 深圳 / 香港 实时可用
</div> </div>
<div class="hero-note"><span class="dot ok"></span> 双镜像 · 深圳 / 香港 实时在线</div>
</div> </div>
<div class="hero-globe"> <div class="hero-globe">
<canvas id="globe" aria-label="PMM 服务分布地球"></canvas> <canvas id="globe" aria-label="PMM 服务分布地球"></canvas>
@@ -103,127 +72,37 @@ for ($r = 0; $r < $ROWS; $r++)
</div> </div>
</header> </header>
<section id="status" class="section"> <section class="section entry-section">
<div class="wrap"> <div class="wrap">
<h2 class="section-title">服务 <span class="accent">状态</span></h2> <div class="entry-grid">
<p class="section-sub">实时监控 PMM 镜像服务器可用性与延迟(每 12 秒刷新)。</p> <a class="entry-card" href="features.php">
<div class="status-grid" id="status-grid"> <span class="entry-icon">⌘</span>
<div class="status-card"> <span class="entry-title">特性</span>
<div class="status-head"><span class="pin">深圳</span><span class="status-dot" id="dot-0"></span></div> <span class="entry-desc">apt 式多镜像、git release、.pdm、依赖解析、并发下载</span>
<div class="status-ip">sz.pmm.parlz.com</div>
<div class="status-host">https://sz.pmm.parlz.com/mirror/packages</div>
<div class="status-meta" id="meta-0">检测中…</div>
</div>
<div class="status-card">
<div class="status-head"><span class="pin">香港</span><span class="status-dot" id="dot-1"></span></div>
<div class="status-ip">pmm.parlz.com</div>
<div class="status-host">https://pmm.parlz.com/mirror/packages</div>
<div class="status-meta" id="meta-1">检测中…</div>
</div>
</div>
</div>
</section>
<section id="features" class="section alt">
<div class="wrap">
<h2 class="section-title">核心 <span class="accent">特性</span></h2>
<div class="grid">
<div class="card"><h3>apt 式多镜像源</h3><p>支持 <code>mirror.ini</code> / <code>mirror.conf</code>,按优先级自动回退;默认带 <code>sz</code>(默认)与 <code>main</code> 两个镜像。</p></div>
<div class="card"><h3>Git Release 安装</h3><p><code>pmm install --git repo</code> 自动识别 GitHub / GitLab / Gitea / Forgejo 及私有部署,按平台挑资产。</p></div>
<div class="card"><h3>.pdm 打包格式</h3><p><code>pmm pack &lt;dir&gt;</code> 生成含 <code>control.tar.gz</code> / <code>data.tar.gz</code> / <code>sha256sums</code> 的归档,思路同 .deb。</p></div>
<div class="card"><h3>哈希校验</h3><p>内置 SHA-256 与 SHA-1 实现,安装前强校验下载文件完整性,防篡改。</p></div>
<div class="card"><h3>版本选择</h3><p>支持 <code>pmm install pkg==1.2.3</code>、<code>&gt;=</code>、<code>&lt;=</code>、<code>&gt;</code>、<code>&lt;</code> 等 pip 式约束。</p></div>
<div class="card"><h3>进度条 &amp; 注册表</h3><p>ASCII 下载进度条;Windows 下为已装软件写入注册表卸载项,可用“应用和功能”卸载。</p></div>
</div>
</div>
</section>
<section id="install" class="section">
<div class="wrap">
<h2 class="section-title">安装 <span class="accent">PMM</span></h2>
<p class="section-sub">下载对应平台二进制,一条命令搞定,完成后自动加入 PATH。</p>
<div class="tabs" data-tabs>
<button class="tab active" data-tab="win">Windows</button>
<button class="tab" data-tab="linux">Linux</button>
<button class="tab" data-tab="macos">macOS</button>
</div>
<div class="code-box" data-pane="win" data-active="1">
<pre><code># ZIP 免安装版(解压即用,pmm.exe 在压缩包内)
curl -L -o pmm.zip https://pmm.parlz.com/downloads/pmm-<?php echo $VERSION; ?>-windows-amd64.zip
# 解压后把 pmm.exe 所在目录加入 PATH,然后:
pmm -v</code></pre>
<button class="copy-btn" data-copy="win">复制</button>
</div>
<div class="code-box" data-pane="linux">
<pre><code># 方式一:sh 一键安装(自动下载二进制 + 加 PATH)
curl -sSL https://pmm.parlz.com/install.sh | bash
# 方式二:.deb 安装
curl -L -o pmm.deb https://pmm.parlz.com/downloads/pmm_<?php echo $VERSION; ?>_amd64.deb
sudo apt install ./pmm.deb
pmm -v</code></pre>
<button class="copy-btn" data-copy="linux">复制</button>
</div>
<div class="code-box" data-pane="macos">
<pre><code># sh 一键安装
curl -sSL https://pmm.parlz.com/install.sh | bash
pmm -v</code></pre>
<button class="copy-btn" data-copy="macos">复制</button>
</div>
</div>
</section>
<section id="download" class="section alt">
<div class="wrap">
<h2 class="section-title">下载 <span class="accent">PMM</span></h2>
<p class="section-sub">可从官方镜像站或 GitHub Release 获取。镜像地址不变。</p>
<div class="dl-grid">
<a class="dl-card" href="downloads/pmm-<?php echo $VERSION; ?>-windows-amd64.zip" download>
<span class="dl-os">Windows</span><span class="dl-file">pmm-<?php echo $VERSION; ?>-windows-amd64.zip · 免安装</span><span class="dl-arrow">↓</span>
</a> </a>
<a class="dl-card" href="downloads/pmm_<?php echo $VERSION; ?>_amd64.deb" download> <a class="entry-card" href="install.php">
<span class="dl-os">Linux</span><span class="dl-file">pmm_<?php echo $VERSION; ?>_amd64.deb</span><span class="dl-arrow"></span> <span class="entry-icon"></span>
<span class="entry-title">安装</span>
<span class="entry-desc">三条命令装好 Windows / Linux / macOS 客户端</span>
</a> </a>
<a class="dl-card" href="install.sh" download> <a class="entry-card" href="download.php">
<span class="dl-os">Linux/macOS</span><span class="dl-file">install.sh · 一键安装</span><span class="dl-arrow">↓</span> <span class="entry-icon">↓</span>
<span class="entry-title">下载</span>
<span class="entry-desc">zip / deb / install.sh / .pdm 全部安装产物</span>
</a> </a>
<a class="dl-card" href="https://github.com/JGZYES/ParlzPackageManger/releases/latest" target="_blank" rel="noopener"> <a class="entry-card" href="servers.php">
<span class="dl-os">全部</span><span class="dl-file">GitHub Releases / 源码</span><span class="dl-arrow"></span> <span class="entry-icon"></span>
<span class="entry-title">服务状态</span>
<span class="entry-desc">深圳 / 香港镜像的实时探测与详细信息</span>
</a> </a>
</div> </div>
<p class="tips">Linux 也可 <code>curl -sSL https://pmm.parlz.com/install.sh | bash</code>;<code>.pdm</code> 包仍可从镜像 <code>pmm install pmm</code> / <code>pmm install php</code> 安装。</p>
</div> </div>
</section> </section>
<section id="mirror" class="section"> <p class="tips" style="padding-bottom:40px"><code>pmm install nodejs</code> · <code>pmm install php</code> · <code>pmm install pmm</code>(升级自身)</p>
<div class="wrap">
<h2 class="section-title">镜像 <span class="accent">源</span></h2>
<p class="section-sub">PMM 默认镜像(apt 式,按优先级回退),地址与 <code>mirror.ini</code> 一致。</p>
<div class="mirror-list">
<div class="mirror-item"><span class="mirror-tag default">sz · 默认</span><code>https://sz.pmm.parlz.com/mirror/packages</code></div>
<div class="mirror-item"><span class="mirror-tag">main · 备份</span><code>https://pmm.parlz.com/mirror/packages</code></div>
</div>
<a class="btn btn-ghost" href="mirror/" target="_blank" rel="noopener">浏览镜像目录 ↗</a>
<p class="tips"><strong>快速开始:</strong><br><code>pmm install nodejs</code> — Node.js<br><code>pmm install php</code> — PHP<br><code>pmm install pmm</code> — 升级 PMM 自身</p>
</div>
</section>
<footer class="footer">
<div class="wrap">
<div class="footer-top"><span class="brand-name">ParlzPackageManager</span><span>GNU GPL-3.0 · C11 · 跨平台</span></div>
<div class="footer-links">
<a href="https://github.com/JGZYES/ParlzPackageManger" target="_blank" rel="noopener">GitHub</a>
<a href="https://github.com/JGZYES/ParlzPackageManger/releases" target="_blank" rel="noopener">Releases</a>
<a href="#top">回到顶部</a>
</div>
<p class="footer-note">© 2026 ParlzPackageManager Project · 深圳 sz.pmm.parlz.com · 香港 pmm.parlz.com</p>
</div>
</footer>
<script> <script>
window.PMM_MAP = <?php echo json_encode($dots); ?>; window.PMM_MAP = <?php echo json_encode($dots); ?>;
window.PMM_DIMS = { rows: <?php echo $ROWS; ?>, cols: <?php echo $COLS; ?> }; window.PMM_DIMS = { rows: <?php echo $ROWS; ?>, cols: <?php echo $COLS; ?> };
</script> </script>
<script src="assets/main.js"></script> <?php pmm_footer(); ?>
</body>
</html>
+52
查看文件
@@ -0,0 +1,52 @@
<?php
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
pmm_header('install', '安装');
pmm_page_open('INSTALL', '安装 PMM');
?>
<section class="section">
<div class="wrap">
<p class="section-sub">下载对应平台二进制,一条命令搞定,完成后自动加入 PATH。</p>
<div class="tabs" data-tabs>
<button class="tab active" data-tab="win">Windows</button>
<button class="tab" data-tab="linux">Linux</button>
<button class="tab" data-tab="macos">macOS</button>
</div>
<div class="code-box" data-pane="win" data-active="1">
<pre><code># ZIP 免安装版(解压即用,pmm.exe 在压缩包内)
curl -L -o pmm.zip https://pmm.parlz.com/downloads/pmm-<?php echo PMM_VERSION; ?>-windows-amd64.zip
# 解压后把 pmm.exe 所在目录加入 PATH,然后:
pmm -v</code></pre>
<button class="copy-btn" data-copy="win">复制</button>
</div>
<div class="code-box" data-pane="linux">
<pre><code># 方式一:sh 一键安装(自动下载二进制 + 加 PATH)
curl -sSL https://pmm.parlz.com/install.sh | bash
# 方式二:.deb 安装
curl -L -o pmm.deb https://pmm.parlz.com/downloads/pmm_<?php echo PMM_VERSION; ?>_amd64.deb
sudo apt install ./pmm.deb
pmm -v</code></pre>
<button class="copy-btn" data-copy="linux">复制</button>
</div>
<div class="code-box" data-pane="macos">
<pre><code># sh 一键安装
curl -sSL https://pmm.parlz.com/install.sh | bash
pmm -v</code></pre>
<button class="copy-btn" data-copy="macos">复制</button>
</div>
<h2 class="section-title" style="margin-top:56px;font-size:24px">装好之后</h2>
<div class="code-box">
<pre><code>pmm search node # 在镜像里搜包
pmm install nodejs # 安装(自动解析依赖)
pmm install nodejs==24.20.0 # 指定版本
pmm install --git owner/repo@v1.2.3 # 从 git release 安装指定 tag
pmm list # 已装列表
pmm remove nodejs # 卸载</code></pre>
</div>
</div>
</section>
<?php pmm_footer(); ?>
+26
查看文件
@@ -0,0 +1,26 @@
<?php
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
pmm_header('status', '服务状态');
pmm_page_open('SERVER STATUS', '服务状态');
?>
<section class="section" style="padding-top:24px">
<div class="wrap">
<p class="section-sub">实时监控 PMM 双镜像(深圳 / 香港)的注册表、下载服务与 Web 浏览三项能力,每 15 秒自动刷新。不暴露任何服务器 IP。</p>
<div class="status-toolbar">
<span class="status-updated" id="status-updated">检测中…</span>
<button class="btn btn-ghost btn-sm" id="status-refresh" type="button">手动刷新</button>
</div>
<div class="svc-grid" id="svc-grid">
<div class="status-card"><div class="status-head"><span class="pin">深圳</span><span class="status-dot" id="dot-0"></span></div><div class="status-ip">sz.pmm.parlz.com</div><div class="status-host">https://sz.pmm.parlz.com/mirror/packages</div><div class="status-meta" id="meta-0">检测中…</div></div>
<div class="status-card"><div class="status-head"><span class="pin">香港</span><span class="status-dot" id="dot-1"></span></div><div class="status-ip">pmm.parlz.com</div><div class="status-host">https://pmm.parlz.com/mirror/packages</div><div class="status-meta" id="meta-1">检测中…</div></div>
</div>
<div id="svc-detail"></div>
</div>
</section>
<?php pmm_footer(); ?>
+56 -48
查看文件
@@ -1,64 +1,72 @@
<?php <?php
/* Status probe for PMM mirror servers. /* Detailed status probe for the PMM mirror servers.
* Server-side GET (curl -k, since we hit IPs/hosts that may not match the * For each mirror we probe three endpoints (registry index / package download /
* browser's TLS expectations) -> returns JSON for the homepage status panel. */ * web dir listing) and report HTTP code, latency, server software and a
* timestamp. Output is JSON for servers.php. No server IPs are exposed. */
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store'); header('Cache-Control: no-store');
$servers = [ $servers = [
['name'=>'深圳', 'url'=>'https://sz.pmm.parlz.com/mirror/packages/pmm.json'], ['name' => '深圳', 'region' => 'CN · 深圳', 'host' => 'sz.pmm.parlz.com'],
['name'=>'香港', 'url'=>'https://pmm.parlz.com/mirror/packages/pmm.json'], ['name' => '香港', 'region' => 'HK · 香港', 'host' => 'pmm.parlz.com'],
]; ];
function probe($url) { /* Probe one URL with shell curl: returns [code, ms, server, ctype]. */
$t = microtime(true); function probe_url(string $url): array {
/* 1) PHP curl extension (preferred) */ $cmd = 'curl -k -s -o /dev/null -D - --max-time 8 '
if (function_exists('curl_init')) { . '-w "__PMM_PROBE__%{http_code} %{time_total}" ' . escapeshellarg($url);
$ch = curl_init($url); $out = (string)@shell_exec($cmd);
curl_setopt_array($ch, [ $code = 0; $ms = 0; $server = ''; $ctype = '';
CURLOPT_RETURNTRANSFER => true, if ($out !== '') {
CURLOPT_NOBODY => true, /* headers before the marker, "code ms" after it */
CURLOPT_CONNECTTIMEOUT => 3, $pos = strrpos($out, '__PMM_PROBE__');
CURLOPT_TIMEOUT => 6, if ($pos !== false) {
CURLOPT_SSL_VERIFYPEER => false, $meta = trim(substr($out, $pos + strlen('__PMM_PROBE__')));
CURLOPT_SSL_VERIFYHOST => false, $parts = preg_split('/\s+/', $meta);
CURLOPT_FOLLOWLOCATION => true, if (isset($parts[0])) $code = (int)$parts[0];
CURLOPT_USERAGENT => 'pmm-status/1.0', if (isset($parts[1])) $ms = (int)round(((float)$parts[1]) * 1000);
]); $head = substr($out, 0, $pos);
curl_exec($ch); if (preg_match('/^Server:\s*(.+)$/mi', $head, $m)) $server = trim($m[1]);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); if (preg_match('/^Content-Type:\s*(.+)$/mi', $head, $m)) $ctype = trim($m[1]);
$errno = curl_errno($ch); }
curl_close($ch);
if ($errno === 0 && $code > 0)
return ['online'=>$code < 500, 'code'=>$code, 'ms'=>(int)round((microtime(true)-$t)*1000)];
} }
/* 2) shell curl (pmm depends on the curl binary, so it is present) */ return [$code, $ms, $server, $ctype];
if (function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) { }
$cmd = 'curl -k -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 6 ' . escapeshellarg($url);
$out = trim((string)@shell_exec($cmd)); function probe_check(string $url): array {
$code = (int)$out; [$code, $ms, $server, $ctype] = probe_url($url);
if ($code > 0) return [
return ['online'=>$code < 500, 'code'=>$code, 'ms'=>(int)round((microtime(true)-$t)*1000)]; 'ok' => ($code >= 200 && $code < 500 && $code !== 0),
} 'code' => $code,
/* 3) stream context fallback */ 'ms' => $ms,
$ctx = stream_context_create([ 'server' => $server,
'http' => ['method'=>'HEAD','timeout'=>6,'follow_location'=>1,'ignore_errors'=>true], 'ctype' => $ctype,
'ssl' => ['verify_peer'=>false,'verify_peer_name'=>false], ];
]);
$fp = @fopen($url, 'r', false, $ctx);
$ms = (int)round((microtime(true) - $t) * 1000);
if ($fp) { fclose($fp); return ['online'=>true,'code'=>200,'ms'=>$ms]; }
return ['online'=>false,'code'=>0,'ms'=>$ms];
} }
$out = []; $out = [];
foreach ($servers as $s) { foreach ($servers as $s) {
$st = probe($s['url']); $h = $s['host'];
$reg = probe_check("https://{$h}/mirror/packages/pmm.json");
$dl = probe_check("https://{$h}/mirror/packages/pmm/" . PMM_VERSION . "-linux-amd64.pdm");
$web = probe_check("https://{$h}/mirror/");
$avg = 0; $n = 0;
foreach ([$reg, $dl, $web] as $c) if ($c['ok']) { $avg += $c['ms']; $n++; }
$avg = $n ? (int)round($avg / $n) : 0;
$out[] = [ $out[] = [
'name' => $s['name'], 'name' => $s['name'],
'online' => $st['online'], 'region' => $s['region'],
'code' => $st['code'], 'host' => $h,
'ms' => $st['ms'], 'online' => $reg['ok'],
'avg_ms' => $avg,
'checks' => [
['item' => '注册表 (packages API)', 'url' => "https://{$h}/mirror/packages/pmm.json", 'result' => $reg],
['item' => '下载服务 (.pdm 分发)', 'url' => "https://{$h}/mirror/packages/pmm/" . PMM_VERSION . "-linux-amd64.pdm", 'result' => $dl],
['item' => 'Web 目录浏览', 'url' => "https://{$h}/mirror/", 'result' => $web],
],
'checked_at' => gmdate('Y-m-d\TH:i:s\Z'),
]; ];
} }
echo json_encode($out, JSON_UNESCAPED_UNICODE); echo json_encode($out, JSON_UNESCAPED_UNICODE);