mirror of
https://github.com/kopia/kopia.git
synced 2026-01-02 19:47:51 -05:00
275 lines
9.0 KiB
JavaScript
275 lines
9.0 KiB
JavaScript
import { ipcMain } from 'electron';
|
|
const path = await import('path');
|
|
const https = await import('https');
|
|
|
|
import { defaultServerBinary } from './utils.js';
|
|
import { spawn } from 'child_process';
|
|
import log from "electron-log";
|
|
import { configDir, isPortableConfig } from './config.js';
|
|
|
|
let servers = {};
|
|
|
|
function newServerForRepo(repoID) {
|
|
let runningServerProcess = null;
|
|
let runningServerCertSHA256 = "";
|
|
let runningServerPassword = "";
|
|
let runningServerControlPassword = "";
|
|
let runningServerAddress = "";
|
|
let runningServerCertificate = "";
|
|
let runningServerStatusDetails = {
|
|
startingUp: true,
|
|
};
|
|
let serverLog = [];
|
|
|
|
const maxLogLines = 100;
|
|
|
|
return {
|
|
actuateServer() {
|
|
log.info('actuating Server', repoID);
|
|
this.stopServer();
|
|
this.startServer();
|
|
},
|
|
|
|
startServer() {
|
|
let kopiaPath = defaultServerBinary();
|
|
let args = [];
|
|
|
|
args.push('server', 'start', '--ui',
|
|
'--tls-print-server-cert',
|
|
'--tls-generate-cert-name=127.0.0.1',
|
|
'--random-password',
|
|
'--random-server-control-password',
|
|
'--tls-generate-cert',
|
|
'--async-repo-connect',
|
|
'--error-notifications=always',
|
|
'--kopiaui-notifications', // will print notification JSON to stderr
|
|
'--shutdown-on-stdin', // shutdown the server when parent dies
|
|
'--address=127.0.0.1:0');
|
|
|
|
|
|
args.push("--config-file", path.resolve(configDir(), repoID + ".config"));
|
|
if (isPortableConfig()) {
|
|
const cacheDir = path.resolve(configDir(), "cache", repoID);
|
|
const logsDir = path.resolve(configDir(), "logs", repoID);
|
|
args.push("--cache-directory", cacheDir);
|
|
args.push("--log-dir", logsDir);
|
|
}
|
|
|
|
log.info(`spawning ${kopiaPath} ${args.join(' ')}`);
|
|
runningServerProcess = spawn(kopiaPath, args, {
|
|
});
|
|
this.raiseStatusUpdatedEvent();
|
|
|
|
runningServerProcess.stdout.on('data', this.appendToLog.bind(this));
|
|
runningServerProcess.stderr.on('data', this.detectServerParam.bind(this));
|
|
|
|
const p = runningServerProcess;
|
|
|
|
log.info('starting polling loop');
|
|
|
|
const statusUpdated = this.raiseStatusUpdatedEvent.bind(this);
|
|
|
|
const pollInterval = 3000;
|
|
|
|
function pollOnce() {
|
|
if (!runningServerAddress || !runningServerCertificate || !runningServerPassword || !runningServerControlPassword) {
|
|
return;
|
|
}
|
|
|
|
const req = https.request({
|
|
ca: [runningServerCertificate],
|
|
host: "127.0.0.1",
|
|
port: parseInt(new URL(runningServerAddress).port),
|
|
method: "GET",
|
|
path: "/api/v1/control/status",
|
|
timeout: pollInterval,
|
|
headers: {
|
|
'Authorization': 'Basic ' + Buffer.from("server-control" + ':' + runningServerControlPassword).toString('base64')
|
|
}
|
|
}, (resp) => {
|
|
if (resp.statusCode === 200) {
|
|
resp.on('data', x => {
|
|
try {
|
|
const newDetails = JSON.parse(x);
|
|
if (JSON.stringify(newDetails) != JSON.stringify(runningServerStatusDetails)) {
|
|
runningServerStatusDetails = newDetails;
|
|
statusUpdated();
|
|
}
|
|
} catch (e) {
|
|
log.warn('unable to parse status JSON', e);
|
|
}
|
|
});
|
|
} else {
|
|
log.warn('error fetching status', resp.statusMessage);
|
|
}
|
|
});
|
|
req.on('error', (e) => {
|
|
log.info('error fetching status', e);
|
|
});
|
|
req.end();
|
|
}
|
|
|
|
const statusPollInterval = setInterval(pollOnce, pollInterval);
|
|
|
|
runningServerProcess.on('close', (code, signal) => {
|
|
this.appendToLog(`child process exited with code ${code} and signal ${signal}`);
|
|
if (runningServerProcess === p) {
|
|
clearInterval(statusPollInterval);
|
|
|
|
runningServerAddress = "";
|
|
runningServerPassword = "";
|
|
runningServerControlPassword = "";
|
|
runningServerCertSHA256 = "";
|
|
runningServerProcess = null;
|
|
this.raiseStatusUpdatedEvent();
|
|
}
|
|
});
|
|
},
|
|
|
|
detectServerParam(data) {
|
|
let lines = (data + '').split('\n');
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const p = lines[i].indexOf(": ");
|
|
if (p < 0) {
|
|
continue
|
|
}
|
|
|
|
const key = lines[i].substring(0, p);
|
|
const value = lines[i].substring(p + 2);
|
|
switch (key) {
|
|
case "SERVER PASSWORD":
|
|
runningServerPassword = value;
|
|
this.raiseStatusUpdatedEvent();
|
|
break;
|
|
|
|
case "SERVER CONTROL PASSWORD":
|
|
runningServerControlPassword = value;
|
|
this.raiseStatusUpdatedEvent();
|
|
break;
|
|
|
|
case "SERVER CERT SHA256":
|
|
runningServerCertSHA256 = value;
|
|
this.raiseStatusUpdatedEvent();
|
|
break;
|
|
|
|
case "SERVER CERTIFICATE":
|
|
runningServerCertificate = Buffer.from(value, 'base64').toString('ascii');
|
|
this.raiseStatusUpdatedEvent();
|
|
break;
|
|
|
|
case "SERVER ADDRESS":
|
|
runningServerAddress = value;
|
|
this.raiseStatusUpdatedEvent();
|
|
break;
|
|
|
|
case "NOTIFICATION":
|
|
try {
|
|
this.raiseNotificationEvent(JSON.parse(value));
|
|
} catch (e) {
|
|
log.warn('unable to parse notification JSON', e);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.appendToLog(data);
|
|
},
|
|
|
|
appendToLog(data) {
|
|
const l = serverLog.push(data);
|
|
if (l > maxLogLines) {
|
|
serverLog.splice(0, 1);
|
|
}
|
|
|
|
ipcMain.emit('logs-updated-event', {
|
|
repoID: repoID,
|
|
logs: serverLog.join(''),
|
|
});
|
|
log.info(`${data}`);
|
|
},
|
|
|
|
stopServer() {
|
|
if (!runningServerProcess) {
|
|
log.info('stopServer: server not started');
|
|
return;
|
|
}
|
|
|
|
runningServerProcess.kill();
|
|
runningServerAddress = "";
|
|
runningServerPassword = "";
|
|
runningServerCertSHA256 = "";
|
|
runningServerCertificate = "";
|
|
runningServerProcess = null;
|
|
this.raiseStatusUpdatedEvent();
|
|
},
|
|
|
|
getServerAddress() {
|
|
return runningServerAddress;
|
|
},
|
|
|
|
getServerCertSHA256() {
|
|
return runningServerCertSHA256;
|
|
},
|
|
|
|
getServerPassword() {
|
|
return runningServerPassword;
|
|
},
|
|
|
|
getServerStatusDetails() {
|
|
return runningServerStatusDetails;
|
|
},
|
|
|
|
getServerStatus() {
|
|
if (!runningServerProcess) {
|
|
return "Stopped";
|
|
}
|
|
|
|
if (runningServerCertSHA256 && runningServerAddress && runningServerPassword) {
|
|
return "Running";
|
|
}
|
|
|
|
return "Starting";
|
|
},
|
|
|
|
raiseStatusUpdatedEvent() {
|
|
const args = {
|
|
repoID: repoID,
|
|
status: this.getServerStatus(),
|
|
serverAddress: this.getServerAddress() || "<pending>",
|
|
serverCertSHA256: this.getServerCertSHA256() || "<pending>",
|
|
};
|
|
|
|
ipcMain.emit('status-updated-event', args);
|
|
},
|
|
|
|
raiseNotificationEvent(notification) {
|
|
const args = {
|
|
repoID: repoID,
|
|
notification: notification,
|
|
};
|
|
|
|
ipcMain.emit('repo-notification-event', args);
|
|
},
|
|
};
|
|
};
|
|
|
|
ipcMain.on('status-fetch', (event, args) => {
|
|
const repoID = args.repoID;
|
|
const s = servers[repoID]
|
|
if (s) {
|
|
s.raiseStatusUpdatedEvent();
|
|
}
|
|
})
|
|
|
|
export function serverForRepo(repoID) {
|
|
let s = servers[repoID];
|
|
if (s) {
|
|
return s;
|
|
}
|
|
|
|
s = newServerForRepo(repoID);
|
|
servers[repoID] = s;
|
|
return s;
|
|
}
|
|
|