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.
- {sysetup-1.4.2/src/sysetup.egg-info → sysetup-1.4.4}/PKG-INFO +8 -9
- {sysetup-1.4.2 → sysetup-1.4.4}/README.md +1 -1
- {sysetup-1.4.2 → sysetup-1.4.4}/pyproject.toml +13 -23
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/__init__.py +0 -1
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/context.py +5 -5
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/options.py +0 -3
- sysetup-1.4.4/src/sysetup/context/system.py +15 -0
- sysetup-1.4.4/src/sysetup/main/files.py +89 -0
- sysetup-1.4.4/src/sysetup/main/linux/installations.py +41 -0
- sysetup-1.4.4/src/sysetup/main/linux/packages.py +52 -0
- sysetup-1.4.4/src/sysetup/main/linux/setup.py +35 -0
- sysetup-1.4.4/src/sysetup/main/main.py +32 -0
- sysetup-1.4.4/src/sysetup/main/packages.py +37 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/models/path.py +1 -3
- sysetup-1.4.4/src/sysetup/utils/__init__.py +2 -0
- sysetup-1.4.4/src/sysetup/utils/bitwarden.py +57 -0
- sysetup-1.4.4/src/sysetup/utils/download.py +18 -0
- {sysetup-1.4.2 → sysetup-1.4.4/src/sysetup.egg-info}/PKG-INFO +8 -9
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/SOURCES.txt +7 -14
- sysetup-1.4.4/src/sysetup.egg-info/requires.txt +10 -0
- sysetup-1.4.2/bin/pw +0 -4
- sysetup-1.4.2/bin/pw-askpass +0 -4
- sysetup-1.4.2/src/sysetup/context/action.py +0 -8
- sysetup-1.4.2/src/sysetup/context/installations.py +0 -5
- sysetup-1.4.2/src/sysetup/main/files/assets.py +0 -47
- sysetup-1.4.2/src/sysetup/main/files/permissions.py +0 -33
- sysetup-1.4.2/src/sysetup/main/files/settings.py +0 -42
- sysetup-1.4.2/src/sysetup/main/files/setup.py +0 -8
- sysetup-1.4.2/src/sysetup/main/installations.py +0 -95
- sysetup-1.4.2/src/sysetup/main/main.py +0 -27
- sysetup-1.4.2/src/sysetup/main/packages.py +0 -87
- sysetup-1.4.2/src/sysetup/utils/__init__.py +0 -4
- sysetup-1.4.2/src/sysetup/utils/bitwarden.py +0 -57
- sysetup-1.4.2/src/sysetup/utils/download.py +0 -28
- sysetup-1.4.2/src/sysetup.egg-info/requires.txt +0 -11
- sysetup-1.4.2/tests/test_background.py +0 -58
- sysetup-1.4.2/tests/test_cli_entry_point.py +0 -12
- sysetup-1.4.2/tests/test_main.py +0 -12
- {sysetup-1.4.2 → sysetup-1.4.4}/LICENSE +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/setup.cfg +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/__init__.py +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/cli/__init__.py +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/cli/entry_point.py +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/context/secrets_.py +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/main/__init__.py +0 -0
- {sysetup-1.4.2/src/sysetup/main/files → sysetup-1.4.4/src/sysetup/main/linux}/__init__.py +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/models/__init__.py +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup/py.typed +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/dependency_links.txt +0 -0
- {sysetup-1.4.2 → sysetup-1.4.4}/src/sysetup.egg-info/entry_points.txt +0 -0
- {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.
|
|
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<
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
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.
|
|
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.
|
|
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
|
[](https://badge.fury.io/py/sysetup)
|
|
25
24
|

|
|
26
25
|

|
|
27
|
-

|
|
26
|
+

|
|
28
27
|

|
|
29
28
|
## [Plasma](https://kde.org/plasma-desktop/) 6 required
|
|
30
29
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
[](https://badge.fury.io/py/sysetup)
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-

|
|
5
|
+

|
|
6
6
|

|
|
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.
|
|
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 >=
|
|
11
|
-
"
|
|
12
|
-
"
|
|
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.
|
|
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.
|
|
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,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
|
|
23
|
-
return "
|
|
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
|
|
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,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.
|
|
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<
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
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.
|
|
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.
|
|
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
|
[](https://badge.fury.io/py/sysetup)
|
|
25
24
|

|
|
26
25
|

|
|
27
|
-

|
|
26
|
+

|
|
28
27
|

|
|
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/
|
|
20
|
+
src/sysetup/main/files.py
|
|
24
21
|
src/sysetup/main/main.py
|
|
25
22
|
src/sysetup/main/packages.py
|
|
26
|
-
src/sysetup/main/
|
|
27
|
-
src/sysetup/main/
|
|
28
|
-
src/sysetup/main/
|
|
29
|
-
src/sysetup/main/
|
|
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
|
sysetup-1.4.2/bin/pw
DELETED
sysetup-1.4.2/bin/pw-askpass
DELETED
|
@@ -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,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,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,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()
|
sysetup-1.4.2/tests/test_main.py
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|