From b41a6d01f157777009d0b8a60e8854c4e2b80893 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Sun, 30 Aug 2026 18:47:06 +0800 Subject: [PATCH] mirror: drop web UI + upload; keep only static package repo (packages/, serve via any static server) --- README.md | 34 ++++----- mirror/index.html | 172 ---------------------------------------------- mirror/index.php | 150 ---------------------------------------- src/http.c | 8 ++- 4 files changed, 21 insertions(+), 343 deletions(-) delete mode 100644 mirror/index.html delete mode 100644 mirror/index.php diff --git a/README.md b/README.md index 1b727d5..9ff6cca 100644 --- a/README.md +++ b/README.md @@ -102,32 +102,28 @@ priority = 20 - 下载文件时也会把镜像的 `download` 前缀按优先级排在原始 URL 之前逐个尝试。 - 每家的 `priority` 相同则按出现顺序;`pmm mirror use <名>` 可强制优先用某个镜像。 -## 镜像站(mirror/ 网页版) +## 镜像仓库(mirror/ 静态目录) -`mirror/` 目录是一个**纯 PHP** 镜像站(zero-dependency:仅需 PHP 8 的 json/标准扩展), -提供网页浏览、搜索、按平台筛选、上传包并自动生成 registry 索引。 - -**默认站点:`https://pmm.parlz.com/mirror`**(上传生成的下载链接和 `mirror.ini` -都指向它)。本地开发/测试可用 `PMM_BASE_URL` 覆盖: +`mirror/` 现在只做一件事:**静态包仓库**(无网页、无上传),把 `packages/` 目录用 +任意静态文件服务器直接对外提供即可: ```sh cd mirror -PMM_BASE_URL=http://127.0.0.1:8080 php -S 0.0.0.0:8080 index.php # 本地调试 -php -S 0.0.0.0:8080 index.php # 生产默认 https://pmm.parlz.com/mirror +python -m http.server 8080 # 或 nginx/apache 直接指向 mirror/ 目录 ``` -(Apache/Nginx 部署时把所有请求路由到 `index.php` 前端控制器即可。) -- 网页 `/`:浏览/搜索包、按 Windows/Linux/macOS 筛选、复制安装命令 -- `POST /upload`:上传 `.pdm`/`.deb`/`.exe`/`.dmg`/`.tar.gz` 等,自动算 sha256/sha1 -- 上传带 `x-pkg-name`、`x-pkg-version`、`x-pkg-os`、`x-pkg-desc` 头(或表单包名) - 会写 registry。**包文件存到 `packages/<包>/<版本>-<平台>.pdm`,元数据 - `packages/<包>/<版本>-<平台>.json`,`<包>.json` 是 latest 指针(含 `variants` 列表)**。 -- `GET /packages.json`:registry 聚合索引(只列 latest,供 `pmm install` 无版本时用) -- `GET /mirror.ini`:可直接复制到 `~/.pmm/mirror.ini` 的镜像配置 -- 目录约定:`packages/<包>.json`(latest)+ `packages/<包>/<版本>.pdm|.json` +- 目录约定:`packages/<包>.json`(latest,含 `variants`)、 + `packages/<包>/<版本>-<平台>.pdm`(包文件)、`packages/<包>/<版本>-<平台>.json`(元数据)。 +- 客户端把 `~/.pmm/mirror.ini` 指到该目录的 `packages`: + ```ini + [main] + registry = http://:8080/packages # 生产 https://pmm.parlz.com/mirror/packages + priority = 1 + ``` + 然后 `pmm install nodejs`(自动识平台)即可从这个静态仓库下载安装。 +- `mirror.ini` 也可从此目录按需生成(`registry` 指向 `/packages`)。 -把 `mirror.ini` 指向这个镜像后,`pmm install <包>` 装 latest,也支持 -**Python/pip 风格的版本限定**: +`pmm install <包>` 装 latest,也支持 **Python/pip 风格的版本限定**: ```sh pmm install nodejs # latest diff --git a/mirror/index.html b/mirror/index.html deleted file mode 100644 index 6cf28fd..0000000 --- a/mirror/index.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - -PMM 镜像站 - - - -
-

PMM 镜像站

- -
-
-
- - - -
- -
-

上传包(.pdm / .deb / .exe / .dmg / .tar.gz ...)

-
- - - - -
-
-
- -
-
-
- 客户端使用:pmm mirror use mainpmm install <包名>, - 或复制本页 mirror.ini~/.pmm/。 -
-
-
ParlzPackageManger · 镜像站
- - - - diff --git a/mirror/index.php b/mirror/index.php deleted file mode 100644 index 11ba6ec..0000000 --- a/mirror/index.php +++ /dev/null @@ -1,150 +0,0 @@ - browser UI (index.html) - * GET /api/packages.json, /packages.json -> index - * GET /mirror.ini -> client mirror config - * GET /packages/.json -> latest pointer - * GET /packages//-.json|.pdm -> per-variant metadata / download - * POST /upload -> upload a file, write registry variants - * - * Storage: packages//-.pdm, packages//-.json, - * packages/.json (latest + variants). - */ - -$ROOT = __DIR__; -$PKG = $ROOT . '/packages'; -$BASE = preg_replace('#/+\z#', '', getenv('PMM_BASE_URL') ?: 'https://pmm.parlz.com/mirror'); - -function base_url() { global $BASE; return $BASE; } -function pkgs_dir() { global $PKG; if (!is_dir($PKG)) mkdir($PKG, 0777, true); return $PKG; } - -function send_json($data, $code = 200) { - http_response_code($code); - header('Content-Type: application/json'); - echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - exit; -} -function send_file($path, $ctype = 'application/octet-stream', $code = 200) { - if (!is_file($path)) { http_response_code(404); echo 'not found'; exit; } - http_response_code($code); - header('Content-Type: ' . $ctype); - header('Access-Control-Allow-Origin: *'); - header('Content-Length: ' . filesize($path)); - readfile($path); - exit; -} - -function load_index() { - $out = ['generated' => gmdate('c'), 'packages' => []]; - foreach (glob(pkgs_dir() . '/*.json') as $f) { - if (is_dir($f)) continue; - $m = json_decode(file_get_contents($f), true); - if (!is_array($m) || empty($m['variants'])) continue; - $out['packages'][] = $m; - } - usort($out['packages'], fn($a, $b) => strcmp($a['name'] ?? '', $b['name'] ?? '')); - return $out; -} - -function send_index() { send_json(['generated' => gmdate('c'), 'packages' => load_index()['packages']]); } - -function mirror_ini() { - return "# 由 PMM 镜像站 " . base_url() . " 自动生成\n" - . "# 复制到 ~/.pmm/mirror.ini 即可 pmm install\n" - . "[main]\n" - . "registry = " . base_url() . "/packages\n" - . "priority = 1\n"; -} - -function handle_upload() { - $body = file_get_contents('php://input'); - if ($body === false) { http_response_code(400); echo 'no body'; exit; } - $H = fn($k) => trim($_SERVER['HTTP_' . strtoupper(str_replace('-', '_', $k))] ?? ''); - $pkg = $H('X-PKG-NAME'); - $ver = $H('X-PKG-VERSION'); - $os = $H('X-PKG-OS') ?: 'any'; - $desc = $H('X-PKG-DESC') ?: ("Uploaded via PMM mirror (" . base_url() . ")"); - if ($pkg === '') { http_response_code(400); echo 'no x-pkg-name'; exit; } - if ($ver === '') $ver = substr(hash('sha256', $body), 0, 8); - - $dir = pkgs_dir() . '/' . $pkg; - if (!is_dir($dir)) mkdir($dir, 0777, true); - $store = $ver . '-' . $os . '.pdm'; - file_put_contents($dir . '/' . $store, $body); - - $meta = [ - 'name' => $pkg, 'version' => $ver, 'file' => $store, - 'url' => base_url() . '/packages/' . rawurlencode($pkg) . '/' . rawurlencode($store), - 'sha256' => hash('sha256', $body), 'os' => $os, 'description' => $desc, - ]; - file_put_contents($dir . '/' . $ver . '-' . $os . '.json', json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - - // latest pointer + variants - $latestPath = pkgs_dir() . '/' . $pkg . '.json'; - $last = []; - if (is_file($latestPath)) { $last = json_decode(file_get_contents($latestPath), true) ?: []; } - $variants = is_array($last['variants'] ?? null) ? $last['variants'] : []; - $variants = array_values(array_filter($variants, fn($v) => !($v['version'] === $ver && $v['os'] === $os))); - $variants[] = ['version' => $ver, 'os' => $os, 'file' => $store, 'url' => $meta['url'], - 'sha256' => $meta['sha256'], 'description' => $desc]; - $latest = $meta + ['variants' => $variants]; - file_put_contents($latestPath, json_encode($latest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - - send_json(['ok' => true, 'file' => $store, 'version' => $ver, 'size' => strlen($body), - 'sha256' => $meta['sha256'], 'pkg' => $pkg, 'url' => $meta['url'], 'mirror' => base_url()]); -} - -function serve_single_json($rel) { - $f = pkgs_dir() . '/' . $rel; - if (is_file($f)) send_file($f, 'application/json'); - http_response_code(404); echo 'not found'; exit; -} - -// ------- router ------- -header('Access-Control-Allow-Origin: *'); -header('Access-Control-Allow-Headers: *'); -if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') { http_response_code(204); exit; } - -$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH); -$uri = rawurldecode($uri); - -/* strip the deployment sub-path (e.g. "/mirror" when the site is at - * https://host/mirror/index.php) so the router works under a subdirectory too. - * Only strip when the remainder starts with '/' or is empty — so "/mirror.ini" - * is NOT mistaken for the "/mirror" prefix. */ -$basePath = parse_url(base_url(), PHP_URL_PATH) ?: ''; -if ($basePath && $basePath !== '/' && strpos($uri, $basePath) === 0) { - $rest = substr($uri, strlen($basePath)); - if ($rest === '' || $rest[0] === '/') { - $uri = ($rest === '') ? '/' : $rest; - } -} - -if (preg_match('#^/upload$#', $uri)) { - if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { handle_upload(); } - else { http_response_code(405); echo 'method not allowed'; exit; } -} - -if ($uri === '/' || $uri === '/index.html' || $uri === '/index.php') { - send_file($ROOT . '/index.html', 'text/html; charset=utf-8'); -} -if ($uri === '/packages.json' || $uri === '/api/packages.json') send_index(); -if ($uri === '/mirror.ini' || $uri === '/mirror.conf') { header('Content-Type: text/plain'); echo mirror_ini(); exit; } - -// /api/packages/.json or /packages/.json -if (preg_match('#^/(?:api/)?packages/([^/]+)\.json$#', $uri, $m)) serve_single_json($m[1] . '.json'); -// /packages//-.json|.pdm -if (preg_match('#^/(?:api/)?packages/([^/]+)/([^/]+)\.(json|pdm)$#', $uri, $m)) { - $f = pkgs_dir() . '/' . $m[1] . '/' . $m[2] . '.' . $m[3]; - send_file($f, ($m[3] === 'json') ? 'application/json' : 'application/octet-stream'); -} -if ($uri === '/api/health') { echo 'ok'; exit; } -http_response_code(404); echo 'not found'; diff --git a/src/http.c b/src/http.c index 0d3d2f6..dbcfbe0 100644 --- a/src/http.c +++ b/src/http.c @@ -99,8 +99,12 @@ char *http_get(const char *url, int *status) { #include static unsigned long long now_ms(void) { return (unsigned long long)GetTickCount64(); } #else -#include -static unsigned long long now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (unsigned long long)ts.tv_sec * 1000 + (unsigned long long)ts.tv_nsec / 1000000; } +#include +static unsigned long long now_ms(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (unsigned long long)tv.tv_sec * 1000ULL + (unsigned long long)tv.tv_usec / 1000ULL; +} #endif /* human size: B / KB / MB / GB with 1 decimal */