已发布的源代码
下载源代码 ZIP
CH-J Server Manager
浏览特定应用版本的目录和文件。
CH-J / Server Manager
368 个文件
core-20261017T201717Z-057348c4a70f
添加时间: 未记录日期 · 构建时间:
1
"use strict";3
const path = require("node:path");4
const { app, BrowserWindow, clipboard, dialog, ipcMain, protocol, shell } = require("electron");5
const { createMainWindow, hardenSession } = require("./bootstrap/createMainWindow");6
const { ConfigStore } = require("./config/configStore");7
const { registerCoreIpc } = require("./ipc/registerCoreIpc");8
const { Logger } = require("./logging/logger");9
const { LogService } = require("./logging/logService");10
const { RemoteFileService } = require("./files/remoteFileService");11
const { LocalHashService } = require("./hashing/localHashService");12
const { DiagnosticsService } = require("./diagnostics/diagnosticsService");13
const { PLUGIN_API_VERSION, PluginRegistry } = require("./plugins/pluginRegistry");14
const { PluginCatalogProvider } = require("./plugins/pluginCatalogProvider");15
const { PluginInstaller } = require("./plugins/pluginInstaller");16
const { PluginRuntime } = require("./plugins/pluginRuntime");17
const { PluginService } = require("./plugins/pluginService");18
const { KeyGeneratorService } = require("./keys/keyGeneratorService");19
const { ProfileService } = require("./profiles/profileService");20
const { createTestHttpsFetch } = require("./security/testHttpsFetch");21
const { VaultStore } = require("./security/vaultStore");22
const { BiometricService } = require("./security/biometrics/biometricService");23
const { PlatformAdapter } = require("./security/biometrics/platformAdapter");24
const { VaultLockController } = require("./security/vaultLockController");25
const { SessionManager } = require("./sessions/sessionManager");26
const { PrivilegedExecutor } = require("./sessions/privilegedExec");27
const { UserAdminService, createUserAdminApi } = require("./users/userAdminService");28
const { WindowsUserAdminService } = require("./users/windowsUserAdminService");29
const { WindowsExecutor } = require("./sessions/windowsPrivilegedExec");30
const { detectPlatform } = require("./sessions/platformDetect");31
const { VirtualMemoryService } = require("./system/virtualMemoryService");32
const { HashUrlProvider } = require("./updates/hashUrlProvider");33
const { OpenPgpVerifier } = require("./updates/openPgpVerifier");34
const { UpdateInstallerLauncher } = require("./updates/updateInstallerLauncher");35
const { UpdateService } = require("./updates/updateService");36
const buildInfo = require("../shared/buildInfo.json");37
const { LicenseAcceptance } = require("./legal/licenseAcceptance");38
const { showLicenseWindow } = require("./legal/showLicenseWindow");40
app.setName("CH-J Server Manager");41
const buildSmokeUserData = process.env.CHJ_BUILD_SMOKE_USER_DATA_DIR;42
if (buildSmokeUserData && path.isAbsolute(buildSmokeUserData)) app.setPath("userData", buildSmokeUserData);43
if (process.platform === "linux") {44
app.setDesktopName("ch-j-server-manager.desktop");45
app.commandLine.appendSwitch("class", "ch-j-server-manager");46
}47
if (process.platform === "win32") app.setAppUserModelId("de.ch-j.servermanager");48
protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);50
let mainWindow = null;51
let logger = null;52
let updateService = null;53
let sessionManager = null;54
let vaultStore = null;55
let diagnosticsService = null;56
let biometricService = null;57
let vaultLockController = null;58
let licenseWindow = null;59
let coreReady = false;61
function 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(); });73
}75
const hasSingleInstanceLock = buildSmokeUserData ? true : app.requestSingleInstanceLock();76
if (!hasSingleInstanceLock) app.quit();78
function 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();84
}86
async 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.buildId99
});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 host109
// 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
logger146
});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
logger167
});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
logger188
});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
logger204
})205
});206
const signingPublicKeyPath = app.isPackaged207
? 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
logger212
});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
logger226
})227
});228
const updateInstallerLauncher = new UpdateInstallerLauncher({229
platform: process.platform,230
shell,231
logger232
});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: () => mainWindow246
}) });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
logger274
});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
storageRoot300
});301
coreReady = true;302
if (buildSmokeUserData) {303
console.log("CHJ_BUILD_SMOKE_READY");304
setTimeout(() => app.quit(), 50);305
}306
}308
app.on("second-instance", focusMainWindow);309
app.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
});315
app.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
});324
app.on("window-all-closed", () => {325
if (coreReady && process.platform !== "darwin") app.quit();326
});328
app.on("before-quit", () => {329
vaultLockController?.dispose();330
diagnosticsService?.stopAll();331
void sessionManager?.disconnectAll("app-quit");332
vaultStore?.lock();333
});335
process.on("uncaughtException", (error) => {336
logger?.error("Uncaught exception.", { message: error?.message || String(error), stack: error?.stack });337
});338
process.on("unhandledRejection", (error) => {339
logger?.error("Unhandled rejection.", { message: error?.message || String(error), stack: error?.stack });340
});SHA-256
6bb637d86f5fe33e0546468f7acba6247e46fcb9cd8dc5b9299bae2651dde093CH-J Proprietary Software License 1.14 · 源代码依据 CH-J Proprietary Software License 1.14 提供。公开源代码不会改变许可条款,也不会授予额外权利。
压缩包 SHA-256
cf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3