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.

14,7 KB · 328 ZeilenDatei herunterladen
1"use strict";
3const path = require("node:path");
4const { app, BrowserWindow, clipboard, dialog, ipcMain, protocol, shell } = require("electron");
5const { createMainWindow, hardenSession } = require("./bootstrap/createMainWindow");
6const { ConfigStore } = require("./config/configStore");
7const { registerCoreIpc } = require("./ipc/registerCoreIpc");
8const { Logger } = require("./logging/logger");
9const { LogService } = require("./logging/logService");
10const { RemoteFileService } = require("./files/remoteFileService");
11const { LocalHashService } = require("./hashing/localHashService");
12const { DiagnosticsService } = require("./diagnostics/diagnosticsService");
13const { PLUGIN_API_VERSION, PluginRegistry } = require("./plugins/pluginRegistry");
14const { PluginCatalogProvider } = require("./plugins/pluginCatalogProvider");
15const { PluginInstaller } = require("./plugins/pluginInstaller");
16const { PluginRuntime } = require("./plugins/pluginRuntime");
17const { PluginService } = require("./plugins/pluginService");
18const { KeyGeneratorService } = require("./keys/keyGeneratorService");
19const { ProfileService } = require("./profiles/profileService");
20const { createTestHttpsFetch } = require("./security/testHttpsFetch");
21const { VaultStore } = require("./security/vaultStore");
22const { BiometricService } = require("./security/biometrics/biometricService");
23const { PlatformAdapter } = require("./security/biometrics/platformAdapter");
24const { VaultLockController } = require("./security/vaultLockController");
25const { SessionManager } = require("./sessions/sessionManager");
26const { HashUrlProvider } = require("./updates/hashUrlProvider");
27const { OpenPgpVerifier } = require("./updates/openPgpVerifier");
28const { UpdateInstallerLauncher } = require("./updates/updateInstallerLauncher");
29const { UpdateService } = require("./updates/updateService");
30const buildInfo = require("../shared/buildInfo.json");
31const { LicenseAcceptance } = require("./legal/licenseAcceptance");
32const { showLicenseWindow } = require("./legal/showLicenseWindow");
34app.setName("CH-J Server Manager");
35const buildSmokeUserData = process.env.CHJ_BUILD_SMOKE_USER_DATA_DIR;
36if (buildSmokeUserData && path.isAbsolute(buildSmokeUserData)) app.setPath("userData", buildSmokeUserData);
37if (process.platform === "linux") {
38 app.setDesktopName("ch-j-server-manager.desktop");
39 app.commandLine.appendSwitch("class", "ch-j-server-manager");
41if (process.platform === "win32") app.setAppUserModelId("de.ch-j.servermanager");
42protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);
44let mainWindow = null;
45let logger = null;
46let updateService = null;
47let sessionManager = null;
48let vaultStore = null;
49let diagnosticsService = null;
50let biometricService = null;
51let vaultLockController = null;
52let licenseWindow = null;
53let coreReady = false;
55function watchWindow(window) {
56 window.webContents.on("before-input-event", () => vaultLockController?.activity());
57 window.webContents.on("before-mouse-event", () => vaultLockController?.activity());
58 window.on("blur", () => {
59 const checkFocus = () => {
60 if (BrowserWindow.getFocusedWindow()) return;
61 if (biometricService?.active) { setTimeout(checkFocus, 250).unref(); return; }
62 void vaultLockController?.lostFocus().catch(error => logger?.warn("Vault focus lock failed.", { message: error.message }));
63 };
64 setTimeout(checkFocus, 250).unref();
65 });
66 window.on("closed", () => { if (window === mainWindow) diagnosticsService?.stopAll(); });
69const hasSingleInstanceLock = buildSmokeUserData ? true : app.requestSingleInstanceLock();
70if (!hasSingleInstanceLock) app.quit();
72function focusMainWindow() {
73 if (licenseWindow && !licenseWindow.isDestroyed()) { licenseWindow.show(); licenseWindow.focus(); return; }
74 if (!mainWindow || mainWindow.isDestroyed()) return;
75 if (mainWindow.isMinimized()) mainWindow.restore();
76 mainWindow.show();
77 mainWindow.focus();
80async function bootstrap() {
81 const storageRoot = app.getPath("userData");
82 const appIcon = path.join(__dirname, "..", "..", "build", "icon.png");
83 logger = new Logger(storageRoot);
84 const logService = new LogService(logger.logPath);
85 const configStore = new ConfigStore(storageRoot);
86 configStore.load();
87 hardenSession(logger);
88 const acceptance = new LicenseAcceptance({
89 storageRoot,
90 licensePath: app.isPackaged ? path.join(process.resourcesPath, "licenses", "APPLICATION_LICENSE.txt") : path.join(app.getAppPath(), "LICENSE"),
91 docsRoot: path.join(app.isPackaged ? process.resourcesPath : app.getAppPath(), "docs"),
92 appVersion: app.getVersion(), buildId: buildInfo.buildId
93 });
94 if (!acceptance.isAccepted()) {
95 const accepted = await showLicenseWindow({ BrowserWindow, ipcMain, acceptance, language: configStore.get().ui.language,
96 onWindow: window => { licenseWindow = window; } });
97 if (!accepted) { app.quit(); return; }
98 }
99 diagnosticsService = new DiagnosticsService({ storageRoot });
100 const testUpdateFetch = createTestHttpsFetch({
101 allowedBaseUrls: configStore.get().updates.baseUrls,
102 // Jen starý host používá dočasnou alfa výjimku. Kanonický www host
103 // musí vždy projít standardním ověřením veřejného certifikátu.
104 insecureBaseUrls: ["https://sm.ch-j.de/"]
105 });
106 const pluginRegistry = new PluginRegistry(storageRoot, logger, { bundledRoot: path.join(__dirname, "firstPartyPlugins") });
107 vaultStore = new VaultStore(storageRoot);
108 const profileService = new ProfileService(vaultStore);
109 sessionManager = new SessionManager({ profileService, logger });
110 const keyGeneratorService = new KeyGeneratorService({
111 selectSavePath: async (suggestedName) => {
112 const result = await dialog.showSaveDialog(mainWindow, { title: "Uložit pár SSH klíčů", defaultPath: suggestedName });
113 return { canceled: result.canceled, path: result.filePath || null };
114 }
115 });
116 const remoteFileService = new RemoteFileService({
117 sessionManager,
118 selectUploadPath: async () => {
119 const result = await dialog.showOpenDialog(mainWindow, { title: "Vybrat soubory nebo složky k nahrání", properties: ["openFile", "openDirectory", "multiSelections"] });
120 return { canceled: result.canceled, paths: result.filePaths };
121 },
122 selectDownloadPath: async (suggestedName) => {
123 const result = await dialog.showSaveDialog(mainWindow, { title: "Uložit stažený soubor", defaultPath: suggestedName });
124 return { canceled: result.canceled, path: result.filePath || null };
125 },
126 selectDownloadDirectory: async () => {
127 const result = await dialog.showOpenDialog(mainWindow, { title: "Vybrat složku pro stažení", properties: ["openDirectory", "createDirectory"] });
128 return { canceled: result.canceled, path: result.filePaths[0] || null };
129 },
130 selectArchivePath: async (suggestedName, format) => {
131 const extensions = format === "tar.gz" ? ["tar.gz", "tgz"] : [format];
132 const result = await dialog.showSaveDialog(mainWindow, { title: "Uložit vzdálené položky jako archiv", defaultPath: suggestedName, filters: [{ name: format.toUpperCase(), extensions }] });
133 return { canceled: result.canceled, path: result.filePath || null };
134 },
135 logger
136 });
137 const hashText = (key) => require("./firstPartyPlugins/chj.hash-checksum/0.0.1/ui/i18n").createI18n(configStore.get().ui.language).t(key);
138 const localHashService = new LocalHashService({
139 selectFilesDialog: async ({ multiple }) => {
140 const result = await dialog.showOpenDialog(mainWindow, { title: hashText(multiple ? "Select local files to hash" : "Select a local file to hash"), properties: multiple ? ["openFile", "multiSelections"] : ["openFile"] });
141 return { canceled: result.canceled, paths: result.filePaths };
142 },
143 selectDirectoryDialog: async () => {
144 const result = await dialog.showOpenDialog(mainWindow, { title: hashText("Select a local directory"), properties: ["openDirectory"] });
145 return { canceled: result.canceled, path: result.filePaths[0] || null };
146 },
147 selectManifestDialog: async () => {
148 const result = await dialog.showOpenDialog(mainWindow, { title: hashText("Select a checksum manifest"), properties: ["openFile"], filters: [{ name: hashText("Checksum manifests"), extensions: ["sha224", "sha256", "sha384", "sha512", "sha3", "blake3", "md5", "sha1", "sfv", "checksums", "txt"] }] });
149 return { canceled: result.canceled, path: result.filePaths[0] || null };
150 },
151 selectSaveDialog: async ({ suggestedName }) => {
152 const result = await dialog.showSaveDialog(mainWindow, { title: hashText("Save checksum manifest"), defaultPath: suggestedName });
153 return { canceled: result.canceled, path: result.filePath || null };
154 },
155 writeClipboard: (text) => clipboard.writeText(text),
156 logger
157 });
158 const pluginInstaller = new PluginInstaller({ storageRoot, appVersion: app.getVersion(), pluginApiVersion: PLUGIN_API_VERSION, logger });
159 const pluginRuntime = new PluginRuntime({
160 BrowserWindow,
161 registry: pluginRegistry,
162 sessionManager,
163 keyGeneratorService,
164 logService,
165 remoteFileService,
166 localHashService,
167 getLanguage: () => configStore.get().ui.language,
168 getMainWindow: () => mainWindow,
169 isVaultUnlocked: () => vaultStore.status().unlocked,
170 preload: path.join(__dirname, "..", "preload", "pluginPreload.js"),
171 icon: appIcon,
172 onWindowState: (windows) => {
173 if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("core:pluginWindowState", windows);
174 },
175 logger
176 });
177 protocol.handle("chj-plugin", (request) => pluginRuntime.handleRequest(request));
178 pluginRuntime.registerIpc(ipcMain);
179 const pluginService = new PluginService({
180 appVersion: app.getVersion(),
181 pluginApiVersion: PLUGIN_API_VERSION,
182 configStore,
183 registry: pluginRegistry,
184 installer: pluginInstaller,
185 runtime: pluginRuntime,
186 logger,
187 providerFactory: (updateConfig) => new PluginCatalogProvider({
188 baseUrls: updateConfig.baseUrls,
189 downloadRoot: path.join(storageRoot, "plugins", ".downloads"),
190 fetchImpl: testUpdateFetch,
191 logger
192 })
193 });
194 const signingPublicKeyPath = app.isPackaged
195 ? path.join(process.resourcesPath, "signing", "ch-j-signing-public.asc")
196 : path.join(app.getAppPath(), "ch-j-signing-public.asc");
197 const signatureVerifier = new OpenPgpVerifier({
198 publicKeyPath: signingPublicKeyPath,
199 logger
200 });
201 updateService = new UpdateService({
202 appVersion: app.getVersion(),
203 buildId: buildInfo.buildId,
204 platform: process.platform,
205 arch: process.arch,
206 configStore,
207 logger,
208 providerFactory: (updateConfig) => new HashUrlProvider({
209 baseUrls: updateConfig.baseUrls,
210 downloadRoot: path.join(storageRoot, "updates", "downloads"),
211 fetchImpl: testUpdateFetch,
212 signatureVerifier,
213 logger
214 })
215 });
216 const updateInstallerLauncher = new UpdateInstallerLauncher({
217 platform: process.platform,
218 shell,
219 logger
220 });
222 for (const [cache, service] of [["Core update", updateService], ["plugin package", pluginService]]) {
223 try {
224 const result = service.cleanupDownloads();
225 if (result.cleaned) logger.info(`${cache} download cache cleaned on startup.`, { root: result.root });
226 } catch (error) {
227 logger.warn(`${cache} download cache cleanup failed.`, { message: error?.message || String(error) });
228 }
229 }
231 mainWindow = createMainWindow({ logger, icon: appIcon });
232 biometricService = new BiometricService({ storageRoot, vaultStore, configStore, adapter: new PlatformAdapter({
233 resourcesPath: process.resourcesPath, appPath: app.getAppPath(), isPackaged: app.isPackaged, getMainWindow: () => mainWindow
234 }) });
235 vaultLockController = new VaultLockController({ vaultStore, configStore, biometricService, sessionManager, pluginRuntime, diagnosticsService });
236 app.on("browser-window-created", (_event, window) => watchWindow(window));
237 watchWindow(mainWindow);
238 vaultLockController.on("locked", payload => {
239 if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("vault:locked", payload);
240 });
241 registerCoreIpc({
242 ipcMain,
243 clipboard,
244 shell,
245 dialog,
246 app,
247 configStore,
248 pluginRegistry,
249 pluginApiVersion: PLUGIN_API_VERSION,
250 pluginService,
251 pluginRuntime,
252 profileService,
253 sessionManager,
254 vaultStore,
255 biometricService,
256 vaultLockController,
257 updateService,
258 updateInstallerLauncher,
259 diagnosticsService,
260 getMainWindow: () => mainWindow,
261 logger
262 });
264 const sendToRenderer = (channel, payload) => {
265 if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send(channel, payload);
266 };
267 sessionManager.on("data", (payload) => sendToRenderer("ssh:data", payload));
268 sessionManager.on("state", (payload) => sendToRenderer("ssh:state", payload));
269 sessionManager.on("sessionError", (payload) => sendToRenderer("ssh:error", payload));
270 diagnosticsService.on("progress", (payload) => sendToRenderer("diagnostics:progress", payload));
272 mainWindow.webContents.once("did-finish-load", () => {
273 const config = configStore.get();
274 if (!config.updates.autoCheck) return;
275 updateService.check()
276 .then((state) => {
277 if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("core:updateState", state);
278 })
279 .catch((error) => logger.warn("Automatic update check failed.", { message: error?.message || String(error) }));
280 });
282 logger.info("Core initialized.", {
283 version: app.getVersion(),
284 buildId: buildInfo.buildId,
285 platform: process.platform,
286 arch: process.arch,
287 storageRoot
288 });
289 coreReady = true;
290 if (buildSmokeUserData) {
291 console.log("CHJ_BUILD_SMOKE_READY");
292 setTimeout(() => app.quit(), 50);
293 }
296app.on("second-instance", focusMainWindow);
297app.whenReady().then(bootstrap).catch((error) => {
298 logger?.error("Core bootstrap failed.", { message: error?.message || String(error), stack: error?.stack });
299 dialog.showErrorBox("CH-J Server Manager", error?.message || String(error));
300 app.quit();
301});
303app.on("activate", () => {
304 if (!coreReady) { focusMainWindow(); return; }
305 if (BrowserWindow.getAllWindows().length === 0) {
306 mainWindow = createMainWindow({ logger, icon: path.join(__dirname, "..", "..", "build", "icon.png") });
307 } else {
308 focusMainWindow();
309 }
310});
312app.on("window-all-closed", () => {
313 if (coreReady && process.platform !== "darwin") app.quit();
314});
316app.on("before-quit", () => {
317 vaultLockController?.dispose();
318 diagnosticsService?.stopAll();
319 void sessionManager?.disconnectAll("app-quit");
320 vaultStore?.lock();
321});
323process.on("uncaughtException", (error) => {
324 logger?.error("Uncaught exception.", { message: error?.message || String(error), stack: error?.stack });
325});
326process.on("unhandledRejection", (error) => {
327 logger?.error("Unhandled rejection.", { message: error?.message || String(error), stack: error?.stack });
328});

SHA-256: 370de12d8e1ddfb2453402c32fc8bbea8c5226529f7d382ded0a5bbd7a3ea56c

SHA-256 des Archivs: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0