CH-J Toolsserverio valdymas per SSH
Meniu
Paskelbtas šaltinis

CH-J Tools

Naršyti aplankus ir failus konkrečiai programai išleisti.

Atsisiųsti šaltinį ZIP
CH-J / Server Manager 406 failai
core-20261024T135205Z-7c487d719de2 Pridėta: · Pastatytas:
pluginRegistry.js Pridėta: Data neregistruota 6,9 KB · 162 eilutės
Kodas Atsiųsti failą
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.7.0";
9const ALLOWED_PERMISSIONS = new Set([
10 "session.read",
11 "system.metrics.read",
12 "system.memory.manage",
13 "disks.read",
14 "disks.manage",
15 "keys.generate",
16 "files.read",
17 "files.write",
18 "files.transfer",
19 "users.read",
20 "users.manage",
21 "nginx.read",
22 "nginx.manage",
23 "certbot.manage",
24 "mail.read",
25 "mail.manage",
26 "logs.read",
27 "remote.exec",
28 "local.hash"
29]);
31function validateManifest(input) {
32 if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Plugin manifest must be an object.");
33 const id = String(input.id || "").trim();
34 if (!PLUGIN_ID_PATTERN.test(id)) throw new Error("Invalid plugin ID.");
35 const version = String(input.version || "").trim();
36 parseVersion(version);
37 const entry = String(input.entry || "").replace(/\\/g, "/");
38 if (!entry || entry.startsWith("/") || entry.split("/").includes("..")) throw new Error("Invalid plugin entry path.");
39 const permissions = Array.isArray(input.permissions) ? [...new Set(input.permissions.map(String))] : [];
40 for (const permission of permissions) {
41 if (!ALLOWED_PERMISSIONS.has(permission)) throw new Error(`Unsupported plugin permission: ${permission}`);
42 }
43 const schemaVersion = Number(input.schemaVersion || 1);
44 if (schemaVersion !== 1) throw new Error(`Unsupported plugin manifest schema: ${schemaVersion}`);
45 const minAppVersion = String(input.minAppVersion || "0.0.1");
46 parseVersion(minAppVersion);
47 const pluginApi = String(input.pluginApi || "^1.0.0");
48 if (!/^\^1\.\d+\.\d+$/.test(pluginApi)) throw new Error(`Unsupported plugin API: ${pluginApi}`);
49 return {
50 schemaVersion,
51 id,
52 name: String(input.name || id).trim().slice(0, 100),
53 description: String(input.description || "").trim().slice(0, 500),
54 version,
55 publisher: String(input.publisher || "Unknown").trim().slice(0, 100),
56 entry,
57 pluginApi,
58 minAppVersion,
59 permissions
60 };
63function supportsPluginApi(range, supportedVersion = PLUGIN_API_VERSION) {
64 const required = String(range || "").match(/^\^1\.(\d+)\.(\d+)$/);
65 const supported = String(supportedVersion || "").match(/^1\.(\d+)\.(\d+)$/);
66 return Boolean(required && supported && (Number(required[1]) < Number(supported[1]) || (Number(required[1]) === Number(supported[1]) && Number(required[2]) <= Number(supported[2]))));
69class PluginRegistry {
70 constructor(rootDir, logger, options = {}) {
71 this.rootDir = path.join(rootDir, "plugins");
72 this.bundledRoot = options.bundledRoot || null;
73 this.logger = logger;
74 this.locations = new Map();
75 }
77 listInstalled() {
78 fs.mkdirSync(this.rootDir, { recursive: true });
79 this.locations.clear();
80 const plugins = [];
81 const roots = [{ root: this.rootDir, bundled: false }];
82 if (this.bundledRoot && fs.statSync(this.bundledRoot, { throwIfNoEntry: false })?.isDirectory()) roots.push({ root: this.bundledRoot, bundled: true });
83 const candidatesById = new Map();
84 for (const source of roots) for (const idEntry of fs.readdirSync(source.root, { withFileTypes: true })) {
85 if (!idEntry.isDirectory() || !PLUGIN_ID_PATTERN.test(idEntry.name)) continue;
86 const idRoot = path.join(source.root, idEntry.name);
87 const candidates = [];
88 for (const versionEntry of fs.readdirSync(idRoot, { withFileTypes: true })) {
89 if (!versionEntry.isDirectory() || versionEntry.name.startsWith(".")) continue;
90 const manifestPath = path.join(idRoot, versionEntry.name, "manifest.json");
91 try {
92 const manifest = validateManifest(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
93 if (manifest.id !== idEntry.name || manifest.version !== versionEntry.name) {
94 throw new Error("Plugin directory does not match its manifest.");
95 }
96 const entryPath = path.join(idRoot, versionEntry.name, manifest.entry);
97 if (!fs.statSync(entryPath).isFile()) throw new Error("Plugin entry file is missing.");
98 let installation = {};
99 try { installation = JSON.parse(fs.readFileSync(path.join(idRoot, versionEntry.name, ".installation.json"), "utf8")); } catch {}
100 candidates.push({
101 ...manifest,
102 bundled: source.bundled,
103 installedReleaseId: typeof installation.releaseId === "string" ? installation.releaseId : null,
104 installedSha512: /^[a-f0-9]{128}$/i.test(String(installation.sha512 || "")) ? String(installation.sha512).toLowerCase() : null,
105 installedAt: typeof installation.installedAt === "string" ? installation.installedAt : null,
106 installedPublishedAt: typeof installation.publishedAt === "string" ? installation.publishedAt : null,
107 _root: path.join(idRoot, versionEntry.name)
108 });
109 } catch (error) {
110 this.logger?.warn("Ignoring invalid installed plugin.", {
111 pluginId: idEntry.name,
112 version: versionEntry.name,
113 message: error?.message || String(error)
114 });
115 }
116 }
117 if (!candidatesById.has(idEntry.name)) candidatesById.set(idEntry.name, []);
118 candidatesById.get(idEntry.name).push(...candidates);
119 }
120 for (const candidates of candidatesById.values()) {
121 candidates.sort((left, right) => compareVersions(right.version, left.version) || Number(right.bundled) - Number(left.bundled));
122 if (!candidates[0]) continue;
123 const selected = candidates[0];
124 this.locations.set(`${selected.id}@${selected.version}`, selected._root);
125 const { _root, ...publicManifest } = selected;
126 plugins.push(publicManifest);
127 }
128 return plugins.sort((left, right) => left.name.localeCompare(right.name));
129 }
131 getInstalled(pluginId) {
132 const id = String(pluginId || "");
133 if (!PLUGIN_ID_PATTERN.test(id)) throw new Error("Invalid plugin ID.");
134 return this.listInstalled().find((plugin) => plugin.id === id) || null;
135 }
137 resolveEntry(pluginId) {
138 const manifest = this.getInstalled(pluginId);
139 if (!manifest) throw new Error("Plugin is not installed.");
140 const root = this.locations.get(`${manifest.id}@${manifest.version}`) || path.join(this.rootDir, manifest.id, manifest.version);
141 return { manifest, root, entryPath: path.join(root, ...manifest.entry.split("/")) };
142 }
144 removeInstalled(pluginId) {
145 const id = String(pluginId || "");
146 if (!PLUGIN_ID_PATTERN.test(id)) throw new Error("Invalid plugin ID.");
147 const directory = path.join(this.rootDir, id);
148 if (!fs.existsSync(directory)) return { removed: false, pluginId: id };
149 fs.rmSync(directory, { recursive: true, force: true });
150 this.logger?.info("Plugin removed.", { pluginId: id });
151 return { removed: true, pluginId: id };
152 }
155module.exports = {
156 ALLOWED_PERMISSIONS,
157 PLUGIN_API_VERSION,
158 PLUGIN_ID_PATTERN,
159 PluginRegistry,
160 supportsPluginApi,
161 validateManifest
162};
SHA-25639c95e8487b2c5c75be9cc001e0ec5d40f56989a676f363933ebe5d63aec2812

CH-J Proprietary Software License 1.14 · Šaltinis pateiktas pagal CH-J Proprietary Software License 1.14. Jo prieinamumas nekeičia licencijos sąlygų ir nesuteikia papildomų teisių.

Archyvas SHA-256476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6
Failai