spb/maclustr-dispatch
Public
1"""Génération des fichiers de lancement : ecosystem PM2 (.cjs) et plists launchd."""2import json3import plistlib4import shlex567def _mem(v):8 if not v:9 return None10 if isinstance(v, str):11 return v12 return "%dM" % (int(v) // (1024 * 1024))131415def _home(path, ctx):16 """PM2/launchd n'expandent pas « ~ » : chemin absolu obligatoire."""17 if isinstance(path, str) and path.startswith("~"):18 return ctx["HOME"] + path[1:]19 return path202122def ecosystem(m, ctx):23 """Retourne le contenu JS d'un ecosystem.config.cjs pour les processus PM2 + tunnel ngrok."""24 from .manifest import render25 apps = []26 for p in m["processes"]:27 if p.get("manager", "pm2") != "pm2":28 continue29 a = {30 "name": p["name"],31 "script": _home(render(p["script"], ctx), ctx),32 "cwd": _home(render(p.get("cwd") or m["dir"], ctx), ctx),33 "exec_mode": "fork",34 "instances": 1,35 "autorestart": p.get("autorestart", True),36 "merge_logs": True,37 "time": True,38 "env": render(p.get("env") or {}, ctx),39 }40 if p.get("args"):41 args = p["args"]42 a["args"] = " ".join(shlex.quote(str(x)) for x in render(args, ctx)) if isinstance(args, list) else render(args, ctx)43 interp = p.get("interpreter")44 if interp and interp not in ("none", "N/A"):45 a["interpreter"] = render(interp, ctx)46 elif interp in ("none",) or (p["script"].endswith((".sh", ".mjs", ".js", ".py", ".cjs")) is False):47 a["interpreter"] = "none"48 if p.get("cron_restart"):49 a["cron_restart"] = p["cron_restart"]50 if p.get("max_memory_restart"):51 a["max_memory_restart"] = _mem(p["max_memory_restart"])52 if p.get("restart_delay"):53 a["restart_delay"] = p["restart_delay"]54 if p.get("node_args"):55 a["node_args"] = p["node_args"]56 apps.append(a)57 if m.get("ngrok"):58 n = m["ngrok"]59 args = ["http", "--url=%s" % n["url"], str(n["port"]), "--log=stdout"] + list(n.get("extra_args") or [])60 apps.append({61 "name": n["name"], "script": n.get("binary") or "/opt/homebrew/bin/ngrok", "args": " ".join(args),62 "interpreter": "none", "cwd": _home(render(m["dir"], ctx), ctx), "exec_mode": "fork", "autorestart": True,63 "restart_delay": 5000, "merge_logs": True, "time": True,64 })65 head = "// généré par maclustr-dispatch — ne pas éditer à la main (app: %s, nœud: %s)\n" % (m["app"], ctx.get("NODE"))66 return head + "module.exports = { apps: %s };\n" % json.dumps(apps, indent=2, ensure_ascii=False)676869def plist(item, ctx):70 from .manifest import render71 d = {72 "Label": item["label"],73 "ProgramArguments": render(item["program_arguments"], ctx),74 "RunAtLoad": item.get("run_at_load", True),75 "KeepAlive": item.get("keep_alive", True),76 "StandardOutPath": render(item.get("stdout") or "{{HOME}}/Library/Logs/%s.log" % item["label"], ctx),77 "StandardErrorPath": render(item.get("stderr") or "{{HOME}}/Library/Logs/%s.err.log" % item["label"], ctx),78 }79 if item.get("working_directory"):80 d["WorkingDirectory"] = _home(render(item["working_directory"], ctx), ctx)81 env = dict(item.get("env") or {})82 env.setdefault("HOME", ctx["HOME"])83 env.setdefault("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin")84 d["EnvironmentVariables"] = render(env, ctx)85 if item.get("start_interval"):86 d["StartInterval"] = int(item["start_interval"])87 if item.get("throttle_interval"):88 d["ThrottleInterval"] = int(item["throttle_interval"])89 return plistlib.dumps(d).decode()90