web: refactor to minimal homepage + docs site

- New minimal index.php: brand hero (+ rotating Earth canvas) + entry cards to
  docs; no multi-page nav.
- New web/docs/ Markdown site: docs/index.php renders sibling .md via the shared
  md() renderer (moved into _common.php). Pages: index/install/download/features/
  cli/mirror/translate/source.
- _common.php: slim nav (首页/文档), add md(), keep PMM_VERSION + layout.
- Delete old multi-pages (features/install/download/servers/source/git/translate)
  and the release/asset serving (releases/, downloads/, root install.sh,
  status.php). Keep web/mirror/ (package registry + lang packs) so the pmm CLI
  and docs download links (GitHub Release) still work.
- assets/style.css: add docs styling. mirror/index.php: fix  deprecation.
- Fix docs install/download links to point at GitHub Release (releases/ removed).

Verified: php -l passes for _common/index/docs/mirror; homepage (globe+hero),
docs pages and mirror render via the PHP built-in server; no stale references
to deleted pages / releases / downloads / status remain.
这个提交包含在:
JGZYES
2026-09-05 11:40:21 +08:00
父节点 80f5b86396
当前提交 8877856f68
修改 30 个文件,包含 381 行新增845 行删除
+17 -7
查看文件
@@ -8,13 +8,8 @@ define('PMM_VERSION', '0.3.6');
function pmm_nav_links(string $active): string {
$items = [
'home' => ['index.php', '首页'],
'features' => ['features.php', '特性'],
'install' => ['install.php', '安装'],
'download' => ['download.php', '下载'],
'status' => ['servers.php', '服务状态'],
'translate'=> ['translate.php', '翻译'],
'source' => ['git.php', '源码'],
'home' => ['index.php', '首页'],
'docs' => ['docs/index.php', '文档'],
];
$html = '';
foreach ($items as $key => [$href, $label]) {
@@ -24,6 +19,21 @@ function pmm_nav_links(string $active): string {
return $html;
}
/* Minimal markdown renderer (self-contained). Used by the docs site. */
function md(string $t): string {
$t = htmlspecialchars($t, ENT_QUOTES, 'UTF-8');
$t = preg_replace('/```([a-zA-Z0-9]*)\n(.*?)```/s', "<pre class=\"mdcode\"><code>\\2</code></pre>", $t);
$t = preg_replace('/^### (.*)$/m', '<h3>$1</h3>', $t);
$t = preg_replace('/^## (.*)$/m', '<h2>$1</h2>', $t);
$t = preg_replace('/^# (.*)$/m', '<h1>$1</h1>', $t);
$t = preg_replace('/^[-*] (.*)$/m', '<li>$1</li>', $t);
$t = preg_replace('/^\s*---\s*$/m', '<hr>', $t);
$t = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $t);
$t = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2" target="_blank" rel="noopener">$1</a>', $t);
$t = preg_replace('/`([^`]+)`/', '<code>$1</code>', $t);
return $t;
}
function pmm_header(string $active, string $title): void {
$t = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
echo '<!DOCTYPE html>' . "\n" .
+23
查看文件
@@ -297,3 +297,26 @@ code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Co
.rel-size{font-size:11.5px;color:var(--dim);font-family:ui-monospace,monospace;white-space:nowrap}
.rel-dl{margin-left:auto;font-size:12px;color:var(--muted);border:1px solid var(--line);border-radius:6px;padding:3px 10px;text-decoration:none;white-space:nowrap}
.rel-dl:hover{color:#fff;border-color:#fff}
/* ---------- docs site ---------- */
.docs-nav{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px;border-bottom:1px solid var(--line);padding-bottom:14px}
.docs-link{font-size:13.5px;color:var(--muted);text-decoration:none;padding:6px 14px;border:1px solid var(--line);border-radius:999px;white-space:nowrap}
.docs-link:hover{color:#fff;border-color:#fff}
.docs-link.active{color:var(--ink);background:var(--accent);border-color:var(--accent)}
.docs-body{max-width:860px;line-height:1.8;color:var(--text)}
.docs-body h1{font-size:30px;margin:0 0 18px;letter-spacing:-.5px;border-bottom:1px solid var(--line);padding-bottom:14px}
.docs-body h2{font-size:20px;margin:34px 0 12px;color:#fff}
.docs-body h3{font-size:16px;margin:26px 0 10px;color:#fff}
.docs-body p{margin:0 0 14px}
.docs-body li{margin:5px 0}
.docs-body ul,.docs-body ol{padding-left:22px;margin:0 0 14px}
.docs-body code{background:var(--panel);border:1px solid var(--line);padding:1px 6px;border-radius:5px;font-size:13px;font-family:ui-monospace,monospace;color:var(--muted)}
.docs-body pre.mdcode{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px 16px;overflow:auto;margin:0 0 14px}
.docs-body pre.mdcode code{background:none;border:0;padding:0;color:var(--text);font-size:13px;line-height:1.6;display:block}
.docs-body hr{border:0;border-top:1px solid var(--line);margin:22px 0}
.docs-body a{color:#fff;text-decoration:underline}
.docs-body a:hover{color:var(--accent)}
.docs-body strong{color:#fff}
.docs-body table{border-collapse:collapse;width:100%;margin:0 0 16px}
.docs-body th,.docs-body td{border:1px solid var(--line);padding:8px 12px;font-size:13px;text-align:left}
.docs-body th{background:var(--panel);color:#fff}
+57
查看文件
@@ -0,0 +1,57 @@
# 命令参考
## 安装
```bash
pmm install <pkg> # 从注册表安装
pmm install <pkg> [pkg2 ...] # 装多个
pmm install <file.deb|.rpm|.apk|.pdm> # 装本地包
pmm install -dpkg <x.deb> # 强制按 deb 装
pmm install -rpm <x.rpm> # 强制按 rpm 装
pmm install --git <repo> # 从 git Release 装
pmm install --force <pkg> # 强制重装
```
## 卸载 / 查询
```bash
pmm remove <pkg>
pmm list # 已安装
pmm list --upgradable # 可升级的
pmm search <keyword> # 搜索
pmm info <package|file.pdm> # 详情
pmm verify <file> # sha256/sha1
```
## 镜像 / 更新
```bash
pmm mirror list
pmm mirror add <n> <api>
pmm mirror use <n>
pmm mirror remove <n>
pmm mirror check # 探测各镜像源可达性
pmm update # 刷新镜像索引
pmm upgrade [--yes] # 升级所有已装包
pmm self-update # 更新 pmm 自身
```
## 配置
```bash
pmm setting lang -l # 列语言包
pmm setting lang <locale> # 切换语言
pmm setting set <key> <value> # 写 pmm.conf(如 mirror / proxy
pmm setting get # 读 pmm.conf
pmm setting mirror <name> # 设当前镜像
pmm cache clean # 清缓存
```
## 全局选项
```bash
--no-color # 禁用 ANSI 颜色
-q, --quiet # 只显示错误/警告
--verbose # 更多细节
-p<drive> # 指定安装盘符(Windows
```
+36
查看文件
@@ -0,0 +1,36 @@
# 下载
PMM 提供多种安装包。官网镜像站保留二进制注册表(`mirror/`),也可直接从 GitHub Release 获取。
## 一键安装(推荐)
```bash
curl -sSL https://pmm.parlz.com/install.sh | bash
```
> 说明:`install.sh` 现在由 GitHub Release 提供;若你手动部署,可把仓库里的 `install.sh` 放到站点根目录,或直接用下方的 GitHub 链接。
## 各平台产物(GitHub Release
- [pmm-0.3.6-windows-amd64.zip](https://github.com/JGZYES/ParlzPackageManger/releases/latest) — Windows 免安装
- [pmm_0.3.6_amd64.deb](https://github.com/JGZYES/ParlzPackageManger/releases/latest) — Debian/Ubuntu
- [pmm-0.3.6.x86_64.rpm](https://github.com/JGZYES/ParlzPackageManger/releases/latest) — CentOS/RHEL
- [pmm-0.3.6-linux-amd64.pdm](https://github.com/JGZYES/ParlzPackageManger/releases/latest) — `.pdm`
- [pmm-0.3.6.apk](https://github.com/JGZYES/ParlzPackageManger/releases/latest) — Alpine
- [pmm-0.3.6.pkg.tar.zst](https://github.com/JGZYES/ParlzPackageManger/releases/latest) — Arch
## 用 pmm 安装(从镜像注册表)
mirror 注册表保留在 `web/mirror/`,pmm 客户端可直接安装:
```bash
pmm install pmm==0.3.6 # 升级 pmm 自身
pmm install nodejs # 装一个软件包
```
## 镜像源
- **深圳(默认)**`https://sz.pmm.parlz.com/mirror/packages`
- **香港(备份)**`https://pmm.parlz.com/mirror/packages`
全部历史版本的 `.pdm` 都在镜像 `mirror/packages/pmm/` 下。详见 [镜像源](index.php?page=mirror)。
+15
查看文件
@@ -0,0 +1,15 @@
# 核心特性
- **apt 式多镜像源**:按优先级回退,`mirror.ini` 可配多个 registry。
- **Git Release 安装**:直接装 GitHub / GitLab / Gitea / Forgejo 的最新 Release。
- **`.pdm` 打包格式**:deb 风格的内置包管理器,跨平台。
- **依赖解析**:安装时自动解析 `Depends`
- **并发下载 + 断点续传**:大文件分片并行、断点续传。
- **哈希校验**:下载后 sha256 / sha1 校验,拒绝校验失败。
- **本地包安装**`.deb` / `.rpm` / `.apk` / `.msi` / `.zip` / `.pkg.tar.zst` 都能装。
- **搜索与信息**`pmm search` / `pmm info`
- **缓存管理**`pmm cache clean`
- **彩色输出 + 语言包**`[PMM]:[LEVEL]` 结构化输出,`.pjson` 多语言。
- **i18n**`pmm setting lang <locale>` 切换语言,社区可贡献。
详见 [命令参考](index.php?page=cli) 与 [翻译](index.php?page=translate)。
+34
查看文件
@@ -0,0 +1,34 @@
# 快速开始
**PMMParlzPackageManager** 是一个用 C 语言从零编写的跨平台包管理器,理念:**敲即得,装即跑**。
支持 Windows / Linux / macOS。像 apt 一样多镜像源,也能直接装 GitHub / GitLab / Gitea / Forgejo 的 Release。
## 一分钟装好
```bash
curl -sSL https://pmm.parlz.com/install.sh | bash
```
装好后:
```bash
pmm --version # 查看版本
pmm install nodejs # 安装一个软件包
pmm list # 已安装的软件包
```
## 常用命令一览
```bash
pmm install <pkg> 安装
pmm remove <pkg> 卸载
pmm search <kw> 搜索
pmm info <pkg> 详情
pmm update 刷新镜像索引
pmm upgrade 升级所有已装包
pmm mirror list 查看镜像源
pmm setting lang <xx> 切换语言包
```
详见 [命令参考](index.php?page=cli)。
+43
查看文件
@@ -0,0 +1,43 @@
<?php
/* PMM docs site — renders sibling .md files with the shared md() renderer.
* URL: docs/index.php?page=<name> (defaults to "index"). */
define('PMM_SITE', 1);
require dirname(__DIR__) . '/_common.php';
/* doc pages: [slug, title] */
$pages = [
'index' => '快速开始',
'install' => '安装',
'download' => '下载',
'features' => '核心特性',
'cli' => '命令参考',
'mirror' => '镜像源',
'translate' => '翻译 / 语言包',
'source' => '源码',
];
$page = isset($_GET['page']) ? (string)$_GET['page'] : 'index';
if (!isset($pages[$page])) $page = 'index';
$title = $pages[$page];
$mdPath = __DIR__ . '/' . $page . '.md';
$body = '';
if (is_file($mdPath)) {
$body = (string)file_get_contents($mdPath);
$body = str_replace("\r", "", $body); // md() regex hard-codes \n
$body = md($body);
}
pmm_header('docs', '文档 · ' . $title);
pmm_page_open('DOCS', $title);
?>
<section class="section">
<div class="wrap">
<nav class="docs-nav">
<?php foreach ($pages as $slug => $t): ?>
<a class="docs-link<?php echo $slug === $page ? ' active' : ''; ?>" href="index.php?page=<?php echo $slug; ?>"><?php echo htmlspecialchars($t); ?></a>
<?php endforeach; ?>
</nav>
<div class="docs-body"><?php echo $body; ?></div>
</div>
</section>
<?php pmm_footer(); ?>
+49
查看文件
@@ -0,0 +1,49 @@
# 安装
## Linux / macOS 一键
```bash
curl -sSL https://pmm.parlz.com/install.sh | bash
```
## Windows
下载并解压 zip,将 `pmm.exe` 加入 PATH
```bash
# PowerShell
Invoke-WebRequest https://github.com/JGZYES/ParlzPackageManger/releases/latest/download/pmm-0.3.6-windows-amd64.zip -OutFile pmm.zip
Expand-Archive pmm.zip -DestinationPath pmm
# 然后将 pmm\pmm.exe 所在目录加入 PATH
```
## Debian / Ubuntu.deb
```bash
curl -L -o pmm.deb https://github.com/JGZYES/ParlzPackageManger/releases/latest/download/pmm_0.3.6_amd64.deb
sudo apt install ./pmm.deb
```
## CentOS / RHEL.rpm
```bash
curl -L -o pmm.rpm https://github.com/JGZYES/ParlzPackageManger/releases/latest/download/pmm-0.3.6-1.x86_64.rpm
sudo rpm -Uvh pmm.rpm
```
## 用 pmm 安装 pmm 自身
```bash
pmm install pmm==0.3.6
```
## 装好后
```bash
pmm search nodejs
pmm install nodejs
pmm list
pmm remove nodejs
```
详见 [命令参考](index.php?page=cli) 与 [下载](index.php?page=download)。
+40
查看文件
@@ -0,0 +1,40 @@
# 镜像源
PMM 采用 apt 式多镜像源,按优先级回退。配置在 `~/.pmm/mirror.ini`
## 默认镜像
- **深圳(默认)**`https://sz.pmm.parlz.com/mirror/packages`
- **香港(备份)**`https://pmm.parlz.com/mirror/packages`
## mirror.ini 格式
```ini
[sz]
registry = https://sz.pmm.parlz.com/mirror/packages
priority = 1
default = true
[main]
registry = https://pmm.parlz.com/mirror/packages
priority = 20
```
## 管理镜像
```bash
pmm mirror list # 列出
pmm mirror add <n> <api> # 添加
pmm mirror use <n> # 设为当前
pmm mirror remove <n> # 移除
pmm mirror check # 探测每个源的可达性/优先级
```
## 服务状态
首页有深圳/香港双镜像的实时探测:
- 注册表 `mirror/packages/pmm.json`
- `.pdm` 下载 `mirror/packages/pmm/<ver>-linux-amd64.pdm`
- 镜像目录浏览 `mirror/`
详见 [下载](index.php?page=download)。
+22
查看文件
@@ -0,0 +1,22 @@
# 源码
PMM 是开源项目(GPL-3.0)。
- **GitHub 仓库**[JGZYES/ParlzPackageManger](https://github.com/JGZYES/ParlzPackageManger)
- **GitHub Releases**[releases](https://github.com/JGZYES/ParlzPackageManger/releases)
- **提交 Issue / PR**[issues](https://github.com/JGZYES/ParlzPackageManger/issues)
## 构建
需要 C11 编译器 + 静态链接环境:
```bash
# Linux
gcc -O2 -Wall -static -std=c11 -o pmm \
src/main.c src/json.c src/ini.c src/pmm.c src/http.c src/repo.c \
src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c src/i18n.c
```
## 文档规范
界面文案、`.pjson` 语言包键名、版本 banner 等遵循 [STANDARD.md](https://github.com/JGZYES/ParlzPackageManger/blob/main/docs/STANDARD.md)。
+32
查看文件
@@ -0,0 +1,32 @@
# 翻译 / 语言包
PMM 的界面文案由 `.pjson` 语言包驱动。默认内置 **zh-CN****en-US**,社区可以提交新的语言包或修订译文。
## 安装语言包
```bash
pmm setting lang -l # 列已有语言包
pmm setting lang ja-JP # 安装并切换
```
## 如何贡献一门语言
1. 从镜像下载一个语言包作模板(如 `zh-CN.pjson`)。
2. 把键名保持不变、只改值,另存为你的语言,如 `ja-JP.pjson``zh-TW.pjson`
3. 将文件发到 **`luoriguodu@qq.com`**,或在 **GitHub 提 Issue**
4. 通过后进入镜像 `web/mirror/lang/`,用户即可 `pmm setting lang <locale>` 安装。
## 语言包格式
```json
{
"help.banner": "PMM %s (%s)",
"cmd.install": "install",
"desc.install": "根据名称安装指定软件包",
"msg.installed": "已安装 %s"
}
```
键命名:`<区域>.<语义>`(如 `msg.err.not-found`),占位符用 `%s` / `%d`
详见 [STANDARD.md](https://github.com/JGZYES/ParlzPackageManger/blob/main/docs/STANDARD.md)。
-56
查看文件
@@ -1,56 +0,0 @@
<?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="releases/v<?php echo PMM_VERSION; ?>/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="releases/v<?php echo PMM_VERSION; ?>/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="releases/v<?php echo PMM_VERSION; ?>/pmm-<?php echo PMM_VERSION; ?>.x86_64.rpm" download>
<span class="dl-os">Linux</span><span class="dl-file">pmm-<?php echo PMM_VERSION; ?>.x86_64.rpm</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="releases/v<?php echo PMM_VERSION; ?>/pmm-<?php echo PMM_VERSION; ?>-linux-amd64.pdm" download>
<span class="dl-os">.pdm </span><span class="dl-file">pmm-<?php echo PMM_VERSION; ?>-linux-amd64.pdm</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="releases/v<?php echo PMM_VERSION; ?>/pmm-<?php echo PMM_VERSION; ?>.apk" download>
<span class="dl-os">Alpine</span><span class="dl-file">pmm-<?php echo PMM_VERSION; ?>.apk</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="releases/v<?php echo PMM_VERSION; ?>/pmm-<?php echo PMM_VERSION; ?>.pkg.tar.zst" download>
<span class="dl-os">Arch</span><span class="dl-file">pmm-<?php echo PMM_VERSION; ?>.pkg.tar.zst</span><span class="dl-arrow">↓</span>
</a>
<a class="dl-card" href="releases/v<?php echo PMM_VERSION; ?>/install.sh" download>
<span class="dl-os">Linux/macOS</span><span class="dl-file">install.sh · 一键安装脚本</span><span class="dl-arrow"></span>
</a>
<div class="dl-card">
<span class="dl-os">Linux/macOS</span>
<span class="dl-file">一键安装 · 复制命令</span>
<div class="dl-cmd"><code>curl -sSL https://pmm.parlz.com/install.sh | bash</code></div>
<button class="copy-btn" data-copy-text="curl -sSL https://pmm.parlz.com/install.sh | bash" type="button">复制命令</button>
</div>
<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(); ?>
-56
查看文件
@@ -1,56 +0,0 @@
#!/bin/sh
# ParlzPackageManager (PMM) — Linux/macOS one-line installer.
#
# 用法:
# curl -sSL -o install.sh https://pmm.parlz.com/install.sh && bash install.sh
# 或:curl -sSL https://pmm.parlz.com/install.sh | bash
#
# 下载官方 release 二进制并安装到 /usr/local/bin/pmmroot)或 ~/.local/bin/pmm。
set -e
VER="0.3.6"
REPO="JGZYES/ParlzPackageManger"
# ---- 检测平台 ----
OS="$(uname -s)"; ARCH="$(uname -m)"
case "$OS" in
Linux) os=linux ;;
Darwin) os=macos ;;
*) echo "pmm: 不支持的平台 $OS"; exit 1 ;;
esac
case "$ARCH" in
x86_64|amd64) arch=amd64 ;;
aarch64|arm64) arch=arm64 ;;
*) echo "pmm: 不支持的架构 $ARCH"; exit 1 ;;
esac
# ---- 下载 pmmrelease 资产名为 pmm----
URL="https://github.com/$REPO/releases/download/v$VER/pmm"
if [ "$arch" != "amd64" ]; then
echo "pmm: 注意:官方发布目前只提供 amd64 资产,$os/$arch 可能 404"
fi
echo "pmm: 下载 v$VER ($os/$arch) ..."
TMP="$(mktemp)"
# --progress-bar shows a live download progress line (tty only)
if ! curl -fL --progress-bar --max-time 300 -o "$TMP" "$URL"; then
rm -f "$TMP"; echo "pmm: 下载失败($URL"; exit 1
fi
echo ""
chmod +x "$TMP"
# ---- 安装 ----
if [ "$(id -u)" = "0" ]; then
DEST="/usr/local/bin/pmm"
else
DEST="${HOME}/.local/bin/pmm"
fi
mkdir -p "$(dirname "$DEST")"
cp "$TMP" "$DEST" && chmod +x "$DEST" && rm -f "$TMP"
# ---- PATH 提示 ----
case ":$PATH:" in
*":$(dirname "$DEST"):"*) ;;
*) echo "pmm: 请将 $(dirname "$DEST") 加入 PATH(或重新登录 shell)" ;;
esac
echo "pmm: 安装完成 -> $DEST"
"$DEST" -v 2>/dev/null || true
二进制
查看文件
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制
查看文件
二进制文件未显示。
二进制文件未显示。
-24
查看文件
@@ -1,24 +0,0 @@
<?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(); ?>
-366
查看文件
@@ -1,366 +0,0 @@
<?php
/* git.php GitHub-style browser for a LOCAL stored copy of the PMM repository.
* Reads the git repo at $REPO_DIR (default web/repo/), a local clone stored on
* the host. No online clone on view; if the repo isn't stored yet, show setup
* instructions + a one-click clone/pull. */
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
$REPO_URL = 'https://github.com/JGZYES/ParlzPackageManger.git';
/* Local repo clone lives in web/git (i.e. /www/sites/ParlzPackageManger/index/web/git
* on the live host). PMM_SRC_DIR may override it. PHP needs write access there. */
$REPO_DIR = getenv('PMM_SRC_DIR') ?: (__DIR__ . '/git');
/* handle the one-click clone/pull early (no page output yet) */
if (isset($_GET['action']) && $_GET['action'] === 'init') {
header('Content-Type: text/plain; charset=utf-8');
if (!is_dir($REPO_DIR)) @mkdir($REPO_DIR, 0775, true);
if (!is_dir($REPO_DIR . '/.git')) {
$cmd = 'git clone --depth 1 ' . escapeshellarg($REPO_URL) . ' ' . escapeshellarg($REPO_DIR) . ' 2>&1';
$out = (string)@shell_exec($cmd);
if (is_dir($REPO_DIR . '/.git')) echo 'OK';
else echo '克隆失败:' . substr($out, 0, 220) . "\n(请确保 PHP 对 " . $REPO_DIR . " 可写:chmod -R 775 " . $REPO_DIR . " 或其父目录;或设 PMM_SRC_DIR";
} else {
$out = (string)@shell_exec('git -C ' . escapeshellarg($REPO_DIR) . ' pull --depth 1 2>&1');
echo 'OK (pull: ' . substr(trim($out), 0, 80) . ')';
}
exit;
}
$repo_ok = is_dir($REPO_DIR . '/.git');
function sh(string $c): string { return trim((string)@shell_exec($c)); }
function repo_path(string $rel): string {
global $REPO_DIR;
$base = realpath($REPO_DIR);
$full = realpath($REPO_DIR . '/' . $rel);
if (!$base || !$full) return '';
if (strpos(str_replace('\\', '/', $full), str_replace('\\', '/', $base)) !== 0) return '';
return $full;
}
function esc(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
function fmt_size(int $b): string {
if ($b < 1024) return $b . ' B';
if ($b < 1048576) return round($b / 1024, 1) . ' KB';
return round($b / 1048576, 1) . ' MB';
}
function md(string $t): string {
$t = htmlspecialchars($t, ENT_QUOTES, 'UTF-8');
$t = preg_replace('/```([a-zA-Z0-9]*)\n(.*?)```/s', "<pre class=\"mdcode\"><code>\\2</code></pre>", $t);
$t = preg_replace('/^### (.*)$/m', '<h3>$1</h3>', $t);
$t = preg_replace('/^## (.*)$/m', '<h2>$1</h2>', $t);
$t = preg_replace('/^# (.*)$/m', '<h1>$1</h1>', $t);
$t = preg_replace('/^[-*] (.*)$/m', '<li>$1</li>', $t);
$t = preg_replace('/^\s*---\s*$/m', '<hr>', $t);
$t = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $t);
$t = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2" target="_blank" rel="noopener">$1</a>', $t);
$t = preg_replace('/`([^`]+)`/', '<code>$1</code>', $t);
return $t;
}
function gh_api(string $url): array {
$json = null;
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false, CURLOPT_USERAGENT => 'pmm-site']);
$r = curl_exec($ch); curl_close($ch);
if (is_string($r) && $r !== '') $json = $r;
}
if ($json === null) $json = (string)@shell_exec('curl -s --max-time 15 ' . escapeshellarg($url));
$d = json_decode((string)$json, true);
return is_array($d) ? $d : [];
}
/* Prefer a local copy of a release asset on this server; fall back to GitHub. */
/* Prefer a local copy of a release asset (web/releases/<tag>/, web/downloads/,
* web/mirror/, web root) on this server; fall back to GitHub. */
function local_asset(string $name, string $tag = ''): ?string {
$cands = [];
if ($tag !== '') $cands[] = __DIR__ . '/releases/' . $tag . '/' . $name;
$cands = array_merge($cands, [__DIR__ . '/downloads/' . $name, __DIR__ . '/mirror/packages/pmm/' . $name, __DIR__ . '/' . $name]);
$base = realpath(__DIR__);
foreach ($cands as $p) {
if (@is_file($p)) {
$rp = realpath($p);
if (strpos($rp, $base) !== 0) continue;
return '/' . str_replace('\\', '/', substr(str_replace('\\', '/', $rp), strlen($base) + 1));
}
}
return null;
}
function render_releases(): void {
$rels = gh_api('https://api.github.com/repos/JGZYES/ParlzPackageManger/releases?per_page=30');
if (!$rels) { echo '<div class="tips">无法获取 Releases(网络或 API 受限)。</div>'; return; }
/* left: tag list (like GitHub's folded releases) */
echo '<div class="rel-layout"><aside class="rel-tags"><div class="rel-tags-h">Releases</div><ul>';
$i = 0;
foreach ($rels as $r) {
$t = $r['tag_name'] ?? '?';
echo '<li' . ($i === 0 ? ' class="on"' : '') . '><a href="#rel-' . esc($t) . '">' . esc($t) . '</a></li>';
$i++;
}
echo '</ul></aside>';
/* right: release entries */
echo '<div class="rel-main">';
$i = 0;
foreach ($rels as $r):
$tag = $r['tag_name'] ?? '?'; $name = $r['name'] ?? $tag;
$date = $r['published_at'] ?? ($r['created_at'] ?? '');
$date = substr(str_replace('T', ' ', (string)$date), 0, 10);
$prere = !empty($r['prerelease']);
$assets = $r['assets'] ?? [];
$body = (string)($r['body'] ?? '');
?>
<div class="rel-entry" id="rel-<?php echo esc($tag); ?>"<?php echo $i === 0 ? ' data-latest="1"' : ''; ?>>
<div class="rel-card">
<div class="rel-head">
<h2 class="rel-version"><span class="rel-ico">&#10225;</span> <?php echo esc($name); ?></h2>
<div class="rel-badges">
<?php if ($i === 0): ?><span class="rel-latest">Latest</span><?php endif; ?>
<?php if ($prere): ?><span class="rel-latest prere">Pre-release</span><?php endif; ?>
<span class="rel-date"><?php echo esc($date); ?></span>
</div>
</div>
<?php if ($body !== ''): ?>
<div class="rel-body mdbody"><?php echo md($body); ?></div>
<?php endif; ?>
<?php if ($assets): ?>
<?php
$tot = 0; foreach ($assets as $a) $tot += (int)($a['size'] ?? 0);
$cnt = count($assets);
?>
<div class="rel-assets">
<button class="rel-assets-h" type="button" data-toggle="assets" aria-expanded="false">
<span class="rel-chev">&#9656;</span> Assets · 下载
<span class="rel-count"><?php echo $cnt; ?> files · <?php echo fmt_size($tot); ?></span>
</button>
<div class="rel-assets-list" hidden>
<?php foreach ($assets as $a):
$local = local_asset((string)($a['name'] ?? ''), $tag);
$href = $local ?: ($a['browser_download_url'] ?? '#');
$sz = isset($a['size']) ? fmt_size((int)$a['size']) : '';
?>
<div class="rel-asset-row">
<a class="rel-asset<?php echo $local ? ' local' : ''; ?>"
href="<?php echo esc($href); ?>" title="<?php echo esc($href); ?>"
<?php echo $local ? ' download' : ' target="_blank" rel="noopener"'; ?>>
<span class="rel-a-ico">&#128196;</span> <?php echo esc($a['name'] ?? 'asset'); ?>
</a>
<span class="rel-size"><?php echo esc($sz); ?></span>
<a class="rel-dl" href="<?php echo esc($href); ?>" <?php echo $local ? ' download' : ' target="_blank" rel="noopener"'; ?>>下载</a>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<p class="rel-tagline">标签 <code><?php echo esc($tag); ?></code></p>
</div>
</div>
<?php $i++; endforeach;
echo '</div></div>';
}
$view = $_GET['view'] ?? '';
$branch = $repo_ok ? sh("git -C " . escapeshellarg($REPO_DIR) . " rev-parse --abbrev-ref HEAD 2>/dev/null") : '';
$last = $repo_ok ? sh('git -C ' . escapeshellarg($REPO_DIR) . " log -1 --format=\"%H|%s|%an|%ad\" --date=short 2>/dev/null") : '';
[$lhash, $lsubj, $luser, $ldate] = array_pad(explode('|', $last, 4), 4, '');
$lhash7 = substr($lhash, 0, 7);
pmm_header('source', '源码浏览');
?>
<header class="page-hero"><div class="wrap">
<div class="page-kicker">SOURCE</div>
<h1>源码 <span class="accent">浏览</span></h1>
<div class="src-clone-note">
<?php echo $repo_ok
? '本地仓库 <code>JGZYES/ParlzPackageManager</code> · 分支 <code>' . esc($branch) . '</code> · ' . esc($REPO_DIR)
: '提示:本地依赖仓库尚未初始化,请先 <code>git clone</code> 或点击下方“初始化/更新”。'; ?>
</div>
</div></header>
<section class="section" style="padding-top:18px">
<div class="wrap">
<?php if (!$repo_ok): ?>
<div class="gh-card">
<div class="gh-repo">初始化本地源码仓库</div>
<p class="src-clone-note">页面将 PMM 仓库副本存入 <code><?php echo esc($REPO_DIR); ?></code>。若 PHP 无写权限(web 根归 root),先
<code>chmod -R 775 <?php echo esc($REPO_DIR); ?></code> 或其父目录;或用环境变量 <code>PMM_SRC_DIR</code> 指定持久目录。初始化命令:</p>
<pre class="gh-code"><code>git clone --depth 1 <?php echo esc($REPO_URL); ?> <?php echo esc($REPO_DIR); ?></code></pre>
<div style="margin-top:12px">
<button class="btn btn-sm" id="git-now" type="button">在线初始化 / 更新</button>
<span id="git-msg" class="src-clone-note" style="margin-left:10px"></span>
</div>
</div>
<?php else:
$rel = $_GET['path'] ?? '';
$rel = str_replace(['../', '..\\', "\0"], '', $rel);
$rel = ltrim($rel, '/');
$abs = repo_path($rel);
if (!$abs) { $abs = realpath($REPO_DIR); $rel = ''; }
$is_file = is_file($abs);
$parts = $rel === '' ? [] : explode('/', $rel);
?>
<div class="gh-card">
<div class="gh-top">
<a class="gh-repo" href="<?php echo $REPO_URL; ?>" target="_blank" rel="noopener">JGZYES / ParlzPackageManager</a>
<div class="gh-actions">
<a class="btn btn-sm" href="git.php">Code</a>
<a class="btn btn-sm" href="git.php?view=releases">Releases</a>
<span class="gh-branch"> <?php echo esc($branch ?: 'main'); ?></span>
</div>
</div>
<div class="gh-commit">
<span class="gh-hash" title="<?php echo esc($lhash); ?>"><?php echo esc($lhash7); ?></span>
<span class="gh-subj"><?php echo esc($lsubj); ?></span>
<span class="gh-meta"><?php echo esc($luser); ?> · <?php echo esc($ldate); ?></span>
</div>
</div>
<?php if ($view === 'releases'): ?>
<style>
html{scroll-padding-top:88px}
.rel-entry{scroll-margin-top:88px}
.rel-layout{display:grid;grid-template-columns:220px 1fr;gap:26px;align-items:start}
@media(max-width:840px){.rel-layout{grid-template-columns:1fr}}
.rel-tags{position:sticky;top:70px;border-right:1px solid var(--line);padding-right:12px}
.rel-tags-h{font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);padding:8px 4px;font-weight:700}
.rel-tags ul{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.rel-tags li a{display:block;padding:6px 10px;border-radius:6px;font-family:ui-monospace,monospace;font-size:12.5px;color:var(--muted);text-decoration:none}
.rel-tags li a:hover{background:var(--panel2);color:#fff}
.rel-tags li.on a{background:var(--panel);color:#fff;font-weight:600}
.rel-main{display:grid;gap:24px;min-width:0}
.rel-entry{display:grid;gap:14px}
.rel-card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 22px}
.rel-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.rel-version{font-weight:800;font-size:19px;color:#fff;margin:0;display:flex;align-items:center;gap:9px;letter-spacing:-.3px}
.rel-ico{color:var(--dim);font-size:16px}
.rel-badges{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.rel-latest{font-size:10.5px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#111;background:#fff;border-radius:999px;padding:2px 9px}
.rel-latest.prere{color:#fff;background:transparent;border:1px solid var(--line);font-weight:600}
.rel-date{font-size:12.5px;color:var(--dim);font-family:ui-monospace,monospace}
.rel-body{border-top:1px solid var(--line);padding-top:14px;margin-top:4px}
.rel-tagline{font-size:12px;color:var(--dim);margin-top:12px}
.rel-assets{border-top:1px solid var(--line);margin-top:16px;padding-top:14px}
.rel-assets-h{display:flex;align-items:center;gap:9px;background:transparent;border:0;color:var(--text);cursor:pointer;font-size:12.5px;letter-spacing:.4px;text-transform:uppercase;font-weight:700;padding:4px 0;width:100%;text-align:left}
.rel-assets-h:hover{color:#fff}
.rel-chev{transition:transform .18s;font-size:11px;color:var(--dim)}
.rel-assets.open .rel-chev{transform:rotate(90deg)}
.rel-count{margin-left:auto;font-size:11.5px;color:var(--dim);text-transform:none;letter-spacing:0;font-weight:500}
.rel-assets-list{display:grid;gap:8px}
.rel-assets-list[hidden]{display:none}
.rel-assets.open .rel-assets-list{margin-top:12px}
.rel-asset-row{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:#0c1017}
.rel-asset{display:inline-flex;align-items:center;gap:7px;color:var(--text);font-family:ui-monospace,monospace;font-size:12.5px;text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rel-asset:hover{color:#fff;text-decoration:underline}
.rel-asset .rel-a-ico{color:var(--dim)}
.rel-asset.local{color:#fff}
.rel-size{font-size:11.5px;color:var(--dim);font-family:ui-monospace,monospace;white-space:nowrap}
.rel-dl{margin-left:auto;font-size:12px;color:var(--muted);border:1px solid var(--line);border-radius:6px;padding:3px 10px;text-decoration:none;white-space:nowrap}
.rel-dl:hover{color:#fff;border-color:#fff}
</style>
<h2 class="section-title" style="text-align:left;font-size:22px;margin:6px 0 20px">Releases</h2>
<?php render_releases(); ?>
<?php else: ?>
<div class="gh-path">
<a href="git.php"><code>source</code></a>
<?php $acc=''; foreach ($parts as $p) { $acc = $acc==='' ? $p : $acc.'/'.$p;
echo ' / <a href="git.php?path=' . urlencode($acc) . '">' . esc($p) . '</a>'; } ?>
</div>
<?php if ($is_file): ?>
<div class="gh-filehead"><span class="gh-filelink"><?php echo esc(end($parts)); ?></span>
<span class="gh-meta"><?php echo fmt_size((int)filesize($abs)); ?> bytes</span></div>
<pre class="gh-code"><code><?php echo esc(substr((string)@file_get_contents($abs), 0, 400000)); ?></code></pre>
<?php else:
$dirs = []; $files = [];
foreach (scandir($abs) as $e) {
if ($e === '.' || $e === '..' || $e === '.git') continue;
$p = $abs . '/' . $e;
if (is_dir($p)) $dirs[] = $e; else $files[] = $e;
}
sort($dirs); sort($files);
$readme = '';
foreach (['README.md', 'README.MD', 'readme.md'] as $r) if (is_file($abs . '/' . $r)) { $readme = $abs . '/' . $r; break; }
?>
<table class="gh-list">
<thead><tr><th>名称</th><th>提交</th><th>时间</th><th>大小</th></tr></thead>
<tbody>
<?php
$rows = array_merge(array_map(fn($d) => [true, $d], $dirs), array_map(fn($f) => [false, $f], $files));
foreach ($rows as [$isD, $name]):
$sub = $rel === '' ? $name : "$rel/$name";
$href = 'git.php?path=' . urlencode($sub);
$commit = sh('git -C ' . escapeshellarg($REPO_DIR) . " log -1 --format=\"%h|%an|%ad\" --date=short -- " . escapeshellarg($sub) . ' 2>/dev/null');
[$ch, $ca, $cd] = array_pad(explode('|', $commit, 3), 3, '');
?>
<tr>
<td class="gh-name"><a class="<?php echo $isD ? 'gh-dir' : 'gh-file'; ?>" href="<?php echo $href; ?>">
<span class="gh-ico"><?php echo $isD ? '&#128193;' : '&#128196;'; ?></span> <?php echo esc($name); ?></a></td>
<td class="gh-c"><code><?php echo esc($ch); ?></code> <?php echo esc($ca); ?></td>
<td class="gh-c"><?php echo esc($cd); ?></td>
<td class="gh-c"><?php echo $isD ? '—' : fmt_size((int)@filesize($abs . '/' . $name)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if ($readme): ?>
<div class="gh-readme"><div class="gh-readme-t">README</div><div class="mdbody"><?php echo md((string)@file_get_contents($readme)); ?></div></div>
<?php endif; ?>
<?php endif; endif; endif; ?>
</div>
</section>
<script>
(function(){
var btn = document.getElementById("git-now");
if (btn) btn.addEventListener("click", function(){
var msg = document.getElementById("git-msg");
msg.textContent = "正在初始化…";
fetch("git.php?action=init", { cache: "no-store" })
.then(function(r){ return r.text(); })
.then(function(t){ msg.textContent = t; if (t.indexOf("OK") === 0) setTimeout(function(){ location.reload(); }, 1200); })
.catch(function(){ msg.textContent = "失败"; });
});
/* Releases: Assets collapsible (GitHub-like) — click header to expand/collapse */
document.querySelectorAll(".rel-assets-h").forEach(function(h){
h.addEventListener("click", function(){
var box = h.closest(".rel-assets");
var list = box.querySelector(".rel-assets-list");
var open = box.classList.toggle("open");
list.hidden = !open;
h.setAttribute("aria-expanded", open ? "true" : "false");
});
});
/* Releases: clicking a left tag moves the active (on) highlight there */
var tags = document.querySelectorAll(".rel-tags li a");
if (tags.length) {
tags.forEach(function(a){
a.addEventListener("click", function(){
document.querySelectorAll(".rel-tags li").forEach(function(li){ li.classList.remove("on"); });
a.parentElement.classList.add("on");
});
});
/* keep the left highlight in sync with the release in view */
var entries = document.querySelectorAll(".rel-entry");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function(en){
en.forEach(function(e){
if (e.isIntersecting) {
var id = e.target.id.replace("rel-", "");
document.querySelectorAll(".rel-tags li").forEach(function(li){
li.classList.toggle("on", li.querySelector("a").textContent.trim() === id);
});
}
});
}, { rootMargin: "-80px 0px -60% 0px" });
entries.forEach(function(en){ io.observe(en); });
}
}
})();
</script>
<?php pmm_footer(); ?>
+12 -21
查看文件
@@ -1,5 +1,6 @@
<?php
/* PMM home — hero + rotating dotted Earth + entry cards to the 4 pages. */
/* PMM home brand hero + rotating Earth + live mirror status. Content lives
* in the docs site (web/docs). */
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
@@ -60,8 +61,8 @@ pmm_header('home', '跨平台 C 语言包管理器');
<h1>ParlzPackageManager <span class="accent">(PMM)</span></h1>
<p class="hero-tag">一个用 <strong>C 语言</strong>从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 <strong>GitHub / GitLab / Gitea / Forgejo</strong> Release 装包。</p>
<div class="hero-cta">
<a class="btn btn-primary" href="install.php">快速安装</a>
<a class="btn btn-ghost" href="download.php">下载 v<?php echo PMM_VERSION; ?> ↓</a>
<a class="btn btn-primary" href="docs/index.php">文档 · 安装</a>
<a class="btn btn-ghost" href="docs/index.php?page=download">下载 v<?php echo PMM_VERSION; ?> ↓</a>
</div>
<div class="hero-note"><span class="dot ok"></span> 双镜像 · 深圳 / 香港 实时在线</div>
</div>
@@ -75,30 +76,20 @@ pmm_header('home', '跨平台 C 语言包管理器');
<section class="section entry-section">
<div class="wrap">
<div class="entry-grid">
<a class="entry-card" href="features.php">
<span class="entry-icon"></span>
<span class="entry-title">特性</span>
<span class="entry-desc">apt 式多镜像、git release、.pdm、依赖解析、并发下载</span>
<a class="entry-card" href="docs/index.php">
<span class="entry-icon"></span>
<span class="entry-title">文档</span>
<span class="entry-desc">特性、安装、下载、CLI、镜像、翻译</span>
</a>
<a class="entry-card" href="install.php">
<a class="entry-card" href="docs/index.php?page=install">
<span class="entry-icon"></span>
<span class="entry-title">安装</span>
<span class="entry-desc">三条命令装好 Windows / Linux / macOS 客户端</span>
<span class="entry-desc">Windows / Linux / macOS 一键安装</span>
</a>
<a class="entry-card" href="download.php">
<a class="entry-card" href="docs/index.php?page=download">
<span class="entry-icon"></span>
<span class="entry-title">下载</span>
<span class="entry-desc">zip / deb / install.sh / .pdm 全部安装产物</span>
</a>
<a class="entry-card" href="servers.php">
<span class="entry-icon"></span>
<span class="entry-title">服务状态</span>
<span class="entry-desc">深圳 / 香港镜像的实时探测与详细信息</span>
</a>
<a class="entry-card" href="git.php">
<span class="entry-icon">&lt;/&gt;</span>
<span class="entry-title">源码</span>
<span class="entry-desc">GitHub 风格浏览 PMM 源码(本地仓库副本)</span>
<span class="entry-desc">zip / deb / rpm / install.sh / .pdm</span>
</a>
</div>
</div>
-56
查看文件
@@ -1,56 +0,0 @@
<?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/releases/v<?php echo PMM_VERSION; ?>/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/releases/v<?php echo PMM_VERSION; ?>/pmm_<?php echo PMM_VERSION; ?>_amd64.deb
sudo apt install ./pmm.deb
# 方式三:.rpm 安装
curl -L -o pmm.rpm https://pmm.parlz.com/releases/v<?php echo PMM_VERSION; ?>/pmm-<?php echo PMM_VERSION; ?>.x86_64.rpm
sudo rpm -Uvh pmm.rpm
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(); ?>
-56
查看文件
@@ -1,56 +0,0 @@
#!/bin/sh
# ParlzPackageManager (PMM) — Linux/macOS one-line installer.
#
# 用法:
# curl -sSL -o install.sh https://pmm.parlz.com/install.sh && bash install.sh
# 或:curl -sSL https://pmm.parlz.com/install.sh | bash
#
# 下载官方 release 二进制并安装到 /usr/local/bin/pmmroot)或 ~/.local/bin/pmm。
set -e
VER="0.3.6"
REPO="JGZYES/ParlzPackageManger"
# ---- 检测平台 ----
OS="$(uname -s)"; ARCH="$(uname -m)"
case "$OS" in
Linux) os=linux ;;
Darwin) os=macos ;;
*) echo "pmm: 不支持的平台 $OS"; exit 1 ;;
esac
case "$ARCH" in
x86_64|amd64) arch=amd64 ;;
aarch64|arm64) arch=arm64 ;;
*) echo "pmm: 不支持的架构 $ARCH"; exit 1 ;;
esac
# ---- 下载 pmmrelease 资产名为 pmm----
URL="https://github.com/$REPO/releases/download/v$VER/pmm"
if [ "$arch" != "amd64" ]; then
echo "pmm: 注意:官方发布目前只提供 amd64 资产,$os/$arch 可能 404"
fi
echo "pmm: 下载 v$VER ($os/$arch) ..."
TMP="$(mktemp)"
# --progress-bar shows a live download progress line (tty only)
if ! curl -fL --progress-bar --max-time 300 -o "$TMP" "$URL"; then
rm -f "$TMP"; echo "pmm: 下载失败($URL"; exit 1
fi
echo ""
chmod +x "$TMP"
# ---- 安装 ----
if [ "$(id -u)" = "0" ]; then
DEST="/usr/local/bin/pmm"
else
DEST="${HOME}/.local/bin/pmm"
fi
mkdir -p "$(dirname "$DEST")"
cp "$TMP" "$DEST" && chmod +x "$DEST" && rm -f "$TMP"
# ---- PATH 提示 ----
case ":$PATH:" in
*":$(dirname "$DEST"):"*) ;;
*) echo "pmm: 请将 $(dirname "$DEST") 加入 PATH(或重新登录 shell)" ;;
esac
echo "pmm: 安装完成 -> $DEST"
"$DEST" -v 2>/dev/null || true
+1 -1
查看文件
@@ -89,7 +89,7 @@ if ($dirRel !== '') {
}
foreach ($dirs as $d) {
$href = '?dir=' . rawurlencode($webRel === '' ? $d : $webRel . '/' . $d);
echo "<li>" . ic('folder') . "<a href='${href}'>" . en($d) . "/</a></li>";
echo "<li>" . ic('folder') . "<a href='{$href}'>" . en($d) . "/</a></li>";
}
foreach ($files as $f) {
$href = url_enc_path($webRel, $f);
-26
查看文件
@@ -1,26 +0,0 @@
<?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(); ?>
-3
查看文件
@@ -1,3 +0,0 @@
<?php
/* Source browsing now lives in git.php (local stored repo copy). */
header('Location: git.php'); exit;
-99
查看文件
@@ -1,99 +0,0 @@
<?php
/* Detailed status probe for the PMM mirror servers.
* For each mirror we probe three endpoints (registry index / package download /
* 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('Cache-Control: no-store');
$servers = [
['name' => '深圳', 'region' => 'CN · 深圳', 'host' => 'sz.pmm.parlz.com'],
['name' => '香港', 'region' => 'HK · 香港', 'host' => 'pmm.parlz.com'],
];
/* Probe one URL: returns [code, ms, server, ctype].
* Prefers the PHP curl extension (reliable CURLINFO_TOTAL_TIME for ms, and works
* where shell_exec/curl-binary are unavailable); falls back to shell curl. */
function probe_url(string $url): array {
$code = 0; $ms = 0; $server = ''; $ctype = '';
/* 1) PHP curl extension */
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOBODY => true,
CURLOPT_HEADER => true,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 8,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => 'pmm-status/1.0',
]);
$resp = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$ms = (int)round(((float)curl_getinfo($ch, CURLINFO_TOTAL_TIME)) * 1000.0);
$ctype = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
if (preg_match('/^Server:\s*(.+)$/mi', (string)$resp, $m)) $server = trim($m[1]);
curl_close($ch);
if ($code > 0) return [$code, $ms, $server, $ctype];
}
/* 2) shell curl (works where the binary exists; parses -D - headers + -w marker) */
$cmd = 'curl -k -s -o /dev/null -D - --max-time 8 '
. '-w "__PMM_PROBE__%{http_code} %{time_total}" ' . escapeshellarg($url);
$out = (string)@shell_exec($cmd);
if ($out !== '') {
/* headers before the marker, "code ms" after it */
$pos = strrpos($out, '__PMM_PROBE__');
if ($pos !== false) {
$meta = trim(substr($out, $pos + strlen('__PMM_PROBE__')));
$parts = preg_split('/\s+/', $meta);
if (isset($parts[0])) $code = (int)$parts[0];
if (isset($parts[1])) $ms = (int)round(((float)$parts[1]) * 1000);
$head = substr($out, 0, $pos);
if (preg_match('/^Server:\s*(.+)$/mi', $head, $m)) $server = trim($m[1]);
if (preg_match('/^Content-Type:\s*(.+)$/mi', $head, $m)) $ctype = trim($m[1]);
}
}
return [$code, $ms, $server, $ctype];
}
function probe_check(string $url): array {
[$code, $ms, $server, $ctype] = probe_url($url);
return [
'ok' => ($code >= 200 && $code < 500 && $code !== 0),
'code' => $code,
'ms' => $ms,
'server' => $server,
'ctype' => $ctype,
];
}
$out = [];
foreach ($servers as $s) {
$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[] = [
'name' => $s['name'],
'region' => $s['region'],
'host' => $h,
'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);
-74
查看文件
@@ -1,74 +0,0 @@
<?php
define('PMM_SITE', 1);
require __DIR__ . '/_common.php';
/* Language packs under web/mirror/lang/. */
$langDir = __DIR__ . '/mirror/lang';
$packs = [];
if (is_dir($langDir)) {
foreach (glob($langDir . '/*.pjson') as $f) {
$locale = basename($f, '.pjson');
$keys = 0; $raw = '';
$data = json_decode((string)file_get_contents($f), true);
if (is_array($data)) {
$keys = count($data);
ksort($data);
$raw = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
$packs[] = [
'locale' => $locale,
'keys' => $keys,
'file' => 'mirror/lang/' . basename($f),
'raw' => $raw,
];
}
}
usort($packs, function ($a, $b) { return strcmp($a['locale'], $b['locale']); });
$mail = 'luoriguodu@qq.com';
$issues = 'https://github.com/JGZYES/ParlzPackageManger/issues/new';
pmm_header('translate', '翻译 · Translate');
pmm_page_open('TRANSLATE', '翻译 / Language');
?>
<section class="section">
<div class="wrap">
<p class="section-sub">PMM 的界面文案由 <code>.pjson</code> 语言包驱动。每门语言一个包;想新增语言或修订译文,请
<a href="mailto:<?php echo $mail; ?>?subject=<?php echo rawurlencode('[PMM 翻译] 语言包建议'); ?>&body=<?php echo rawurlencode('我想添加/修订语言包:'); ?>">发邮件</a>
或在 <a href="<?php echo $issues; ?>" target="_blank" rel="noopener">GitHub Issue</a></p>
<div class="dl-grid" style="grid-template-columns:repeat(auto-fill,minmax(260px,1fr));">
<?php if (!$packs): ?>
<div class="dl-card"><span class="dl-os"></span><span class="dl-file">暂无语言包</span></div>
<?php else: foreach ($packs as $p): ?>
<div class="dl-card">
<span class="dl-os"><?php echo htmlspecialchars($p['locale']); ?></span>
<span class="dl-file"><?php echo (int)$p['keys']; ?> 键</span>
<details class="lang-view">
<summary class="btn btn-sm" style="display:inline-block; margin:8px 0 6px;">查看</summary>
<pre class="code-box" style="max-height:220px; overflow:auto; white-space:pre-wrap; word-break:break-all;"><?php echo htmlspecialchars($p['raw']); ?></pre>
</details>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<a class="btn btn-sm" href="<?php echo htmlspecialchars($p['file']); ?>" download>下载 .pjson </a>
<a class="btn btn-sm" href="mailto:<?php echo $mail; ?>?subject=<?php echo rawurlencode('[PMM 翻译] 修订 ' . $p['locale']); ?>&body=<?php echo rawurlencode('针对 ' . $p['locale'] . ' 语言包的建议:'); ?>">贡献修订</a>
</div>
</div>
<?php endforeach; endif; ?>
</div>
<h2 class="section-title" style="margin-top:56px">如何贡献一门语言</h2>
<ol class="tips" style="line-height:1.9;">
<li>打开任一语言(如 <code>zh-CN</code>)卡片 <b>下载 .pjson</b></li>
<li> <code>zh-CN</code> 的键名保留、<b>只改值</b>,另存为你的语言, <code>ja-JP.pjson</code><code>zh-TW.pjson</code></li>
<li>将文件作为附件发到 <a href="mailto:<?php echo $mail; ?>"><?php echo $mail; ?></a>,或在 <a href="<?php echo $issues; ?>" target="_blank" rel="noopener">GitHub 提 Issue</a> 粘贴内容。</li>
<li>通过后即进入镜像 <code>web/mirror/lang/</code>,用户用 <code>pmm setting lang &lt;locale&gt;</code> 安装。</li>
</ol>
<p>安装语言包:</p>
<pre class="code-box"><code>pmm setting lang ja-JP
pmm setting lang -l</code></pre>
</div>
</section>
<?php pmm_footer(); ?>