diff --git a/README.md b/README.md index 1697978..40ec64b 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,10 @@ server-config zsh install 会执行: - `apt update && apt upgrade` -- 安装 `zsh git curl` +- 安装 `zsh git curl wget` - 切换当前用户 shell 到 `/bin/zsh` - 安装 oh-my-zsh +- 安装 nvm,并执行 `nvm install --lts` - 安装 `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"` -- `server_port = 15443` -- `tls_enable = false` -- `tcp_mux = true` -- `log_file = "/var/log/frpc.log"` -- `log_level = "info"` -- `log_max_days = 7` +- `serverAddr = "81.70.134.9"` +- `serverPort = 15443` +- `auth.token = "$FRP_TOKEN"` +- `transport.tls.enable = false` +- `transport.tcpMux = true` +- `log.to = "/var/log/frpc.log"` +- `log.level = "info"` +- `log.maxDays = 7` - frp 版本:`0.58.1` - 安装目录:`/opt/frp/frp_0.58.1_linux_amd64` - systemd 服务:`frpc` diff --git a/src/cli.js b/src/cli.js index 6d7864c..e48f4e1 100644 --- a/src/cli.js +++ b/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_PORT = 15443; 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) { const parsed = parseArgs(argv); @@ -95,13 +112,14 @@ function handleFrp(action, argv, rootFlags, runner) { function installZsh(runner, flags) { runner.run("sudo", ["apt", "update"]); 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("sh", [ "-c", '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"); installGitPlugin( @@ -175,8 +193,9 @@ function installFrp(runner, flags) { function initFrpConfig(flags, runner) { const configPath = getConfigPath(flags); 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.`); return; } @@ -186,16 +205,24 @@ function initFrpConfig(flags, runner) { throw new Error("frp token is required. Pass --token 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({ - server_addr: stringFlag(flags, "server-addr", DEFAULT_FRP_SERVER_ADDR), - server_port: numberFlag(flags, "server-port", DEFAULT_FRP_SERVER_PORT), - token, - tls_enable: booleanFlag(flags, "tls-enable", false), - tcp_mux: booleanFlag(flags, "tcp-mux", true), - log_file: stringFlag(flags, "log-file", "/var/log/frpc.log"), - log_level: stringFlag(flags, "log-level", "info"), - log_max_days: numberFlag(flags, "log-max-days", 7), - }, []); + serverAddr: stringFlag(flags, "server-addr", DEFAULT_FRP_SERVER_ADDR), + serverPort: numberFlag(flags, "server-port", DEFAULT_FRP_SERVER_PORT), + "auth.method": "token", + "auth.token": token, + "transport.tls.enable": booleanFlag(flags, "tls-enable", false), + "transport.tcpMux": booleanFlag(flags, "tcp-mux", true), + "log.to": stringFlag(flags, "log-file", "/var/log/frpc.log"), + "log.level": stringFlag(flags, "log-level", "info"), + "log.maxDays": numberFlag(flags, "log-max-days", 7), + }, proxies); writeFile(configPath, config, runner); } @@ -300,6 +327,12 @@ function parseFrpConfig(text) { let current = null; 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*$/); if (sectionMatch) { current = { name: sectionMatch[1], values: {} }; @@ -308,19 +341,65 @@ function parseFrpConfig(text) { } if (!current) { - globals.push(line); + globals.push(migrateFrpGlobalLine(line)); continue; } const keyValue = parseTomlKeyValue(line); if (keyValue) { - current.values[keyValue.key] = keyValue.value; + applyProxyConfigValue(current, keyValue, 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) { const withoutComment = line.replace(/\s+#.*$/, "").trim(); const match = withoutComment.match(/^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/); @@ -374,11 +453,12 @@ function renderFrpConfig(globals, proxies) { } function renderProxy(name, values) { - return `[${name}] + return `[[proxies]] +name = ${formatTomlValue(name)} type = ${formatTomlValue(values.type || "tcp")} -local_ip = ${formatTomlValue(values.local_ip || "127.0.0.1")} -local_port = ${formatTomlValue(values.local_port)} -remote_port = ${formatTomlValue(values.remote_port)} +localIP = ${formatTomlValue(values.local_ip || "127.0.0.1")} +localPort = ${formatTomlValue(values.local_port)} +remotePort = ${formatTomlValue(values.remote_port)} `; } diff --git a/test/frp-config.test.js b/test/frp-config.test.js index be04608..8762e3a 100644 --- a/test/frp-config.test.js +++ b/test/frp-config.test.js @@ -9,6 +9,7 @@ test("parses and renders frp proxy sections", () => { const parsed = parseFrpConfig(`server_addr = "81.70.134.9" server_port = 15443 token = "secret" +log_file = "/var/log/frpc.log" [ssh] type = "tcp" @@ -32,8 +33,32 @@ remote_port = 17227 const rendered = renderParsedFrpConfig(parsed); - assert.match(rendered, /\[ssh]/); - assert.match(rendered, /local_port = 22/); - assert.match(rendered, /\[mysql]/); - assert.match(rendered, /remote_port = 33061/); + assert.match(rendered, /serverAddr = "81.70.134.9"/); + assert.match(rendered, /serverPort = 15443/); + assert.match(rendered, /auth.token = "secret"/); + 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); });