CH-J Server ManagerSerververwaltung über SSH
Menü
Veröffentlichte Quellen

CH-J Server Manager

Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.

Quellen als ZIP herunterladen
CH-J Proprietary Software License 1.14

Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.

12,2 KB · 235 ZeilenDatei herunterladen
1"use strict";
3const path = require("node:path");
4const { LICENSE_NAME, LICENSE_VERSION } = require("../legal/licenseAcceptance");
6const PRIVATE_KEY_DIALOG_COPY = Object.freeze({
7 cs: { title: "Vybrat soukromý SSH klíč", keys: "Soukromé SSH klíče", all: "Všechny soubory" },
8 de: { title: "Privaten SSH-Schlüssel auswählen", keys: "Private SSH-Schlüssel", all: "Alle Dateien" },
9 en: { title: "Select an SSH private key", keys: "SSH private keys", all: "All files" }
10});
12function registerCoreIpc(options) {
13 const {
14 ipcMain, shell, dialog, clipboard, app, configStore, pluginRegistry, pluginService, pluginRuntime, profileService,
15 sessionManager, vaultStore, biometricService, vaultLockController, updateService, updateInstallerLauncher, diagnosticsService, getMainWindow, logger
16 } = options;
18 function assertTrustedSender(event) {
19 const mainWindow = getMainWindow();
20 if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id ||
21 (mainWindow.webContents.mainFrame && event.senderFrame !== mainWindow.webContents.mainFrame)) {
22 const error = new Error("Untrusted IPC sender.");
23 error.code = "UNTRUSTED_IPC_SENDER";
24 throw error;
25 }
26 }
28 function handle(channel, handler) {
29 ipcMain.handle(channel, async (event, payload) => {
30 assertTrustedSender(event);
31 try {
32 return await handler(payload);
33 } catch (error) {
34 logger?.error("IPC operation failed.", { channel, message: error?.message || String(error), code: error?.code });
35 throw error;
36 }
37 });
38 }
40 function send(channel, handler) {
41 ipcMain.on(channel, (event, payload) => {
42 try {
43 assertTrustedSender(event);
44 handler(payload);
45 } catch (error) {
46 logger?.warn("IPC event was rejected.", { channel, message: error?.message || String(error), code: error?.code });
47 }
48 });
49 }
51 function sessionResult(action) {
52 return async (payload) => {
53 try {
54 return { ok: true, value: await action(payload || {}) };
55 } catch (error) {
56 return {
57 ok: false,
58 error: typeof error?.toJSON === "function"
59 ? error.toJSON()
60 : { code: error?.code || "SSH_OPERATION_FAILED", message: error?.message || String(error) }
61 };
62 }
63 };
64 }
66 handle("core:getInfo", async () => ({
67 name: app.getName(),
68 version: app.getVersion(),
69 platform: process.platform,
70 arch: process.arch,
71 coreApi: "1.0.0",
72 pluginApi: options.pluginApiVersion || require("../plugins/pluginRegistry").PLUGIN_API_VERSION,
73 dataSchema: 1,
74 license: `${LICENSE_NAME} ${LICENSE_VERSION}`,
75 copyright: "Copyright 2026 Josef Chudy"
76 }));
77 handle("legal:open", async (payload = {}) => {
78 const documents = {
79 documentation: "README.md",
80 safetyCzech: "provozni-bezpecnost-a-zalohovani.md",
81 safetyEnglish: "operational-safety-and-backup-guidance.md",
82 safetyGerman: "betriebssicherheit-und-datensicherung.md"
83 };
84 const documentKey = String(payload.document || "");
85 if (Object.hasOwn(documents, documentKey)) {
86 const docsRoot = path.join(app.isPackaged ? process.resourcesPath : app.getAppPath(), "docs");
87 const error = await shell.openPath(path.join(docsRoot, documents[documentKey]));
88 if (error) throw new Error(`The documentation could not be opened: ${error}`);
89 return { ok: true };
90 }
91 const names = {
92 application: "APPLICATION_LICENSE.txt",
93 notice: "NOTICE.txt",
94 thirdParty: "THIRD_PARTY_NOTICES.txt",
95 thirdPartyBundle: "THIRD_PARTY_LICENSES.zip",
96 electron: "ELECTRON_LICENSE.txt",
97 chromium: "CHROMIUM_LICENSES.html"
98 };
99 const filename = Object.hasOwn(names, documentKey) ? names[documentKey] : null;
100 if (!filename) throw new Error("Unknown legal document.");
101 const root = app.isPackaged ? path.join(process.resourcesPath, "licenses") : app.getAppPath();
102 const developmentFiles = {
103 APPLICATION_LICENSE: "LICENSE",
104 NOTICE: "NOTICE",
105 THIRD_PARTY_NOTICES: "THIRD_PARTY_NOTICES.txt",
106 THIRD_PARTY_LICENSES: "THIRD_PARTY_LICENSES.zip",
107 ELECTRON_LICENSE: path.join("node_modules", "electron", "dist", "LICENSE"),
108 CHROMIUM_LICENSES: path.join("node_modules", "electron", "dist", "LICENSES.chromium.html")
109 };
110 const stem = path.parse(filename).name;
111 const filePath = app.isPackaged ? path.join(root, filename) : path.join(root, developmentFiles[stem]);
112 const error = await shell.openPath(filePath);
113 if (error) throw new Error(`The legal document could not be opened: ${error}`);
114 return { ok: true };
115 });
116 handle("config:get", async () => configStore.get());
117 handle("config:update", async (payload = {}) => {
118 if (payload.security) {
119 await vaultStore.verifyPassword(payload.masterPassword);
120 if (payload.security.requireSystemAuthentication && !(await biometricService.getStatus()).available) {
121 throw new Error("BIOMETRIC_UNAVAILABLE");
122 }
123 }
124 const config = configStore.update(payload);
125 pluginRuntime.notifyLanguageChanged(config.ui.language);
126 return config;
127 });
128 handle("plugins:list", async () => pluginRegistry.listInstalled());
129 handle("plugins:getState", async () => pluginService.getState());
130 handle("plugins:checkCatalog", async () => pluginService.checkCatalog());
131 handle("plugins:install", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.install(payload.releaseId); });
132 handle("plugins:uninstall", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.uninstall(payload.pluginId); });
133 handle("plugins:open", async (payload = {}) => pluginService.open(payload.pluginId));
134 handle("vault:status", async () => vaultStore.status());
135 handle("vault:create", async (payload = {}) => { const status = await vaultStore.create(payload.password); vaultLockController?.activity(); return status; });
136 handle("vault:unlock", async (payload = {}) => { biometricService?.cancel(); const status = await vaultStore.unlock(payload.password); vaultLockController?.activity(); return status; });
137 handle("biometrics:status", sessionResult(() => biometricService.getStatus()));
138 handle("biometrics:enable", sessionResult(() => biometricService.enable()));
139 handle("biometrics:disable", sessionResult(() => biometricService.disable()));
140 handle("biometrics:unlock", sessionResult(async () => {
141 const status = await biometricService.unlock(); vaultLockController?.activity(); return status;
142 }));
143 handle("biometrics:authenticate", sessionResult(() => biometricService.authenticateSensitiveAction()));
144 handle("vault:lock", async () => {
145 if (vaultLockController) return vaultLockController.lock();
146 pluginRuntime.closeAll();
147 await sessionManager.disconnectAll("vault-lock");
148 return vaultStore.lock();
149 });
150 handle("vault:reset", async (payload = {}) => {
151 if (payload.confirmation !== "SMAZAT") throw Object.assign(new Error("Vault reset confirmation is invalid."), { code: "VAULT_RESET_CONFIRMATION_REQUIRED" });
152 if (vaultStore.status().unlocked) await biometricService?.requireSensitiveAction();
153 await biometricService?.disable({ reset: true });
154 pluginRuntime.closeAll();
155 await sessionManager.disconnectAll("vault-reset");
156 const status = vaultStore.reset(payload.confirmation);
157 logger?.warn("Encrypted user vault was reset by the user.");
158 return status;
159 });
160 handle("profiles:list", async () => profileService.list());
161 handle("profiles:save", async (payload = {}) => profileService.save(payload));
162 handle("profiles:delete", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return profileService.delete(String(payload.id || "")); });
163 handle("profiles:selectPrivateKey", async () => {
164 const mainWindow = getMainWindow();
165 const copy = PRIVATE_KEY_DIALOG_COPY[configStore.get().ui.language] || PRIVATE_KEY_DIALOG_COPY.cs;
166 const result = await dialog.showOpenDialog(mainWindow, {
167 title: copy.title,
168 properties: ["openFile"],
169 filters: [{ name: copy.keys, extensions: ["pem", "key", "ppk"] }, { name: copy.all, extensions: ["*"] }]
170 });
171 return { canceled: result.canceled, path: result.filePaths[0] || null };
172 });
173 handle("ssh:list", async () => sessionManager.list());
174 const diagnosticsResult = (action) => async (payload = {}) => {
175 try { return { ok: true, value: await action(payload) }; }
176 catch (error) { return { ok: false, error: { code: error.code || "DIAGNOSTICS_ERROR", message: String(error.message || error).slice(0, 400) } }; }
177 };
178 handle("diagnostics:resolve", diagnosticsResult((payload) => diagnosticsService.resolve(payload)));
179 handle("diagnostics:start", diagnosticsResult((payload) => diagnosticsService.start(payload)));
180 handle("diagnostics:cancel", diagnosticsResult((payload) => diagnosticsService.cancel(payload.id, payload.tool)));
181 handle("diagnostics:get", diagnosticsResult((payload) => diagnosticsService.get(payload.id)));
182 handle("diagnostics:history", diagnosticsResult(() => diagnosticsService.history.list()));
183 handle("diagnostics:removeHistory", diagnosticsResult((payload) => diagnosticsService.history.remove(payload.id)));
184 handle("diagnostics:copy", diagnosticsResult((payload) => {
185 clipboard.writeText(diagnosticsService.export(payload.id, payload.format || "txt"));
186 return { copied: true };
187 }));
188 handle("diagnostics:export", diagnosticsResult(async (payload) => {
189 const content = diagnosticsService.export(payload.id, payload.format);
190 const result = await dialog.showSaveDialog(getMainWindow(), { defaultPath: `diagnostics.${payload.format}`, filters: [{ name: payload.format.toUpperCase(), extensions: [payload.format] }] });
191 if (result.canceled || !result.filePath) return { canceled: true };
192 await require("node:fs").promises.writeFile(result.filePath, content, { mode: 0o600 });
193 return { canceled: false };
194 }));
195 handle("diagnostics:import", diagnosticsResult(async () => {
196 const result = await dialog.showOpenDialog(getMainWindow(), { properties: ["openFile"], filters: [{ name: "Diagnostics JSON", extensions: ["json"] }] });
197 if (result.canceled || !result.filePaths[0]) return { canceled: true };
198 const fs = require("node:fs"); const stat = await fs.promises.lstat(result.filePaths[0]);
199 if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw Object.assign(new Error("IMPORT_LIMIT"), { code: "IMPORT_LIMIT" });
200 const id = diagnosticsService.history.import(await fs.promises.readFile(result.filePaths[0], "utf8"));
201 return { id };
202 }));
203 handle("ssh:connect", sessionResult((payload) => sessionManager.connect(payload)));
204 handle("ssh:trustHostKey", sessionResult((payload) => sessionManager.trustPending(payload)));
205 handle("ssh:disconnect", sessionResult((payload) => sessionManager.disconnect(payload.sessionId, "user")));
206 handle("ssh:resize", sessionResult((payload) => sessionManager.resize(payload.sessionId, payload.cols, payload.rows)));
207 send("ssh:input", (payload = {}) => sessionManager.write(payload.sessionId, payload.data));
208 handle("updates:getState", async () => updateService.getState());
209 handle("updates:check", async () => updateService.check());
210 handle("updates:select", async (payload = {}) => updateService.select(payload.id));
211 handle("updates:download", async () => updateService.download());
212 handle("updates:install", async () => {
213 await biometricService?.requireSensitiveAction();
214 // The renderer supplies neither a path nor a verification flag. The main
215 // process re-hashes and re-verifies its internally tracked artifact here.
216 const filePath = await updateService.prepareInstallerLaunch();
217 const result = await updateInstallerLauncher.install(filePath);
218 updateService.markInstallerLaunched();
219 if (result.restartApplication) {
220 setTimeout(() => {
221 app.relaunch();
222 app.quit();
223 }, 250).unref?.();
224 }
225 return { ok: true, installed: result.installed === true, restarting: result.restartApplication === true };
226 });
227 handle("updates:reveal", async () => {
228 const filePath = updateService.getVerifiedDownloadPath();
229 if (!filePath) throw new Error("No verified update has been downloaded.");
230 shell.showItemInFolder(filePath);
231 return { ok: true };
232 });
235module.exports = { registerCoreIpc };

SHA-256: 721d3f635c587e475ee10e11593f6c6973dd56d7ef8709c4ff2c886233bccbfc

SHA-256 des Archivs: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0