Example #1
0
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
Example #2
0
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!")
Example #3
0
    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)
Example #4
0
def reload():
    """
    Do a daemon-reload for systemd
    """
    systemctl("daemon-reload")
Example #5
0
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())
Example #6
0
    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)
Example #7
0
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)