def install(args): service_name, service = create_service_template(args) try: with open( os.path.join(args.systempath, service_name) + ".service", "w") as f: print(service) f.write(service) except PermissionError: print("Need sudo to create systemd unit service file.") sys.exit(1) create_mail_on_failure_service(args) _ = systemctl("daemon-reload") _ = systemctl("enable {}".format(service_name)) create_timer = create_timer_service(service_name, args) if create_timer: _ = systemctl("enable {}.timer".format(service_name)) _ = systemctl("start {}.timer".format(service_name)) else: monitor = create_service_monitor_template(service_name, args) with open( os.path.join(args.systempath, service_name) + "_monitor.service", "w") as f: f.write(monitor) _ = systemctl("start --no-block {}".format(service_name)) _ = systemctl("enable {}_monitor".format(service_name)) _ = systemctl("start {}_monitor".format(service_name)) return service_name
def delete(unit, systempath): service_name = unit.replace(".", "_") path = systempath + "/" + service_name for s in [ service_name, service_name + "_monitor", service_name + ".timer" ]: if is_unit_enabled(s): _ = systemctl("disable {}".format(s)) print("Disabled unit {}".format(s)) else: print("Unit {} was not enabled so no need to disable it".format(s)) if is_unit_running(s): _ = systemctl("stop {}".format(s)) print("Stopped unit {}".format(s)) else: print("Unit {} was not started so no need to stop it".format(s)) _ = systemctl("daemon-reload") o = run_quiet("rm {}".format(path + ".service")) o = run_quiet("rm {}".format(path + "_monitor.service")) o = run_quiet("rm {}".format(path + ".timer")) print("Deleted {}".format(path + ".service")) print("Deleted {}".format(path + "_monitor.service")) print("Deleted {}".format(path + ".timer")) print("Delete Succeeded!")
def create( self, fname_or_cmd: str, restart=True, timer: str = None, killaftertimeout=90, delay=0.2, extensions=[], exclude_patterns=[], ls=True, root=False, notify_cmd="-1", notify_status_cmd="systemctl --user status -l -n 1000 %i", notify_cmd_args='-s "%i failed on %H"', ): """ Create a systemd unit file :param fname_or_cmd: File/cmd to run :param restart: Whether to prevent auto restart on error :param timer: Used to set timer. Checked to be valid. E.g. *-*-* 03:00:00 for daily at 3 am. :param killaftertimeout: Time before sending kill signal if unresponsive when try to restart :param delay: Set a delay in the unit file before attempting restart :param extensions: Patterns of files to watch (by default inferred) :param exclude_patterns: Patterns of files to ignore (by default inferred) :param ls: Only create but do not list :param root: Only possible when using sudo :param notify_cmd: Binary command that will notify. -1 will add no notifier. Possible: e.g. yagmail :param notify_status_cmd: Command that echoes output to the notifier on failure :param notify_cmd_args: Arguments passed to notify command. """ print("Creating systemd unit...") service_name, service = create_service_template( fname_or_cmd, notify_cmd, timer, delay, root, killaftertimeout) try: with open( os.path.join(self.systempath, service_name) + ".service", "w") as f: print(service) f.write(service) except PermissionError: print("Need sudo to create systemd unit service file.") sys.exit(1) create_mail_on_failure_service(self.systempath, notify_cmd, notify_status_cmd, notify_status_cmd, root) _ = systemctl("daemon-reload") create_timer = create_timer_service(self.systempath, service_name, timer) if create_timer: _ = systemctl("enable {}.timer".format(service_name)) _ = systemctl("start {}.timer".format(service_name)) else: _ = systemctl("enable {}".format(service_name)) monitor_str = create_service_monitor_template( service_name, fname_or_cmd, extensions, exclude_patterns, root) with open( os.path.join(self.systempath, service_name) + "_monitor.service", "w") as f: f.write(monitor_str) _ = systemctl("start --no-block {}".format(service_name)) _ = systemctl("enable {}_monitor".format(service_name)) _ = systemctl("start {}_monitor".format(service_name)) print(linger()) print("Done") if ls: monitor(service_name, self.systempath)
def reload(): """ Do a daemon-reload for systemd """ systemctl("daemon-reload")
def monitor(unit, systempath): t = Terminal() print(t.enter_fullscreen()) mapping = [ "[R] Restart service ", "[S] Stop service ", "[T] Enable on startup [b] Back ", "[g] Grep (filter) a pattern [q] Quit view ", ] OFFSET = 12 resized = [True] signal.signal(signal.SIGWINCH, lambda *args: resized.append(True)) logo = "[sysdm]" # seen = set() Y_BANNER_OFFSET = len(mapping) + 1 + 2 # mapping, banner, in between lines grep = "" x_banner_offset = 0 left_offset = 0 log_offset = 0 timer = None with t.hidden_cursor(): try: while True: print(t.move(0, 0)) y, x = t.get_location() if resized: print(t.clear()) resized = [] timed = is_unit_running(unit + ".timer") is_running = is_unit_running(unit) or timed is_enabled = is_unit_enabled(unit) if timed: timer_text = "" while not timer_text: status = systemctl("list-timers " + unit + ".timer") timer_text = status.split("\n")[1][4 : status.index("LEFT") - 2] status = "Next: " + t.green(timer_text) timer = datetime.strptime(timer_text[:19], "%Y-%m-%d %H:%M:%S") else: status = "Active: " + (t.green("✓") if is_running else t.red("✗")) with t.location(OFFSET, 0): enabled = t.green("✓") if is_enabled else t.red("✗") line = "Unit: {} {} On Startup: {}".format(t.bold(unit), status, enabled) x_banner_offset = len(line) print(line) with t.location(t.width - len(logo), 0): print(t.bold(logo)) with t.location(0, 1): print(t.center("-" * (t.width - 16))) for num, line in enumerate(mapping): with t.location(0, num + 2): if not is_running: line = line.replace("Stop service ", "Start service") if is_enabled: line = line.replace("Enable on startup", "Disable on startup") line = line.replace("[", "[" + t.green).replace("]", t.normal + "]") print(" " * OFFSET + (line + " " * t.width)[: t.width + 3]) with t.location(0, 6): print(t.center("-" * (t.width - 16))) # if timer just expired, refresh to get the new date if timer is not None and datetime.now() > timer: resized = [True] if t.width - x_banner_offset > 50: res = "| {} |".format(time.asctime()) if is_running: ps_aux = get_output("ps ax -o pid,%cpu,%mem,ppid,args -ww") ps_info = read_ps_aux_by_unit(systempath, unit, ps_aux) if ps_info is not None: res = "| {} | PID={} | CPU {:>4}% | MEM {:>4}%".format( time.asctime(), *ps_info ) with t.location(x_banner_offset, 0): print(res) with t.location(0, Y_BANNER_OFFSET): n = t.height - Y_BANNER_OFFSET - 1 w = t.width g = "--grep " + grep if grep else "" u_sep = "-u" if IS_SUDO else "--user-unit" output = journalctl( "journalctl {u_sep} {u} {u_sep} {u}_monitor {u_sep} {u}.timer -n {n} --no-pager {g}".format( u=unit, n=n + log_offset + 100, g=g, u_sep=u_sep ) ) outp = [] for line in output.split("\n"): # replace e.g. python[pidnum123]: real output line = re.sub("(?<=:\d\d ).+?\[\d+\]: ", "| ", line) if grep: rmatch = re.search(grep, line) if rmatch is not None: s = rmatch.start() e = rmatch.end() line = line[:s] + t.red(line[s:e]) + line[e:] l = (line + " " * 200)[left_offset : w + left_offset - 5] if "Stopped" in l: l = t.bold(l) if "Started" in l: if timed: ln = len(l) white = " " * 200 l = (l.split("|")[0] + "| Succesfully ran on timer" + white)[:ln] l = t.green(l) if "WARNING: " in l: l = t.yellow(l) if "ERROR: " in l: l = t.red(l) if "Failed to start " in l: l = t.red(l) if "Triggering OnFailure= " in l: l = t.yellow(l) outp.append(l) if log_offset: print("\n".join(outp[-n - log_offset + 1 : -log_offset])) else: print("\n".join(outp[-n - log_offset + 1 :])) with t.cbreak(): inp = t.inkey(0.3) if inp == "q": if grep: grep = "" else: print(t.clear()) sys.exit(0) elif inp == "S": print(t.clear()) if is_running: print("Stopping unit {unit}".format(unit=unit)) systemctl("stop {unit}".format(unit=unit)) systemctl("stop {unit}.timer".format(unit=unit)) else: print("Starting unit {unit}".format(unit=unit)) systemctl("start --no-block {unit}".format(unit=unit)) systemctl("start {unit}.timer".format(unit=unit)) resized = [True] elif inp == "R": print(t.clear()) print("Restarting unit {unit}".format(unit=unit)) systemctl("restart {unit}".format(unit=unit)) resized = [True] elif inp == "b" or inp.name == "KEY_DELETE": return elif inp == "T": print(t.clear()) if is_enabled: print("Disabling unit {unit} on startup".format(unit=unit)) systemctl("disable {unit}".format(unit=unit)) systemctl("disable {unit}.timer".format(unit=unit)) else: print("Enabling unit {unit} on startup".format(unit=unit)) systemctl("enable {unit}".format(unit=unit)) systemctl("enable {unit}.timer".format(unit=unit)) resized = [True] elif inp == " ": print(t.clear()) resized = [True] elif inp == "g": print(t.clear()) if grep: grep = "" else: grep = input( "Grep pattern to search for (leave blank for cancel): " ).strip() resized = [True] elif inp.name == "KEY_RIGHT": print(t.erase()) left_offset = min(left_offset + 5, t.width) elif inp.name == "KEY_LEFT": print(t.erase()) left_offset = max(0, left_offset - 5) elif inp.name == "KEY_UP": print(t.erase()) log_offset = min(log_offset + 5, t.height) elif inp.name == "KEY_DOWN": print(t.erase()) log_offset = max(0, log_offset - 5) else: print(t.erase()) except KeyboardInterrupt: pass print(t.clear())
def create(self, fname_or_cmd: str, restart=True, timer: str = None, killaftertimeout=90, delay=0.2, extensions=[], exclude_patterns=[], ls=True, root=False, n_notifier: Optional[str] = None, n_user: Optional[str] = None, n_to: Optional[Union[str, int]] = None, n_pw: Optional[str] = None, n_msg: Optional[str] = "%i failed on %H", n_status_cmd="journalctl {user} --no-pager -n 1000", workdir: str = "", env_vars: list[str] = []): """ Create a systemd unit file :param fname_or_cmd: File/cmd to run :param restart: Whether to prevent auto restart on error :param timer: Used to set timer. Checked to be valid. E.g. *-*-* 03:00:00 for daily at 3 am. :param killaftertimeout: Time before sending kill signal if unresponsive when try to restart :param delay: Set a delay in the unit file before attempting restart :param extensions: Patterns of files to watch (by default inferred) :param exclude_patterns: Patterns of files to ignore (by default inferred) :param ls: Only create but do not list :param root: Only possible when using sudo :param notify_cmd: Binary command that will notify. -1 will add no notifier. Possible: e.g. yagmail :param notify_status_cmd: Command that echoes output to the notifier on failure :param notify_cmd_args: Arguments passed to notify command. :param workdir: Location from which command is run :param env_vars: can be passed like FOO=1 or FOO (which would inherit FOO current shell) """ if n_notifier is not None: install_notifier_dependencies(n_notifier) print("Creating systemd unit...") service_name, service = create_service_template( fname_or_cmd, n_notifier, timer, delay, root, killaftertimeout, restart, workdir, env_vars) user = "******" if IS_SUDO else "--user-unit %i" n_status_cmd = n_status_cmd.format(user=user) try: with open( os.path.join(self.systempath, service_name) + ".service", "w") as f: print(service) f.write(service) except PermissionError: print("Need sudo to create systemd unit service file.") sys.exit(1) create_notification_on_failure_service(self.systempath, service_name, n_notifier, n_user, n_to, n_pw, n_msg, n_status_cmd, root) _ = systemctl("daemon-reload") create_timer = create_timer_service(self.systempath, service_name, timer) if create_timer: _ = systemctl("enable {}.timer".format(service_name)) _ = systemctl("start {}.timer".format(service_name)) else: _ = systemctl("enable {}".format(service_name)) monitor_str = create_service_monitor_template( service_name, fname_or_cmd, extensions, exclude_patterns, root) with open( os.path.join(self.systempath, service_name) + "_monitor.service", "w") as f: f.write(monitor_str) _ = systemctl("start --no-block {}".format(service_name)) _ = systemctl("enable {}_monitor".format(service_name)) _ = systemctl("start {}_monitor".format(service_name)) print(linger()) print("Done") if ls: monitor(service_name, self.systempath)
def _main(): parser, args = get_argparser() try: if args.systempath is None: args.systempath = "/etc/systemd/system" if IS_SUDO else "~/.config/systemd/user" args.systempath = os.path.expanduser(args.systempath) args.systempath = args.systempath.rstrip("/") try: os.makedirs(args.systempath) except FileExistsError: pass except AttributeError: # most commands have it, but not all pass if args.command == "create": print("Creating systemd unit...") service_name = install(args) print("Done") if not args.nolist: monitor(service_name, args.systempath) elif args.command == "view": service_name = to_sn(args.unit) if not os.path.exists(args.systempath + "/" + service_name + ".service"): print( "Service file does not exist. You can start by running:\n\n sysdm create {}\n\nto create a service or run:\n\n sysdm ls\n\nto see the services already created by sysdm." .format(args.unit)) sys.exit(1) monitor(service_name, args.systempath) elif args.command == "run": if args.unit is None: units = ls(args) unit = choose_unit(args.systempath, units) if unit is None: sys.exit() else: unit = args.unit with open(args.systempath + "/" + unit + ".service") as f: for line in f: line = line.strip() if line.startswith("ExecStart="): cmd = line.split("ExecStart=")[1] if args.debug: cmd = cmd.replace("python3 -u", "python3 -u -m pdb") cmd = cmd.replace("python -u", "python -u -m pdb") elif line.startswith("WorkingDirectory="): cwd = line.split("WorkingDirectory=")[1] os.system("cd {!r} && {}".format(cwd, cmd)) elif args.command == "show_unit": show(args) elif args.command == "reload": systemctl("daemon-reload") elif args.command == "watch": watch(args) elif args.command == "delete": if args.unit is None: units = ls(args) unit = choose_unit(args.systempath, units) if unit is None: sys.exit() inp = input( "Are you sure you want to delete '{}'? [y/N]: ".format(unit)) if inp.lower().strip() != "y": print("Aborting") return else: unit = args.unit delete(unit, args.systempath) elif args.command == "edit": if args.unit is None: units = ls(args) unit = choose_unit(args.systempath, units) if unit is None: sys.exit() else: unit = args.unit unit = unit if unit.endswith(".service") else unit + ".service" os.system("$EDITOR {}/{}".format(args.systempath, unit)) elif args.command == "ls": while True: units = ls(args) if units: unit = choose_unit(args.systempath, units) if unit is None: sys.exit() monitor(unit, args.systempath) else: print( "sysdm knows of no units. Why don't you make one? `sysdm create file_i_want_as_service.py`" ) break else: parser.print_help(sys.stderr) sys.exit(1)