diff --git a/Makefile b/Makefile index 28b9a68..ed7516b 100644 --- a/Makefile +++ b/Makefile @@ -53,3 +53,10 @@ ifeq ($(OS), Linux) fi @export echo $DBUS_SESSION_BUS_ADDRESS && systemctl --user daemon-reload && echo "Systemd Daemon reloaded!" endif + +update: +ifeq ($(OS), Linux) + $(info Starting update from the main repository...) + @. .pyenv/bin/activate && python core/install.py --git-update + $(info Update done!) +endif diff --git a/README.md b/README.md index ecae94a..d651878 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ Il permet aux opérateurs de gérer efficacement un canal, tout en offrant aux u - Python version 3.10 ou supérieure ```bash # Bash - $ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git + $ git clone https://github.com/adator85/DEFENDER.git defender + $ cd defender/ # Renommer le fichier exemple_configuration.json en configuration.json # Configurer le fichier configuration.json - $ python3 main.py + $ make install ``` Si votre configuration est bonne, votre service est censé etre connecté a votre réseau IRC Pour Les prochains lancement de defender vous devez utiliser la commande suivante: @@ -49,11 +50,11 @@ Pour Les prochains lancement de defender vous devez utiliser la commande suivant # Installation manuelle: ```bash # Bash - $ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git - $ cd IRC_DEFENDER_MODULES + $ git clone https://github.com/adator85/DEFENDER.git defender + $ cd defender/ $ python3 -m venv .pyenv $ source .pyenv/bin/activate - (pyenv)$ pip install sqlalchemy, psutil, requests, faker, unrealircd_rpc_py, pyyaml + (pyenv)$ pip install -r requirements.txt # Créer un service nommé "defender.service" # pour votre service et placer le dans "/PATH/TO/USER/.config/systemd/user/" @@ -104,87 +105,91 @@ Pour Les prochains lancement de defender vous devez utiliser la commande suivant GLINE_DURATION: Durée de bannissement temporaire d'un utilisateur en minutes. (default : "30") DEBUG (Debug) - DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus grand est le nombre, plus il y a d'informations). (default : 20) Pour une production + DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus petit est le nombre, plus il y a d'informations). (default : 20) Pour une production + DEBUG_HARD: Généralement utiliser pour les developpeurs. ``` Modification de la configuration - Vous devez modifier le fichier configuration.json en remplaçant les valeurs par défaut avec vos propres informations. Assurez-vous de bien lire la description de chaque paramètre pour une configuration optimale du service. + Vous devez modifier le fichier configuration.yaml en remplaçant les valeurs par défaut avec vos propres informations. Assurez-vous de bien lire la description de chaque paramètre pour une configuration optimale du service. ## Exemple de configuration de base -```json -{ - "SERVEUR_IP": "IP.DE.TON.SERVER", - "SERVEUR_HOSTNAME": "HOST.DE.TON.SERVER", - "SERVEUR_LINK": "LINK.DE.TON.SERVER", - "SERVEUR_PORT": 6901, - "SERVEUR_PASSWORD": "MOT_DE_PASS_DE_TON_LINK", - "SERVEUR_ID": "10Z", - "SERVEUR_SSL": true, +```yaml +configuration: + SERVEUR_IP: "YOUR.SERVER.IP" + SERVEUR_HOSTNAME: "YOUR.SERVER.HOST" + SERVEUR_LINK: "LINK.DE.TON.SERVER" + SERVEUR_PORT: 7002 + SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD" + SERVEUR_ID: "006" + SERVEUR_SSL: true - "SERVICE_NAME": "defender", - "SERVICE_NICKNAME": "PyDefender", - "SERVICE_REALNAME": "Python Defender Security", - "SERVICE_USERNAME": "PyDefender", - "SERVICE_HOST": "HOST.DE.TON.DEFENDER", + SERVICE_NAME: "defender" + SERVICE_NICKNAME: "PyDefender" + SERVICE_REALNAME: "Python Defender Security" + SERVICE_USERNAME: "PyDefender" + SERVICE_HOST: "HOST.DE.TON.DEFENDER" + SERVICE_INFO: "Network IRC Service" + SERVICE_CHANLOG: "#services" + SERVICE_SMODES: "+ioqBS" + SERVICE_CMODES: "ntsOP" + SERVICE_UMODES: "o" + SERVICE_PREFIX: "!" - "OWNER": "TON_NICK_NAME", - "PASSWORD": "TON_PASSWORD" - -} + OWNER: "TON_NICK_NAME" + PASSWORD: "TON_PASSWORD" ``` ## Exemple complet de configuration -```json -{ - "SERVEUR_IP": "YOUR.SERVER.IP", - "SERVEUR_HOSTNAME": "YOUR.SERVER.HOST", - "SERVEUR_LINK": "LINK.DE.TON.SERVER", - "SERVEUR_PORT": 6901, - "SERVEUR_PASSWORD": "YOUR_LINK_PASSWORD", - "SERVEUR_ID": "10Z", - "SERVEUR_SSL": true, +```yaml +configuration: + SERVEUR_IP: "YOUR.SERVER.IP" + SERVEUR_HOSTNAME: "YOUR.SERVER.HOST" + SERVEUR_LINK: "LINK.DE.TON.SERVER" + SERVEUR_PORT: 7002 + SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD" + SERVEUR_ID: "006" + SERVEUR_SSL: true - "SERVICE_NAME": "defender", - "SERVICE_NICKNAME": "PyDefender", - "SERVICE_REALNAME": "Python Defender Security", - "SERVICE_USERNAME": "PyDefender", - "SERVICE_HOST": "HOST.DE.TON.DEFENDER", - "SERVICE_INFO": "Network IRC Service", - "SERVICE_CHANLOG": "#services", - "SERVICE_SMODES": "+ioqBS", - "SERVICE_CMODES": "ntsOP", - "SERVICE_UMODES": "o", - "SERVICE_PREFIX": "!", + SERVICE_NAME: "defender" + SERVICE_NICKNAME: "PyDefender" + SERVICE_REALNAME: "Python Defender Security" + SERVICE_USERNAME: "PyDefender" + SERVICE_HOST: "HOST.DE.TON.DEFENDER" + SERVICE_INFO: "Network IRC Service" + SERVICE_CHANLOG: "#services" + SERVICE_SMODES: "+ioqBS" + SERVICE_CMODES: "ntsOP" + SERVICE_UMODES: "o" + SERVICE_PREFIX: "!" - "OWNER": "TON_NICK_NAME", - "PASSWORD": "TON_PASSWORD", + OWNER: "TON_NICK_NAME" + PASSWORD: "TON_PASSWORD" - "JSONRPC_URL": "https://your.domaine.com:8600/api", - "JSONRPC_PATH_TO_SOCKET_FILE": "/PATH/TO/YOUR/IRCD/data/rpc.socket", - "JSONRPC_METHOD": "socket", - "JSONRPC_USER": "YOUR_RPC_USER", - "JSONRPC_PASSWORD": "YOUR_RPC_PASSWORD", + JSONRPC_URL: "https://your.domaine.com:8600/api" + JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket" + JSONRPC_METHOD: "unixsocket" + JSONRPC_USER: "YOUR_RPC_USER" + JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD" - "SALON_JAIL": "#jail", - "SALON_JAIL_MODES": "sS", - "SALON_LIBERER": "#welcome", + SALON_JAIL: "#jail" + SALON_JAIL_MODES: "sS" + SALON_LIBERER: "#welcome" - "CLONE_CHANNEL": "#clones", - "CLONE_CMODES": "+nts", - "CLONE_LOG_HOST_EXEMPT": ["HOST.TO.SKIP"], - "CLONE_CHANNEL_PASSWORD": "YOUR_CHANNEL_PASSWORD", + CLONE_CHANNEL: "#clones" + CLONE_CMODES: "+nts" + CLONE_LOG_HOST_EXEMPT: ["HOST.TO.SKIP"] + CLONE_CHANNEL_PASSWORD: "YOUR_CHANNEL_PASSWORD" - "API_TIMEOUT": 2, + API_TIMEOUT: 2 - "PORTS_TO_SCAN": [3028, 8080, 1080, 1085, 4145, 9050], - "WHITELISTED_IP": ["127.0.0.1"], - "GLINE_DURATION": "30", + PORTS_TO_SCAN: [3028 8080 1080 1085 4145 9050] + WHITELISTED_IP: ["127.0.0.1"] + GLINE_DURATION: "30" - "DEBUG_LEVEL": 20 - -} + DEBUG_LEVEL: 20 + DEBUG_HARD: true ``` # \\!/ Attention \\!/ @@ -192,7 +197,7 @@ Le mot de passe de l'administrateur et le mot de passe du service doivent être Ne partagez pas vos informations de connexion au serveur IRC avec des tiers. a votre premiere connexion vous devez tapez ``` - /msg [NomDuService] auth [nickname] [password] + /msg [NomDuService] firstauth [nickname] [password] -- Une fois identifié tapez la commande suivante /msg [NomDuService] editaccess [nickname] [Nouveau-Password] 5 ``` diff --git a/config/exemple_configuration.yaml b/config/exemple_configuration.yaml index 6bd50a6..0f40bfc 100644 --- a/config/exemple_configuration.yaml +++ b/config/exemple_configuration.yaml @@ -24,7 +24,7 @@ configuration: JSONRPC_URL: "https://your.domaine.com:8600/api" JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket" - JSONRPC_METHOD: "socket" + JSONRPC_METHOD: "unixsocket" JSONRPC_USER: "YOUR_RPC_USER" JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD" diff --git a/core/install.py b/core/install.py index dd5457c..8ca3b78 100644 --- a/core/install.py +++ b/core/install.py @@ -1,23 +1,99 @@ import argparse import os import sys +import json +from dataclasses import dataclass +from subprocess import check_call, CalledProcessError, check_output from pathlib import Path from platform import python_version_tuple +import traceback parser = argparse.ArgumentParser(description="Python Installation Code") parser.add_argument('--check-version', action='store_true', help='Check if the python version is ok!') parser.add_argument('--install', action='store_true', help='Run the installation') +parser.add_argument('--git-update', action='store_true', help='Update from git (main repository)') args = parser.parse_args() PYTHON_REQUIRED_VERSION = (3, 10, 0) PYTHON_SYSTEM_VERSION = tuple(map(int, python_version_tuple())) ROOT_PATH = os.getcwd() -PYENV = Path(f'{ROOT_PATH}/.pyenv/bin/python') +PYENV = Path(ROOT_PATH).joinpath('.pyenv/bin/python') if os.name != 'nt' else Path(ROOT_PATH).joinpath('.pyenv/Scripts/python.exe') +PIPENV = Path(f'{ROOT_PATH}/.pyenv/bin/pip') if os.name != 'nt' else Path(f'{ROOT_PATH}/.pyenv/Scripts/pip.exe') USER_HOME_DIRECTORY = Path.home() SYSTEMD_PATH = Path(USER_HOME_DIRECTORY).joinpath('.config', 'systemd', 'user') PY_EXEC = 'defender.py' SERVICE_FILE_NAME = 'defender.service' +@dataclass +class Package: + name: str = None + version: str = None + +def __load_required_package_versions() -> list[Package]: + """This will create Package model with package names and required version + """ + try: + DB_PACKAGES: list[Package] = [] + version_filename = Path(ROOT_PATH).joinpath('version.json') # f'.{os.sep}version.json' + with open(version_filename, 'r') as version_data: + package_info:dict[str, str] = json.load(version_data) + + for name, version in package_info.items(): + if name == 'version': + continue + DB_PACKAGES.append( + Package(name=name, version=version) + ) + + return DB_PACKAGES + + except FileNotFoundError as fe: + print(f"File not found: {fe}") + except Exception as err: + print(f"General Error: {err}") + +def update_packages() -> None: + try: + newVersion = False + db_packages = __load_required_package_versions() + print(ROOT_PATH) + if sys.prefix not in PYENV.__str__(): + print(f"You are probably running a new installation or you are not using your virtual env {PYENV}") + return newVersion + + print(f"> Checking for dependencies versions ==> WAIT") + for package in db_packages: + newVersion = False + _required_version = package.version + _installed_version: str = None + output = check_output([PIPENV, 'show', package.name]) + for line in output.decode().splitlines(): + if line.startswith('Version:'): + _installed_version = line.split(':')[1].strip() + break + + required_version = tuple(map(int, _required_version.split('.'))) + installed_version = tuple(map(int, _installed_version.split('.'))) + + if required_version > installed_version: + print(f'> New version of {package.name} is available {installed_version} ==> {required_version}') + newVersion = True + + if newVersion: + check_call([PIPENV, 'install', '--upgrade', package.name]) + + print(f"> Dependencies versions ==> OK") + return newVersion + + except CalledProcessError: + print(f"[!] Package {package.name} not installed [!]") + except Exception as err: + print(f"UpdatePackage Error: {err}") + traceback.print_exc() + +def run_git_update() -> None: + check_call(['git', 'pull', 'origin', 'main']) + def check_python_requirement(): if PYTHON_SYSTEM_VERSION < PYTHON_REQUIRED_VERSION: raise RuntimeError(f"Your Python Version is not meeting the requirement, System Version: {PYTHON_SYSTEM_VERSION} < Required Version {PYTHON_REQUIRED_VERSION}") @@ -64,6 +140,10 @@ def main(): if args.install: create_service_file() sys.exit(0) + + if args.git_update: + run_git_update() + sys.exit(0) if __name__ == "__main__": diff --git a/core/installation.py b/core/installation.py deleted file mode 100644 index dcbe1e4..0000000 --- a/core/installation.py +++ /dev/null @@ -1,314 +0,0 @@ -import os -import json -from sys import exit, prefix -from dataclasses import dataclass -from subprocess import check_call, run, CalledProcessError, PIPE, check_output -from platform import python_version, python_version_tuple - -class Install: - - @dataclass - class CoreConfig: - install_log_file: str - unix_systemd_folder: str - service_file_name: str - service_cmd_executable: list - service_cmd_daemon_reload: list - defender_main_executable: str - python_min_version: str - python_current_version_tuple: tuple[int, int, int] - python_current_version: tuple[int, int, int] - defender_install_folder: str - venv_folder: str - venv_cmd_installation: list - venv_cmd_requirements: list[str] - venv_pip_executable: str - venv_python_executable: str - - @dataclass - class Package: - name: str = None - version: str = None - - DB_PACKAGES: list[Package] = [] - - def __init__(self) -> None: - - self.set_configuration() - - if self.skip_install: - self.install_dependencies() - self.check_packages_version() - return None - - self.check_packages_version() - - # Sinon tester les dependances python et les installer avec pip - if self.do_install(): - - self.install_dependencies() - - self.create_service_file() - - self.print_final_message() - - return None - - def set_configuration(self): - - self.skip_install = False - defender_install_folder = os.getcwd() - venv_folder = '.pyenv' - unix_user_home_directory = os.path.expanduser("~") - unix_systemd_folder = os.path.join(unix_user_home_directory, '.config', 'systemd', 'user') - defender_main_executable = os.path.join(defender_install_folder, 'defender.py') - - self.config = self.CoreConfig( - install_log_file='install.log', - unix_systemd_folder=unix_systemd_folder, - service_file_name='defender.service', - service_cmd_executable=['systemctl', '--user', 'start', 'defender'], - service_cmd_daemon_reload=['systemctl', '--user', 'daemon-reload'], - defender_main_executable=defender_main_executable, - python_min_version=(3, 10, 0), - python_current_version_tuple=tuple(map(int, python_version_tuple())), - python_current_version=python_version(), - defender_install_folder=defender_install_folder, - venv_folder=venv_folder, - venv_cmd_installation=['python3', '-m', 'venv', venv_folder], - venv_cmd_requirements=['sqlalchemy','psutil','requests','faker','pyyaml','unrealircd_rpc_py'], - venv_pip_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}pip', - venv_python_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}python' - ) - - if not self.check_python_version(): - # If the Python version is not good then Exit - exit("[!] Python version error [!]") - - if not os.path.exists(os.path.join(self.config.defender_install_folder, 'config', 'configuration.yaml')): - # If configuration file do not exist - exit("[!] Configuration file (core/configuration.yaml) doesn't exist! please create it [!]") - - # Exclude Windows OS from the installation - if os.name == 'nt': - # If windows, modify pip and python virtual environment executable - self.config.venv_pip_executable = f'{os.path.join(defender_install_folder, venv_folder, "Scripts")}{os.sep}pip.exe' - self.config.venv_python_executable = f'{os.path.join(defender_install_folder, venv_folder, "Scripts")}{os.sep}python.exe' - self.skip_install = True - return False - - if self.is_root(): - exit(f'[!] I highly not recommend running Defender as root [!]') - self.skip_install = True - return False - - def is_root(self) -> bool: - - if os.geteuid() != 0: - print('> User without privileges ==> OK') - return False - elif os.geteuid() == 0: - print('[!] Do not use root to install Defender [!]') - exit("Do not use root to install Defender") - return True - - def do_install(self) -> bool: - - full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name) - - if not os.path.exists(full_service_file_path): - print(f'[!] Service file does not exist [!]') - return True - - # Check if virtual env exist - if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'): - self.run_subprocess(self.config.venv_cmd_installation) - print(f'[!] Virtual env does not exist run the install [!]') - return True - - def run_subprocess(self, command:list) -> None: - - print(f'> {command}') - try: - check_call(command) - print("The command completed successfully.") - except CalledProcessError as e: - print(f"The command failed with the return code: {e.returncode}") - print(f"Try to install dependencies ...") - exit(5) - - def get_packages_version_from_json(self) -> None: - """This will create Package model with package names and required version - """ - try: - - version_filename = f'.{os.sep}version.json' - with open(version_filename, 'r') as version_data: - package_info:dict[str, str] = json.load(version_data) - - for name, version in package_info.items(): - if name == 'version': - continue - - self.DB_PACKAGES.append( - self.Package(name=name, version=version) - ) - - return None - except FileNotFoundError as fe: - print(f"File not found: {fe}") - except Exception as err: - print(f"General Error: {err}") - - def check_packages_version(self) -> bool: - - try: - newVersion = False - self.get_packages_version_from_json() - - if not self.config.venv_folder in prefix: - print(f"You are probably running a new installation or you are not using your virtual env {self.config.venv_folder}") - return newVersion - - print(f"> Checking for dependencies versions ==> WAIT") - for package in self.DB_PACKAGES: - newVersion = False - _required_version = package.version - _installed_version: str = None - - output = check_output([self.config.venv_pip_executable, 'show', package.name]) - for line in output.decode().splitlines(): - if line.startswith('Version:'): - _installed_version = line.split(':')[1].strip() - break - - required_version = tuple(map(int, _required_version.split('.'))) - installed_version = tuple(map(int, _installed_version.split('.'))) - - if required_version > installed_version: - print(f'> New version of {package.name} is available {installed_version} ==> {required_version}') - newVersion = True - - if newVersion: - self.run_subprocess([self.config.venv_pip_executable, 'install', '--upgrade', package.name]) - - print(f"> Dependencies versions ==> OK") - return newVersion - - except CalledProcessError: - print(f"[!] Package {package.name} not installed [!]") - except Exception as err: - print(f"General Error: {err}") - - def check_python_version(self) -> bool: - """Test si la version de python est autorisée ou non - - Returns: - bool: True si la version de python est autorisé sinon False - """ - if self.config.python_current_version_tuple < self.config.python_min_version: - print(f"## Your python version must be greather than or equal to {self.config.python_min_version} ##") - return False - - print(f"> Version of python : {self.config.python_current_version} ==> OK") - return True - - def check_package(self, package_name) -> bool: - - try: - # Run a command in the virtual environment's Python to check if the package is installed - run([self.config.venv_python_executable, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE) - return True - except CalledProcessError as cpe: - print(cpe) - return False - - def install_dependencies(self) -> None: - """### Verifie les dépendances si elles sont installées - - Test si les modules sont installés - - Met a jour pip - - Install les modules manquants - """ - do_install = False - - # Check if virtual env exist - if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'): - self.run_subprocess(self.config.venv_cmd_installation) - do_install = True - - for module in self.config.venv_cmd_requirements: - module = module.replace('pyyaml', 'yaml') - if not self.check_package(module): - do_install = True - - if not do_install: - return None - - print("===> Clean pip cache") - self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge']) - - print("===> Check if pip is up to date") - self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip']) - - if not self.check_package('greenlet'): - self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet']) - print('====> Greenlet installed') - - for module in self.config.venv_cmd_requirements: - if not self.check_package(module): - print("### Trying to install missing python packages ###") - self.run_subprocess([self.config.venv_pip_executable, 'install', module]) - print(f"====> Module {module} installed!") - else: - print(f"==> {module} already installed") - - def create_service_file(self) -> None: - - full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name) - - if os.path.exists(full_service_file_path): - print(f'[!] Service file already exist [!]') - self.run_subprocess(self.config.service_cmd_executable) - return None - - contain = f'''[Unit] -Description=Defender IRC Service - -[Service] -ExecStart={self.config.venv_python_executable} {self.config.defender_main_executable} -WorkingDirectory={self.config.defender_install_folder} -SyslogIdentifier=Defender -Restart=on-failure - -[Install] -WantedBy=default.target -''' - # Check if user systemd is available (.config/systemd/user/) - if not os.path.exists(self.config.unix_systemd_folder): - self.run_subprocess(['mkdir', '-p', self.config.unix_systemd_folder]) - - with open(full_service_file_path, 'w+') as servicefile: - servicefile.write(contain) - servicefile.close() - print('Service file generated with current configuration') - print('Running IRC Service ...') - self.run_subprocess(self.config.service_cmd_daemon_reload) - self.run_subprocess(self.config.service_cmd_executable) - - else: - with open(full_service_file_path, 'w+') as servicefile: - servicefile.write(contain) - servicefile.close() - print('Service file generated with current configuration') - print('Running IRC Service ...') - self.run_subprocess(self.config.service_cmd_daemon_reload) - self.run_subprocess(self.config.service_cmd_executable) - - def print_final_message(self) -> None: - - print(f"#"*24) - print("Installation complete ...") - print("If the configuration is correct, then you must see your service connected to your irc server") - print(f"If any issue, you can see the log file for debug {self.config.defender_install_folder}{os.sep}logs{os.sep}defender.log") - print(f"#"*24) - exit(1) diff --git a/defender.py b/defender.py index 10577d2..6fc06f2 100644 --- a/defender.py +++ b/defender.py @@ -1,4 +1,4 @@ -from core import installation +from core import install ############################################# # @Version : 6.3 # @@ -10,7 +10,7 @@ from core import installation ############################################# try: - installation.Install() + install.update_packages() from core.loader import Loader loader = Loader() loader.Irc.init_irc() diff --git a/requirements.txt b/requirements.txt index f8c2d3a..005b826 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -Faker==33.1.2 -psutil==6.1.1 -PyYAML==6.0.2 -requests==2.32.3 -SQLAlchemy==2.0.36 +Faker==37.12.0 +psutil==7.1.2 +PyYAML==6.0.3 +requests==2.32.5 +SQLAlchemy==2.0.44 unrealircd_rpc_py==3.0.2 \ No newline at end of file diff --git a/version.json b/version.json index b0b4b2f..257fd31 100644 --- a/version.json +++ b/version.json @@ -1,10 +1,10 @@ { "version": "6.3.2", - "requests": "2.32.3", - "psutil": "6.0.0", + "requests": "2.32.5", + "psutil": "7.1.2", "unrealircd_rpc_py": "3.0.1", - "sqlalchemy": "2.0.35", - "faker": "30.1.0", - "pyyaml": "6.0.2" + "sqlalchemy": "2.0.44", + "faker": "37.12.0", + "pyyaml": "6.0.3" }