Switched the variable type of user defined flatpak packages to match the type of the user systemd services

This commit is contained in:
Dávid Groniewsky
2025-10-13 09:36:46 +02:00
parent 69bcaadf90
commit 6ae8c78959
7 changed files with 144 additions and 70 deletions
Binary file not shown.
+2 -2
View File
@@ -116,8 +116,8 @@ class MyModule(Module):
def flatpak_packages(self) -> list[str]: def flatpak_packages(self) -> list[str]:
return ["org.mozilla.firefox"] return ["org.mozilla.firefox"]
def flatpak_user_packages(self) -> list[tuple[str,str]]: def flatpak_user_packages(self) -> dict[str, list[str]]:
return [("username", "io.github.kolunmi.Bazaar")] return {"username": ["io.github.kolunmi.Bazaar"]}
def systemd_units(self) -> list[str]: def systemd_units(self) -> list[str]:
return ["reflector.timer"] return ["reflector.timer"]
+4 -3
View File
@@ -38,9 +38,10 @@ decman.flatpak_packages += ["dev.qwery.AddWater"]
decman.ignored_flatpak_packages += ["org.signal.Signal"] decman.ignored_flatpak_packages += ["org.signal.Signal"]
# You can also install them to your user installation instead of the system installation # You can also install them to your user installation instead of the system installation
decman.flatpak_user_packages += [ # Ensure that previous user installed flatpak declarations aren't overwritten and they are initialized.
("username", "dev.zed.Zed") decman.flatpak_user_packages["kk"] = decman.flatpak_user_packages.get("kk", [])
] # Now add the package.
decman.flatpak_user_packages["kk"].append("dev.zed.Zed")
# To import GPG keys, set the GNUPGHOME environment variable. # To import GPG keys, set the GNUPGHOME environment variable.
# It can easily be done with python as well. # It can easily be done with python as well.
+3 -3
View File
@@ -387,12 +387,12 @@ class Module:
""" """
return [] return []
def flatpak_user_packages(self) -> list[tuple[str, str]]: def flatpak_user_packages(self) -> dict[str, list[str]]:
""" """
Override this method to return flatpak packages that should be installed to the user installation as a part of this Override this method to return flatpak packages that should be installed to the user installation as a part of this
Module. Module.
""" """
return [] return {}
def systemd_units(self) -> list[str]: def systemd_units(self) -> list[str]:
""" """
@@ -427,5 +427,5 @@ files: dict[str, File] = {}
directories: dict[str, Directory] = {} directories: dict[str, Directory] = {}
modules: list[Module] = [] modules: list[Module] = []
flatpak_packages: list[str] = [] flatpak_packages: list[str] = []
flatpak_user_packages: list[tuple[str, str]] = [] flatpak_user_packages: dict[str, list[str]] = {}
ignored_flatpak_packages: list[str] = [] ignored_flatpak_packages: list[str] = []
+52 -14
View File
@@ -238,6 +238,7 @@ class Core:
""" """
Run the main logic of decman. Run the main logic of decman.
""" """
if self.update_units: if self.update_units:
self._disable_units() self._disable_units()
@@ -305,16 +306,33 @@ class Core:
self._remove_user_flatpaks() self._remove_user_flatpaks()
def _remove_user_flatpaks(self, only_print: bool = False): def _remove_user_flatpaks(self, only_print: bool = False):
"""
# get all users through a command instead of pwd because pwd also lists all 'virtual' users. add root since they can also have user installed packages # get all users through a command instead of pwd because pwd also lists all 'virtual' users. add root since they can also have user installed packages
users = subprocess.run(["users"], check=True, stdout=subprocess.PIPE).stdout.decode().strip().split('\n') users = (
users.append("root") subprocess.run(["users"], check=True, stdout=subprocess.PIPE)
.stdout.decode()
.strip()
.split("\n")
)
print()
users.append("root")"""
users = pwd.getpwall()
for user in users: for user in users:
currently_installed_flatpak = self.flatpak.get_installed(as_user=True, which_user=user) user = user.pw_name
to_remove_flatpak = self.source.flatpak_packages_to_remove(currently_installed_flatpak, as_user=True, which_user=user) currently_installed_flatpak = self.flatpak.get_installed(
l.print_list(f"Removing flatpak packages from user installation for user {user}", to_remove_flatpak) as_user=True, which_user=user
)
to_remove_flatpak = self.source.flatpak_packages_to_remove(
currently_installed_flatpak, as_user=True, which_user=user
)
l.print_list(
f"Removing flatpak packages from user installation for user {user}",
to_remove_flatpak,
)
if only_print: continue if only_print:
continue
self.flatpak.remove(to_remove_flatpak, True, user) self.flatpak.remove(to_remove_flatpak, True, user)
@@ -373,16 +391,32 @@ class Core:
self._install_user_flatpaks() self._install_user_flatpaks()
def _install_user_flatpaks(self, only_print: bool = False): def _install_user_flatpaks(self, only_print: bool = False):
# get all users through a command instead of pwd because pwd also lists all 'virtual' users. add root since they can also have user installed packages """# get all users through a command instead of pwd because pwd also lists all 'virtual' users. add root since they can also have user installed packages
users = subprocess.run(["users"], check=True, stdout=subprocess.PIPE).stdout.decode().strip().split('\n') users = (
users.append("root") subprocess.run(["users"], check=True, stdout=subprocess.PIPE)
.stdout.decode()
.strip()
.split("\n")
)
users.append("root")"""
users = pwd.getpwall()
for user in users: for user in users:
currently_installed_flatpak = self.flatpak.get_installed(as_user=True, which_user=user) user = user.pw_name
to_install_flatpak = self.source.flatpak_packages_to_install(currently_installed_flatpak, as_user=True, which_user=user) currently_installed_flatpak = self.flatpak.get_installed(
l.print_list(f"Installing flatpak packages to user installation for user {user}", to_install_flatpak) as_user=True, which_user=user
)
to_install_flatpak = self.source.flatpak_packages_to_install(
currently_installed_flatpak, as_user=True, which_user=user
)
if only_print: continue if only_print:
l.print_list(
f"Installing flatpak packages to user installation for user {user}",
to_install_flatpak,
)
continue
self.flatpak.install(to_install_flatpak, True, user) self.flatpak.install(to_install_flatpak, True, user)
@@ -444,6 +478,10 @@ def _resolve_source() -> l.Source:
for user, units in decman.enabled_systemd_user_units.items(): for user, units in decman.enabled_systemd_user_units.items():
enabled_systemd_user_units[user] = set(units) enabled_systemd_user_units[user] = set(units)
flatpak_user_packages = {}
for user, pkgs in decman.flatpak_user_packages.items():
flatpak_user_packages[user] = set(pkgs)
return l.Source( return l.Source(
pacman_packages=set(decman.packages), pacman_packages=set(decman.packages),
aur_packages=set(decman.aur_packages), aur_packages=set(decman.aur_packages),
@@ -455,7 +493,7 @@ def _resolve_source() -> l.Source:
directories=decman.directories, directories=decman.directories,
modules=set(decman.modules), modules=set(decman.modules),
flatpak_packages=set(decman.flatpak_packages), flatpak_packages=set(decman.flatpak_packages),
flatpak_user_packages=set(decman.flatpak_user_packages), flatpak_user_packages=flatpak_user_packages,
ignored_flatpak_packages=set(decman.ignored_flatpak_packages), ignored_flatpak_packages=set(decman.ignored_flatpak_packages),
) )
+10 -4
View File
@@ -65,7 +65,7 @@ class Commands:
""" """
Running this command installs all listed packages, and their dependencies/runtimes automatically. Running this command installs all listed packages, and their dependencies/runtimes automatically.
""" """
return ["flatpak", "install", "--user" if as_user else "--system"] + pkgs return ["flatpak", "install", "-y", "--user" if as_user else "--system"] + pkgs
def install_files(self, pkg_files: list[str]) -> list[str]: def install_files(self, pkg_files: list[str]) -> list[str]:
""" """
@@ -102,7 +102,7 @@ class Commands:
""" """
Updates all installed flatpak REFs including runtimes and dependencies. Updates all installed flatpak REFs including runtimes and dependencies.
""" """
return ["flatpak", "update", "--user" if as_user else "--system"] return ["flatpak", "update", "-y", "--user" if as_user else "--system"]
def remove(self, pkgs: list[str]) -> list[str]: def remove(self, pkgs: list[str]) -> list[str]:
""" """
@@ -115,13 +115,19 @@ class Commands:
""" """
Running this command will remove the listed REFs. Unused dependencies might be kept, but to remove them another command needs to be run. Running this command will remove the listed REFs. Unused dependencies might be kept, but to remove them another command needs to be run.
""" """
return ["flatpak", "remove", "--user" if as_user else "--system"] + pkgs return ["flatpak", "remove", "-y", "--user" if as_user else "--system"] + pkgs
def remove_unused_flatpak(self, as_user: bool = False) -> list[str]: def remove_unused_flatpak(self, as_user: bool = False) -> list[str]:
""" """
This will remove all unused flatpak dependencies and runtimes. This will remove all unused flatpak dependencies and runtimes.
""" """
return ["flatpak", "remove", "--unused", "--user" if as_user else "--system"] return [
"flatpak",
"remove",
"-y",
"--unused",
"--user" if as_user else "--system",
]
def enable_units(self, units: list[str]) -> list[str]: def enable_units(self, units: list[str]) -> list[str]:
""" """
+62 -33
View File
@@ -36,6 +36,7 @@ def print_continuation(msg: str, level: int = SUMMARY):
if level == SUMMARY or conf.debug_output or not conf.quiet_output: if level == SUMMARY or conf.debug_output or not conf.quiet_output:
print(f"{_CONTINUATION_PREFIX}{msg}") print(f"{_CONTINUATION_PREFIX}{msg}")
def print_error(error_msg: str): def print_error(error_msg: str):
""" """
Prints an error message to the user. Prints an error message to the user.
@@ -43,6 +44,7 @@ def print_error(error_msg: str):
print(f"{_DECMAN_MSG_TAG} {_RED_PREFIX}ERROR{_RESET_SUFFIX}: {error_msg}") print(f"{_DECMAN_MSG_TAG} {_RED_PREFIX}ERROR{_RESET_SUFFIX}: {error_msg}")
def print_warning(msg: str): def print_warning(msg: str):
""" """
Prints a warning to the user. Prints a warning to the user.
@@ -50,6 +52,7 @@ def print_warning(msg: str):
print(f"{_DECMAN_MSG_TAG} {_YELLOW_PREFIX}WARNING{_RESET_SUFFIX}: {msg}") print(f"{_DECMAN_MSG_TAG} {_YELLOW_PREFIX}WARNING{_RESET_SUFFIX}: {msg}")
def print_summary(msg: str): def print_summary(msg: str):
""" """
Prints a summary message to the user. Prints a summary message to the user.
@@ -57,6 +60,7 @@ def print_summary(msg: str):
print(f"{_DECMAN_MSG_TAG} {_CYAN_PREFIX}SUMMARY{_RESET_SUFFIX}: {msg}") print(f"{_DECMAN_MSG_TAG} {_CYAN_PREFIX}SUMMARY{_RESET_SUFFIX}: {msg}")
def print_list( def print_list(
msg: str, msg: str,
l: list[str], l: list[str],
@@ -116,6 +120,7 @@ def print_list(
print_continuation("", level=level) print_continuation("", level=level)
def print_info(msg: str): def print_info(msg: str):
""" """
Prints a detailed message to the user if verbose output is not disabled. Prints a detailed message to the user if verbose output is not disabled.
@@ -123,6 +128,7 @@ def print_info(msg: str):
if conf.debug_output or not conf.quiet_output: if conf.debug_output or not conf.quiet_output:
print(f"{_DECMAN_MSG_TAG} INFO: {msg}") print(f"{_DECMAN_MSG_TAG} INFO: {msg}")
def print_debug(msg: str): def print_debug(msg: str):
""" """
Prints a detailed message to the user if debug messages are enabled. Prints a detailed message to the user if debug messages are enabled.
@@ -130,6 +136,7 @@ def print_debug(msg: str):
if conf.debug_output: if conf.debug_output:
print(f"{_DECMAN_MSG_TAG} {_GRAY_PREFIX}DEBUG{_RESET_SUFFIX}: {msg}") print(f"{_DECMAN_MSG_TAG} {_GRAY_PREFIX}DEBUG{_RESET_SUFFIX}: {msg}")
def prompt_number( def prompt_number(
msg: str, min_num: int, max_num: int, default: typing.Optional[int] = None msg: str, min_num: int, max_num: int, default: typing.Optional[int] = None
) -> int: ) -> int:
@@ -412,7 +419,7 @@ class Source:
directories: dict[str, decman.Directory], directories: dict[str, decman.Directory],
modules: set[decman.Module], modules: set[decman.Module],
flatpak_packages: set[str], flatpak_packages: set[str],
flatpak_user_packages: set[tuple[str,str]], flatpak_user_packages: dict[str, set[str]],
ignored_flatpak_packages: set[str], ignored_flatpak_packages: set[str],
): ):
self.pacman_packages = pacman_packages self.pacman_packages = pacman_packages
@@ -638,7 +645,10 @@ class Source:
return result return result
def flatpak_packages_to_install( def flatpak_packages_to_install(
self, currently_installed_packages: list[str], as_user: bool = False, which_user: str = "" self,
currently_installed_packages: list[str],
as_user: bool = False,
which_user: str = "",
) -> list[str]: ) -> list[str]:
""" """
Returns all flatpak packages, that are not installed or ignored Returns all flatpak packages, that are not installed or ignored
@@ -653,7 +663,10 @@ class Source:
return result return result
def flatpak_packages_to_remove( def flatpak_packages_to_remove(
self, currently_installed_packages: list[str], as_user: bool = False, which_user: str = "" self,
currently_installed_packages: list[str],
as_user: bool = False,
which_user: str = "",
) -> list[str]: ) -> list[str]:
""" """
This returns a list of flatpak app ids, that need to be removed since they are installed but not found in either the list of ignored packages, This returns a list of flatpak app ids, that need to be removed since they are installed but not found in either the list of ignored packages,
@@ -697,28 +710,23 @@ class Source:
result.update(module.pacman_packages()) result.update(module.pacman_packages())
return result return result
def _all_flatpak_packages(self, as_user: bool = False, which_user: str = "") -> set[str]: def _all_flatpak_packages(
self, as_user: bool = False, which_user: str = ""
) -> set[str]:
# loop through all the user packages and save which ones are owned by the currently selected user # loop through all the user packages and save which ones are owned by the currently selected user
current_user_flatpak_packages = [] current_user_flatpak_packages = self.flatpak_user_packages.get(which_user, [])
for pkg in self.flatpak_user_packages:
if not pkg[0] == which_user: continue
current_user_flatpak_packages.append(pkg[1])
result = set() result = set()
result.update( result.update(
self.flatpak_packages if not as_user else current_user_flatpak_packages self.flatpak_packages if not as_user else current_user_flatpak_packages
) )
for module in self.modules: for module in self.modules:
if module.enabled: if not module.enabled:
module_current_user_flatpak_packages = [] continue
for pkg in module.flatpak_user_packages():
if not pkg[0] == which_user: continue
module_current_user_flatpak_packages.append(pkg[1])
result.update( result.update(
module.flatpak_packages() module.flatpak_packages()
if not as_user if not as_user
else module_current_user_flatpak_packages else module.flatpak_user_packages().get(which_user, [])
) )
return result return result
@@ -956,6 +964,7 @@ def print_highlighted_pacman_messages(output: str):
# Break, as to not print the same line again if it contains multiple keywords # Break, as to not print the same line again if it contains multiple keywords
break break
def echo_and_capture_command(program: list[str]) -> tuple[int, str]: def echo_and_capture_command(program: list[str]) -> tuple[int, str]:
""" """
Runs the given CLI program and arguments. Runs the given CLI program and arguments.
@@ -975,10 +984,12 @@ def echo_and_capture_command(program: list[str]) -> tuple[int, str]:
return (returncode, output) return (returncode, output)
def get_user_info(username: str) -> tuple[int, int]: def get_user_info(username: str) -> tuple[int, int]:
info = pwd.getpwnam(username) info = pwd.getpwnam(username)
return (info.pw_uid, info.pw_gid) return (info.pw_uid, info.pw_gid)
class Flatpak: class Flatpak:
def __init__(self) -> None: def __init__(self) -> None:
pass pass
@@ -988,19 +999,28 @@ class Flatpak:
Return all of the installed applications. Dependencies and runtimes are exluded since they will not be explicitly installed and thus flatpak will manage them. Return all of the installed applications. Dependencies and runtimes are exluded since they will not be explicitly installed and thus flatpak will manage them.
""" """
try: try:
uinfo: tuple[int, int] = (0, 0)
env = os.environ.copy()
user_env = env.copy()
user_env["HOME"] = os.path.expanduser(f"~{which_user}")
if as_user:
uinfo = get_user_info(which_user) uinfo = get_user_info(which_user)
packages = (
subprocess.run( proc = subprocess.run(
conf.commands.list_flatpak_pkgs(as_user), conf.commands.list_flatpak_pkgs(as_user),
check=True, check=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
user=uinfo[0] if as_user else 0, user=uinfo[0],
group=uinfo[1] if as_user else 0, group=uinfo[1],
) env=user_env if as_user else env,
.stdout.decode()
.strip()
.split("\n")
) )
packages = proc.stdout.decode().strip().split("\n")
# print(
# f"as_user: {as_user}, which_user: {which_user}, uinfo: {uinfo}, stdout: {proc.stdout.decode()}, packages: {packages}"
# )
# The header might be included. It might also not. This will make sure that it is not present. # The header might be included. It might also not. This will make sure that it is not present.
if "Application ID" in packages: if "Application ID" in packages:
@@ -1022,7 +1042,10 @@ class Flatpak:
if not packages: if not packages:
return return
uinfo: tuple[int, int] = (0, 0)
if as_user:
uinfo = get_user_info(which_user) uinfo = get_user_info(which_user)
env = os.environ.copy() env = os.environ.copy()
user_env = env.copy() user_env = env.copy()
user_env["HOME"] = os.path.expanduser(f"~{which_user}") user_env["HOME"] = os.path.expanduser(f"~{which_user}")
@@ -1031,9 +1054,9 @@ class Flatpak:
conf.commands.install_flatpak_pkgs(packages, as_user), conf.commands.install_flatpak_pkgs(packages, as_user),
check=True, check=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
user=uinfo[0] if as_user else 0, user=uinfo[0],
group=uinfo[1] if as_user else 0, group=uinfo[1],
env=user_env if as_user else env env=user_env if as_user else env,
) )
if proc.returncode != 0: if proc.returncode != 0:
@@ -1045,10 +1068,12 @@ class Flatpak:
""" """
Upgrade all flatpak packages. Upgrade all flatpak packages.
""" """
returncode, _output = echo_and_capture_command(conf.commands.upgrade_flatpak()) proc = subprocess.run(
if not returncode == 0: conf.commands.upgrade_flatpak(), check=True, stdout=subprocess.PIPE
)
if not proc.returncode == 0:
raise err.UserFacingError( raise err.UserFacingError(
f"Failed to upgrade flatpak packages. Process exited with code {returncode}." f"Failed to upgrade flatpak packages. Process exited with code {proc.returncode}."
) )
def remove(self, packages: list[str], as_user: bool = False, which_user: str = ""): def remove(self, packages: list[str], as_user: bool = False, which_user: str = ""):
@@ -1058,7 +1083,10 @@ class Flatpak:
if not packages: if not packages:
return return
uinfo: tuple[int, int] = (0, 0)
if as_user:
uinfo = get_user_info(which_user) uinfo = get_user_info(which_user)
env = os.environ.copy() env = os.environ.copy()
user_env = env.copy() user_env = env.copy()
user_env["HOME"] = os.path.expanduser(f"~{which_user}") user_env["HOME"] = os.path.expanduser(f"~{which_user}")
@@ -1067,9 +1095,9 @@ class Flatpak:
conf.commands.remove_flatpak(packages, as_user), conf.commands.remove_flatpak(packages, as_user),
check=True, check=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
user=uinfo[0] if as_user else 0, user=uinfo[0],
group=uinfo[1] if as_user else 0, group=uinfo[1],
env=user_env if as_user else env env=user_env if as_user else env,
) )
if not proc.returncode == 0: if not proc.returncode == 0:
@@ -1083,7 +1111,7 @@ class Flatpak:
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
user=uinfo[0] if as_user else 0, user=uinfo[0] if as_user else 0,
group=uinfo[1] if as_user else 0, group=uinfo[1] if as_user else 0,
env=user_env if as_user else env env=user_env if as_user else env,
) )
if not proc.returncode == 0: if not proc.returncode == 0:
@@ -1091,6 +1119,7 @@ class Flatpak:
f"Failed to remove unused flatpak packages. Process exited with code {proc.returncode}." f"Failed to remove unused flatpak packages. Process exited with code {proc.returncode}."
) )
class Systemd: class Systemd:
""" """
Interface for interacting with systemd. Interface for interacting with systemd.