CH-J Server ManagerSSH-এর মাধ্যমে সার্ভার ব্যবস্থাপনা
মেনু
প্রকাশিত সোর্স

CH-J Server Manager

নির্দিষ্ট একটি অ্যাপ্লিকেশন রিলিজের ডিরেক্টরি ও ফাইল ব্রাউজ করুন।

সোর্স ZIP ডাউনলোড করুন
CH-J / Server Manager 377 টি ফাইল
core-20261019T111616Z-160a00bfcddc যোগ করা হয়েছে: · বিল্ড হয়েছে:
registerCoreIpc.js যোগ করা হয়েছে: তারিখ নথিভুক্ত নেই ১৫.০ KB · 285 লাইন
কোড ফাইল ডাউনলোড করুন
1"use strict";
3const path = require("node:path");
4const fs = require("node:fs");
5const { ProfileTransfer, serializeProfiles, MAX_BYTES } = require("../profiles/profileTransfer");
6const { LICENSE_NAME, LICENSE_VERSION } = require("../legal/licenseAcceptance");
8const PRIVATE_KEY_DIALOG_COPY = Object.freeze({
9 cs: { title: "Vybrat soukromý SSH klíč", keys: "Soukromé SSH klíče", all: "Všechny soubory" },
10 de: { title: "Privaten SSH-Schlüssel auswählen", keys: "Private SSH-Schlüssel", all: "Alle Dateien" },
11 en: { title: "Select an SSH private key", keys: "SSH private keys", all: "All files" }
12});
14function registerCoreIpc(options) {
15 const {
16 ipcMain, shell, dialog, clipboard, app, configStore, pluginRegistry, pluginService, pluginRuntime, profileService,
17 sessionManager, vaultStore, biometricService, vaultLockController, updateService, updateInstallerLauncher, diagnosticsService, getMainWindow, logger
18 } = options;
19 const profileTransfer = new ProfileTransfer(profileService);
21 function assertTrustedSender(event) {
22 const mainWindow = getMainWindow();
23 if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id ||
24 (mainWindow.webContents.mainFrame && event.senderFrame !== mainWindow.webContents.mainFrame)) {
25 const error = new Error("Untrusted IPC sender.");
26 error.code = "UNTRUSTED_IPC_SENDER";
27 throw error;
28 }
29 }
31 function handle(channel, handler) {
32 ipcMain.handle(channel, async (event, payload) => {
33 assertTrustedSender(event);
34 try {
35 return await handler(payload);
36 } catch (error) {
37 logger?.error("IPC operation failed.", { channel, message: error?.message || String(error), code: error?.code });
38 throw error;
39 }
40 });
41 }
43 function send(channel, handler) {
44 ipcMain.on(channel, (event, payload) => {
45 try {
46 assertTrustedSender(event);
47 handler(payload);
48 } catch (error) {
49 logger?.warn("IPC event was rejected.", { channel, message: error?.message || String(error), code: error?.code });
50 }
51 });
52 }
54 function sessionResult(action) {
55 return async (payload) => {
56 try {
57 return { ok: true, value: await action(payload || {}) };
58 } catch (error) {
59 return {
60 ok: false,
61 error: typeof error?.toJSON === "function"
62 ? error.toJSON()
63 : { code: error?.code || "SSH_OPERATION_FAILED", message: error?.message || String(error) }
64 };
65 }
66 };
67 }
69 handle("core:getInfo", async () => ({
70 name: app.getName(),
71 version: app.getVersion(),
72 platform: process.platform,
73 arch: process.arch,
74 coreApi: "1.0.0",
75 pluginApi: options.pluginApiVersion || require("../plugins/pluginRegistry").PLUGIN_API_VERSION,
76 dataSchema: 1,
77 license: `${LICENSE_NAME} ${LICENSE_VERSION}`,
78 copyright: "Copyright 2026 Josef Chudy"
79 }));
80 handle("legal:open", async (payload = {}) => {
81 const documents = {
82 documentation: "README.md",
83 safetyCzech: "provozni-bezpecnost-a-zalohovani.md",
84 safetyEnglish: "operational-safety-and-backup-guidance.md",
85 safetyGerman: "betriebssicherheit-und-datensicherung.md"
86 };
87 const documentKey = String(payload.document || "");
88 if (Object.hasOwn(documents, documentKey)) {
89 const docsRoot = path.join(app.isPackaged ? process.resourcesPath : app.getAppPath(), "docs");
90 const error = await shell.openPath(path.join(docsRoot, documents[documentKey]));
91 if (error) throw new Error(`The documentation could not be opened: ${error}`);
92 return { ok: true };
93 }
94 const names = {
95 application: "APPLICATION_LICENSE.txt",
96 notice: "NOTICE.txt",
97 thirdParty: "THIRD_PARTY_NOTICES.txt",
98 thirdPartyBundle: "THIRD_PARTY_LICENSES.zip",
99 electron: "ELECTRON_LICENSE.txt",
100 chromium: "CHROMIUM_LICENSES.html"
101 };
102 const filename = Object.hasOwn(names, documentKey) ? names[documentKey] : null;
103 if (!filename) throw new Error("Unknown legal document.");
104 const root = app.isPackaged ? path.join(process.resourcesPath, "licenses") : app.getAppPath();
105 const developmentFiles = {
106 APPLICATION_LICENSE: "LICENSE",
107 NOTICE: "NOTICE",
108 THIRD_PARTY_NOTICES: "THIRD_PARTY_NOTICES.txt",
109 THIRD_PARTY_LICENSES: "THIRD_PARTY_LICENSES.zip",
110 ELECTRON_LICENSE: path.join("node_modules", "electron", "dist", "LICENSE"),
111 CHROMIUM_LICENSES: path.join("node_modules", "electron", "dist", "LICENSES.chromium.html")
112 };
113 const stem = path.parse(filename).name;
114 const filePath = app.isPackaged ? path.join(root, filename) : path.join(root, developmentFiles[stem]);
115 const error = await shell.openPath(filePath);
116 if (error) throw new Error(`The legal document could not be opened: ${error}`);
117 return { ok: true };
118 });
119 handle("config:get", async () => configStore.get());
120 handle("config:update", async (payload = {}) => {
121 if (payload.security) {
122 await vaultStore.verifyPassword(payload.masterPassword);
123 if (payload.security.requireSystemAuthentication && !(await biometricService.getStatus()).available) {
124 throw new Error("BIOMETRIC_UNAVAILABLE");
125 }
126 }
127 const config = configStore.update(payload);
128 pluginRuntime.notifyLanguageChanged(config.ui.language);
129 return config;
130 });
131 handle("plugins:list", async () => pluginRegistry.listInstalled());
132 handle("plugins:getState", async () => pluginService.getState());
133 handle("plugins:checkCatalog", async () => pluginService.checkCatalog());
134 handle("plugins:install", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.install(payload.releaseId); });
135 handle("plugins:uninstall", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.uninstall(payload.pluginId); });
136 handle("plugins:open", async (payload = {}) => pluginService.open(payload.pluginId));
137 handle("vault:status", async () => vaultStore.status());
138 handle("vault:create", async (payload = {}) => { const status = await vaultStore.create(payload.password); vaultLockController?.activity(); return status; });
139 handle("vault:unlock", async (payload = {}) => { biometricService?.cancel(); const status = await vaultStore.unlock(payload.password); biometricService?.disarmAutoUnlock?.(); vaultLockController?.activity(); return status; });
140 handle("biometrics:status", sessionResult(() => biometricService.getStatus()));
141 handle("biometrics:enable", sessionResult(() => biometricService.enable()));
142 handle("biometrics:disable", sessionResult(() => biometricService.disable()));
143 handle("biometrics:unlock", sessionResult(async () => {
144 const status = await biometricService.unlock(); vaultLockController?.activity(); return status;
145 }));
146 handle("biometrics:autoUnlock", sessionResult(async () => {
147 const window = getMainWindow?.();
148 if (window && (window.isDestroyed() || !window.isVisible() || window.isMinimized())) return { attempted: false, reason: "WINDOW_NOT_READY" };
149 const result = await biometricService.autoUnlock();
150 if (result.status?.unlocked) vaultLockController?.activity();
151 return result;
152 }));
153 handle("biometrics:authenticate", sessionResult(() => biometricService.authenticateSensitiveAction()));
154 handle("vault:lock", async () => {
155 if (vaultLockController) return vaultLockController.lock();
156 pluginRuntime.closeAll();
157 await sessionManager.disconnectAll("vault-lock");
158 return vaultStore.lock();
159 });
160 handle("vault:reset", async (payload = {}) => {
161 if (payload.confirmation !== "SMAZAT") throw Object.assign(new Error("Vault reset confirmation is invalid."), { code: "VAULT_RESET_CONFIRMATION_REQUIRED" });
162 if (vaultStore.status().unlocked) await biometricService?.requireSensitiveAction();
163 await biometricService?.disable({ reset: true });
164 pluginRuntime.closeAll();
165 await sessionManager.disconnectAll("vault-reset");
166 const status = vaultStore.reset(payload.confirmation);
167 logger?.warn("Encrypted user vault was reset by the user.");
168 return status;
169 });
170 handle("profiles:list", async () => profileService.list());
171 handle("profiles:export", async (payload = {}) => {
172 const format = payload.format || "json";
173 const profiles = profileService.list();
174 const selected = payload.ids === undefined ? profiles : profiles.filter((profile) => Array.isArray(payload.ids) && payload.ids.includes(profile.id));
175 if (!selected.length) throw new Error("Select at least one server to export.");
176 const content = serializeProfiles(selected, format);
177 const result = await dialog.showSaveDialog(getMainWindow(), {
178 defaultPath: `chj-server-profiles.${format}`,
179 filters: [{ name: format.toUpperCase(), extensions: [format] }]
180 });
181 if (result.canceled || !result.filePath) return { canceled: true };
182 await fs.promises.writeFile(result.filePath, content, { encoding: "utf8", mode: 0o600 });
183 return { canceled: false, count: selected.length };
184 });
185 handle("profiles:previewImport", async () => {
186 profileService.list(); // Require an unlocked vault before opening the picker.
187 profileTransfer.cancel();
188 const result = await dialog.showOpenDialog(getMainWindow(), {
189 properties: ["openFile"], filters: [{ name: "JSON / CSV", extensions: ["json", "csv"] }]
190 });
191 if (result.canceled || !result.filePaths[0]) return { canceled: true };
192 const file = await fs.promises.open(result.filePaths[0], "r");
193 try {
194 const buffer = Buffer.alloc(MAX_BYTES + 1);
195 let length = 0;
196 while (length < buffer.length) {
197 const read = await file.read(buffer, length, buffer.length - length, null);
198 if (!read.bytesRead) break;
199 length += read.bytesRead;
200 }
201 if (length > MAX_BYTES) throw new Error("Profile file exceeds the 5 MiB limit.");
202 const content = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length));
203 return { canceled: false, ...profileTransfer.preview(content) };
204 } finally { await file.close(); }
205 });
206 handle("profiles:commitImport", async (payload = {}) => {
207 await biometricService?.requireSensitiveAction();
208 return profileTransfer.commit(payload.token, payload.policy);
209 });
210 handle("profiles:cancelImport", async () => { profileTransfer.cancel(); return { canceled: true }; });
211 handle("profiles:save", async (payload = {}) => profileService.save(payload));
212 handle("profiles:delete", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return profileService.delete(String(payload.id || "")); });
213 handle("profiles:selectPrivateKey", async () => {
214 const mainWindow = getMainWindow();
215 const copy = PRIVATE_KEY_DIALOG_COPY[configStore.get().ui.language] || PRIVATE_KEY_DIALOG_COPY.cs;
216 const result = await dialog.showOpenDialog(mainWindow, {
217 title: copy.title,
218 properties: ["openFile"],
219 filters: [{ name: copy.keys, extensions: ["pem", "key", "ppk"] }, { name: copy.all, extensions: ["*"] }]
220 });
221 return { canceled: result.canceled, path: result.filePaths[0] || null };
222 });
223 handle("ssh:list", async () => sessionManager.list());
224 const diagnosticsResult = (action) => async (payload = {}) => {
225 try { return { ok: true, value: await action(payload) }; }
226 catch (error) { return { ok: false, error: { code: error.code || "DIAGNOSTICS_ERROR", message: String(error.message || error).slice(0, 400) } }; }
227 };
228 handle("diagnostics:resolve", diagnosticsResult((payload) => diagnosticsService.resolve(payload)));
229 handle("diagnostics:start", diagnosticsResult((payload) => diagnosticsService.start(payload)));
230 handle("diagnostics:cancel", diagnosticsResult((payload) => diagnosticsService.cancel(payload.id, payload.tool)));
231 handle("diagnostics:get", diagnosticsResult((payload) => diagnosticsService.get(payload.id)));
232 handle("diagnostics:history", diagnosticsResult(() => diagnosticsService.history.list()));
233 handle("diagnostics:removeHistory", diagnosticsResult((payload) => diagnosticsService.history.remove(payload.id)));
234 handle("diagnostics:copy", diagnosticsResult((payload) => {
235 clipboard.writeText(diagnosticsService.export(payload.id, payload.format || "txt"));
236 return { copied: true };
237 }));
238 handle("diagnostics:export", diagnosticsResult(async (payload) => {
239 const content = diagnosticsService.export(payload.id, payload.format);
240 const result = await dialog.showSaveDialog(getMainWindow(), { defaultPath: `diagnostics.${payload.format}`, filters: [{ name: payload.format.toUpperCase(), extensions: [payload.format] }] });
241 if (result.canceled || !result.filePath) return { canceled: true };
242 await require("node:fs").promises.writeFile(result.filePath, content, { mode: 0o600 });
243 return { canceled: false };
244 }));
245 handle("diagnostics:import", diagnosticsResult(async () => {
246 const result = await dialog.showOpenDialog(getMainWindow(), { properties: ["openFile"], filters: [{ name: "Diagnostics JSON", extensions: ["json"] }] });
247 if (result.canceled || !result.filePaths[0]) return { canceled: true };
248 const fs = require("node:fs"); const stat = await fs.promises.lstat(result.filePaths[0]);
249 if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw Object.assign(new Error("IMPORT_LIMIT"), { code: "IMPORT_LIMIT" });
250 const id = diagnosticsService.history.import(await fs.promises.readFile(result.filePaths[0], "utf8"));
251 return { id };
252 }));
253 handle("ssh:connect", sessionResult((payload) => sessionManager.connect(payload)));
254 handle("ssh:trustHostKey", sessionResult((payload) => sessionManager.trustPending(payload)));
255 handle("ssh:disconnect", sessionResult((payload) => sessionManager.disconnect(payload.sessionId, "user")));
256 handle("ssh:resize", sessionResult((payload) => sessionManager.resize(payload.sessionId, payload.cols, payload.rows)));
257 send("ssh:input", (payload = {}) => sessionManager.write(payload.sessionId, payload.data));
258 handle("updates:getState", async () => updateService.getState());
259 handle("updates:check", async () => updateService.check());
260 handle("updates:select", async (payload = {}) => updateService.select(payload.id));
261 handle("updates:download", async () => updateService.download());
262 handle("updates:install", async () => {
263 await biometricService?.requireSensitiveAction();
264 // The renderer supplies neither a path nor a verification flag. The main
265 // process re-hashes and re-verifies its internally tracked artifact here.
266 const filePath = await updateService.prepareInstallerLaunch();
267 const result = await updateInstallerLauncher.install(filePath);
268 updateService.markInstallerLaunched();
269 if (result.restartApplication) {
270 setTimeout(() => {
271 app.relaunch();
272 app.quit();
273 }, 250).unref?.();
274 }
275 return { ok: true, installed: result.installed === true, restarting: result.restartApplication === true };
276 });
277 handle("updates:reveal", async () => {
278 const filePath = updateService.getVerifiedDownloadPath();
279 if (!filePath) throw new Error("No verified update has been downloaded.");
280 shell.showItemInFolder(filePath);
281 return { ok: true };
282 });
285module.exports = { registerCoreIpc };
SHA-256de8c503c6dd20a3874a8c2bf6df1bdce2ab03927c2b48268e0d70f7898de54a4

CH-J Proprietary Software License 1.14 · সোর্স CH-J Proprietary Software License 1.14-এর অধীনে দেওয়া হয়। এর প্রাপ্যতা লাইসেন্সের শর্ত বদলায় না এবং অতিরিক্ত কোনো অধিকারও দেয় না।

আর্কাইভ SHA-256a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a
ফাইল