我在基于Linux的设备上工作,无论是在工作中还是在我的个人生活中,都有必要经常擦除这些设备--有时每周多次擦一次。在这种情况下,使用某种脚本来恢复一套有用的软件显然是非常有用的。
在过去的几年里,我一直在使用Shell脚本,但是,由于GitHib决定更加强烈地鼓励使用访问令牌,所以我转而使用Python。设置Git是脚本中最关键的操作之一。其他职责:
我将在下面发布脚本核心类的代码。如果你想自己运行脚本,你可以下载完整的回购-它只是几个文件- 这里。但请注意,它将开始尝试安装的东西!
核心课程:
"""
This code defines a class which installs the various packages and repositories
required on this computer.
"""
# Standard imports.
import os
import pathlib
import shutil
import subprocess
import urllib.parse
# Local imports.
from git_credentials import set_up_git_credentials, \
DEFAULT_PATH_TO_GIT_CREDENTIALS, DEFAULT_PATH_TO_PAT, \
DEFAULT_USERNAME as DEFAULT_GIT_USERNAME, DEFAULT_EMAIL_ADDRESS
# Local constants.
DEFAULT_OS = "ubuntu"
DEFAULT_TARGET_DIR = str(pathlib.Path.home())
DEFAULT_PATH_TO_WALLPAPER_DIR = \
os.path.join(DEFAULT_TARGET_DIR, "hmss/wallpaper/")
##############
# MAIN CLASS #
##############
class HMSoftwareInstaller:
""" The class in question. """
# Class constants.
CHROME_DEB = "google-chrome-stable_current_amd64.deb"
CHROME_STEM = "https://dl.google.com/linux/direct/"
EXPECTED_PATH_TO_GOOGLE_CHROME_COMMAND = "/usr/bin/google-chrome"
GIT_URL_STEM = "https://github.com/"
MISSING_FROM_CHROME = ("eog", "nautilus")
OTHER_THIRD_PARTY = ("gedit-plugins", "inkscape")
SUPPORTED_OSS = set(("ubuntu", "chrome-os", "raspian", "linux-based"))
WALLPAPER_STEM = "wallpaper_t"
WALLPAPER_EXT = ".png"
def __init__(
self,
this_os=DEFAULT_OS,
target_dir=DEFAULT_TARGET_DIR,
thunderbird_num=None,
path_to_git_credentials=DEFAULT_PATH_TO_GIT_CREDENTIALS,
path_to_pat=DEFAULT_PATH_TO_PAT,
git_username=DEFAULT_GIT_USERNAME,
email_address=DEFAULT_EMAIL_ADDRESS,
path_to_wallpaper_dir=DEFAULT_PATH_TO_WALLPAPER_DIR
):
self.this_os = this_os
self.target_dir = target_dir
self.thunderbird_num = thunderbird_num
self.path_to_git_credentials = path_to_git_credentials
self.path_to_pat = path_to_pat
self.git_username = git_username
self.email_address = email_address
self.path_to_wallpaper_dir = path_to_wallpaper_dir
self.failure_log = []
def check_os(self):
""" Test whether the OS we're using is supported. """
if self.this_os in self.SUPPORTED_OSS:
return True
return False
def move_to_target_dir(self):
""" Change into the directory where we want to install stuff. """
os.chdir(self.target_dir)
def set_up_git(self):
""" Install Git and set up a personal access token. """
install_result = install_via_apt("git")
if not install_result:
return False
pat_result = \
set_up_git_credentials(
username=self.git_username,
email_address=self.email_address,
path_to_git_credentials=self.path_to_git_credentials,
path_to_pat=self.path_to_pat
)
if not pat_result:
return False
return True
def install_google_chrome(self):
""" Ronseal. """
if (
check_command_exists("google-chrome") or
self.this_os == "chrome-os"
):
return True
chrome_url = urllib.parse.urljoin(self.CHROME_STEM, self.CHROME_DEB)
chrome_deb_path = "./"+self.CHROME_DEB
download_process = subprocess.run(["wget", chrome_url])
if download_process.returncode != 0:
return False
if not install_via_apt(chrome_deb_path):
return False
os.remove(chrome_deb_path)
return True
def change_wallpaper(self):
""" Change the wallpaper on the desktop of this computer. """
if not os.path.exists(self.path_to_wallpaper_dir):
return False
if self.thunderbird_num:
wallpaper_filename = (
self.WALLPAPER_STEM+
str(self.thunderbird_num)+
self.WALLPAPER_EXT
)
else:
wallpaper_filename = "default.jpg"
wallpaper_path = \
os.path.join(self.path_to_wallpaper_dir, wallpaper_filename)
if self.this_os == "ubuntu":
arguments = [
"gsettings",
"set",
"org.gnome.desktop.background",
"picture-uri",
"file:///"+wallpaper_path
]
elif self.this_os == "raspbian":
arguments = ["pcmanfm", "--set-wallpaper", wallpaper_path]
else:
return False
result = run_with_indulgence(arguments)
return result
def make_git_url(self, repo_name):
""" Make the URL pointing to a given repo. """
suffix = self.git_username+"/"+repo_name+".git"
result = urllib.parse.urljoin(self.GIT_URL_STEM, suffix)
return result
def install_own_repo(
self,
repo_name,
dependent_packages=None,
installation_arguments=None
):
""" Install a custom repo. """
if os.path.exists(repo_name):
print("Looks like "+repo_name+" already exists...")
return True
if dependent_packages:
for package_name in dependent_packages:
if not install_via_apt(package_name):
return False
arguments = ["git", "clone", self.make_git_url(repo_name)]
if not run_with_indulgence(arguments):
return False
os.chdir(repo_name)
if installation_arguments:
if not run_with_indulgence(arguments):
os.chdir(self.target_dir)
return False
os.chdir(self.target_dir)
return True
def install_kingdom_of_cyprus(self):
""" Install the Kingdom of Cyprus repo. """
repo_name = "kingdom-of-cyprus"
dependent_packages = ("sqlite", "sqlitebrowser", "nodejs", "npm")
result = \
self.install_own_repo(
repo_name,
dependent_packages=dependent_packages
)
return result
def install_chancery(self):
""" Install the Chancery repos. """
repo_name = "chancery"
if not self.install_own_repo(repo_name):
return False
repo_name_b = "chancery-b"
installation_arguments_b = ("sh", "install_3rd_party")
result = \
self.install_own_repo(
repo_name_b,
installation_arguments=installation_arguments_b
)
return result
def install_hmss(self):
""" Install the HMSS repo. """
result = self.install_own_repo("hmss")
return result
def install_hgmj(self):
""" Install the HGMJ repo. """
repo_name = "hgmj"
installation_arguments = ("sh", "install_3rd_party")
result = \
self.install_own_repo(
repo_name,
installation_arguments=installation_arguments
)
return result
def install_other_third_party(self):
""" Install some other useful packages. """
result = True
for package in self.OTHER_THIRD_PARTY:
if not install_via_apt(package):
result = False
if self.this_os == "chrome-os":
for package in self.MISSING_FROM_CHROME:
if not install_via_apt(package):
result = False
return result
def run_essentials(self):
""" Run those processes which, if they fail, we will have to stop
the entire program there. """
print("Checking OS...")
if not self.check_os():
self.failure_log.append("Check OS")
return False
print("Updating and upgrading...")
if not update_and_upgrade():
self.failure_log.append("Update and upgrade")
return False
print("Upgrading Python...")
if not upgrade_python():
self.failure_log.append("Upgrade Python")
return False
print("Setting up Git...")
if not self.set_up_git():
self.failure_log.append("Set up Git")
return False
return True
def run_non_essentials(self):
""" Run the installation processes. """
result = True
print("Installing Google Chrome...")
if not self.install_google_chrome():
self.failure_log.append("Install Google Chrome")
result = False
print("Installing HMSS...")
if not self.install_hmss():
self.failure_log.append("Install HMSS")
print("Installing Kingdom of Cyprus...")
if not self.install_kingdom_of_cyprus():
self.failure_log.append("Install Kingdom of Cyprus")
result = False
print("Installing Chancery repos...")
if not self.install_chancery():
self.failure_log.append("Install Chancery repos")
result = False
print("Installing HGMJ...")
if not self.install_hgmj():
self.failure_log.append("Install HGMJ")
result = False
print("Installing other third party...")
if not self.install_other_third_party():
self.failure_log.append("Install other third party")
result = False
print("Changing wallpaper...")
if not self.change_wallpaper():
self.failure_log.append("Change wallpaper")
# It doesn't matter too much if this fails.
return result
def print_outcome(self, passed, with_flying_colours):
""" Print a list of what failed to the screen. """
if passed and with_flying_colours:
print("Installation PASSED with flying colours!")
return
if passed:
print("Installation PASSED but with non-essential failures.")
else:
print("Installation FAILED.")
print("\nThe following items failed:\n")
for item in self.failure_log:
print(" * "+item)
print(" ")
def run(self):
""" Run the software installer. """
print("Running His Majesty's Software Installer...")
get_sudo()
self.move_to_target_dir()
if not self.run_essentials():
print("\nFinished.\n\n")
self.print_outcome(False, False)
return False
with_flying_colours = self.run_non_essentials()
print("\nComplete!\n")
self.print_outcome(True, with_flying_colours)
return True
####################
# HELPER FUNCTIONS #
####################
def get_sudo():
""" Get superuser privileges. """
print("I'm going to need superuser privileges for this...")
subprocess.run(
["sudo", "echo", "Superuser privileges: activate!"],
check=True
)
def run_with_indulgence(arguments, show_output=False):
""" Run a command, and don't panic immediately if we get a non-zero
return code. """
if show_output:
print("Running subprocess.run() with arguments:")
print(arguments)
process = subprocess.run(arguments)
else:
process = subprocess.run(arguments, stdout=subprocess.DEVNULL)
if process.returncode == 0:
return True
return False
def run_apt_with_argument(argument):
""" Run APT with an argument, and tell me how it went. """
arguments = ["sudo", "apt-get", argument]
result = run_with_indulgence(arguments)
return result
def check_against_dpkg(package_name):
""" Check whether a given package is on the books with DPKG. """
result = run_with_indulgence(["dpkg", "--status", package_name])
return result
def check_command_exists(command):
""" Check whether a given command exists on this computer. """
if shutil.which(command):
return True
return False
def install_via_apt(package_name, command=None):
""" Attempt to install a package, and tell me how it went. """
if not command:
command = package_name
if check_command_exists(command):
return True
arguments = ["sudo", "apt-get", "--yes", "install", package_name]
result = run_with_indulgence(arguments)
return result
def update_and_upgrade():
""" Update and upgrade the existing software. """
if not run_apt_with_argument("update"):
return False
if not run_apt_with_argument("upgrade"):
return False
if not install_via_apt("software-properties-common"):
return False
return True
def pip3_install(package):
""" Run `pip3 install [package]`. """
if run_with_indulgence(["pip3", "install", package]):
return True
return False
def upgrade_python():
""" Install PIP3 and other useful Python hangers-on. """
result = True
if not install_via_apt("python3-pip"):
result = False
if not pip3_install("pylint"):
result = False
if not pip3_install("pytest"):
result = False
return result发布于 2022-01-03 23:59:33
有必要经常擦拭这些设备--有时每周多次擦一次
拥有这样的脚本是有用的,因为可重复的环境是有用的;只要您不依赖它来解决其他问题。你的两种情景:
在工作中:我们正在生产嵌入式Linux设备,我们需要确保每一个新的软件版本都能在未被触摸的硬件上正常安装。
这是像这样的安装脚本的一个很好的应用程序。
在家里:我的笔记本电脑是带有Linux的Chromebook;每隔几个月,我就会遇到一个问题,就是删除VM并重新启动会更快。
很好,只要你明白出了什么问题,并能够从中吸取教训,然后才能对你的操作系统进行核武器化。
CHROME_DEB似乎是一个奇怪的选择。你在Ubuntu上,为什么不直接从回购处安装它呢?
您应该能够从EXPECTED_PATH_TO_GOOGLE_CHROME_COMMAND的结果中推断出大部分的which。
set(("ubuntu", "chrome-os", "raspian", "linux-based"))应该是{"ubuntu", "chrome-os", "raspian", "linux-based"}。
在您的__init__中看到的那种死记硬背的成员初始化可以通过使用@dataclass来消除。
self.failure_log = []意味着您要登录到内存中。这是危险的:如果您的进程崩溃,您不会丢失您的日志吗?考虑使用标准的logging模块并将其记录到文件中。
这是:
if self.this_os in self.SUPPORTED_OSS:
return True
return False应该只是
return self.this_os in self.SUPPORTED_OSS避免换行符(如pat_result = \中的换行符),这可能只是
pat_result = set_up_git_credentials(
username=self.git_username,
email_address=self.email_address,
path_to_git_credentials=self.path_to_git_credentials,
path_to_pat=self.path_to_pat
)这是:
if not pat_result:
return False
return True可能只是
return bool(pat_result)这种对subprocess的使用:
download_process = subprocess.run(["wget", chrome_url])
if download_process.returncode != 0:
return False应该用subprocess.check_call代替。这将引发异常,而不是布尔故障指示符--您也应该这样做。对失败状态使用异常而不是布尔返回。您的“基本故障”应该由允许解除堆栈的异常来表示,而您的“非必要故障”应该记录在except中。
将对os.path.exists等的调用替换为对pathlib替代品的调用。
你的install_own_repo很好奇。您接受安装repo_name之前所需的依赖包列表。这表明缺乏适当的包装。如果正确编写,您自己的repos应该构建表达自己依赖关系的debs,这样您就可以安装顶级包,并且依赖项将被自动拉入。使用distutils setup.py来表示依赖关系是您自己开发的另一种方式,如果所有的依赖项都是纯Python的话,这更好。
发布于 2022-01-04 12:42:34
def run_non_essentials(self):
""" Run the installation processes. """
result = True
print("Installing Google Chrome...")
if not self.install_google_chrome():
self.failure_log.append("Install Google Chrome")
result = False
print("Installing HMSS...")
if not self.install_hmss():
self.failure_log.append("Install HMSS")
print("Installing Kingdom of Cyprus...")
if not self.install_kingdom_of_cyprus():
self.failure_log.append("Install Kingdom of Cyprus")
result = False
print("Installing Chancery repos...")
if not self.install_chancery():
self.failure_log.append("Install Chancery repos")
result = False
print("Installing HGMJ...")
if not self.install_hgmj():
self.failure_log.append("Install HGMJ")
result = False
print("Installing other third party...")
if not self.install_other_third_party():
self.failure_log.append("Install other third party")
result = False
print("Changing wallpaper...")
if not self.change_wallpaper():
self.failure_log.append("Change wallpaper")
# It doesn't matter too much if this fails.
return result可概括为:
OPTIONAL_PROGRAMS = {
"Google Chrome": install_google_chrome,
"HMSS": install_hmss,
"Kingdom of Cyprus": install_kingdom_of_cyprus,
...
}
[...]
class HMSoftwareInstaller:
[...]
def install(name: str, function: Callable[[], bool]) -> bool:
print(f"Installing {name}...")
if not (result := function()):
self.failure_log.append(f"Install {name}")
return result
def run_non_essentials(self) -> bool:
""" Run the installation processes. """
return all([self.install(*program) for program in OPTIONAL_PROGRAMS.items()])这同样适用于run_essentials()。
被调用的函数无论如何都不使用self,因此应该将它们重新定义为空闲函数。
pathlib.Path...to管理路径,而不是os.*函数。
函数upgrade_python()不必要地存储状态。您可以将其改写为:
def upgrade_python():
""" Install PIP3 and other useful Python hangers-on. """
return all([
install_via_apt("python3-pip"),
pip3_install("pylint"),
pip3_install("pytest")
])https://codereview.stackexchange.com/questions/272595
复制相似问题