""" Systemd service generator — a single, generic Generator that produces a fully-configured systemd unit for arbitrary user-provided commands. Plus presets for common cases. """ from . import register, Generator, Field from . import bash_header, quote, bool_str, yes class SystemdGeneric(Generator): id = "systemd-generic" title = "通用 systemd 服务" category = "systemd" icon = "🛠️" tags = ["systemd", "service", "unit"] description = "为任意可执行命令生成 systemd unit 文件,支持开机自启、自动重启、日志轮转。" fields = [ Field("service_name", "服务名", "text", default="myapp", help="将创建 /etc/systemd/system/.service"), Field("description", "Description", "text", default="My custom app"), Field("exec_start", "ExecStart 命令", "textarea", required=True, default="/opt/myapp/bin/server --config /etc/myapp/server.conf", help="要执行的命令,可以有多行(用 ; 串接)或用绝对路径。"), Field("exec_stop", "ExecStop 命令", "text", default="", help="留空则发 SIGTERM;填入完整命令。"), Field("exec_reload", "ExecReload 命令", "text", default="", help="通常是 'kill -HUP $MAINPID' 或 nginx -s reload。"), Field("type", "Type", "select", default="simple", options=["simple", "forking", "oneshot", "notify", "exec"]), Field("user", "运行用户", "text", default="root"), Field("group", "运行组", "text", default="root"), Field("working_dir", "WorkingDirectory", "text", default="/"), Field("env_vars", "环境变量", "textarea", default="NODE_ENV=production\nLOG_LEVEL=info", help="每行 KEY=VALUE。"), Field("restart", "Restart 策略", "select", default="on-failure", options=["no", "always", "on-success", "on-failure", "on-abnormal", "on-abort", "on-watchdog"]), Field("restart_sec", "RestartSec (秒)", "number", default="5"), Field("limit_nofile", "LimitNOFILE", "number", default="65536"), Field("limit_nproc", "LimitNPROC", "number", default="65536"), Field("kill_mode", "KillMode", "select", default="mixed", options=["control-group", "process", "mixed", "none"]), Field("enable_after_network", "After=network.target", "checkbox", default="yes"), Field("enable_log_dir", "创建日志目录 /var/log/", "checkbox", default="yes"), Field("auto_start", "脚本执行完自动 enable+start", "checkbox", default="yes"), ] def render(self, p): name = p.get("service_name", "myapp").replace(" ", "-") desc = p.get("description", "My custom app") exec_start = p.get("exec_start", "").strip() if not exec_start: raise ValueError("exec_start 不能为空") exec_stop = p.get("exec_stop", "").strip() exec_reload = p.get("exec_reload", "").strip() type_ = p.get("type", "simple") user = p.get("user", "root") group = p.get("group", "root") wd = p.get("working_dir", "/") envs = [e.strip() for e in (p.get("env_vars", "") or "").splitlines() if e.strip() and "=" in e] restart = p.get("restart", "on-failure") restart_sec = str(p.get("restart_sec", "5")) nofile = str(p.get("limit_nofile", "65536")) nproc = str(p.get("limit_nproc", "65536")) kill = p.get("kill_mode", "mixed") after_nw = bool_str(p.get("enable_after_network", True)) log_dir = bool_str(p.get("enable_log_dir", True)) auto = bool_str(p.get("auto_start", True)) out = [bash_header(self.title)] out.append('log "Writing unit file /etc/systemd/system/' + name + '.service"') out.append('mkdir -p /etc/systemd/system') if log_dir: out.append('mkdir -p /var/log/' + name) unit = ['[Unit]'] unit.append('Description=' + desc) if after_nw: unit.append('After=network.target network-online.target') unit.append('Wants=network-online.target') unit.append('') unit.append('[Service]') unit.append('Type=' + type_) unit.append('User=' + user) unit.append('Group=' + group) unit.append('WorkingDirectory=' + wd) for env in envs: unit.append('Environment="' + env.replace('"', '\\"') + '"') unit.append('ExecStart=' + exec_start.replace('\n', ' && ')) if exec_stop: unit.append('ExecStop=' + exec_stop) if exec_reload: unit.append('ExecReload=' + exec_reload) unit.append('Restart=' + restart) unit.append('RestartSec=' + restart_sec) unit.append('LimitNOFILE=' + nofile) unit.append('LimitNPROC=' + nproc) unit.append('KillMode=' + kill) unit.append('StandardOutput=journal') unit.append('StandardError=journal') if log_dir: unit.append('StandardOutput=append:/var/log/' + name + '/stdout.log') unit.append('StandardError=append:/var/log/' + name + '/stderr.log') unit.append('') unit.append('[Install]') unit.append('WantedBy=multi-user.target') out.append('cat > /etc/systemd/system/' + name + '.service <