fix
This commit is contained in:
335
src/lib/tasks.js
Normal file
335
src/lib/tasks.js
Normal file
@@ -0,0 +1,335 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
|
||||
const {
|
||||
DEFAULT_FRP_VERSION,
|
||||
DEFAULT_FRP_ARCH,
|
||||
DEFAULT_FRP_SERVER_ADDR,
|
||||
DEFAULT_FRP_SERVER_PORT,
|
||||
DEFAULT_SERVICE_NAME,
|
||||
defaultInstallDir,
|
||||
defaultConfigPath,
|
||||
parseFrpConfig,
|
||||
renderParsedFrpConfig,
|
||||
renderFrpConfig,
|
||||
buildGlobals,
|
||||
createUniqueProxyName,
|
||||
assertProxyName,
|
||||
} = require("./frp-config");
|
||||
|
||||
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`;
|
||||
|
||||
const OH_MY_ZSH_COMMAND =
|
||||
'RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://gitee.com/pocmon/ohmyzsh/raw/master/tools/install.sh)"';
|
||||
|
||||
function zshInstallPlan() {
|
||||
const customDir = process.env.ZSH_CUSTOM || path.join(os.homedir(), ".oh-my-zsh", "custom");
|
||||
const autosuggestionsDir = path.join(customDir, "plugins", "zsh-autosuggestions");
|
||||
const syntaxHighlightingDir = path.join(customDir, "plugins", "zsh-syntax-highlighting");
|
||||
|
||||
const steps = [
|
||||
run("apt update", "sudo", ["apt", "update"]),
|
||||
run("apt upgrade", "sudo", ["apt", "upgrade", "-y"]),
|
||||
run("install zsh & friends", "sudo", ["apt", "install", "zsh", "git", "curl", "wget", "-y"]),
|
||||
run("change shell to zsh", "chsh", ["-s", "/bin/zsh"]),
|
||||
run("install oh-my-zsh", "sh", ["-c", OH_MY_ZSH_COMMAND]),
|
||||
run("install nvm + node LTS", "bash", ["-c", NVM_INSTALL_COMMAND]),
|
||||
gitPluginStep("zsh-autosuggestions", "https://github.com/zsh-users/zsh-autosuggestions", autosuggestionsDir),
|
||||
gitPluginStep(
|
||||
"zsh-syntax-highlighting",
|
||||
"https://github.com/zsh-users/zsh-syntax-highlighting.git",
|
||||
syntaxHighlightingDir,
|
||||
),
|
||||
{
|
||||
kind: "zshrc-plugins",
|
||||
label: "update ~/.zshrc plugin list",
|
||||
path: path.join(os.homedir(), ".zshrc"),
|
||||
plugins: ["git", "zsh-autosuggestions", "zsh-syntax-highlighting"],
|
||||
},
|
||||
];
|
||||
|
||||
return { title: "Install zsh + oh-my-zsh + nvm", steps };
|
||||
}
|
||||
|
||||
function gitPluginStep(name, repo, destination) {
|
||||
if (fs.existsSync(destination)) {
|
||||
return run(`update ${name}`, "git", ["-C", destination, "pull", "--ff-only"]);
|
||||
}
|
||||
return run(`clone ${name}`, "git", ["clone", repo, destination]);
|
||||
}
|
||||
|
||||
function sshInstallPlan() {
|
||||
return {
|
||||
title: "Install OpenSSH server",
|
||||
steps: [
|
||||
run("apt update", "sudo", ["apt", "update"]),
|
||||
run("install openssh-server", "sudo", ["apt", "install", "openssh-server", "-y"]),
|
||||
run("enable + start ssh", "sudo", ["systemctl", "enable", "--now", "ssh"]),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function frpInstallPlan(options = {}) {
|
||||
const version = options.version || DEFAULT_FRP_VERSION;
|
||||
const arch = options.arch || DEFAULT_FRP_ARCH;
|
||||
const installDir = options.installDir || defaultInstallDir(version, arch);
|
||||
const configPath = options.configPath || defaultConfigPath(installDir);
|
||||
const serviceName = options.serviceName || DEFAULT_SERVICE_NAME;
|
||||
const archiveName = `frp_${version}_linux_${arch}.tar.gz`;
|
||||
const archivePath = `/tmp/${archiveName}`;
|
||||
const extractedDir = `/tmp/frp_${version}_linux_${arch}`;
|
||||
const url = `https://github.com/fatedier/frp/releases/download/v${version}/${archiveName}`;
|
||||
|
||||
const token = requireToken(options.token);
|
||||
const force = Boolean(options.force);
|
||||
|
||||
const proxies = (fs.existsSync(configPath) && force)
|
||||
? [...parseFrpConfig(fs.readFileSync(configPath, "utf8")).sections.values()].map((section) => ({
|
||||
name: section.name,
|
||||
...section.values,
|
||||
}))
|
||||
: [];
|
||||
|
||||
const configContent = renderFrpConfig(buildGlobalsFromOptions(token, options), proxies);
|
||||
const serviceContent = renderSystemdService(installDir, configPath);
|
||||
|
||||
const steps = [
|
||||
run("download frp archive", "wget", ["-O", archivePath, url]),
|
||||
run("extract archive", "tar", ["-zxf", archivePath, "-C", "/tmp"]),
|
||||
run("create install dir", "sudo", ["mkdir", "-p", installDir]),
|
||||
run("copy frpc binary", "sudo", [
|
||||
"cp",
|
||||
"-f",
|
||||
path.join(extractedDir, "frpc"),
|
||||
path.join(installDir, "frpc"),
|
||||
]),
|
||||
run("chmod +x frpc", "sudo", ["chmod", "+x", path.join(installDir, "frpc")]),
|
||||
];
|
||||
|
||||
if (!fs.existsSync(configPath) || force) {
|
||||
steps.push(writeFile(`write ${path.basename(configPath)}`, configPath, configContent));
|
||||
}
|
||||
steps.push(writeFile(`write ${serviceName}.service`, `/etc/systemd/system/${serviceName}.service`, serviceContent));
|
||||
steps.push(run("systemctl daemon-reload", "sudo", ["systemctl", "daemon-reload"]));
|
||||
steps.push(run(`enable + start ${serviceName}`, "sudo", ["systemctl", "enable", "--now", serviceName]));
|
||||
|
||||
return { title: `Install frp ${version} (${serviceName})`, steps };
|
||||
}
|
||||
|
||||
function frpInitConfigPlan(options = {}) {
|
||||
const installDir = options.installDir || defaultInstallDir();
|
||||
const configPath = options.configPath || defaultConfigPath(installDir);
|
||||
const exists = fs.existsSync(configPath);
|
||||
|
||||
if (exists && !options.force) {
|
||||
throw new Error(`${configPath} already exists. Re-run with --force to rewrite.`);
|
||||
}
|
||||
|
||||
const token = requireToken(options.token);
|
||||
const proxies = (exists && options.force)
|
||||
? [...parseFrpConfig(fs.readFileSync(configPath, "utf8")).sections.values()].map((section) => ({
|
||||
name: section.name,
|
||||
...section.values,
|
||||
}))
|
||||
: [];
|
||||
|
||||
const content = renderFrpConfig(buildGlobalsFromOptions(token, options), proxies);
|
||||
|
||||
return {
|
||||
title: `Rewrite ${configPath}`,
|
||||
steps: [writeFile(`write ${path.basename(configPath)}`, configPath, content)],
|
||||
};
|
||||
}
|
||||
|
||||
function frpAddProxyPlan(options) {
|
||||
const baseName = options.name;
|
||||
if (!baseName) {
|
||||
throw new Error("Proxy name is required.");
|
||||
}
|
||||
assertProxyName(baseName);
|
||||
if (!options.localPort || !options.remotePort) {
|
||||
throw new Error("local port and remote port are required.");
|
||||
}
|
||||
|
||||
const installDir = options.installDir || defaultInstallDir();
|
||||
const configPath = options.configPath || defaultConfigPath(installDir);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
throw new Error(`${configPath} does not exist. Install frp or run init first.`);
|
||||
}
|
||||
|
||||
const config = parseFrpConfig(fs.readFileSync(configPath, "utf8"));
|
||||
const proxyName = createUniqueProxyName(baseName, config.sections);
|
||||
config.sections.set(proxyName, {
|
||||
name: proxyName,
|
||||
values: {
|
||||
type: options.type || "tcp",
|
||||
local_ip: options.localIp || "127.0.0.1",
|
||||
local_port: options.localPort,
|
||||
remote_port: options.remotePort,
|
||||
},
|
||||
});
|
||||
|
||||
const content = renderParsedFrpConfig(config);
|
||||
const steps = [writeFile(`write proxy ${proxyName}`, configPath, content)];
|
||||
if (options.restart) {
|
||||
steps.push(run(`restart ${options.serviceName || DEFAULT_SERVICE_NAME}`, "sudo", [
|
||||
"systemctl",
|
||||
"restart",
|
||||
options.serviceName || DEFAULT_SERVICE_NAME,
|
||||
]));
|
||||
}
|
||||
|
||||
return {
|
||||
title: `Add proxy ${proxyName}`,
|
||||
steps,
|
||||
meta: { proxyName },
|
||||
};
|
||||
}
|
||||
|
||||
function frpRemoveProxyPlan(options) {
|
||||
if (!options.name) {
|
||||
throw new Error("Proxy name is required.");
|
||||
}
|
||||
|
||||
const installDir = options.installDir || defaultInstallDir();
|
||||
const configPath = options.configPath || defaultConfigPath(installDir);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
throw new Error(`${configPath} does not exist.`);
|
||||
}
|
||||
|
||||
const config = parseFrpConfig(fs.readFileSync(configPath, "utf8"));
|
||||
if (!config.sections.delete(options.name)) {
|
||||
throw new Error(`No proxy named ${options.name} in ${configPath}.`);
|
||||
}
|
||||
|
||||
const content = renderParsedFrpConfig(config);
|
||||
const steps = [writeFile(`remove proxy ${options.name}`, configPath, content)];
|
||||
if (options.restart) {
|
||||
steps.push(run(`restart ${options.serviceName || DEFAULT_SERVICE_NAME}`, "sudo", [
|
||||
"systemctl",
|
||||
"restart",
|
||||
options.serviceName || DEFAULT_SERVICE_NAME,
|
||||
]));
|
||||
}
|
||||
|
||||
return { title: `Remove proxy ${options.name}`, steps };
|
||||
}
|
||||
|
||||
function frpRestartPlan(options = {}) {
|
||||
const serviceName = options.serviceName || DEFAULT_SERVICE_NAME;
|
||||
return {
|
||||
title: `Restart ${serviceName}`,
|
||||
steps: [run(`restart ${serviceName}`, "sudo", ["systemctl", "restart", serviceName])],
|
||||
};
|
||||
}
|
||||
|
||||
function bootstrapPlan(options) {
|
||||
const zsh = zshInstallPlan();
|
||||
const ssh = sshInstallPlan();
|
||||
const frp = frpInstallPlan(options);
|
||||
|
||||
return {
|
||||
title: "Bootstrap: zsh + ssh + frp",
|
||||
steps: [...zsh.steps, ...ssh.steps, ...frp.steps],
|
||||
};
|
||||
}
|
||||
|
||||
function listProxies(options = {}) {
|
||||
const installDir = options.installDir || defaultInstallDir();
|
||||
const configPath = options.configPath || defaultConfigPath(installDir);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
throw new Error(`${configPath} does not exist.`);
|
||||
}
|
||||
|
||||
const config = parseFrpConfig(fs.readFileSync(configPath, "utf8"));
|
||||
return [...config.sections.values()].map((section) => ({
|
||||
name: section.name,
|
||||
type: section.values.type || "tcp",
|
||||
localIp: section.values.local_ip || "127.0.0.1",
|
||||
localPort: section.values.local_port,
|
||||
remotePort: section.values.remote_port,
|
||||
}));
|
||||
}
|
||||
|
||||
function buildGlobalsFromOptions(token, options) {
|
||||
return buildGlobals({
|
||||
serverAddr: options.serverAddr || DEFAULT_FRP_SERVER_ADDR,
|
||||
serverPort: options.serverPort || DEFAULT_FRP_SERVER_PORT,
|
||||
token,
|
||||
tlsEnable: options.tlsEnable ?? false,
|
||||
tcpMux: options.tcpMux ?? true,
|
||||
logFile: options.logFile || "/var/log/frpc.log",
|
||||
logLevel: options.logLevel || "info",
|
||||
logMaxDays: options.logMaxDays ?? 7,
|
||||
});
|
||||
}
|
||||
|
||||
function renderSystemdService(installDir, configPath) {
|
||||
return `[Unit]
|
||||
Description=frp client
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${path.join(installDir, "frpc")} -c ${configPath}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`;
|
||||
}
|
||||
|
||||
function requireToken(value) {
|
||||
const token = value || process.env.FRP_TOKEN || "";
|
||||
if (!token) {
|
||||
throw new Error("frp token is required.");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function run(label, command, args) {
|
||||
return { kind: "run", label, command, args };
|
||||
}
|
||||
|
||||
function writeFile(label, filePath, content) {
|
||||
return { kind: "write", label, path: filePath, content };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
zshInstallPlan,
|
||||
sshInstallPlan,
|
||||
frpInstallPlan,
|
||||
frpInitConfigPlan,
|
||||
frpAddProxyPlan,
|
||||
frpRemoveProxyPlan,
|
||||
frpRestartPlan,
|
||||
bootstrapPlan,
|
||||
listProxies,
|
||||
DEFAULTS: {
|
||||
version: DEFAULT_FRP_VERSION,
|
||||
arch: DEFAULT_FRP_ARCH,
|
||||
serverAddr: DEFAULT_FRP_SERVER_ADDR,
|
||||
serverPort: DEFAULT_FRP_SERVER_PORT,
|
||||
serviceName: DEFAULT_SERVICE_NAME,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user