支持toml配置文件的stcp

This commit is contained in:
刘嘉伟 2024-08-17 00:20:47 +08:00
parent 3961475421
commit 1d9ac090f3
4 changed files with 295 additions and 234 deletions

View File

@ -1,17 +1,17 @@
import {app, ipcMain, Notification} from "electron"; import { app, ipcMain, Notification } from "electron";
import {Config, getConfig} from "../storage/config"; import { getConfig } from "../storage/config";
import {listProxy, Proxy} from "../storage/proxy"; import { listProxy } from "../storage/proxy";
import {getVersionById} from "../storage/version"; import { getVersionById } from "../storage/version";
import treeKill from "tree-kill"; import treeKill from "tree-kill";
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const {exec, spawn} = require("child_process"); const { exec, spawn } = require("child_process");
const log = require('electron-log'); const log = require("electron-log");
export let frpcProcess = null; export let frpcProcess = null;
const runningCmd = { const runningCmd = {
commandPath: null, commandPath: null,
configPath: null configPath: null
}; };
let frpcStatusListener = null; let frpcStatusListener = null;
@ -21,16 +21,16 @@ let frpcStatusListener = null;
* @param callback * @param callback
*/ */
const getFrpcVersionWorkerPath = ( const getFrpcVersionWorkerPath = (
versionId: string, versionId: number,
callback: (workerPath: string) => void callback: (workerPath: string) => void
) => { ) => {
getVersionById(versionId, (err2, version) => { getVersionById(versionId, (err2, version) => {
if (!err2) { if (!err2) {
if (version) { if (version) {
callback(version["frpcVersionPath"]); callback(version["frpcVersionPath"]);
} }
} }
}); });
}; };
/** /**
@ -38,47 +38,86 @@ const getFrpcVersionWorkerPath = (
* @param config * @param config
* @param proxys * @param proxys
*/ */
const genTomlConfig = (config: Config, proxys: Proxy[]) => { const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyToml = proxys.map(m => { const proxyToml = proxys.map(m => {
let toml = ` let toml = `
[[proxies]] [[${m.type === 'stcp' && m.stcpModel === 'visitors' ? 'visitors' : 'proxies'}]]
name = "${m.name}" name = "${m.name}"
type = "${m.type}" type = "${m.type}"
`;
switch (m.type) {
case "tcp":
case "udp":
toml += `
localIP = "${m.localIp}" localIP = "${m.localIp}"
localPort = ${m.localPort} localPort = ${m.localPort}
remotePort = ${m.remotePort}
`; `;
switch (m.type) { break;
case "tcp": case "http":
case "udp": case "https":
toml += `remotePort = ${m.remotePort}`; toml += `
break; localIP = "${m.localIp}"
case "http": localPort = ${m.localPort}
case "https": customDomains=[${m.customDomains.map(m => `"${m}"`)}]
toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`; `;
break; break;
default: case "stcp":
break; if (m.stcpModel === "visitors") {
// 访问者
toml += `
serverName = "${m.serverName}"
bindAddr = "${m.bindAddr}"
bindPort = ${m.bindPort}
`;
} else if (m.stcpModel === "visited") {
// 被访问者
toml += `
localIP = "${m.localIp}"
localPort = ${m.localPort}`;
} }
toml += `
secretKey="${m.secretKey}"
`;
default:
break;
}
return toml; return toml;
}); });
const toml = ` const toml = `
serverAddr = "${config.serverAddr}" serverAddr = "${config.serverAddr}"
serverPort = ${config.serverPort} serverPort = ${config.serverPort}
${config.authMethod === 'token' ? ` ${
config.authMethod === "token"
? `
auth.method = "token" auth.method = "token"
auth.token = "${config.authToken}" auth.token = "${config.authToken}"
` : ""} `
${config.authMethod === 'multiuser' ? ` : ""
}
${
config.authMethod === "multiuser"
? `
user = "${config.user}" user = "${config.user}"
metadatas.token = "${config.metaToken}" metadatas.token = "${config.metaToken}"
` : ""} `
${config.transportHeartbeatInterval ? ` : ""
}
${
config.transportHeartbeatInterval
? `
transport.heartbeatInterval = ${config.transportHeartbeatInterval} transport.heartbeatInterval = ${config.transportHeartbeatInterval}
` : ""} `
${config.transportHeartbeatTimeout ? ` : ""
}
${
config.transportHeartbeatTimeout
? `
transport.heartbeatTimeout = ${config.transportHeartbeatTimeout} transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}
` : ""} `
: ""
}
log.to = "frpc.log" log.to = "frpc.log"
@ -87,69 +126,92 @@ log.maxDays = ${config.logMaxDays}
webServer.addr = "127.0.0.1" webServer.addr = "127.0.0.1"
webServer.port = 57400 webServer.port = 57400
transport.tls.enable = ${config.tlsConfigEnable} transport.tls.enable = ${config.tlsConfigEnable}
${config.tlsConfigEnable ? ` ${
config.tlsConfigEnable
? `
transport.tls.certFile = "${config.tlsConfigCertFile}" transport.tls.certFile = "${config.tlsConfigCertFile}"
transport.tls.keyFile = "${config.tlsConfigKeyFile}" transport.tls.keyFile = "${config.tlsConfigKeyFile}"
transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}" transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"
transport.tls.serverName = "${config.tlsConfigServerName}" transport.tls.serverName = "${config.tlsConfigServerName}"
` : ""} `
${config.proxyConfigEnable ? ` : ""
}
${
config.proxyConfigEnable
? `
transport.proxyURL = "${config.proxyConfigProxyUrl}" transport.proxyURL = "${config.proxyConfigProxyUrl}"
` : ""} `
: ""
}
${proxyToml.join("")} ${proxyToml.join("")}
`; `;
return toml; return toml;
} };
/** /**
* ini配置 * ini配置
* @param config * @param config
* @param proxys * @param proxys
*/ */
const genIniConfig = (config: Config, proxys: Proxy[]) => { const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyIni = proxys.map(m => { const proxyIni = proxys.map(m => {
let ini = ` let ini = `
[${m.name}] [${m.name}]
type = "${m.type}" type = "${m.type}"
local_ip = "${m.localIp}" local_ip = "${m.localIp}"
local_port = ${m.localPort} local_port = ${m.localPort}
`; `;
switch (m.type) { switch (m.type) {
case "tcp": case "tcp":
case "udp": case "udp":
ini += `remote_port = ${m.remotePort}`; ini += `remote_port = ${m.remotePort}`;
break; break;
case "http": case "http":
case "https": case "https":
ini += `custom_domains=[${m.customDomains.map(m => `"${m}"`)}]`; ini += `custom_domains=[${m.customDomains.map(m => `"${m}"`)}]`;
break; break;
default: default:
break; break;
} }
return ini; return ini;
}); });
const ini = ` const ini = `
[common] [common]
server_addr = ${config.serverAddr} server_addr = ${config.serverAddr}
server_port = ${config.serverPort} server_port = ${config.serverPort}
${config.authMethod === 'token' ? ` ${
config.authMethod === "token"
? `
authentication_method = ${config.authMethod} authentication_method = ${config.authMethod}
token = ${config.authToken} token = ${config.authToken}
` : ""} `
${config.authMethod === 'multiuser' ? ` : ""
}
${
config.authMethod === "multiuser"
? `
user = ${config.user} user = ${config.user}
meta_token = ${config.metaToken} meta_token = ${config.metaToken}
` : ""} `
: ""
}
${config.transportHeartbeatInterval ? ` ${
config.transportHeartbeatInterval
? `
heartbeat_interval = ${config.transportHeartbeatInterval} heartbeat_interval = ${config.transportHeartbeatInterval}
` : ""} `
${config.transportHeartbeatTimeout ? ` : ""
}
${
config.transportHeartbeatTimeout
? `
heartbeat_timeout = ${config.transportHeartbeatTimeout} heartbeat_timeout = ${config.transportHeartbeatTimeout}
` : ""} `
: ""
}
log_file = "frpc.log" log_file = "frpc.log"
log_level = ${config.logLevel} log_level = ${config.logLevel}
@ -157,55 +219,63 @@ log_max_days = ${config.logMaxDays}
admin_addr = 127.0.0.1 admin_addr = 127.0.0.1
admin_port = 57400 admin_port = 57400
tls_enable = ${config.tlsConfigEnable} tls_enable = ${config.tlsConfigEnable}
${config.tlsConfigEnable ? ` ${
config.tlsConfigEnable
? `
tls_cert_file = ${config.tlsConfigCertFile} tls_cert_file = ${config.tlsConfigCertFile}
tls_key_file = ${config.tlsConfigKeyFile} tls_key_file = ${config.tlsConfigKeyFile}
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile} tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
tls_server_name = ${config.tlsConfigServerName} tls_server_name = ${config.tlsConfigServerName}
` : ""} `
${config.proxyConfigEnable ? ` : ""
}
${
config.proxyConfigEnable
? `
http_proxy = "${config.proxyConfigProxyUrl}" http_proxy = "${config.proxyConfigProxyUrl}"
` : ""} `
: ""
}
${proxyIni.join("")} ${proxyIni.join("")}
` `;
return ini; return ini;
} };
/** /**
* *
*/ */
export const generateConfig = ( export const generateConfig = (
config: Config, config: FrpConfig,
callback: (configPath: string) => void callback: (configPath: string) => void
) => { ) => {
listProxy((err3, proxys) => { listProxy((err3, proxys) => {
if (!err3) { if (!err3) {
const {currentVersion} = config; const { currentVersion } = config;
let filename = null; let filename = null;
let configContent = ""; let configContent = "";
if (currentVersion < 124395282) { if (currentVersion < 124395282) {
// 版本小于v0.52.0 // 版本小于v0.52.0
filename = "frp.ini"; filename = "frp.ini";
configContent = genIniConfig(config, proxys) configContent = genIniConfig(config, proxys);
} else { } else {
filename = "frp.toml"; filename = "frp.toml";
configContent = genTomlConfig(config, proxys) configContent = genTomlConfig(config, proxys);
} }
const configPath = path.join(app.getPath("userData"), filename) const configPath = path.join(app.getPath("userData"), filename);
log.info(`生成配置成功 配置路径:${configPath}`) log.info(`生成配置成功 配置路径:${configPath}`);
fs.writeFile( fs.writeFile(
configPath, // 配置文件目录 configPath, // 配置文件目录
configContent, // 配置文件内容 configContent, // 配置文件内容
{flag: "w"}, { flag: "w" },
err => { err => {
if (!err) { if (!err) {
callback(filename); callback(filename);
} }
}
);
} }
}); );
}
});
}; };
/** /**
@ -215,149 +285,136 @@ export const generateConfig = (
* @param configPath * @param configPath
*/ */
const startFrpcProcess = (commandPath: string, configPath: string) => { const startFrpcProcess = (commandPath: string, configPath: string) => {
log.info(`启动frpc 目录:${app.getPath("userData")} 命令:${commandPath}`,) log.info(`启动frpc 目录:${app.getPath("userData")} 命令:${commandPath}`);
const command = `${commandPath} -c ${configPath}`; const command = `${commandPath} -c ${configPath}`;
frpcProcess = spawn(command, { frpcProcess = spawn(command, {
cwd: app.getPath("userData"), cwd: app.getPath("userData"),
shell: true shell: true
}); });
runningCmd.commandPath = commandPath; runningCmd.commandPath = commandPath;
runningCmd.configPath = configPath; runningCmd.configPath = configPath;
frpcProcess.stdout.on("data", data => { frpcProcess.stdout.on("data", data => {
log.debug(`启动输出:${data}`) log.debug(`启动输出:${data}`);
}); });
frpcProcess.stdout.on("error", data => { frpcProcess.stdout.on("error", data => {
log.error(`启动错误:${data}`) log.error(`启动错误:${data}`);
stopFrpcProcess(() => { stopFrpcProcess(() => {});
}) });
}); frpcStatusListener = setInterval(() => {
frpcStatusListener = setInterval(() => { const status = frpcProcessStatus();
const status = frpcProcessStatus() log.debug(`监听frpc子进程状态${status}`);
log.debug(`监听frpc子进程状态${status}`) if (!status) {
if (!status) { new Notification({
new Notification({ title: "Frpc Desktop",
title: "Frpc Desktop", body: "连接已断开,请前往日志查看原因"
body: "连接已断开,请前往日志查看原因" }).show();
}).show() clearInterval(frpcStatusListener);
clearInterval(frpcStatusListener) }
} }, 3000);
}, 3000)
}; };
/** /**
* frpc配置 * frpc配置
*/ */
export const reloadFrpcProcess = () => { export const reloadFrpcProcess = () => {
if (frpcProcess && !frpcProcess.killed) { if (frpcProcess && !frpcProcess.killed) {
getConfig((err1, config) => { getConfig((err1, config) => {
if (!err1) { if (!err1) {
if (config) { if (config) {
generateConfig(config, configPath => { generateConfig(config, configPath => {
const command = `${runningCmd.commandPath} reload -c ${configPath}`; const command = `${runningCmd.commandPath} reload -c ${configPath}`;
log.info(`重载配置:${command}`) log.info(`重载配置:${command}`);
exec(command, { exec(command, {
cwd: app.getPath("userData"), cwd: app.getPath("userData"),
shell: true shell: true
}); });
}); });
} }
} }
}); });
} }
}; };
/** /**
* frpc子进程 * frpc子进程
*/ */
export const stopFrpcProcess = (callback?: () => void) => { export const stopFrpcProcess = (callback?: () => void) => {
if (frpcProcess) { if (frpcProcess) {
treeKill(frpcProcess.pid, (error: Error) => { treeKill(frpcProcess.pid, (error: Error) => {
if (error) { if (error) {
log.error(`关闭frpc子进程失败 pid${frpcProcess.pid} error${error}`) log.error(`关闭frpc子进程失败 pid${frpcProcess.pid} error${error}`);
callback() callback();
} else { } else {
log.info(`关闭frpc子进程成功`) log.info(`关闭frpc子进程成功`);
frpcProcess = null frpcProcess = null;
clearInterval(frpcStatusListener) clearInterval(frpcStatusListener);
callback() callback();
} }
}) });
} else { } else {
callback() callback();
} }
} };
/** /**
* frpc子进程状态 * frpc子进程状态
*/ */
export const frpcProcessStatus = () => { export const frpcProcessStatus = () => {
if (!frpcProcess) { if (!frpcProcess) {
return false; return false;
} }
try { try {
// 发送信号给进程,如果进程存在,会正常返回 // 发送信号给进程,如果进程存在,会正常返回
process.kill(frpcProcess.pid, 0); process.kill(frpcProcess.pid, 0);
return true; return true;
} catch (error) { } catch (error) {
// 进程不存在,抛出异常 // 进程不存在,抛出异常
return false; return false;
} }
} };
/** /**
* frpc流程 * frpc流程
* @param config * @param config
*/ */
export const startFrpWorkerProcess = async (config: Config) => { export const startFrpWorkerProcess = async (config: FrpConfig) => {
getFrpcVersionWorkerPath( getFrpcVersionWorkerPath(config.currentVersion, (frpcVersionPath: string) => {
config.currentVersion, if (frpcVersionPath) {
(frpcVersionPath: string) => { generateConfig(config, configPath => {
if (frpcVersionPath) { const platform = process.platform;
generateConfig(config, configPath => { if (platform === "win32") {
const platform = process.platform; startFrpcProcess(path.join(frpcVersionPath, "frpc.exe"), configPath);
if (platform === 'win32') { } else {
startFrpcProcess( startFrpcProcess(path.join(frpcVersionPath, "frpc"), configPath);
path.join(frpcVersionPath, "frpc.exe"),
configPath
);
} else {
startFrpcProcess(
path.join(frpcVersionPath, "frpc"),
configPath
);
}
});
}
} }
); });
} }
});
};
export const initFrpcApi = () => { export const initFrpcApi = () => {
ipcMain.handle("frpc.running", async (event, args) => { ipcMain.handle("frpc.running", async (event, args) => {
return frpcProcessStatus() return frpcProcessStatus();
}); });
ipcMain.on("frpc.start", async (event, args) => { ipcMain.on("frpc.start", async (event, args) => {
getConfig((err1, config) => { getConfig((err1, config) => {
if (!err1) { if (!err1) {
if (config) { if (config) {
startFrpWorkerProcess(config) startFrpWorkerProcess(config);
} else { } else {
event.reply( event.reply(
"Home.frpc.start.error.hook", "请先前往设置页面,修改配置后再启动" "Home.frpc.start.error.hook",
); "请先前往设置页面,修改配置后再启动"
} );
}
});
});
ipcMain.on("frpc.stop", () => {
if (frpcProcess && !frpcProcess.killed) {
stopFrpcProcess(() => {
})
} }
}
}); });
});
ipcMain.on("frpc.stop", () => {
if (frpcProcess && !frpcProcess.killed) {
stopFrpcProcess(() => {});
}
});
}; };

View File

@ -32,7 +32,7 @@ export const listVersion = (
}; };
export const getVersionById = ( export const getVersionById = (
id: string, id: number,
callback: (err: Error | null, document: FrpVersion) => void callback: (err: Error | null, document: FrpVersion) => void
) => { ) => {
versionDB.findOne({id: id}, callback); versionDB.findOne({id: id}, callback);

View File

@ -43,7 +43,7 @@ const defaultForm = ref<Proxy>({
localPort: 8080, localPort: 8080,
remotePort: 8080, remotePort: 8080,
customDomains: [""], customDomains: [""],
stcpModel: "visitor", stcpModel: "visitors",
serverName: "", serverName: "",
secretKey: "", secretKey: "",
bindAddr: "", bindAddr: "",
@ -63,7 +63,7 @@ const proxyTypes = ref(["http", "https", "tcp", "udp", "stcp"]);
const stcpModels = ref([ const stcpModels = ref([
{ {
label: "访问者", label: "访问者",
value: "visitor" value: "visitors"
}, },
{ {
label: "被访问者", label: "被访问者",
@ -469,7 +469,7 @@ onUnmounted(() => {
</el-form-item> </el-form-item>
</el-col> </el-col>
</template> </template>
<el-col :span="editForm.stcpModel === 'visitor' ? 12 : 24"> <el-col :span="editForm.stcpModel === 'visitors' ? 12 : 24">
<el-form-item label="代理名称:" prop="name"> <el-form-item label="代理名称:" prop="name">
<el-input <el-input
v-model="editForm.name" v-model="editForm.name"
@ -625,7 +625,7 @@ onUnmounted(() => {
</el-col> </el-col>
</template> </template>
<template <template
v-if="editForm.type === 'stcp' && editForm.stcpModel === 'visitor'" v-if="editForm.type === 'stcp' && editForm.stcpModel === 'visitors'"
> >
<el-col :span="12"> <el-col :span="12">
<el-form-item label="被访问者代理名称:" prop="serverName"> <el-form-item label="被访问者代理名称:" prop="serverName">
@ -791,6 +791,10 @@ onUnmounted(() => {
background: #5ec7fe; background: #5ec7fe;
} }
.stcp {
background: #D63DA6;
}
.domain-input { .domain-input {
width: calc(100% - 115px); width: calc(100% - 115px);
} }

2
types/global.d.ts vendored
View File

@ -53,7 +53,7 @@ declare global {
* *
*/ */
type FrpConfig = { type FrpConfig = {
currentVersion: string; currentVersion: number;
serverAddr: string; serverAddr: string;
serverPort: number; serverPort: number;
authMethod: string; authMethod: string;