update:
This commit is contained in:
18
README.md
18
README.md
@@ -32,9 +32,10 @@ server-config zsh install
|
|||||||
会执行:
|
会执行:
|
||||||
|
|
||||||
- `apt update && apt upgrade`
|
- `apt update && apt upgrade`
|
||||||
- 安装 `zsh git curl`
|
- 安装 `zsh git curl wget`
|
||||||
- 切换当前用户 shell 到 `/bin/zsh`
|
- 切换当前用户 shell 到 `/bin/zsh`
|
||||||
- 安装 oh-my-zsh
|
- 安装 oh-my-zsh
|
||||||
|
- 安装 nvm,并执行 `nvm install --lts`
|
||||||
- 安装 `zsh-autosuggestions` 和 `zsh-syntax-highlighting`
|
- 安装 `zsh-autosuggestions` 和 `zsh-syntax-highlighting`
|
||||||
- 更新 `~/.zshrc` 的插件列表为 `git zsh-autosuggestions zsh-syntax-highlighting`
|
- 更新 `~/.zshrc` 的插件列表为 `git zsh-autosuggestions zsh-syntax-highlighting`
|
||||||
|
|
||||||
@@ -63,13 +64,14 @@ server-config frp install --token "$FRP_TOKEN"
|
|||||||
|
|
||||||
默认配置:
|
默认配置:
|
||||||
|
|
||||||
- `server_addr = "81.70.134.9"`
|
- `serverAddr = "81.70.134.9"`
|
||||||
- `server_port = 15443`
|
- `serverPort = 15443`
|
||||||
- `tls_enable = false`
|
- `auth.token = "$FRP_TOKEN"`
|
||||||
- `tcp_mux = true`
|
- `transport.tls.enable = false`
|
||||||
- `log_file = "/var/log/frpc.log"`
|
- `transport.tcpMux = true`
|
||||||
- `log_level = "info"`
|
- `log.to = "/var/log/frpc.log"`
|
||||||
- `log_max_days = 7`
|
- `log.level = "info"`
|
||||||
|
- `log.maxDays = 7`
|
||||||
- frp 版本:`0.58.1`
|
- frp 版本:`0.58.1`
|
||||||
- 安装目录:`/opt/frp/frp_0.58.1_linux_amd64`
|
- 安装目录:`/opt/frp/frp_0.58.1_linux_amd64`
|
||||||
- systemd 服务:`frpc`
|
- systemd 服务:`frpc`
|
||||||
|
|||||||
114
src/cli.js
114
src/cli.js
@@ -10,6 +10,23 @@ const DEFAULT_FRP_ARCH = "amd64";
|
|||||||
const DEFAULT_FRP_SERVER_ADDR = "81.70.134.9";
|
const DEFAULT_FRP_SERVER_ADDR = "81.70.134.9";
|
||||||
const DEFAULT_FRP_SERVER_PORT = 15443;
|
const DEFAULT_FRP_SERVER_PORT = 15443;
|
||||||
const DEFAULT_SERVICE_NAME = "frpc";
|
const DEFAULT_SERVICE_NAME = "frpc";
|
||||||
|
const NVM_INSTALL_VERSION = "v0.40.4";
|
||||||
|
const NVM_INSTALL_COMMAND = `set -euo pipefail
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_INSTALL_VERSION}/install.sh | bash
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_INSTALL_VERSION}/install.sh | bash
|
||||||
|
else
|
||||||
|
echo "curl or wget is required to install nvm" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
. "$NVM_DIR/nvm.sh"
|
||||||
|
nvm install --lts
|
||||||
|
nvm alias default 'lts/*'
|
||||||
|
nvm use --lts`;
|
||||||
|
|
||||||
function main(argv) {
|
function main(argv) {
|
||||||
const parsed = parseArgs(argv);
|
const parsed = parseArgs(argv);
|
||||||
@@ -95,13 +112,14 @@ function handleFrp(action, argv, rootFlags, runner) {
|
|||||||
function installZsh(runner, flags) {
|
function installZsh(runner, flags) {
|
||||||
runner.run("sudo", ["apt", "update"]);
|
runner.run("sudo", ["apt", "update"]);
|
||||||
runner.run("sudo", ["apt", "upgrade", "-y"]);
|
runner.run("sudo", ["apt", "upgrade", "-y"]);
|
||||||
runner.run("sudo", ["apt", "install", "zsh", "git", "curl", "-y"]);
|
runner.run("sudo", ["apt", "install", "zsh", "git", "curl", "wget", "-y"]);
|
||||||
runner.run("chsh", ["-s", "/bin/zsh"]);
|
runner.run("chsh", ["-s", "/bin/zsh"]);
|
||||||
|
|
||||||
runner.run("sh", [
|
runner.run("sh", [
|
||||||
"-c",
|
"-c",
|
||||||
'RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://gitee.com/pocmon/ohmyzsh/raw/master/tools/install.sh)"',
|
'RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://gitee.com/pocmon/ohmyzsh/raw/master/tools/install.sh)"',
|
||||||
]);
|
]);
|
||||||
|
runner.run("bash", ["-c", NVM_INSTALL_COMMAND]);
|
||||||
|
|
||||||
const customDir = process.env.ZSH_CUSTOM || path.join(os.homedir(), ".oh-my-zsh", "custom");
|
const customDir = process.env.ZSH_CUSTOM || path.join(os.homedir(), ".oh-my-zsh", "custom");
|
||||||
installGitPlugin(
|
installGitPlugin(
|
||||||
@@ -175,8 +193,9 @@ function installFrp(runner, flags) {
|
|||||||
function initFrpConfig(flags, runner) {
|
function initFrpConfig(flags, runner) {
|
||||||
const configPath = getConfigPath(flags);
|
const configPath = getConfigPath(flags);
|
||||||
const force = Boolean(flags.force);
|
const force = Boolean(flags.force);
|
||||||
|
const configExists = fs.existsSync(configPath);
|
||||||
|
|
||||||
if (fs.existsSync(configPath) && !force) {
|
if (configExists && !force) {
|
||||||
console.log(`${configPath} already exists. Use --force to rewrite it.`);
|
console.log(`${configPath} already exists. Use --force to rewrite it.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -186,16 +205,24 @@ function initFrpConfig(flags, runner) {
|
|||||||
throw new Error("frp token is required. Pass --token <value> or set FRP_TOKEN.");
|
throw new Error("frp token is required. Pass --token <value> or set FRP_TOKEN.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proxies = configExists && force
|
||||||
|
? [...parseFrpConfig(fs.readFileSync(configPath, "utf8")).sections.values()].map((section) => ({
|
||||||
|
name: section.name,
|
||||||
|
...section.values,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
const config = renderFrpConfig({
|
const config = renderFrpConfig({
|
||||||
server_addr: stringFlag(flags, "server-addr", DEFAULT_FRP_SERVER_ADDR),
|
serverAddr: stringFlag(flags, "server-addr", DEFAULT_FRP_SERVER_ADDR),
|
||||||
server_port: numberFlag(flags, "server-port", DEFAULT_FRP_SERVER_PORT),
|
serverPort: numberFlag(flags, "server-port", DEFAULT_FRP_SERVER_PORT),
|
||||||
token,
|
"auth.method": "token",
|
||||||
tls_enable: booleanFlag(flags, "tls-enable", false),
|
"auth.token": token,
|
||||||
tcp_mux: booleanFlag(flags, "tcp-mux", true),
|
"transport.tls.enable": booleanFlag(flags, "tls-enable", false),
|
||||||
log_file: stringFlag(flags, "log-file", "/var/log/frpc.log"),
|
"transport.tcpMux": booleanFlag(flags, "tcp-mux", true),
|
||||||
log_level: stringFlag(flags, "log-level", "info"),
|
"log.to": stringFlag(flags, "log-file", "/var/log/frpc.log"),
|
||||||
log_max_days: numberFlag(flags, "log-max-days", 7),
|
"log.level": stringFlag(flags, "log-level", "info"),
|
||||||
}, []);
|
"log.maxDays": numberFlag(flags, "log-max-days", 7),
|
||||||
|
}, proxies);
|
||||||
|
|
||||||
writeFile(configPath, config, runner);
|
writeFile(configPath, config, runner);
|
||||||
}
|
}
|
||||||
@@ -300,6 +327,12 @@ function parseFrpConfig(text) {
|
|||||||
let current = null;
|
let current = null;
|
||||||
|
|
||||||
for (const line of text.split(/\r?\n/)) {
|
for (const line of text.split(/\r?\n/)) {
|
||||||
|
const proxyMatch = line.match(/^\s*\[\[proxies\]\]\s*$/);
|
||||||
|
if (proxyMatch) {
|
||||||
|
current = { name: "", values: {} };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const sectionMatch = line.match(/^\s*\[([A-Za-z0-9_.-]+)]\s*$/);
|
const sectionMatch = line.match(/^\s*\[([A-Za-z0-9_.-]+)]\s*$/);
|
||||||
if (sectionMatch) {
|
if (sectionMatch) {
|
||||||
current = { name: sectionMatch[1], values: {} };
|
current = { name: sectionMatch[1], values: {} };
|
||||||
@@ -308,19 +341,65 @@ function parseFrpConfig(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
globals.push(line);
|
globals.push(migrateFrpGlobalLine(line));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyValue = parseTomlKeyValue(line);
|
const keyValue = parseTomlKeyValue(line);
|
||||||
if (keyValue) {
|
if (keyValue) {
|
||||||
current.values[keyValue.key] = keyValue.value;
|
applyProxyConfigValue(current, keyValue, sections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { globals: trimTrailingBlankLines(globals), sections };
|
return { globals: trimTrailingBlankLines(globals), sections };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrateFrpGlobalLine(line) {
|
||||||
|
const keyMap = {
|
||||||
|
server_addr: "serverAddr",
|
||||||
|
server_port: "serverPort",
|
||||||
|
token: "auth.token",
|
||||||
|
tls_enable: "transport.tls.enable",
|
||||||
|
tcp_mux: "transport.tcpMux",
|
||||||
|
log_file: "log.to",
|
||||||
|
log_level: "log.level",
|
||||||
|
log_max_days: "log.maxDays",
|
||||||
|
};
|
||||||
|
const match = line.match(/^(\s*)([A-Za-z0-9_.-]+)(\s*=.*)$/);
|
||||||
|
if (!match) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, indent, key, rest] = match;
|
||||||
|
return `${indent}${keyMap[key] || key}${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyProxyConfigValue(current, keyValue, sections) {
|
||||||
|
const key = normalizeProxyKey(keyValue.key);
|
||||||
|
|
||||||
|
if (key === "name") {
|
||||||
|
if (current.name) {
|
||||||
|
sections.delete(current.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
current.name = String(keyValue.value);
|
||||||
|
sections.set(current.name, current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.values[key] = keyValue.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeProxyKey(key) {
|
||||||
|
const keyMap = {
|
||||||
|
localIP: "local_ip",
|
||||||
|
localPort: "local_port",
|
||||||
|
remotePort: "remote_port",
|
||||||
|
};
|
||||||
|
|
||||||
|
return keyMap[key] || key;
|
||||||
|
}
|
||||||
|
|
||||||
function parseTomlKeyValue(line) {
|
function parseTomlKeyValue(line) {
|
||||||
const withoutComment = line.replace(/\s+#.*$/, "").trim();
|
const withoutComment = line.replace(/\s+#.*$/, "").trim();
|
||||||
const match = withoutComment.match(/^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/);
|
const match = withoutComment.match(/^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/);
|
||||||
@@ -374,11 +453,12 @@ function renderFrpConfig(globals, proxies) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderProxy(name, values) {
|
function renderProxy(name, values) {
|
||||||
return `[${name}]
|
return `[[proxies]]
|
||||||
|
name = ${formatTomlValue(name)}
|
||||||
type = ${formatTomlValue(values.type || "tcp")}
|
type = ${formatTomlValue(values.type || "tcp")}
|
||||||
local_ip = ${formatTomlValue(values.local_ip || "127.0.0.1")}
|
localIP = ${formatTomlValue(values.local_ip || "127.0.0.1")}
|
||||||
local_port = ${formatTomlValue(values.local_port)}
|
localPort = ${formatTomlValue(values.local_port)}
|
||||||
remote_port = ${formatTomlValue(values.remote_port)}
|
remotePort = ${formatTomlValue(values.remote_port)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ test("parses and renders frp proxy sections", () => {
|
|||||||
const parsed = parseFrpConfig(`server_addr = "81.70.134.9"
|
const parsed = parseFrpConfig(`server_addr = "81.70.134.9"
|
||||||
server_port = 15443
|
server_port = 15443
|
||||||
token = "secret"
|
token = "secret"
|
||||||
|
log_file = "/var/log/frpc.log"
|
||||||
|
|
||||||
[ssh]
|
[ssh]
|
||||||
type = "tcp"
|
type = "tcp"
|
||||||
@@ -32,8 +33,32 @@ remote_port = 17227
|
|||||||
|
|
||||||
const rendered = renderParsedFrpConfig(parsed);
|
const rendered = renderParsedFrpConfig(parsed);
|
||||||
|
|
||||||
assert.match(rendered, /\[ssh]/);
|
assert.match(rendered, /serverAddr = "81.70.134.9"/);
|
||||||
assert.match(rendered, /local_port = 22/);
|
assert.match(rendered, /serverPort = 15443/);
|
||||||
assert.match(rendered, /\[mysql]/);
|
assert.match(rendered, /auth.token = "secret"/);
|
||||||
assert.match(rendered, /remote_port = 33061/);
|
assert.match(rendered, /log.to = "\/var\/log\/frpc.log"/);
|
||||||
|
assert.match(rendered, /\[\[proxies\]\]/);
|
||||||
|
assert.match(rendered, /name = "ssh"/);
|
||||||
|
assert.match(rendered, /localPort = 22/);
|
||||||
|
assert.match(rendered, /name = "mysql"/);
|
||||||
|
assert.match(rendered, /remotePort = 33061/);
|
||||||
|
assert.doesNotMatch(rendered, /log_file/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parses modern frp proxy arrays", () => {
|
||||||
|
const parsed = parseFrpConfig(`serverAddr = "81.70.134.9"
|
||||||
|
serverPort = 15443
|
||||||
|
auth.token = "secret"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "ssh"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
remotePort = 17227
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.equal(parsed.sections.get("ssh").values.local_ip, "127.0.0.1");
|
||||||
|
assert.equal(parsed.sections.get("ssh").values.local_port, 22);
|
||||||
|
assert.equal(parsed.sections.get("ssh").values.remote_port, 17227);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user