From ab198819d3e2810a158d021b84f12bf469686a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Daniel=20Colmenares=20Oviedo?= Date: Mon, 25 Nov 2024 22:42:26 -0400 Subject: [PATCH 1/4] Add FreeBSD operations & facts --- pyinfra/facts/freebsd.py | 75 +++++++ pyinfra/operations/freebsd/__init__.py | 12 ++ pyinfra/operations/freebsd/freebsd_update.py | 68 ++++++ pyinfra/operations/freebsd/pkg.py | 207 +++++++++++++++++++ pyinfra/operations/freebsd/service.py | 117 +++++++++++ pyinfra/operations/freebsd/sysrc.py | 92 +++++++++ 6 files changed, 571 insertions(+) create mode 100644 pyinfra/facts/freebsd.py create mode 100644 pyinfra/operations/freebsd/__init__.py create mode 100644 pyinfra/operations/freebsd/freebsd_update.py create mode 100644 pyinfra/operations/freebsd/pkg.py create mode 100644 pyinfra/operations/freebsd/service.py create mode 100644 pyinfra/operations/freebsd/sysrc.py diff --git a/pyinfra/facts/freebsd.py b/pyinfra/facts/freebsd.py new file mode 100644 index 000000000..dc42297f0 --- /dev/null +++ b/pyinfra/facts/freebsd.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from typing_extensions import Optional + +from pyinfra.api import FactBase +from pyinfra.api.command import QuoteString, make_formatted_string_command + + +class ServiceScript(FactBase): + @staticmethod + def command(srvname: str, jail: Optional[str] = None): + if jail is None: + jail = "" + + return make_formatted_string_command( + ( + "for service in `service -j {0} -l {1}`; do " + 'if [ {1} = \\"$service\\" ]; ' + 'then printf \\"%s\\\\n\\" \\"$service\\"; ' + "fi; " + "done" + ), + QuoteString(jail), + QuoteString(srvname), + ) + + +class ServiceStatus(FactBase): + @staticmethod + def command(srvname: str, jail: Optional[str] = None): + if jail is None: + jail = "" + + return make_formatted_string_command( + ( + "service -j {0} {1} status > /dev/null 2>&1; " + "if [ $? -eq 0 ]; then " + "echo running; " + "fi" + ), + QuoteString(jail), + QuoteString(srvname), + ) + + +class Sysrc(FactBase): + @staticmethod + def command(parameter: str, jail: Optional[str] = None): + if jail is None: + command = make_formatted_string_command( + ("sysrc -in -- {0} || true"), QuoteString(parameter) + ) + else: + command = make_formatted_string_command( + ("sysrc -j {0} -in -- {1} || true"), QuoteString(jail), QuoteString(parameter) + ) + + return command + + +class PkgPackage(FactBase): + @staticmethod + def command(package: str, jail: Optional[str] = None): + if jail is None: + command = make_formatted_string_command( + ("pkg info -E -- {0} 2> /dev/null || true"), QuoteString(package) + ) + else: + command = make_formatted_string_command( + ("pkg -j {0} info -E -- {1} 2> /dev/null || true"), + QuoteString(jail), + QuoteString(package), + ) + + return command diff --git a/pyinfra/operations/freebsd/__init__.py b/pyinfra/operations/freebsd/__init__.py new file mode 100644 index 000000000..6da4fc977 --- /dev/null +++ b/pyinfra/operations/freebsd/__init__.py @@ -0,0 +1,12 @@ +# This file only exists to support: +# from pyinfra.operations import freebsd +# freebsd.X.Y + +from glob import glob +from os import path + +module_filenames = glob(path.join(path.dirname(__file__), "*.py")) +module_names = [path.basename(name)[:-3] for name in module_filenames] +__all__ = [name for name in module_names if name != "__init__"] + +from . import * # noqa diff --git a/pyinfra/operations/freebsd/freebsd_update.py b/pyinfra/operations/freebsd/freebsd_update.py new file mode 100644 index 000000000..03ffcfb8a --- /dev/null +++ b/pyinfra/operations/freebsd/freebsd_update.py @@ -0,0 +1,68 @@ +""" +Fetch and install binary updates to FreeBSD. +""" + +from __future__ import annotations + +from typing_extensions import Optional + +from pyinfra.api import QuoteString, StringCommand, operation + + +@operation() +def update( + force: bool = False, + basedir: Optional[str] = None, + workdir: Optional[str] = None, + conffile: Optional[str] = None, + jail: Optional[str] = None, + key: Optional[str] = None, + currently_running: Optional[str] = None, + server: Optional[str] = None, +): + """ + Based on the currently installed world and the configuration options set, fetch + all available binary updates and install them. + + + force: See ``-F`` in ``freebsd-update(8)``. + + basedir: See ``-b`` in ``freebsd-update(8)``. + + workdir: See ``-d`` in ``freebsd-update(8)``. + + conffile: See ``-f`` in ``freebsd-update(8)``. + + jail: See ``-j`` in ``freebsd-update(8)``. + + key: See ``-k`` in ``freebsd-update(8)``. + + currently_running: See ``--currently-running`` in ``freebsd-update(8)``. + + server: See ``-s`` in ``freebsd-update(8)``. + + **Example:** + + .. code:: python + + freebsd_update.update() + """ + + args = ["PAGER=cat", "freebsd-update", "--not-running-from-cron"] + + if force: + args.append("-F") + + if basedir is not None: + args.extend(["-b", QuoteString(basedir)]) + + if workdir is not None: + args.extend(["-d", QuoteString(workdir)]) + + if conffile is not None: + args.extend(["-f", QuoteString(conffile)]) + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + if key is not None: + args.extend(["-k", QuoteString(key)]) + + if server is not None: + args.extend(["-s", QuoteString(server)]) + + args.extend(["fetch", "install"]) + + yield StringCommand(*args) diff --git a/pyinfra/operations/freebsd/pkg.py b/pyinfra/operations/freebsd/pkg.py new file mode 100644 index 000000000..4fd5b986a --- /dev/null +++ b/pyinfra/operations/freebsd/pkg.py @@ -0,0 +1,207 @@ +""" +Manage FreeBSD packages. +""" + +from __future__ import annotations + +from typing_extensions import Optional + +from pyinfra import host +from pyinfra.api import QuoteString, StringCommand, operation +from pyinfra.facts.freebsd import PkgPackage + + +@operation() +def update(jail: Optional[str] = None, force: bool = False, reponame: Optional[str] = None): + """ + Update the local catalogues of the enabled package repositories. + + + jail: See ``-j`` in ``pkg(8)``. + + force: See ``-f`` in ``pkg-update(8)``. + + reponame: See ``-r`` in ``pkg-update(8)`` + + **Examples:** + + .. code:: python + + # host + pkg.update() + + # jail + pkg.update( + jail="nginx" + ) + """ + + args = ["pkg"] + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["update"]) + + if force: + args.append("-f") + + if reponame is not None: + args.extend(["-r", QuoteString(reponame)]) + + yield StringCommand(*args) + + +@operation() +def upgrade(jail: Optional[str] = None, force: bool = False, reponame: Optional[str] = None): + """ + Perform upgrades of package software distributions. + + + jail: See ``-j`` in ``pkg(8)``. + + force: See ``-f`` in ``pkg-upgrade(8)``. + + reponame: See ``-r`` in ``pkg-upgrade(8)``. + + **Examples:** + + .. code:: python + + # host + pkg.upgrade() + + # jail + pkg.upgrade( + jail="nginx" + ) + """ + + args = ["pkg"] + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["upgrade", "-y"]) + + if force: + args.append("-f") + + if reponame is not None: + args.extend(["-r", reponame]) + + yield StringCommand(*args) + + +@operation() +def install(package: str, jail: Optional[str] = None, reponame: Optional[str] = None): + """ + Install packages from remote packages repositories or local archives. + + + package: Package to install. + + jail: See ``-j`` in ``pkg(8)``. + + reponame: See ``-r`` in ``pkg-install(8)``. + + **Example:** + + .. code:: python + + pkg.install("nginx") + """ + + if host.get_fact(PkgPackage, package=package): + host.noop(f"Package '{package}' already installed") + return + + args = ["pkg"] + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["install", "-y"]) + + if reponame is not None: + args.extend(["-r", reponame]) + + args.extend(["--", QuoteString(package)]) + + yield StringCommand(*args) + + +@operation() +def remove(package: str, jail: Optional[str] = None): + """ + Deletes packages from the database and the system. + + + package: Package to remove. + + jail: See ``-j`` in ``pkg(8)``. + + **Example:** + + .. code:: python + + pkg.remove("nginx") + """ + + if not host.get_fact(PkgPackage, package=package): + host.noop(f"Package '{package}' cannot be found") + return + + args = ["pkg"] + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["remove", "-y"]) + + args.extend(["--", QuoteString(package)]) + + yield StringCommand(*args) + + +@operation() +def autoremove(jail: Optional[str] = None): + """ + Remove orphan packages. + + + jail: See ``-j`` in ``pkg(8)``. + + **Example:** + + .. code:: python + + pkg.autoremove() + """ + + args = ["pkg"] + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["autoremove", "-y"]) + + yield StringCommand(*args) + + +@operation() +def clean(all_pkg: bool = False, jail: Optional[str] = None): + """ + Clean the local cache of fetched remote packages. + + + all_pkg: See ``-a`` in ``pkg-clean(8)``. + + jail: See ``-j`` in ``pkg(8)``. + + **Example:** + + .. code:: python + + pkg.clean( + all_pkg=True + ) + """ + + args = ["pkg"] + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["clean", "-y"]) + + if all_pkg: + args.append("-a") + + yield StringCommand(*args) diff --git a/pyinfra/operations/freebsd/service.py b/pyinfra/operations/freebsd/service.py new file mode 100644 index 000000000..0a6b7a3d9 --- /dev/null +++ b/pyinfra/operations/freebsd/service.py @@ -0,0 +1,117 @@ +""" +Manage FreeBSD services. +""" + +from __future__ import annotations + +from enum import Enum + +from typing_extensions import List, Optional, Union + +from pyinfra import host +from pyinfra.api import QuoteString, StringCommand, operation +from pyinfra.api.exceptions import OperationValueError +from pyinfra.facts.freebsd import ServiceScript, ServiceStatus + + +class ServiceStates(Enum): + SRV_STARTED: int = 0 + SRV_STOPPED: int = 1 + SRV_RESTARTED: int = 2 + SRV_RELOADED: int = 3 + SRV_CUSTOM: int = 4 + + +@operation() +def service( + srvname: str, + jail: Optional[str] = None, + state: "ServiceStates" = ServiceStates.SRV_STARTED, + command: Optional[Union[str, List[str]]] = None, + environment: Optional[List[str]] = None, + verbose: bool = False, +): + """ + Control (start/stop/etc.) ``rc(8)`` scripts. + + + srvname: Service. + + jail: See ``-j`` in ``service(8)``. + + state: Desire state of the service. + + command: When ``state`` is ``SRV_CUSTOM``, the command to execute. + + environment: See ``-E`` in ``service(8)``. + + verbose: See ``-v`` in ``service(8)``. + + States: + There are a few states you can use to manipulate the service: + + - SRV_STARTED: The service must be started. + - SRV_STOPPED: The service must be stopped. + - SRV_RESTARTED: The service must be restarted. + - SRV_RELOADED: The service must be reloaded. + - SRV_CUSTOM: Run a custom command for this service. + + **Examples:** + + .. code:: python + + # Start a service. + service.service( + "beanstalkd", + state=service.ServiceStates.SRV_STARTED + ) + + # Execute a custom command. + service.service( + "sopel", + state=service.ServiceStates.SRV_CUSTOM, + command="configure" + ) + """ + + if not host.get_fact(ServiceScript, srvname=srvname, jail=jail): + host.noop(f"Cannot find rc(8) script '{srvname}'") + return + + args = ["service"] + + if verbose: + args.append("-v") + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + if environment is not None: + for env_var in environment: + args.extend(["-E", QuoteString(env_var)]) + + if state == ServiceStates.SRV_STARTED: + if host.get_fact(ServiceStatus, srvname=srvname, jail=jail): + host.noop(f"Service '{srvname}' already started") + return + + args.extend([srvname, "start"]) + + elif state == ServiceStates.SRV_STOPPED: + if not host.get_fact(ServiceStatus, srvname=srvname, jail=jail): + host.noop(f"Service '{srvname}' already stopped") + return + + args.extend([srvname, "stop"]) + elif state == ServiceStates.SRV_RESTARTED: + args.extend([srvname, "restart"]) + + elif state == ServiceStates.SRV_RELOADED: + args.extend([srvname, "reload"]) + + elif state == ServiceStates.SRV_CUSTOM: + args.append(QuoteString(srvname)) + + if not isinstance(command, str): + command = [command] + + args.extend((QuoteString(c) for c in command)) + + else: + raise OperationValueError("Invalid service command!") + + yield StringCommand(*args) diff --git a/pyinfra/operations/freebsd/sysrc.py b/pyinfra/operations/freebsd/sysrc.py new file mode 100644 index 000000000..ba44a952a --- /dev/null +++ b/pyinfra/operations/freebsd/sysrc.py @@ -0,0 +1,92 @@ +""" +Manipulate system rc files. +""" + +from __future__ import annotations + +from enum import Enum + +from typing_extensions import Optional + +from pyinfra import host +from pyinfra.api import QuoteString, StringCommand, operation +from pyinfra.api.exceptions import OperationValueError +from pyinfra.facts.freebsd import Sysrc + + +class SysrcCommands(Enum): + SYSRC_ADD: int = 0 + SYSRC_SUB: int = 1 + SYSRC_SET: int = 2 + SYSRC_DEL: int = 3 + + +@operation() +def sysrc( + parameter: str, + value: str, + jail: Optional[str] = None, + command: "SysrcCommands" = SysrcCommands.SYSRC_SET, + overwrite: bool = False, +): + """ + Safely edit system rc files. + + + parameter: Parameter to manipulate. + + value: Value, if the parameter requires it. + + jail: See ``-j`` in ``sysrc(8)``. + + command: Desire state of the parameter. + + overwrite: Overwrite the value of the parameter when ``command`` is set to ``SYSRC_SET``. + + Commands: + There are a few commands you can use to manipulate the rc file: + + - SYSRC_ADD: Adds the value to the parameter. + - SYSRC_SUB: Delete the parameter value. + - SYSRC_SET: Change the parameter value. If the parameter already has a value + set, the changes will not be applied unless ``overwrite`` is set + to ``True``. + - SYSRC_DEL: Delete the parameter. + + **Example:** + + .. code:: python + + sysrc.sysrc( + "beanstalkd_enable", + "YES", + command=sysrc.sysrc.SysrcCommands.SYSRC_SET + ) + """ + + args = ["sysrc", "-i"] + + if command == SysrcCommands.SYSRC_DEL: + sign = "=" + + if not host.get_fact(Sysrc, parameter=parameter, jail=jail): + host.noop(f"Cannot find sysrc(8) parameter '{parameter}'") + return + + elif command == SysrcCommands.SYSRC_SET: + sign = "=" + + if not overwrite and host.get_fact(Sysrc, parameter=parameter, jail=jail): + host.noop(f"sysrc(8) parameter '{parameter}' already set") + return + + elif command == SysrcCommands.SYSRC_ADD: + sign = "+=" + + elif command == SysrcCommands.SYSRC_SUB: + sign = "-=" + + else: + raise OperationValueError("Invalid sysrc command!") + + if jail is not None: + args.extend(["-j", QuoteString(jail)]) + + args.extend(["--", QuoteString(f"{parameter}{sign}{value}")]) + + yield StringCommand(*args) From cd2d05fee6bbabacc84226de3f59d53b4f1e2e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Daniel=20Colmenares=20Oviedo?= Date: Tue, 26 Nov 2024 19:56:32 -0400 Subject: [PATCH 2/4] Escape more strings --- pyinfra/operations/freebsd/pkg.py | 4 ++-- pyinfra/operations/freebsd/service.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyinfra/operations/freebsd/pkg.py b/pyinfra/operations/freebsd/pkg.py index 4fd5b986a..e63107db8 100644 --- a/pyinfra/operations/freebsd/pkg.py +++ b/pyinfra/operations/freebsd/pkg.py @@ -82,7 +82,7 @@ def upgrade(jail: Optional[str] = None, force: bool = False, reponame: Optional[ args.append("-f") if reponame is not None: - args.extend(["-r", reponame]) + args.extend(["-r", QuoteString(reponame)]) yield StringCommand(*args) @@ -115,7 +115,7 @@ def install(package: str, jail: Optional[str] = None, reponame: Optional[str] = args.extend(["install", "-y"]) if reponame is not None: - args.extend(["-r", reponame]) + args.extend(["-r", QuoteString(reponame)]) args.extend(["--", QuoteString(package)]) diff --git a/pyinfra/operations/freebsd/service.py b/pyinfra/operations/freebsd/service.py index 0a6b7a3d9..99b0b80f5 100644 --- a/pyinfra/operations/freebsd/service.py +++ b/pyinfra/operations/freebsd/service.py @@ -89,19 +89,19 @@ def service( host.noop(f"Service '{srvname}' already started") return - args.extend([srvname, "start"]) + args.extend([QuoteString(srvname), "start"]) elif state == ServiceStates.SRV_STOPPED: if not host.get_fact(ServiceStatus, srvname=srvname, jail=jail): host.noop(f"Service '{srvname}' already stopped") return - args.extend([srvname, "stop"]) + args.extend([QuoteString(srvname), "stop"]) elif state == ServiceStates.SRV_RESTARTED: - args.extend([srvname, "restart"]) + args.extend([QuoteString(srvname), "restart"]) elif state == ServiceStates.SRV_RELOADED: - args.extend([srvname, "reload"]) + args.extend([QuoteString(srvname), "reload"]) elif state == ServiceStates.SRV_CUSTOM: args.append(QuoteString(srvname)) From d30f408783b73ebe707f355dbd6cfb1b69e0e37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Daniel=20Colmenares=20Oviedo?= Date: Tue, 26 Nov 2024 19:57:05 -0400 Subject: [PATCH 3/4] Fix logic in SRV_CUSTOM --- pyinfra/operations/freebsd/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyinfra/operations/freebsd/service.py b/pyinfra/operations/freebsd/service.py index 99b0b80f5..4daaf0222 100644 --- a/pyinfra/operations/freebsd/service.py +++ b/pyinfra/operations/freebsd/service.py @@ -106,7 +106,7 @@ def service( elif state == ServiceStates.SRV_CUSTOM: args.append(QuoteString(srvname)) - if not isinstance(command, str): + if isinstance(command, str): command = [command] args.extend((QuoteString(c) for c in command)) From 4e142595b38b0ff61c244ef66988e6963cf01308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Daniel=20Colmenares=20Oviedo?= Date: Wed, 27 Nov 2024 20:49:00 -0400 Subject: [PATCH 4/4] Fix type-checking errors --- pyinfra/operations/freebsd/freebsd_update.py | 6 +++-- pyinfra/operations/freebsd/pkg.py | 26 ++++++++++++++------ pyinfra/operations/freebsd/service.py | 11 ++++++--- pyinfra/operations/freebsd/sysrc.py | 6 +++-- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/pyinfra/operations/freebsd/freebsd_update.py b/pyinfra/operations/freebsd/freebsd_update.py index 03ffcfb8a..c66316814 100644 --- a/pyinfra/operations/freebsd/freebsd_update.py +++ b/pyinfra/operations/freebsd/freebsd_update.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing_extensions import Optional +from typing_extensions import List, Optional, Union from pyinfra.api import QuoteString, StringCommand, operation @@ -40,7 +40,9 @@ def update( freebsd_update.update() """ - args = ["PAGER=cat", "freebsd-update", "--not-running-from-cron"] + args: List[Union[str, "QuoteString"]] = [] + + args.extend(["PAGER=cat", "freebsd-update", "--not-running-from-cron"]) if force: args.append("-F") diff --git a/pyinfra/operations/freebsd/pkg.py b/pyinfra/operations/freebsd/pkg.py index e63107db8..dffb1b2a0 100644 --- a/pyinfra/operations/freebsd/pkg.py +++ b/pyinfra/operations/freebsd/pkg.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing_extensions import Optional +from typing_extensions import List, Optional, Union from pyinfra import host from pyinfra.api import QuoteString, StringCommand, operation @@ -33,7 +33,9 @@ def update(jail: Optional[str] = None, force: bool = False, reponame: Optional[s ) """ - args = ["pkg"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("pkg") if jail is not None: args.extend(["-j", QuoteString(jail)]) @@ -71,7 +73,9 @@ def upgrade(jail: Optional[str] = None, force: bool = False, reponame: Optional[ ) """ - args = ["pkg"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("pkg") if jail is not None: args.extend(["-j", QuoteString(jail)]) @@ -107,7 +111,9 @@ def install(package: str, jail: Optional[str] = None, reponame: Optional[str] = host.noop(f"Package '{package}' already installed") return - args = ["pkg"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("pkg") if jail is not None: args.extend(["-j", QuoteString(jail)]) @@ -141,7 +147,9 @@ def remove(package: str, jail: Optional[str] = None): host.noop(f"Package '{package}' cannot be found") return - args = ["pkg"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("pkg") if jail is not None: args.extend(["-j", QuoteString(jail)]) @@ -167,7 +175,9 @@ def autoremove(jail: Optional[str] = None): pkg.autoremove() """ - args = ["pkg"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("pkg") if jail is not None: args.extend(["-j", QuoteString(jail)]) @@ -194,7 +204,9 @@ def clean(all_pkg: bool = False, jail: Optional[str] = None): ) """ - args = ["pkg"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("pkg") if jail is not None: args.extend(["-j", QuoteString(jail)]) diff --git a/pyinfra/operations/freebsd/service.py b/pyinfra/operations/freebsd/service.py index 4daaf0222..1ff63cdf5 100644 --- a/pyinfra/operations/freebsd/service.py +++ b/pyinfra/operations/freebsd/service.py @@ -72,7 +72,9 @@ def service( host.noop(f"Cannot find rc(8) script '{srvname}'") return - args = ["service"] + args: List[Union[str, "QuoteString"]] = [] + + args.append("service") if verbose: args.append("-v") @@ -106,10 +108,11 @@ def service( elif state == ServiceStates.SRV_CUSTOM: args.append(QuoteString(srvname)) - if isinstance(command, str): - command = [command] + if command is not None: + if isinstance(command, str): + command = [command] - args.extend((QuoteString(c) for c in command)) + args.extend([QuoteString(c) for c in command]) else: raise OperationValueError("Invalid service command!") diff --git a/pyinfra/operations/freebsd/sysrc.py b/pyinfra/operations/freebsd/sysrc.py index ba44a952a..c3858f5ce 100644 --- a/pyinfra/operations/freebsd/sysrc.py +++ b/pyinfra/operations/freebsd/sysrc.py @@ -6,7 +6,7 @@ from enum import Enum -from typing_extensions import Optional +from typing_extensions import List, Optional, Union from pyinfra import host from pyinfra.api import QuoteString, StringCommand, operation @@ -59,7 +59,9 @@ def sysrc( ) """ - args = ["sysrc", "-i"] + args: List[Union[str, "QuoteString"]] = [] + + args.extend(["sysrc", "-i"]) if command == SysrcCommands.SYSRC_DEL: sign = "="