Run flatpak commands on users with UID>=1000

This commit is contained in:
Kivi Kaitaniemi
2025-12-10 00:29:15 +02:00
parent a54fad6570
commit 2378f49676
6 changed files with 85 additions and 54 deletions
+8 -8
View File
@@ -1,16 +1,16 @@
# Decman # Decman
> 🎉 Support for Flatpaks was just added! 🎉 > 🎉 Early 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. > 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 > ```py
> import decman > import decman
> decman.aur_packages += ["decman"] > import decman.config
> decman.ignored_packages += ["decman-git"] > 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. 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.
+2 -2
View File
@@ -1,7 +1,7 @@
# from import is ok for importing classes and functions # from import is ok for importing classes and functions
# just remember to not import variables this way # 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): class MyModule(Module):
+2 -2
View File
@@ -1,8 +1,8 @@
# This example covers all decman features and many useful ways of configuring a system. # 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. # Configuration can be as simple or as complex as is needed.
import socket
import os import os
import socket
# Note: Do NOT use from imports for global variables # Note: Do NOT use from imports for global variables
# BAD: from decman import packages/modules/etc # BAD: from decman import packages/modules/etc
@@ -10,7 +10,7 @@ import decman
import decman.config import decman.config
# This is fine since the thing being imported is a class and not a global variable. # 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. # Flatpaks are disabled by default. This way no already installed flatpaks will get suddenly deleted.
decman.config.enable_flatpak = True decman.config.enable_flatpak = True
+37 -32
View File
@@ -5,11 +5,10 @@ Module containing the CLI Application.
import argparse import argparse
import os import os
import pwd
import shutil import shutil
import subprocess
import sys import sys
import traceback import traceback
import pwd
import decman import decman
import decman.config as conf import decman.config as conf
@@ -291,7 +290,7 @@ class Core:
l.print_list("Removing pacman packages:", to_remove) 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) l.print_list("Removing flatpak packages:", to_remove_flatpak)
self._remove_user_flatpaks(only_print=True) self._remove_user_flatpaks(only_print=True)
@@ -306,20 +305,15 @@ 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 non system users (users that have uid >= 1000), also ignore nobody
# 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 = [
users = ( u.pw_name
subprocess.run(["users"], check=True, stdout=subprocess.PIPE) for u in pwd.getpwall()
.stdout.decode() if u.pw_uid >= 1000 and u.pw_name not in ("nobody",)
.strip() ]
.split("\n") # Add root to users
) users.append("root")
print()
users.append("root")"""
users = pwd.getpwall()
for user in users: for user in users:
user = user.pw_name
currently_installed_flatpak = self.flatpak.get_installed( currently_installed_flatpak = self.flatpak.get_installed(
as_user=True, which_user=user as_user=True, which_user=user
) )
@@ -353,7 +347,18 @@ class Core:
# flatpak # flatpak
if conf.enable_flatpak and self.update_flatpaks: if conf.enable_flatpak and self.update_flatpaks:
l.print_summary("Upgrading flatpak packages.")
self.flatpak.upgrade() 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): def _install_pkgs(self):
""" """
@@ -373,13 +378,15 @@ class Core:
l.print_list("Installing pacman packages:", to_install_pacman) 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 # fpm prints a summary so no need to print it twice
if self.only_print: if self.only_print:
l.print_list("Installing foreign packages:", to_install_fpm) 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 return
self.pacman.install(to_install_pacman) self.pacman.install(to_install_pacman)
@@ -388,22 +395,20 @@ class Core:
if conf.enable_flatpak and self.update_flatpaks: if conf.enable_flatpak and self.update_flatpaks:
self.flatpak.install(to_install_flatpak) self.flatpak.install(to_install_flatpak)
# Print summary before the action
self._install_user_flatpaks(only_print=True)
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 non system users (users that have uid >= 1000), also ignore nobody
users = ( users = [
subprocess.run(["users"], check=True, stdout=subprocess.PIPE) u.pw_name
.stdout.decode() for u in pwd.getpwall()
.strip() if u.pw_uid >= 1000 and u.pw_name not in ("nobody",)
.split("\n") ]
) # Add root to users
users.append("root")""" users.append("root")
users = pwd.getpwall()
for user in users: for user in users:
user = user.pw_name
currently_installed_flatpak = self.flatpak.get_installed( currently_installed_flatpak = self.flatpak.get_installed(
as_user=True, which_user=user as_user=True, which_user=user
) )
+15 -2
View File
@@ -102,7 +102,13 @@ class Commands:
""" """
Updates all installed flatpak REFs including runtimes and dependencies. 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]: 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. 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]: def remove_unused_flatpak(self, as_user: bool = False) -> list[str]:
""" """
@@ -124,6 +136,7 @@ class Commands:
return [ return [
"flatpak", "flatpak",
"remove", "remove",
"--noninteractive",
"-y", "-y",
"--unused", "--unused",
"--user" if as_user else "--system", "--user" if as_user else "--system",
+21 -8
View File
@@ -5,11 +5,11 @@ Library module for decman.
import json import json
import os import os
import pty import pty
import pwd
import shutil import shutil
import subprocess import subprocess
import time import time
import typing import typing
import pwd
import decman import decman
import decman.config as conf 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}." user_facing_msg=f"Failed to get installed flatpak packages using '{error.cmd}'. Output: {error.stdout}."
) from error ) 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. Install the listed flatpak packages.
""" """
@@ -1053,7 +1055,6 @@ class Flatpak:
proc = subprocess.run( proc = subprocess.run(
conf.commands.install_flatpak_pkgs(packages, as_user), conf.commands.install_flatpak_pkgs(packages, as_user),
check=True, check=True,
stdout=subprocess.PIPE,
user=uinfo[0], user=uinfo[0],
group=uinfo[1], group=uinfo[1],
env=user_env if as_user else env, 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}." 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. 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( 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: if not proc.returncode == 0:
raise err.UserFacingError( raise err.UserFacingError(
f"Failed to upgrade flatpak packages. Process exited with code {proc.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 = "root"
):
""" """
Remove all the listed packages and their unused dependecies. This has to happen in two steps. 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( proc = subprocess.run(
conf.commands.remove_flatpak(packages, as_user), conf.commands.remove_flatpak(packages, as_user),
check=True, check=True,
stdout=subprocess.PIPE,
user=uinfo[0], user=uinfo[0],
group=uinfo[1], group=uinfo[1],
env=user_env if as_user else env, env=user_env if as_user else env,
@@ -1108,7 +1122,6 @@ class Flatpak:
proc = subprocess.run( proc = subprocess.run(
conf.commands.remove_unused_flatpak(as_user), conf.commands.remove_unused_flatpak(as_user),
check=True, check=True,
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,