diff --git a/.ex.jpg b/.ex.jpg new file mode 100644 index 0000000..08f3c71 Binary files /dev/null and b/.ex.jpg differ diff --git a/.ex.png b/.ex.png deleted file mode 100644 index 155aa6f..0000000 Binary files a/.ex.png and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58d60cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +old/ +VoidServiceControl/main.py +__pycache__ +.idea +.venv +probe.py diff --git a/README.md b/README.md index 9c2e3bb..0377e96 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,30 @@ # Void Service Control (VSC) ## A simple script that will allow you to manage runit services in Void Linux +- [Installation](#install) +- [Usage](#usage) +- [Build from sources](#build-from_sources) +- [Example (Screenshot)](#example) + + ### Install: **You can install vsc using pip:** +From [git.orudo.ru](https://git.orudo.ru/trueold89/void-service-control/releases): + +```bash +$ pip install https://git.orudo.ru/trueold89/void-service-control/releases/download/0.2/VoidServiceControl-0.2.tar.gz +``` +or pypi + ```bash $ pip install void-service-control ``` --- -or by downloading the pre-built binary from the **[releases](https://git.orudo.ru/trueold89/void-service-control/releases)** page +**Or by downloading the pre-built binary / xbps-package from the **[releases](https://git.orudo.ru/trueold89/void-service-control/releases)** page** *** @@ -29,15 +42,40 @@ $ vsc e $ vsc d ``` -**Print help** +**Print help:** ```bash $ vsc --help ``` *All commands require root privileges* +### Build from sources + +**Clone repo:** +```bash +$ git clone https://git.orudo.ru/trueold89/void-service-control.git --depth=1 && cd void-service-control +``` + +**Install deps:** +```bash +$ pip install setuptools +``` + +**Build sdist:** +```bash +$ python3 setup.py sdist +``` + +**Install:** +```bash +$ pip install dist/* +``` + +*Last command require root privileges* *** ## Example: -![](.ex.png) + +![](.ex.jpg) + diff --git a/VoidServiceControl/__init__.py b/VoidServiceControl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/VoidServiceControl/classes.py b/VoidServiceControl/classes.py new file mode 100644 index 0000000..0186c76 --- /dev/null +++ b/VoidServiceControl/classes.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +import importlib.resources +import json +from enum import Enum +from os import listdir as ls +from subprocess import run as execute +from subprocess import PIPE +from sys import argv + + +from VoidServiceControl.env import * + + +class Help(Exception): + def __str__(self) -> str: + """ + Returns the program usage help + :return: Program usage help + """ + return lang.messages["help"] + + +def Su() -> None: + """ + Checks if the user has administrator rights + """ + user = execute('whoami', shell=True, text=True, stdout=PIPE).stdout[:-1] + if user != 'root': + raise PermissionError(lang.errors["su"]) + + +# Types of service actions +class Action(Enum): + ENABLE = ["enable", "e", "on", "up"] + DISABLE = ["disable", "d", "off", "down"] + + +# Types of run arguments +class Arg(Enum): + HELP = ["--help", "-h", "help"] + + @classmethod + def all(cls) -> list[str]: + """ + Returns all types of run arguments + :return: All types of run arguments list + """ + return cls.HELP.value + cls.__action() + + @classmethod + def __action(cls) -> list[str]: + """ + Returns all types of service actions + :return: All types of service actions list + """ + actions = list(map(lambda action: action.value, Action)) + out = [] + for lst in actions: + out.extend(lst) + return out + + +class Args(object): + + def __init__(self) -> None: + self.__arguments = argv[1:] + self.__check() + + def __check(self) -> None: + """ + Checks the arguments + """ + if len(self.__arguments) == 0: + raise TypeError(lang.errors["usage"]) + if len(self.__arguments) != 2 and self.__arguments[0] not in Arg.HELP.value: + raise TypeError(lang.errors["usage"]) + if self.__arguments[0] in Arg.HELP.value: + raise Help() + if self.__arguments[0] not in Arg.all(): + raise TypeError(lang.errors["args"]) + + def __action(self) -> Action: + """ + Returns the action to the service + :return: Service action + """ + actions = list(Action) + for action in actions: + if self.__arguments[0] in action.value: + return action + + def __iter__(self): + yield self.__action() + yield self.__arguments[1] + + +class Service(object): + __enabled = False + + def __init__(self, name: str) -> None: + self.__name = name + self.__check() + + def __check(self) -> None: + """ + Checks if the service exists and is enabled + """ + all_services = ls(SV_PATH) + enabled_services = ls(ENABLED_PATH) + if self.__name not in all_services: + raise ValueError(lang.errors["ne"].format(self.getname())) + if self.__name in enabled_services: + self.__enabled = True + + def getname(self) -> str: + """ + Returns the service name + :return: Service name + """ + return self.__name + + def enable(self) -> None: + """ + Enable the service + """ + if self.__enabled: + raise ValueError(lang.errors["enabled"].format(self.getname())) + execute(f'ln -s {SV_PATH}/{self.__name} {ENABLED_PATH}/', shell=True) + + def disable(self): + """ + Disable the service + """ + if not self.__enabled: + raise ValueError(lang.errors["disabled"].format(self.getname())) + execute(f'rm {ENABLED_PATH}/{self.__name}', shell=True) + + +class Interface(object): + + def __init__(self, service: str) -> None: + self.service = Service(service) + + def action(self, action: Action) -> None: + """ + Performs an action on the service + :param action: Action on the service + """ + match action: + case Action.ENABLE: + self.service.enable() + print(lang.messages["enabled"].format(self.service.getname())) + case Action.DISABLE: + self.service.disable() + print(lang.messages["disabled"].format(self.service.getname())) + + +class Translate(object): + + def __init__(self, language: str): + """ + :param language: Language (ex. en_US) + """ + self.lang = language + self.errors = self.__translation["errors"] + self.messages = self.__translation["messages"] + + @property + def __translation(self): + try: + with importlib.resources.open_text(__package__, f"{self.lang}.json", "utf-8") as json_file: + return json.loads(json_file.read()) + except FileNotFoundError: + with importlib.resources.open_text(__package__, f"en_US.json", "utf-8") as json_file: + return json.loads(json_file.read()) + + +lang = Translate(LANG) diff --git a/VoidServiceControl/en_US.json b/VoidServiceControl/en_US.json new file mode 100644 index 0000000..0f32167 --- /dev/null +++ b/VoidServiceControl/en_US.json @@ -0,0 +1 @@ +{"errors": {"su": "Error: Access denied", "usage": "Bad usage", "args": "Error: Invalid args", "ne": "Error: Service '{}' doesn't exists", "enabled": "Error: Service '{}' already enabled", "disabled": "Error: Service '{}' already disabled"}, "messages": {"enabled": "Service '{}' successfully enabled", "disabled": "Service '{}' successfully disabled", "help": "Usage:\n---\nvsc {e/enable/on/up} - Run service and add it to autostart\nvsc {d/disable/off/down - Stop service and remove it from autostart"}} \ No newline at end of file diff --git a/VoidServiceControl/env.py b/VoidServiceControl/env.py new file mode 100644 index 0000000..edacbcc --- /dev/null +++ b/VoidServiceControl/env.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from os import environ + +# Edit these lines if you are using custom paths to Runit services + +SV_PATH = '/etc/sv' +ENABLED_PATH = '/var/service' + + +# Lang settings +def lang() -> str: + """ + Get system lang + :return: system lang + """ + try: + return environ["LANG"][:5] + except KeyError: + return "en_US" + + +LANG = lang() diff --git a/VoidServiceControl/ru_RU.json b/VoidServiceControl/ru_RU.json new file mode 100644 index 0000000..26a2938 --- /dev/null +++ b/VoidServiceControl/ru_RU.json @@ -0,0 +1 @@ +{"errors": {"su": "\u041e\u0448\u0438\u0431\u043a\u0430: \u0414\u043e\u0441\u0442\u0443\u043f \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d", "usage": "\u041e\u0448\u0438\u0431\u043a\u0430: \u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435", "args": "\u041e\u0448\u0438\u0431\u043a\u0430: \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b", "ne": "\u041e\u0448\u0438\u0431\u043a\u0430: \u0421\u0435\u0440\u0432\u0438\u0441 '{}' \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442", "enabled": "\u041e\u0448\u0438\u0431\u043a\u0430: \u0421\u0435\u0440\u0432\u0438\u0441 '{}' \u0443\u0436\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d", "disabled": "\u041e\u0448\u0438\u0431\u043a\u0430: \u0421\u0435\u0440\u0432\u0438\u0441 '{}' \u0443\u0436\u0435 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d"}, "messages": {"enabled": "\u0421\u0435\u0440\u0432\u0438\u0441 '{}' \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d", "disabled": "\u0421\u0435\u0440\u0432\u0438\u0441 '{}' \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d", "help": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:\n---\nvsc {e/enable/on/up} <\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435_\u0441\u0435\u0440\u0432\u0438\u0441\u0430> - \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0430\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a\nvsc {d/disable/off/down <\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435_\u0441\u0435\u0440\u0432\u0438\u0441\u0430> - \u0412\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437 \u0430\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a\u0430"}} \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f716069 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup + +setup( + name='VoidServiceControl', + version='0.2', + url='https://git.orudo.ru/trueold89/void-service-control', + author='trueold89', + author_email='trueold89@orudo.ru', + description="A simple script that will allow you to manage runit services in Void Linux", + packages=['VoidServiceControl'], + long_description=open('README.md').read(), + entry_points={ + "console_scripts": ["vsc = VoidServiceControl.main:main"] + }, + package_data={ + 'VoidServiceControl': ['*.json'], + }, +) diff --git a/vsc.py b/vsc.py deleted file mode 100755 index a9c5ed3..0000000 --- a/vsc.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -# Import -import os, subprocess, sys - -# Const -SV_PATH = '/etc/sv' -ENABLED_PATH = '/var/service' - -# Access -def su(): - out = subprocess.run('whoami', shell=True, text=True, stdout=subprocess.PIPE) - user = out.stdout[:-1] - if user == 'root': - return True - else: - return False - -# Print help message -def helpmsg(): - print(''' -Usage: ---- -vsc {e/enable/on/up} - Run service and add it to autostart -vsc {d/disable/off/down - Stop service and remove it from autostart ---- -''') - sys.exit(1) - -# Check exec args -def args_check(): - args = sys.orig_argv[2:] - if len(args) == 2: - return args - elif len(args) == 1 and args[0] in ['--h','--help']: - helpmsg() - else: - return False - -# Check availability -def availability(service, action): - all = os.listdir(SV_PATH) - enabled = os.listdir(ENABLED_PATH) - all = service in all - if all == False: - raise Exception(f"Service '{service}' doesn't exist") - if service in enabled: - if action == 'on': - raise Exception(f"Service '{service}' already enabled") - elif action == 'off': - return True - else: - if action == 'off': - raise Exception(f"Service '{service}' already disabled") - elif action == 'on': - return True - -# Main -def main(): - try: - args = args_check() - if args == False: - raise Exception('Bad usage. See --help') - if su() == False: - raise Exception('Access denied') - action, service = args - if action in ['enable','e','on','up']: - if availability(service,'on'): - subprocess.run(f'ln -s {SV_PATH}/{service} {ENABLED_PATH}/', shell=True) - print(f"Service '{service}' successfully enabled") - if action in ['disable','d','off','down']: - if availability(service,'off'): - subprocess.run(f'rm {ENABLED_PATH}/{service}', shell=True) - print(f"Service '{service}' successfully disabled") - except Exception as ex: - print(ex) - sys.exit(0) - -if __name__ == '__main__': - main()