This commit is contained in:
2026-04-15 15:20:27 +08:00
parent de93193279
commit e3f83f4746
3 changed files with 136 additions and 29 deletions

View File

@@ -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`

View File

@@ -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 <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({
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)}
`;
}

View File

@@ -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);
});