sysetup 1.4.2__tar.gz → 1.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {sysetup-1.4.2/src/sysetup.egg-info → sysetup-1.4.4}/PKG-INFO +8 -9
  2. {sysetup-1.4.2 → sysetup-1.4.4}/README.md +1 -1
  3. {sysetup-1.4.2 → sysetup-1.4.4}/pyproject.toml +13 -23
  4. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/__init__.py +0 -1
  5. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/context.py +5 -5
  6. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/options.py +0 -3
  7. sysetup-1.4.4/src/sysetup/context/system.py +15 -0
  8. sysetup-1.4.4/src/sysetup/main/files.py +89 -0
  9. sysetup-1.4.4/src/sysetup/main/linux/installations.py +41 -0
  10. sysetup-1.4.4/src/sysetup/main/linux/packages.py +52 -0
  11. sysetup-1.4.4/src/sysetup/main/linux/setup.py +35 -0
  12. sysetup-1.4.4/src/sysetup/main/main.py +32 -0
  13. sysetup-1.4.4/src/sysetup/main/packages.py +37 -0
  14. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/models/path.py +1 -3
  15. sysetup-1.4.4/src/sysetup/utils/__init__.py +2 -0
  16. sysetup-1.4.4/src/sysetup/utils/bitwarden.py +57 -0
  17. sysetup-1.4.4/src/sysetup/utils/download.py +18 -0
  18. {sysetup-1.4.2 → sysetup-1.4.4/src/sysetup.egg-info}/PKG-INFO +8 -9
  19. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/SOURCES.txt +7 -14
  20. sysetup-1.4.4/src/sysetup.egg-info/requires.txt +10 -0
  21. sysetup-1.4.2/bin/pw +0 -4
  22. sysetup-1.4.2/bin/pw-askpass +0 -4
  23. sysetup-1.4.2/src/sysetup/context/action.py +0 -8
  24. sysetup-1.4.2/src/sysetup/context/installations.py +0 -5
  25. sysetup-1.4.2/src/sysetup/main/files/assets.py +0 -47
  26. sysetup-1.4.2/src/sysetup/main/files/permissions.py +0 -33
  27. sysetup-1.4.2/src/sysetup/main/files/settings.py +0 -42
  28. sysetup-1.4.2/src/sysetup/main/files/setup.py +0 -8
  29. sysetup-1.4.2/src/sysetup/main/installations.py +0 -95
  30. sysetup-1.4.2/src/sysetup/main/main.py +0 -27
  31. sysetup-1.4.2/src/sysetup/main/packages.py +0 -87
  32. sysetup-1.4.2/src/sysetup/utils/__init__.py +0 -4
  33. sysetup-1.4.2/src/sysetup/utils/bitwarden.py +0 -57
  34. sysetup-1.4.2/src/sysetup/utils/download.py +0 -28
  35. sysetup-1.4.2/src/sysetup.egg-info/requires.txt +0 -11
  36. sysetup-1.4.2/tests/test_background.py +0 -58
  37. sysetup-1.4.2/tests/test_cli_entry_point.py +0 -12
  38. sysetup-1.4.2/tests/test_main.py +0 -12
  39. {sysetup-1.4.2 → sysetup-1.4.4}/LICENSE +0 -0
  40. {sysetup-1.4.2 → sysetup-1.4.4}/setup.cfg +0 -0
  41. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/__init__.py +0 -0
  42. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/cli/__init__.py +0 -0
  43. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/cli/entry_point.py +0 -0
  44. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/secrets_.py +0 -0
  45. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/main/__init__.py +0 -0
  46. {sysetup-1.4.2/src/sysetup/main/files → sysetup-1.4.4/src/sysetup/main/linux}/__init__.py +0 -0
  47. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/models/__init__.py +0 -0
  48. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/py.typed +0 -0
  49. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/dependency_links.txt +0 -0
  50. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/entry_points.txt +0 -0
  51. {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sysetup
3
- Version: 1.4.2
3
+ Version: 1.4.4
4
4
  Summary: Personal system setup
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
6
  License-Expression: MIT
@@ -8,23 +8,22 @@ Project-URL: Source Code, https://github.com/quintenroets/sysetup
8
8
  Requires-Python: >=3.10
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: backupmaster<2,>=1.2.21
12
- Requires-Dist: dbus-next<1,>=0.2.3
13
- Requires-Dist: package-utils[context]<1,>=0.6.1
14
- Requires-Dist: powercli<1,>=0.3.1
11
+ Requires-Dist: backupmaster<3,>=2.0.5
12
+ Requires-Dist: package-utils[context]<1,>=0.8.3
13
+ Requires-Dist: powercli<1,>=0.3.7
15
14
  Requires-Dist: requests<3,>=2.32.3
16
- Requires-Dist: superpathlib<3,>=2.0.4
15
+ Requires-Dist: superpathlib<3,>=2.0.11
17
16
  Provides-Extra: dev
17
+ Requires-Dist: dirhash<1,>=0.5.0; extra == "dev"
18
18
  Requires-Dist: types-requests<3,>=2.32.0.20250306; extra == "dev"
19
- Requires-Dist: package-dev-tools<1,>=0.7.1; extra == "dev"
20
- Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
19
+ Requires-Dist: package-dev-tools<1,>=0.8.0; extra == "dev"
21
20
  Dynamic: license-file
22
21
 
23
22
  # Sysetup
24
23
  [![PyPI version](https://badge.fury.io/py/sysetup.svg)](https://badge.fury.io/py/sysetup)
25
24
  ![PyPI downloads](https://img.shields.io/pypi/dm/sysetup)
26
25
  ![Python version](https://img.shields.io/badge/python-3.10+-brightgreen)
27
- ![Operating system](https://img.shields.io/badge/os-linux-brightgreen)
26
+ ![Operating system](https://img.shields.io/badge/os-linux%20%7c%20macOS-brightgreen)
28
27
  ![Coverage](https://img.shields.io/badge/coverage-66%25-brightgreen)
29
28
  ## [Plasma](https://kde.org/plasma-desktop/) 6 required
30
29
 
@@ -2,7 +2,7 @@
2
2
  [![PyPI version](https://badge.fury.io/py/sysetup.svg)](https://badge.fury.io/py/sysetup)
3
3
  ![PyPI downloads](https://img.shields.io/pypi/dm/sysetup)
4
4
  ![Python version](https://img.shields.io/badge/python-3.10+-brightgreen)
5
- ![Operating system](https://img.shields.io/badge/os-linux-brightgreen)
5
+ ![Operating system](https://img.shields.io/badge/os-linux%20%7c%20macOS-brightgreen)
6
6
  ![Coverage](https://img.shields.io/badge/coverage-66%25-brightgreen)
7
7
  ## [Plasma](https://kde.org/plasma-desktop/) 6 required
8
8
 
@@ -1,25 +1,24 @@
1
1
  [project]
2
2
  name = "sysetup"
3
- version = "1.4.2"
3
+ version = "1.4.4"
4
4
  description = "Personal system setup"
5
5
  authors = [{name = "Quinten Roets", email = "qdr2104@columbia.edu"}]
6
6
  license = "MIT"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.10"
9
9
  dependencies = [
10
- "backupmaster >=1.2.21, <2",
11
- "dbus-next >=0.2.3, <1",
12
- "package-utils[context] >=0.6.1, <1",
13
- "powercli >=0.3.1, <1",
10
+ "backupmaster >=2.0.5, <3",
11
+ "package-utils[context] >=0.8.3, <1",
12
+ "powercli >=0.3.7, <1",
14
13
  "requests >=2.32.3, <3",
15
- "superpathlib >=2.0.4, <3",
14
+ "superpathlib >=2.0.11, <3",
16
15
  ]
17
16
 
18
17
  [project.optional-dependencies]
19
18
  dev = [
19
+ "dirhash >=0.5.0, <1",
20
20
  "types-requests >=2.32.0.20250306, <3",
21
- "package-dev-tools >=0.7.1, <1",
22
- "package-dev-utils >=0.1.6, <1",
21
+ "package-dev-tools >=0.8.0, <1",
23
22
  ]
24
23
 
25
24
  [project.urls]
@@ -33,22 +32,10 @@ exportcrontab = "sysetup.main.files.assets:move_crontab"
33
32
  requires = ["setuptools"]
34
33
  build-backend = "setuptools.build_meta"
35
34
 
36
- [tool.coverage.run]
37
- command_line = "-m pytest tests"
38
-
39
- [tool.coverage.report]
40
- precision = 4
41
- fail_under = 60
42
-
43
35
  [tool.mypy]
44
36
  strict = true
45
37
  no_implicit_reexport = false
46
38
 
47
- [tool.pytest.ini_options]
48
- pythonpath = [
49
- "src", ".",
50
- ]
51
-
52
39
  [tool.ruff]
53
40
  fix = true
54
41
 
@@ -59,13 +46,16 @@ ignore = [
59
46
  "D", # docstrings
60
47
  "G004", # logging f-string
61
48
  "S101", # assert used
49
+ "D1", # missing docstrings
50
+ "D200", # one-line docstring
51
+ "D203", # conflicts with D211
52
+ "D205", # blank line between summary and description
53
+ "D212", # conflicts with D213
54
+ "D401", # imperative first line
62
55
  ]
63
56
 
64
57
  [tool.ruff.lint.per-file-ignores]
65
58
  "__init__.py" = ["F401"]
66
59
 
67
- [tool.setuptools]
68
- script-files = ["bin/pw", "bin/pw-askpass"]
69
-
70
60
  [tool.setuptools.package-data]
71
61
  sysetup = ["assets/scripts/update_wallpaper.js", "py.typed"]
@@ -1,2 +1 @@
1
- from .action import Action
2
1
  from .context import Context, context
@@ -1,11 +1,11 @@
1
- import os
2
1
  from functools import cached_property
2
+ from pathlib import Path
3
3
 
4
- from package_utils.context import Context as Context_
4
+ from package_utils.context.context import Context as Context_
5
5
 
6
- from .installations import is_installed
7
6
  from .options import Options
8
7
  from .secrets_ import Secrets
8
+ from .system import is_installed
9
9
 
10
10
 
11
11
  class Context(Context_[Options, None, Secrets]):
@@ -19,8 +19,8 @@ class Context(Context_[Options, None, Secrets]):
19
19
  return self.package_manager == "apt-get"
20
20
 
21
21
  @cached_property
22
- def is_running_in_test(self) -> bool:
23
- return "DISPLAY" not in os.environ
22
+ def is_running_in_container(self) -> bool:
23
+ return Path("/.dockerenv").exists()
24
24
 
25
25
 
26
26
  context = Context(Options, Secrets=Secrets)
@@ -3,11 +3,8 @@ from typing import Annotated
3
3
 
4
4
  import typer
5
5
 
6
- from .action import Action
7
-
8
6
 
9
7
  @dataclass
10
8
  class Options:
11
9
  bitwarden_password: Annotated[str, typer.Option()] = ""
12
10
  bitwarden_email: Annotated[str, typer.Option()] = "quinten.roets@gmail.com"
13
- action: Annotated[Action, typer.Argument(help="The part to setup")] = Action.all
@@ -0,0 +1,15 @@
1
+ import platform
2
+
3
+ import cli
4
+
5
+
6
+ def is_mac() -> bool:
7
+ return platform.system() == "Darwin"
8
+
9
+
10
+ def is_linux() -> bool:
11
+ return platform.system() == "Linux"
12
+
13
+
14
+ def is_installed(package: str) -> bool:
15
+ return cli.completes_successfully("which", package)
@@ -0,0 +1,89 @@
1
+ import stat
2
+
3
+ import cli
4
+
5
+ from sysetup.context import context
6
+ from sysetup.context.system import is_linux, is_mac
7
+ from sysetup.models import Path
8
+ from sysetup.utils import download_directory, ensure_downloaded
9
+
10
+ from .packages import install
11
+
12
+
13
+ def setup() -> None:
14
+ configure_git()
15
+ configure_ssh()
16
+ remove_clutter()
17
+ install_custom_certificate()
18
+
19
+
20
+ def configure_git() -> None:
21
+ directory = Path.HOME / ".config" / "git" / "hooks"
22
+ download_directory(directory)
23
+ for path in directory.iterdir():
24
+ cli.run("chmod +x", path)
25
+
26
+
27
+ def configure_ssh() -> None:
28
+ directory = Path.HOME / ".ssh"
29
+ download_directory(directory)
30
+ if is_linux():
31
+ remove_macos_ssh_options(directory / "config")
32
+ for path in directory.glob("id_*"):
33
+ if path.suffix != ".pub":
34
+ check_permissions(path)
35
+
36
+
37
+ def remove_macos_ssh_options(config: Path) -> None:
38
+ lines = (line for line in config.lines if "UseKeychain" not in line)
39
+ config.lines = list(lines)
40
+
41
+
42
+ def check_permissions(path: Path) -> None:
43
+ permissions = path.stat().st_mode
44
+ other_users_can_read = permissions & (stat.S_IRGRP | stat.S_IROTH)
45
+ if other_users_can_read:
46
+ path.chmod(0o600)
47
+
48
+
49
+ def remove_clutter() -> None:
50
+ names = (
51
+ "Desktop",
52
+ "Downloads",
53
+ "Music",
54
+ "Pictures",
55
+ "Public",
56
+ "Templates",
57
+ "Videos",
58
+ )
59
+ for name in names:
60
+ path = Path.HOME / name
61
+ path.rmtree(missing_ok=True)
62
+
63
+ root = Path("/") if is_linux() else Path("/") / "opt" / "homebrew"
64
+ nginx_path = root / "etc" / "nginx" / "sites-enabled" / "default"
65
+ if nginx_path.exists():
66
+ cli.run("rm", nginx_path, root=True)
67
+
68
+
69
+ def install_custom_certificate() -> None:
70
+ certificate_file = Path.assets / "certificates" / "certificate.crt"
71
+ ensure_downloaded(certificate_file)
72
+ if is_mac():
73
+ keychain = "/Library/Keychains/System.keychain"
74
+ command = (
75
+ f"security add-trusted-cert -d -r trustRoot "
76
+ f"-k {keychain} {certificate_file}"
77
+ )
78
+ else:
79
+ install(["libnss3-tools"])
80
+ certificate_directory = Path.HOME / ".pki" / "nssdb"
81
+ if not certificate_directory.exists():
82
+ certificate_directory.mkdir(parents=True)
83
+ cli.run(f"certutil -d sql:{certificate_directory} -N --empty-password")
84
+ command = (
85
+ f"certutil -d sql:{certificate_directory} "
86
+ f'-A -t "C,," -n "QCA" -i {certificate_file}'
87
+ )
88
+ if not context.is_running_in_container:
89
+ cli.run(command, root=is_mac())
@@ -0,0 +1,41 @@
1
+ import cli
2
+
3
+ from sysetup.context.system import is_installed
4
+ from sysetup.main.packages import install
5
+ from sysetup.models import Path
6
+
7
+
8
+ def setup() -> None:
9
+ install_repository("keyd", "rvaiya/keyd")
10
+ install_repository("ydotool", "ReimuNotMoe/ydotool")
11
+ enable_service("ssh")
12
+ install_language_support()
13
+
14
+
15
+ def install_language_support() -> None:
16
+ try:
17
+ packages = cli.capture_output("check-language-support")
18
+ except FileNotFoundError:
19
+ pass
20
+ else:
21
+ if packages:
22
+ install(packages)
23
+
24
+
25
+ def install_repository(name: str, repository: str) -> None:
26
+ if not is_installed(name):
27
+ if name == "ydotool":
28
+ cli.run("apt-get install scdoc", root=True)
29
+ url = f"https://github.com/{repository}"
30
+ with Path.tempdir() as directory:
31
+ cli.run("git clone", url, directory)
32
+ if (directory / "CMakeLists.txt").exists():
33
+ cli.run("apt-get install -y cmake", root=True)
34
+ cli.run("cmake .", cwd=directory)
35
+ cli.run_commands("make", "sudo make install", cwd=directory)
36
+ enable_service(name)
37
+
38
+
39
+ def enable_service(name: str) -> None:
40
+ if Path("/run/systemd/system").exists():
41
+ cli.run(f"systemctl enable --now {name}", root=True)
@@ -0,0 +1,52 @@
1
+ import cli
2
+
3
+ from sysetup.context import context
4
+ from sysetup.context.system import is_installed
5
+ from sysetup.main.packages import install_packages
6
+ from sysetup.models import Path
7
+ from sysetup.utils import bitwarden_client
8
+
9
+
10
+ def setup() -> None:
11
+ enable_sudo()
12
+ update_package_manager()
13
+ install_packages()
14
+ cleanup_after_install()
15
+
16
+
17
+ def enable_sudo() -> None:
18
+ password = bitwarden_client().fetch_secret("Laptop")
19
+ cli.run("sudo -S true", input=password)
20
+
21
+
22
+ def update_package_manager() -> None:
23
+ if context.apt_is_installed:
24
+ update_apt()
25
+ else:
26
+ cli.run("pacman -Syy", root=True)
27
+
28
+
29
+ def update_apt() -> None:
30
+ value = (
31
+ "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true"
32
+ )
33
+ agree_eula_command = f'echo "{value}" | sudo debconf-set-selections'
34
+ commands = "sudo apt-get update", agree_eula_command
35
+ cli.run_commands_in_shell(*commands)
36
+ if not Path("/snap").exists():
37
+ cli.run("ln -s /var/lib/snapd/snap /snap", root=True)
38
+
39
+
40
+ def cleanup_after_install() -> None:
41
+ if context.apt_is_installed:
42
+ cli.run("sudo apt-get autoremove -y")
43
+ cli.run("tlp start", root=True)
44
+ if is_installed("qdbus"):
45
+ commands = "rm /usr/bin/qdbus", "ln -s /usr/lib/qt6/bin/qdbus /usr/bin/qdbus"
46
+ cli.run_commands(*commands, root=True)
47
+ delete = "apt purge -y" if context.apt_is_installed else "pacman -R --noconfirm"
48
+ commands = (
49
+ "auto-cpufreq --install",
50
+ f"{delete} firefox",
51
+ )
52
+ cli.run_commands(*commands, check=False, root=True)
@@ -0,0 +1,35 @@
1
+ import cli
2
+
3
+ from sysetup.context import context
4
+ from sysetup.context.system import is_installed
5
+ from sysetup.models import Path
6
+ from sysetup.utils import ensure_downloaded
7
+
8
+ from . import installations, packages
9
+
10
+
11
+ def setup() -> None:
12
+ set_background()
13
+ packages.setup()
14
+ install_crontab()
15
+ installations.setup()
16
+
17
+
18
+ def set_background() -> None: # pragma: nocover
19
+ path = (
20
+ Path.HOME / ".local" / "share" / "wallpapers" / "Qwallpapers" / "background.jpg"
21
+ )
22
+ ensure_downloaded(path)
23
+ script = Path.update_wallpaper_script.text
24
+ script = script.replace("__wallpaper_uri__", path.as_uri())
25
+ command = (
26
+ "qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript"
27
+ )
28
+ if not context.is_running_in_container and is_installed("qdbus"):
29
+ cli.run(command, script)
30
+
31
+
32
+ def install_crontab() -> None:
33
+ path = Path.assets / "crontab" / "crontab"
34
+ ensure_downloaded(path)
35
+ cli.run("crontab -", input=path.text)
@@ -0,0 +1,32 @@
1
+ import cli
2
+
3
+ from sysetup.context import context
4
+ from sysetup.context.system import is_linux, is_mac
5
+ from sysetup.models import Path
6
+ from sysetup.utils import bitwarden_client
7
+
8
+ from . import files, linux, packages
9
+
10
+
11
+ def main() -> None:
12
+ """
13
+ Personal system setup.
14
+ """
15
+ files.setup()
16
+ if is_linux():
17
+ linux.setup()
18
+ elif is_mac():
19
+ packages.install_packages()
20
+ install_personal_git_repositories()
21
+ if not context.is_running_in_container:
22
+ flags = ("--include-browser",) if is_linux() else ()
23
+ cli.run("backup pull --no-confirm-push", *flags)
24
+
25
+
26
+ def install_personal_git_repositories() -> None:
27
+ github_token = bitwarden_client().fetch_secret("GitHub Token")
28
+ base_url = f"https://{github_token}@github.com/quintenroets"
29
+ if not Path.extensions.exists():
30
+ command = f"git clone {base_url}/extensions.git"
31
+ cli.run(command, Path.extensions)
32
+ cli.run(f"uv pip install git+{base_url}/system.git")
@@ -0,0 +1,37 @@
1
+ import shlex
2
+ from collections.abc import Iterable
3
+
4
+ import cli
5
+
6
+ from sysetup.context import context
7
+ from sysetup.context.system import is_linux
8
+ from sysetup.models import Path
9
+ from sysetup.utils import download_directory
10
+
11
+
12
+ def install_packages() -> None:
13
+ download_directory(Path.packages)
14
+ installations = (
15
+ {"packages": None, "snap": "snap install"}
16
+ if is_linux()
17
+ else {"brew": "brew install"}
18
+ )
19
+ for name, command in installations.items():
20
+ path = (Path.packages / name).with_suffix(".yaml")
21
+ packages: list[str] = path.yaml
22
+ install(packages, install_command=command)
23
+ if is_linux() and not context.apt_is_installed:
24
+ commands = "sudo pacman -S --noconfirm base-devel", "uv pip install wheel"
25
+ cli.run_commands(*commands)
26
+
27
+
28
+ def install(packages: Iterable[str], install_command: str | None = None) -> None:
29
+ if install_command is None:
30
+ install_command = (
31
+ "apt-get install -y"
32
+ if context.apt_is_installed
33
+ else "pacman -S --noconfirm"
34
+ )
35
+ for package in packages:
36
+ args = shlex.split(package)
37
+ cli.run(install_command, *args, root=is_linux(), check=False)
@@ -1,11 +1,9 @@
1
- from typing import TypeVar, cast
1
+ from typing import cast
2
2
 
3
3
  import superpathlib
4
4
  from simple_classproperty import classproperty
5
5
  from typing_extensions import Self
6
6
 
7
- T = TypeVar("T", bound="Path")
8
-
9
7
 
10
8
  class Path(superpathlib.Path):
11
9
  @classmethod
@@ -0,0 +1,2 @@
1
+ from .bitwarden import bitwarden_client
2
+ from .download import download_directory, ensure_downloaded
@@ -0,0 +1,57 @@
1
+ import io
2
+ import json
3
+ import sys
4
+ import zipfile
5
+ from dataclasses import dataclass
6
+ from functools import cache, cached_property
7
+ from typing import cast
8
+
9
+ import cli
10
+ import requests
11
+ from rich.prompt import Prompt
12
+
13
+ from sysetup.context import context
14
+ from sysetup.models import Path
15
+
16
+
17
+ @dataclass
18
+ class Client:
19
+ password: str
20
+ email: str
21
+
22
+ def fetch_secret(self, name: str) -> str:
23
+ command = "./bw list items --session", self.session_token, "--search", name
24
+ response = cli.capture_output(*command)
25
+ item = json.loads(response)[0]
26
+ secret = item.get("notes") or item["login"]["password"]
27
+ return cast("str", secret)
28
+
29
+ @cached_property
30
+ def session_token(self) -> str:
31
+ if not Path("bw").exists():
32
+ self.download_cli()
33
+
34
+ logged_in = "userEmail" in cli.capture_output("./bw status")
35
+ command: tuple[str, ...] = "./bw unlock --raw", self.password
36
+ if not logged_in:
37
+ if context.secrets.bw_clientid:
38
+ cli.run("./bw login --apikey")
39
+ else:
40
+ command = "./bw login --raw", self.email, self.password
41
+ return cli.capture_output(*command)
42
+
43
+ def download_cli(self) -> None:
44
+ platform = "macos" if sys.platform == "darwin" else "linux"
45
+ download_url = f"https://bitwarden.com/download/?app=cli&platform={platform}"
46
+ response = requests.get(download_url, timeout=10).content
47
+ zip_bytes = io.BytesIO(response)
48
+ with zipfile.ZipFile(zip_bytes, "r") as zip_file:
49
+ zip_file.extractall()
50
+ Path("bw").chmod(0o755)
51
+
52
+
53
+ @cache
54
+ def bitwarden_client() -> Client:
55
+ password = context.options.bitwarden_password
56
+ password = password or Prompt.ask("Bitwarden password", password=True)
57
+ return Client(password=password, email=context.options.bitwarden_email)
@@ -0,0 +1,18 @@
1
+ import os
2
+
3
+ from backup.syncer.builder import create_syncer
4
+
5
+ from sysetup.models import Path
6
+
7
+ from .bitwarden import bitwarden_client
8
+
9
+
10
+ def ensure_downloaded(path: Path) -> None:
11
+ if not path.exists():
12
+ download_directory(path.parent)
13
+
14
+
15
+ def download_directory(directory: Path) -> None:
16
+ if "RCLONE" not in os.environ:
17
+ os.environ["RCLONE"] = bitwarden_client().fetch_secret("Rclone")
18
+ create_syncer(directory=directory).capture_pull()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sysetup
3
- Version: 1.4.2
3
+ Version: 1.4.4
4
4
  Summary: Personal system setup
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
6
  License-Expression: MIT
@@ -8,23 +8,22 @@ Project-URL: Source Code, https://github.com/quintenroets/sysetup
8
8
  Requires-Python: >=3.10
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: backupmaster<2,>=1.2.21
12
- Requires-Dist: dbus-next<1,>=0.2.3
13
- Requires-Dist: package-utils[context]<1,>=0.6.1
14
- Requires-Dist: powercli<1,>=0.3.1
11
+ Requires-Dist: backupmaster<3,>=2.0.5
12
+ Requires-Dist: package-utils[context]<1,>=0.8.3
13
+ Requires-Dist: powercli<1,>=0.3.7
15
14
  Requires-Dist: requests<3,>=2.32.3
16
- Requires-Dist: superpathlib<3,>=2.0.4
15
+ Requires-Dist: superpathlib<3,>=2.0.11
17
16
  Provides-Extra: dev
17
+ Requires-Dist: dirhash<1,>=0.5.0; extra == "dev"
18
18
  Requires-Dist: types-requests<3,>=2.32.0.20250306; extra == "dev"
19
- Requires-Dist: package-dev-tools<1,>=0.7.1; extra == "dev"
20
- Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
19
+ Requires-Dist: package-dev-tools<1,>=0.8.0; extra == "dev"
21
20
  Dynamic: license-file
22
21
 
23
22
  # Sysetup
24
23
  [![PyPI version](https://badge.fury.io/py/sysetup.svg)](https://badge.fury.io/py/sysetup)
25
24
  ![PyPI downloads](https://img.shields.io/pypi/dm/sysetup)
26
25
  ![Python version](https://img.shields.io/badge/python-3.10+-brightgreen)
27
- ![Operating system](https://img.shields.io/badge/os-linux-brightgreen)
26
+ ![Operating system](https://img.shields.io/badge/os-linux%20%7c%20macOS-brightgreen)
28
27
  ![Coverage](https://img.shields.io/badge/coverage-66%25-brightgreen)
29
28
  ## [Plasma](https://kde.org/plasma-desktop/) 6 required
30
29
 
@@ -1,8 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- bin/pw
5
- bin/pw-askpass
6
4
  src/sysetup/__init__.py
7
5
  src/sysetup/py.typed
8
6
  src/sysetup.egg-info/PKG-INFO
@@ -14,25 +12,20 @@ src/sysetup.egg-info/top_level.txt
14
12
  src/sysetup/cli/__init__.py
15
13
  src/sysetup/cli/entry_point.py
16
14
  src/sysetup/context/__init__.py
17
- src/sysetup/context/action.py
18
15
  src/sysetup/context/context.py
19
- src/sysetup/context/installations.py
20
16
  src/sysetup/context/options.py
21
17
  src/sysetup/context/secrets_.py
18
+ src/sysetup/context/system.py
22
19
  src/sysetup/main/__init__.py
23
- src/sysetup/main/installations.py
20
+ src/sysetup/main/files.py
24
21
  src/sysetup/main/main.py
25
22
  src/sysetup/main/packages.py
26
- src/sysetup/main/files/__init__.py
27
- src/sysetup/main/files/assets.py
28
- src/sysetup/main/files/permissions.py
29
- src/sysetup/main/files/settings.py
30
- src/sysetup/main/files/setup.py
23
+ src/sysetup/main/linux/__init__.py
24
+ src/sysetup/main/linux/installations.py
25
+ src/sysetup/main/linux/packages.py
26
+ src/sysetup/main/linux/setup.py
31
27
  src/sysetup/models/__init__.py
32
28
  src/sysetup/models/path.py
33
29
  src/sysetup/utils/__init__.py
34
30
  src/sysetup/utils/bitwarden.py
35
- src/sysetup/utils/download.py
36
- tests/test_background.py
37
- tests/test_cli_entry_point.py
38
- tests/test_main.py
31
+ src/sysetup/utils/download.py
@@ -0,0 +1,10 @@
1
+ backupmaster<3,>=2.0.5
2
+ package-utils[context]<1,>=0.8.3
3
+ powercli<1,>=0.3.7
4
+ requests<3,>=2.32.3
5
+ superpathlib<3,>=2.0.11
6
+
7
+ [dev]
8
+ dirhash<1,>=0.5.0
9
+ types-requests<3,>=2.32.0.20250306
10
+ package-dev-tools<1,>=0.8.0
sysetup-1.4.2/bin/pw DELETED
@@ -1,4 +0,0 @@
1
- #! /bin/bash
2
-
3
- password_name=${1:-Login}
4
- ksshaskpass -- "Enter passphrase for $password_name: "
@@ -1,4 +0,0 @@
1
- #! /bin/bash
2
- # separate script because input message needs to be ignored
3
-
4
- pw
@@ -1,8 +0,0 @@
1
- from enum import Enum
2
-
3
-
4
- class Action(str, Enum):
5
- all = "all"
6
- files = "files"
7
- packages = "packages"
8
- install = "install"
@@ -1,5 +0,0 @@
1
- import cli
2
-
3
-
4
- def is_installed(package: str) -> bool:
5
- return cli.completes_successfully("which", package)
@@ -1,47 +0,0 @@
1
- import cli
2
- from backup.backups.cache import cache
3
-
4
- from sysetup.models import Path
5
- from sysetup.utils import download_directory
6
-
7
-
8
- def setup() -> None:
9
- directories = (
10
- Path.assets,
11
- Path.HOME / ".local" / "share" / "kwalletd",
12
- Path.assets.parent / "backup",
13
- )
14
- for directory in directories:
15
- download_directory(directory)
16
- move_crontab()
17
- move_setup_files()
18
-
19
-
20
- def move_crontab() -> None:
21
- src = Path.assets / "crontab" / "crontab"
22
- cli.run("crontab -", input=src.text)
23
-
24
-
25
- def move_setup_files() -> None:
26
- setup_files_root = Path.assets / "files"
27
- setup_files = []
28
- archived_setup_files = []
29
- for path in setup_files_root.rglob("*"):
30
- if path.is_file():
31
- if path.archive_format is None:
32
- setup_files.append(path)
33
- else:
34
- archived_setup_files.append(path)
35
-
36
- if setup_files:
37
- cache.Backup(paths=setup_files).pull()
38
-
39
- source = cache.Backup().source
40
- for path in archived_setup_files:
41
- dest = (source / path.relative_to(setup_files_root)).parent
42
- if dest.is_root and not dest.exists():
43
- cli.run("mkdir -p", dest, root=True)
44
- else:
45
- dest.create_parent()
46
- with cli.status(f"Unpacking {path.relative_to(setup_files_root)}"):
47
- cli.capture_output("unzip -o", path, "-d", dest, root=dest.is_root)
@@ -1,33 +0,0 @@
1
- import stat
2
-
3
- import cli
4
-
5
- from sysetup.models import Path
6
- from sysetup.utils import download_directory
7
-
8
-
9
- def setup() -> None:
10
- set_git_permissions()
11
- set_ssh_permissions()
12
-
13
-
14
- def set_git_permissions() -> None:
15
- git_hooks_folder = Path.HOME / ".config" / "git" / "hooks"
16
- download_directory(git_hooks_folder)
17
- for path in git_hooks_folder.iterdir():
18
- cli.run("chmod +x", path)
19
-
20
-
21
- def set_ssh_permissions() -> None:
22
- directory = Path.HOME / ".ssh"
23
- download_directory(directory)
24
- for path in directory.glob("id_*"):
25
- if path.suffix != ".pub":
26
- check_permissions(path)
27
-
28
-
29
- def check_permissions(path: Path) -> None:
30
- permissions = path.stat().st_mode
31
- other_users_can_read = permissions & (stat.S_IRGRP | stat.S_IROTH)
32
- if other_users_can_read:
33
- path.chmod(33152)
@@ -1,42 +0,0 @@
1
- import cli
2
-
3
- from sysetup.context import context
4
- from sysetup.models import Path
5
- from sysetup.utils import download_directory, is_installed
6
-
7
-
8
- def remove_clutter() -> None:
9
- names = (
10
- "Desktop",
11
- "Downloads",
12
- "Music",
13
- "Pictures",
14
- "Public",
15
- "Templates",
16
- "Videos",
17
- )
18
- for name in names:
19
- path = Path.HOME / name
20
- path.rmtree(missing_ok=True)
21
-
22
- nginx_path = Path("/") / "etc" / "nginx" / "sites-enabled" / "default"
23
- if nginx_path.exists():
24
- cli.run("rm", nginx_path, root=True)
25
-
26
-
27
- def set_background() -> None: # pragma: nocover
28
- wallpaper_path = (
29
- Path.HOME / ".local" / "share" / "wallpapers" / "Qwallpapers" / "background.jpg"
30
- )
31
- download_directory(wallpaper_path.parent)
32
- script = Path.update_wallpaper_script.text
33
- script = script.replace("__wallpaper_uri__", wallpaper_path.as_uri())
34
- run_kde_script(script)
35
-
36
-
37
- def run_kde_script(script: str) -> None: # pragma: nocover
38
- command = (
39
- "qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript"
40
- )
41
- if not context.is_running_in_test and is_installed("qdbus"):
42
- cli.run(command, script)
@@ -1,8 +0,0 @@
1
- from sysetup.main.files import assets, permissions, settings
2
-
3
-
4
- def setup() -> None:
5
- permissions.setup()
6
- assets.setup()
7
- settings.set_background()
8
- settings.remove_clutter()
@@ -1,95 +0,0 @@
1
- import cli
2
-
3
- from sysetup.context import context
4
- from sysetup.models import Path
5
- from sysetup.utils import bitwarden, is_installed
6
-
7
- from .packages import install
8
-
9
-
10
- def setup() -> None:
11
- install_chromium()
12
- install_keyd()
13
- install_ydotool()
14
- enable_service("ssh")
15
- install_language_support()
16
- install_personal_git_repositories()
17
-
18
-
19
- def install_personal_git_repositories() -> None:
20
- github_token = bitwarden.client.fetch_secret("GitHub")
21
- base_url = f"https://{github_token}@github.com/quintenroets"
22
- if not Path.extensions.exists():
23
- command = f"git clone {base_url}/extensions.git"
24
- cli.run(command, Path.extensions)
25
- cli.run(f"uv pip install git+{base_url}/system.git")
26
-
27
-
28
- def install_language_support() -> None:
29
- try:
30
- packages = cli.capture_output("check-language-support")
31
- except FileNotFoundError:
32
- pass
33
- else:
34
- if packages:
35
- install(packages)
36
-
37
-
38
- def install_chromium() -> None:
39
- if not is_installed("chromium-browser"):
40
- _install_chromium()
41
-
42
-
43
- def _install_chromium() -> None:
44
- release_name = cli.capture_output_lines("lsb_release -sc")[-1]
45
- repo_url = f"https://freeshell.de/phd/chromium/{release_name}"
46
- commands = (
47
- f'echo "deb {repo_url} /" | sudo tee /etc/apt/sources.list.d/phd-chromium.list',
48
- "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 869689FE09306074",
49
- "apt-get update",
50
- "apt-get install -y chromium",
51
- )
52
- check = not context.is_running_in_test
53
- cli.run_commands(*commands, shell=True, root=True, check=check) # noqa: S604
54
- install_custom_certificate()
55
-
56
-
57
- def install_custom_certificate() -> None:
58
- install(["libnss3-tools"])
59
- certificate_directory = Path.HOME / ".pki" / "nssdb"
60
- certificate_file = Path.assets / "certificates" / "certificate.crt"
61
- command = (
62
- f"certutil -d sql:{certificate_directory} "
63
- f'-A -t "C,," -n "QCA" -i {certificate_file}'
64
- )
65
- if not context.is_running_in_test:
66
- cli.run(command)
67
-
68
-
69
- def install_keyd() -> None:
70
- install_repository("keyd", "rvaiya/keyd")
71
- enable_service("keyd")
72
-
73
-
74
- def enable_service(name: str) -> None:
75
- if not context.is_running_in_test:
76
- command = f"systemctl enable --now {name}"
77
- cli.run(command, root=True)
78
-
79
-
80
- def install_repository(name: str, repository: str) -> None:
81
- if not is_installed(name):
82
- url = f"https://github.com/{repository}"
83
- with Path.tempdir() as directory:
84
- cli.run("git clone", url, directory)
85
- if (directory / "CMakeLists.txt").exists():
86
- cli.run("apt-get install -y cmake", root=True)
87
- cli.run("cmake .", cwd=directory)
88
- cli.run_commands("make", "sudo make install", cwd=directory)
89
-
90
-
91
- def install_ydotool() -> None:
92
- if not is_installed("ydotool"):
93
- cli.run("apt-get install scdoc", root=True)
94
- install_repository("ydotool", "ReimuNotMoe/ydotool")
95
- enable_service("ydotoold")
@@ -1,27 +0,0 @@
1
- import cli
2
-
3
- from sysetup.context import Action, context
4
-
5
- from . import files, installations, packages
6
-
7
-
8
- def main() -> None:
9
- """
10
- Personal system setup.
11
- """
12
- action_mapper = {
13
- Action.all.value: setup,
14
- Action.packages.value: packages.setup,
15
- Action.files.value: files.setup,
16
- Action.install.value: installations.setup,
17
- }
18
- action = action_mapper[context.options.action.value]
19
- action()
20
-
21
-
22
- def setup() -> None:
23
- packages.setup()
24
- files.setup()
25
- installations.setup()
26
- if not context.is_running_in_test:
27
- cli.run("backup pull --include-browser --no-confirm-push")
@@ -1,87 +0,0 @@
1
- import platform
2
- import shlex
3
- import warnings
4
- from collections.abc import Iterable
5
-
6
- import cli
7
-
8
- from sysetup.context import context
9
- from sysetup.models import Path
10
- from sysetup.utils import bitwarden, download_directory, is_installed
11
-
12
-
13
- def setup() -> None:
14
- enable_sudo()
15
- update_package_manager()
16
- install_packages()
17
- cleanup_after_install()
18
-
19
-
20
- def enable_sudo() -> None:
21
- password = bitwarden.client.fetch_secret("Laptop")
22
- cli.run("sudo -S true", input=password) # activate sudo without askpass
23
-
24
-
25
- def install_packages() -> None:
26
- download_directory(Path.packages)
27
- installations = {"packages": None, "snap": "snap install"}
28
- for name, command in installations.items():
29
- path = (Path.packages / name).with_suffix(".yaml")
30
- packages: list[str] = path.yaml
31
- install(packages, install_command=command)
32
-
33
- if not context.apt_is_installed:
34
- commands = "sudo pacman -S --noconfirm base-devel", "uv pip install wheel"
35
- cli.run_commands(*commands)
36
-
37
-
38
- def cleanup_after_install() -> None:
39
- if context.apt_is_installed:
40
- cli.run("sudo apt-get autoremove -y")
41
- cli.run("tlp start", root=True)
42
- if is_installed("qdbus"):
43
- commands = "rm /usr/bin/qdbus", "ln -s /usr/lib/qt6/bin/qdbus /usr/bin/qdbus"
44
- cli.run_commands(*commands, root=True)
45
- delete = "apt purge -y" if context.apt_is_installed else "pacman -R --noconfirm"
46
- commands = (
47
- "auto-cpufreq --install", # Fails on VM
48
- f"{delete} firefox", # fails if firefox not installed
49
- )
50
- cli.run_commands(*commands, check=False, root=True)
51
-
52
-
53
- def update_package_manager() -> None:
54
- if context.apt_is_installed:
55
- update_apt()
56
- else:
57
- cli.run("pacman -Syy", root=True)
58
-
59
-
60
- def update_apt() -> None:
61
- value = (
62
- "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true"
63
- )
64
- agree_eula_command = f'echo "{value}" | sudo debconf-set-selections'
65
- commands = "sudo apt-get update", agree_eula_command
66
- cli.run_commands_in_shell(*commands)
67
- if not Path("/snap").exists():
68
- cli.run("ln -s /var/lib/snapd/snap /snap", root=True)
69
-
70
-
71
- def install(packages: Iterable[str], install_command: str | None = None) -> None:
72
- if install_command is None:
73
- install_command = (
74
- "apt-get install -y"
75
- if context.apt_is_installed
76
- else "pacman -S --noconfirm"
77
- )
78
-
79
- is_linux = platform.system() == "Linux"
80
- if not is_linux:
81
- message = "Required packages can only be installed on Linux"
82
- warnings.warn(message, stacklevel=2)
83
- return
84
-
85
- for package in packages:
86
- args = shlex.split(package)
87
- cli.run(install_command, *args, root=True, check=False)
@@ -1,4 +0,0 @@
1
- from sysetup.context.installations import is_installed
2
-
3
- from .bitwarden import bitwarden
4
- from .download import download_directory, download_file
@@ -1,57 +0,0 @@
1
- import io
2
- import json
3
- import zipfile
4
- from dataclasses import dataclass
5
- from functools import cached_property
6
- from typing import cast
7
-
8
- import cli
9
- import requests
10
- from rich.prompt import Prompt
11
-
12
- from sysetup.context import context
13
- from sysetup.models import Path
14
-
15
-
16
- @dataclass
17
- class Client:
18
- password: str
19
- email: str
20
- download_url: str = "https://bitwarden.com/download/?app=cli&platform=linux"
21
-
22
- def fetch_secret(self, name: str) -> str:
23
- command = "./bw list items --session", self.session_token, "--search", name
24
- response = cli.capture_output(*command)
25
- secret = json.loads(response)[0]["notes"]
26
- return cast("str", secret)
27
-
28
- @cached_property
29
- def session_token(self) -> str:
30
- if not Path("bw").exists():
31
- self.download_cli()
32
- command: tuple[str, ...]
33
- if context.secrets.bw_clientid:
34
- cli.run("./bw login --apikey")
35
- command = "./bw unlock --raw", self.password
36
- else:
37
- command = "./bw login --raw", self.email, self.password
38
- return cli.capture_output(*command)
39
-
40
- def download_cli(self) -> None:
41
- response = requests.get(self.download_url, timeout=10).content
42
- zip_bytes = io.BytesIO(response)
43
- with zipfile.ZipFile(zip_bytes, "r") as zip_file:
44
- zip_file.extractall()
45
- Path("bw").chmod(0o755)
46
-
47
-
48
- @dataclass
49
- class Bitwarden:
50
- @cached_property
51
- def client(self) -> Client:
52
- password = context.options.bitwarden_password
53
- password = password or Prompt.ask("Bitwarden password", password=True)
54
- return Client(password=password, email=context.options.bitwarden_email)
55
-
56
-
57
- bitwarden = Bitwarden()
@@ -1,28 +0,0 @@
1
- import os
2
-
3
- import cli
4
- from backup.backups import Backup
5
- from backup.context import context as backup_context
6
- from backup.models import Path as BackupPath
7
-
8
- from sysetup.models import Path
9
-
10
- from .bitwarden import bitwarden
11
-
12
-
13
- def download_directory(path: Path) -> None:
14
- check_authenticated()
15
- Backup(sub_check_path=BackupPath(path), confirm=False).pull()
16
-
17
-
18
- def download_file(path: Path) -> None:
19
- check_authenticated()
20
- Backup(path=BackupPath(path), confirm=False).pull()
21
-
22
-
23
- def check_authenticated() -> None:
24
- try:
25
- assert backup_context.secrets.rclone
26
- except cli.models.CalledProcessError:
27
- os.environ["RCLONE"] = "dummy"
28
- backup_context.secrets.rclone = bitwarden.client.fetch_secret("Rclone")
@@ -1,11 +0,0 @@
1
- backupmaster<2,>=1.2.21
2
- dbus-next<1,>=0.2.3
3
- package-utils[context]<1,>=0.6.1
4
- powercli<1,>=0.3.1
5
- requests<3,>=2.32.3
6
- superpathlib<3,>=2.0.4
7
-
8
- [dev]
9
- types-requests<3,>=2.32.0.20250306
10
- package-dev-tools<1,>=0.7.1
11
- package-dev-utils<1,>=0.1.6
@@ -1,58 +0,0 @@
1
- import os
2
- from collections.abc import Callable, Iterator
3
-
4
- import cli
5
- import pytest
6
- from backup.utils import setup
7
-
8
- from sysetup.main.files.settings import set_background
9
- from sysetup.models import Path
10
-
11
- plasma_config_path = Path.HOME / ".config" / "plasma-org.kde.plasma.desktop-appletsrc"
12
-
13
-
14
- @pytest.fixture
15
- def restore(path: Path) -> Callable[[Path], Iterator[None]]:
16
- def _restore(restored_path: Path) -> Iterator[None]:
17
- exists = restored_path.exists()
18
- if exists:
19
- restored_path.copy_to(path, include_properties=False)
20
- yield
21
- if exists:
22
- path.rename(restored_path, exist_ok=True)
23
-
24
- return _restore
25
-
26
-
27
- @pytest.fixture
28
- def restore_and_check(
29
- restore: Callable[[Path], Iterator[None]],
30
- ) -> Callable[[Path], Iterator[None]]:
31
- setup.check_setup()
32
-
33
- def _restore_and_check(restored_path: Path) -> Iterator[None]:
34
- def extract_content_hash() -> str:
35
- return cli.capture_output("rclone hashsum MD5", restored_path, check=False)
36
-
37
- content_hash = extract_content_hash()
38
- yield from restore(restored_path)
39
- assert extract_content_hash() == content_hash
40
-
41
- return _restore_and_check
42
-
43
-
44
- @pytest.fixture
45
- def restore_config_path(
46
- restore_and_check: Callable[[Path], Iterator[None]],
47
- ) -> Iterator[None]:
48
- yield from restore_and_check(plasma_config_path)
49
-
50
-
51
- def test_wallpaper(
52
- restore_config_path: Callable[[Path], Iterator[None]], # noqa: ARG001
53
- ) -> None:
54
- # "org.kde.PlasmaShell.evaluateScript missing in GITHUB_ACTIONS"
55
- # still run existing test case to maximize code under coverage
56
- if "GITHUB_ACTIONS" not in os.environ:
57
- set_background()
58
- assert "Qwallpapers" in plasma_config_path.text
@@ -1,12 +0,0 @@
1
- from unittest.mock import MagicMock, patch
2
-
3
- from package_dev_utils.tests.args import no_cli_args
4
-
5
- from sysetup import cli
6
-
7
-
8
- @no_cli_args
9
- @patch("sysetup.main.main.setup")
10
- def test_entry_point(mocked_setup: MagicMock) -> None:
11
- cli.entry_point()
12
- mocked_setup.assert_called_once()
@@ -1,12 +0,0 @@
1
- from unittest.mock import MagicMock, patch
2
-
3
- from package_dev_utils.tests.args import no_cli_args
4
-
5
- from sysetup.main.main import main
6
-
7
-
8
- @no_cli_args
9
- @patch("sysetup.main.main.setup")
10
- def test_main(mocked_setup: MagicMock) -> None:
11
- main()
12
- mocked_setup.assert_called_once()
File without changes
File without changes
File without changes
File without changes