CH-J Server Managerإدارة الخوادم عبر SSH
القائمة
المصدر المنشور

CH-J Server Manager

تصفّح الأدلة والملفات الخاصة بإصدار محدد من التطبيق.

تنزيل الشيفرة المصدرية بصيغة ZIP
CH-J / Server Manager 368 ملفات
core-20261017T201717Z-057348c4a70f تاريخ الإضافة: لم يُسجَّل التاريخ · تاريخ البناء:
pluginRegistry.js تاريخ الإضافة: لم يُسجَّل التاريخ 6.9 KB · 160 أسطر
الشيفرة تنزيل الملف
1"use strict";
3const fs = require("node:fs");
4const path = require("node:path");
5const { compareVersions, parseVersion } = require("../../shared/version");
7const PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9.-]{2,63}$/;
8const PLUGIN_API_VERSION = "1.4.0";
9const ALLOWED_PERMISSIONS = new Set([
10 "session.read",
11 "system.metrics.read",
12 "system.memory.manage",
13 "keys.generate",
14 "files.read",
15 "files.write",
16 "files.transfer",
17 "users.read",
18 "users.manage",
19 "nginx.read",
20 "nginx.manage",
21 "certbot.manage",
22 "mail.read",
23 "mail.manage",
24 "logs.read",
25 "remote.exec",
26 "local.hash"
27]);
29function validateManifest(input) {
30 if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Plugin manifest must be an object.");
31 const id = String(input.id || "").trim();
32 if (!PLUGIN_ID_PATTERN.test(id)) throw new Error("Invalid plugin ID.");
33 const version = String(input.version || "").trim();
34 parseVersion(version);
35 const entry = String(input.entry || "").replace(/\\/g, "/");
36 if (!entry || entry.startsWith("/") || entry.split("/").includes("..")) throw new Error("Invalid plugin entry path.");
37 const permissions = Array.isArray(input.permissions) ? [...new Set(input.permissions.map(String))] : [];
38 for (const permission of permissions) {
39 if (!ALLOWED_PERMISSIONS.has(permission)) throw new Error(`Unsupported plugin permission: ${permission}`);
40 }
41 const schemaVersion = Number(input.schemaVersion || 1);
42 if (schemaVersion !== 1) throw new Error(`Unsupported plugin manifest schema: ${schemaVersion}`);
43 const minAppVersion = String(input.minAppVersion || "0.0.1");
44 parseVersion(minAppVersion);
45 const pluginApi = String(input.pluginApi || "^1.0.0");
46 if (!/^\^1\.\d+\.\d+$/.test(pluginApi)) throw new Error(`Unsupported plugin API: ${pluginApi}`);
47 return {
48 schemaVersion,
49 id,
50 name: String(input.name || id).trim().slice(0, 100),
51 description: String(input.description || "").trim().slice(0, 500),
52 version,
53 publisher: String(input.publisher || "Unknown").trim().slice(0, 100),
54 entry,
55 pluginApi,
56 minAppVersion,
57 permissions
58 };
61function supportsPluginApi(range, supportedVersion = PLUGIN_API_VERSION) {
62 const required = String(range || "").match(/^\^1\.(\d+)\.(\d+)$/);
63 const supported = String(supportedVersion || "").match(/^1\.(\d+)\.(\d+)$/);
64 return Boolean(required && supported && (Number(required[1]) < Number(supported[1]) || (Number(required[1]) === Number(supported[1]) && Number(required[2]) <= Number(supported[2]))));
67class PluginRegistry {
68 constructor(rootDir, logger, options = {}) {
69 this.rootDir = path.join(rootDir, "plugins");
70 this.bundledRoot = options.bundledRoot || null;
71 this.logger = logger;
72 this.locations = new Map();
73 }
75 listInstalled() {
76 fs.mkdirSync(this.rootDir, { recursive: true });
77 this.locations.clear();
78 const plugins = [];
79 const roots = [{ root: this.rootDir, bundled: false }];
80 if (this.bundledRoot && fs.statSync(this.bundledRoot, { throwIfNoEntry: false })?.isDirectory()) roots.push({ root: this.bundledRoot, bundled: true });
81 const candidatesById = new Map();
82 for (const source of roots) for (const idEntry of fs.readdirSync(source.root, { withFileTypes: true })) {
83 if (!idEntry.isDirectory() || !PLUGIN_ID_PATTERN.test(idEntry.name)) continue;
84 const idRoot = path.join(source.root, idEntry.name);
85 const candidates = [];
86 for (const versionEntry of fs.readdirSync(idRoot, { withFileTypes: true })) {
87 if (!versionEntry.isDirectory() || versionEntry.name.startsWith(".")) continue;
88 const manifestPath = path.join(idRoot, versionEntry.name, "manifest.json");
89 try {
90 const manifest = validateManifest(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
91 if (manifest.id !== idEntry.name || manifest.version !== versionEntry.name) {
92 throw new Error("Plugin directory does not match its manifest.");
93 }
94 const entryPath = path.join(idRoot, versionEntry.name, manifest.entry);
95 if (!fs.statSync(entryPath).isFile()) throw new Error("Plugin entry file is missing.");
96 let installation = {};
97 try { installation = JSON.parse(fs.readFileSync(path.join(idRoot, versionEntry.name, ".installation.json"), "utf8")); } catch {}
98 candidates.push({
99 ...manifest,
100 bundled: source.bundled,
101 installedReleaseId: typeof installation.releaseId === "string" ? installation.releaseId : null,
102 installedSha512: /^[a-f0-9]{128}$/i.test(String(installation.sha512 || "")) ? String(installation.sha512).toLowerCase() : null,
103 installedAt: typeof installation.installedAt === "string" ? installation.installedAt : null,
104 installedPublishedAt: typeof installation.publishedAt === "string" ? installation.publishedAt : null,
105 _root: path.join(idRoot, versionEntry.name)
106 });
107 } catch (error) {
108 this.logger?.warn("Ignoring invalid installed plugin.", {
109 pluginId: idEntry.name,
110 version: versionEntry.name,
111 message: error?.message || String(error)
112 });
113 }
114 }
115 if (!candidatesById.has(idEntry.name)) candidatesById.set(idEntry.name, []);
116 candidatesById.get(idEntry.name).push(...candidates);
117 }
118 for (const candidates of candidatesById.values()) {
119 candidates.sort((left, right) => compareVersions(right.version, left.version) || Number(right.bundled) - Number(left.bundled));
120 if (!candidates[0]) continue;
121 const selected = candidates[0];
122 this.locations.set(`${selected.id}@${selected.version}`, selected._root);
123 const { _root, ...publicManifest } = selected;
124 plugins.push(publicManifest);
125 }
126 return plugins.sort((left, right) => left.name.localeCompare(right.name));
127 }
129 getInstalled(pluginId) {
130 const id = String(pluginId || "");
131 if (!PLUGIN_ID_PATTERN.test(id)) throw new Error("Invalid plugin ID.");
132 return this.listInstalled().find((plugin) => plugin.id === id) || null;
133 }
135 resolveEntry(pluginId) {
136 const manifest = this.getInstalled(pluginId);
137 if (!manifest) throw new Error("Plugin is not installed.");
138 const root = this.locations.get(`${manifest.id}@${manifest.version}`) || path.join(this.rootDir, manifest.id, manifest.version);
139 return { manifest, root, entryPath: path.join(root, ...manifest.entry.split("/")) };
140 }
142 removeInstalled(pluginId) {
143 const id = String(pluginId || "");
144 if (!PLUGIN_ID_PATTERN.test(id)) throw new Error("Invalid plugin ID.");
145 const directory = path.join(this.rootDir, id);
146 if (!fs.existsSync(directory)) return { removed: false, pluginId: id };
147 fs.rmSync(directory, { recursive: true, force: true });
148 this.logger?.info("Plugin removed.", { pluginId: id });
149 return { removed: true, pluginId: id };
150 }
153module.exports = {
154 ALLOWED_PERMISSIONS,
155 PLUGIN_API_VERSION,
156 PLUGIN_ID_PATTERN,
157 PluginRegistry,
158 supportsPluginApi,
159 validateManifest
160};
SHA-2566c4e585bfe290533fd9619af4b884f53e512d461d3f3a23872b0109cbeb7131e

CH-J Proprietary Software License 1.14 · تُتاح الشيفرة المصدرية بموجب CH-J Proprietary Software License 1.14. وإتاحتها لا تغيّر شروط الترخيص ولا تمنح حقوقًا إضافية.

SHA-256 للأرشيفcf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3
الملفات