CH-J Server Managerspráva serverů přes SSH
Menu
Vydané zdroje

CH-J Server Manager

Procházejte adresáře a soubory konkrétního vydání aplikace.

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

CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.

SHA-256 archivucf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3
Soubory