From 2378f49676e437163f73612ba5f1d880897f82f1 Mon Sep 17 00:00:00 2001 From: Kivi Kaitaniemi Date: Wed, 10 Dec 2025 00:29:15 +0200 Subject: [PATCH] Run flatpak commands on users with UID>=1000 --- README.md | 16 ++++----- example/my_module.py | 4 +-- example/source.py | 4 +-- src/decman/app.py | 69 ++++++++++++++++++++------------------ src/decman/config.py | 17 ++++++++-- src/decman/lib/__init__.py | 29 +++++++++++----- 6 files changed, 85 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 733cfe1..80451ab 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Decman -> 🎉 Support for Flatpaks was just added! 🎉 -> By default flatpak management this feature is disabled. Take a look at the example to learn how to use flatpaks. You can install to the user as well as the system installation. +> 🎉 Early support for Flatpaks was just added! 🎉 +> By default flatpak management is disabled. Support is in early stages so expect bugs. -> 🎉 Decman now has a AUR package! 🎉 -> -> To start using the AUR package simply add `decman` to `decman.aur_packages`. To ensure a smooth change, add decman-git to ignored_packages during the conversion. -> > ```py > import decman -> decman.aur_packages += ["decman"] -> decman.ignored_packages += ["decman-git"] +> import decman.config +> decman.config.enable_flatpak = True +> # You can add system wide packages as well as user packages +> decman.flatpak_packages += ["org.signal.Signal"] +> decman.flatpak_user_packages["user"] = decman.flatpak_user_packages.get("user", []) +> decman.flatpak_user_packages["user"].append("dev.zed.Zed") > ``` Decman is a declarative package & configuration manager for Arch Linux. It allows you to manage installed packages, your dotfiles, enabled systemd units, and run commands automatically. Your system is configured using python so your configuration can be very adaptive. diff --git a/example/my_module.py b/example/my_module.py index ce8a223..05df809 100644 --- a/example/my_module.py +++ b/example/my_module.py @@ -1,7 +1,7 @@ # from import is ok for importing classes and functions # just remember to not import variables this way -from typing import override -from decman import Module, File, Directory, UserPackage, sh, prg + +from decman import Directory, File, Module, UserPackage, prg, sh class MyModule(Module): diff --git a/example/source.py b/example/source.py index 473dc9d..1d6e33f 100644 --- a/example/source.py +++ b/example/source.py @@ -1,8 +1,8 @@ # This example covers all decman features and many useful ways of configuring a system. # Configuration can be as simple or as complex as is needed. -import socket import os +import socket # Note: Do NOT use from imports for global variables # BAD: from decman import packages/modules/etc @@ -10,7 +10,7 @@ import decman import decman.config # This is fine since the thing being imported is a class and not a global variable. -from decman import UserPackage, File, Directory, UserRaisedError +from decman import Directory, File, UserPackage, UserRaisedError # Flatpaks are disabled by default. This way no already installed flatpaks will get suddenly deleted. decman.config.enable_flatpak = True diff --git a/src/decman/app.py b/src/decman/app.py index 3edd658..578d168 100644 --- a/src/decman/app.py +++ b/src/decman/app.py @@ -5,11 +5,10 @@ Module containing the CLI Application. import argparse import os +import pwd import shutil -import subprocess import sys import traceback -import pwd import decman import decman.config as conf @@ -291,7 +290,7 @@ class Core: l.print_list("Removing pacman packages:", to_remove) - if conf.enable_flatpak: + if conf.enable_flatpak and self.update_flatpaks: l.print_list("Removing flatpak packages:", to_remove_flatpak) self._remove_user_flatpaks(only_print=True) @@ -306,20 +305,15 @@ class Core: self._remove_user_flatpaks() 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 - users = ( - subprocess.run(["users"], check=True, stdout=subprocess.PIPE) - .stdout.decode() - .strip() - .split("\n") - ) - print() - users.append("root")""" - - users = pwd.getpwall() + # Get all non system users (users that have uid >= 1000), also ignore nobody + users = [ + u.pw_name + for u in pwd.getpwall() + if u.pw_uid >= 1000 and u.pw_name not in ("nobody",) + ] + # Add root to users + users.append("root") for user in users: - user = user.pw_name currently_installed_flatpak = self.flatpak.get_installed( as_user=True, which_user=user ) @@ -353,7 +347,18 @@ class Core: # flatpak if conf.enable_flatpak and self.update_flatpaks: + l.print_summary("Upgrading flatpak packages.") self.flatpak.upgrade() + users = [ + u.pw_name + for u in pwd.getpwall() + if u.pw_uid >= 1000 and u.pw_name not in ("nobody",) + ] + # Add root to users + users.append("root") + for user in users: + l.print_summary(f"Upgrading flatpak packages for {user}.") + self.flatpak.upgrade(True, user) def _install_pkgs(self): """ @@ -373,13 +378,15 @@ class Core: l.print_list("Installing pacman packages:", to_install_pacman) - if conf.enable_flatpak: - l.print_list("Installing flatpak packages:", to_install_flatpak) - self._install_user_flatpaks(only_print=True) - # fpm prints a summary so no need to print it twice if self.only_print: l.print_list("Installing foreign packages:", to_install_fpm) + + if conf.enable_flatpak and self.update_flatpaks: + l.print_list("Installing flatpak packages:", to_install_flatpak) + + if self.only_print: + self._install_user_flatpaks(only_print=True) return self.pacman.install(to_install_pacman) @@ -388,22 +395,20 @@ class Core: if conf.enable_flatpak and self.update_flatpaks: self.flatpak.install(to_install_flatpak) + # Print summary before the action + self._install_user_flatpaks(only_print=True) self._install_user_flatpaks() 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 - users = ( - subprocess.run(["users"], check=True, stdout=subprocess.PIPE) - .stdout.decode() - .strip() - .split("\n") - ) - users.append("root")""" - - users = pwd.getpwall() - + # Get all non system users (users that have uid >= 1000), also ignore nobody + users = [ + u.pw_name + for u in pwd.getpwall() + if u.pw_uid >= 1000 and u.pw_name not in ("nobody",) + ] + # Add root to users + users.append("root") for user in users: - user = user.pw_name currently_installed_flatpak = self.flatpak.get_installed( as_user=True, which_user=user ) diff --git a/src/decman/config.py b/src/decman/config.py index 78719db..057d7ef 100644 --- a/src/decman/config.py +++ b/src/decman/config.py @@ -102,7 +102,13 @@ class Commands: """ Updates all installed flatpak REFs including runtimes and dependencies. """ - return ["flatpak", "update", "-y", "--user" if as_user else "--system"] + return [ + "flatpak", + "update", + "--noninteractive", + "-y", + "--user" if as_user else "--system", + ] def remove(self, pkgs: list[str]) -> list[str]: """ @@ -115,7 +121,13 @@ 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. """ - return ["flatpak", "remove", "-y", "--user" if as_user else "--system"] + pkgs + return [ + "flatpak", + "remove", + "--noninteractive", + "-y", + "--user" if as_user else "--system", + ] + pkgs def remove_unused_flatpak(self, as_user: bool = False) -> list[str]: """ @@ -124,6 +136,7 @@ class Commands: return [ "flatpak", "remove", + "--noninteractive", "-y", "--unused", "--user" if as_user else "--system", diff --git a/src/decman/lib/__init__.py b/src/decman/lib/__init__.py index 8e1b1e7..2c1478b 100644 --- a/src/decman/lib/__init__.py +++ b/src/decman/lib/__init__.py @@ -5,11 +5,11 @@ Library module for decman. import json import os import pty +import pwd import shutil import subprocess import time import typing -import pwd import decman import decman.config as conf @@ -1035,7 +1035,9 @@ class Flatpak: user_facing_msg=f"Failed to get installed flatpak packages using '{error.cmd}'. Output: {error.stdout}." ) from error - def install(self, packages: list[str], as_user: bool = False, which_user: str = ""): + def install( + self, packages: list[str], as_user: bool = False, which_user: str = "root" + ): """ Install the listed flatpak packages. """ @@ -1053,7 +1055,6 @@ class Flatpak: proc = subprocess.run( conf.commands.install_flatpak_pkgs(packages, as_user), check=True, - stdout=subprocess.PIPE, user=uinfo[0], group=uinfo[1], env=user_env if as_user else env, @@ -1064,19 +1065,33 @@ class Flatpak: f"Failed to install flatpak packages. Process exited with code {proc.returncode}." ) - def upgrade(self) -> None: + def upgrade(self, as_user: bool = False, which_user: str = "root") -> None: """ Upgrade all flatpak packages. """ + uinfo: tuple[int, int] = (0, 0) + if as_user: + uinfo = get_user_info(which_user) + + env = os.environ.copy() + user_env = env.copy() + user_env["HOME"] = os.path.expanduser(f"~{which_user}") + proc = subprocess.run( - conf.commands.upgrade_flatpak(), check=True, stdout=subprocess.PIPE + conf.commands.upgrade_flatpak(as_user=True), + check=True, + user=uinfo[0], + group=uinfo[1], + env=user_env if as_user else env, ) if not proc.returncode == 0: raise err.UserFacingError( 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 = "root" + ): """ Remove all the listed packages and their unused dependecies. This has to happen in two steps. """ @@ -1094,7 +1109,6 @@ class Flatpak: proc = subprocess.run( conf.commands.remove_flatpak(packages, as_user), check=True, - stdout=subprocess.PIPE, user=uinfo[0], group=uinfo[1], env=user_env if as_user else env, @@ -1108,7 +1122,6 @@ class Flatpak: proc = subprocess.run( conf.commands.remove_unused_flatpak(as_user), check=True, - stdout=subprocess.PIPE, user=uinfo[0] if as_user else 0, group=uinfo[1] if as_user else 0, env=user_env if as_user else env,