Skip to content

Commit

Permalink
installer: manage remotes state. (#52)
Browse files Browse the repository at this point in the history
* installer: manage remotes state.

Remotes removed from nix-flatpak config declaration will be uninstalled
at system activation.

Add the capability to delete unmanged remotes at system activation.

Rename `uninstallUnmanagedPackages` to a more generic `uninstallUnmanaged`.

* nixos: pass config value to options.nix.

Fix a regression with config now being available in
options. config is evaluated to override values
from renamed/deprecated options.

* system: home-manager: add deprecation warning.

Trigger a deprecation warning if uninstallUnmanagedPackages
is set.
  • Loading branch information
gmodena authored Mar 31, 2024
1 parent 09d07c7 commit 7c436bd
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 24 deletions.
10 changes: 6 additions & 4 deletions modules/home-manager.nix
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
{ config, lib, pkgs, ... }@args:
let
cfg = config.services.flatpak;
cfg = lib.warnIf (! isNull config.services.flatpak.uninstallUnmanagedPackages)
"uninstallUnmanagedPackages is deprecated since nix-flatpak 0.4.0 and will be removed in 1.0.0. Use uninstallUnamanged instead."
config.services.flatpak;
installation = "user";
in
{

options.services.flatpak = (import ./options.nix { inherit lib pkgs; })
// {
options.services.flatpak = (import ./options.nix { inherit config lib pkgs; })
// {
enable = with lib; mkOption {
type = types.bool;
default = args.osConfig.services.flatpak.enable or false;
description = mkDoc "Whether to enable nix-flatpak declarative flatpak management in home-manager.";
};
};


config = lib.mkIf config.services.flatpak.enable {
systemd.user.services."flatpak-managed-install" = {
Unit = {
Expand Down Expand Up @@ -53,5 +56,4 @@ in

xdg.enable = true;
};

}
63 changes: 54 additions & 9 deletions modules/installer.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,53 @@ let
# be handled. Right now it introduces a small issue of the state file derivation
# not being garbage collected even when this module is removed. You can find
# more details on this design drawback in PR#23
# State is represented by a JSON object with keys `packages`, `overrides` and `remotes`.
# Example `flatpak-state.json`:
# {
# "packages": ["org.gnome.Epiphany", "org.gnome.Epiphany.Devel"],
# "overrides": {
# "org.gnome.Epiphany": {
# "command": "env MOZ_ENABLE_WAYLAND=1 /run/current-system/sw/bin/epiphany",
# "env": "MOZ_ENABLE_WAYLAND=1"
# }
# },
# "remotes": ["flathub", "gnome-nightly"]
# }
gcroots =
if (installation == "system")
then "/nix/var/nix/gcroots/"
else "\${XDG_STATE_HOME:-$HOME/.local/state}/home-manager/gcroots";
stateFile = pkgs.writeText "flatpak-state.json" (builtins.toJSON {
packages = map (builtins.getAttr "appId") cfg.packages;
overrides = cfg.overrides;
remotes = map (builtins.getAttr "name") cfg.remotes;
});

statePath = "${gcroots}/${stateFile.name}";

updateApplications = cfg.update.onActivation || cfg.update.auto.enable;

handleUnmanagedPackagesCmd = installation: uninstallUnmanagedPackages:
lib.optionalString uninstallUnmanagedPackages ''
# This script is used to manage the lifecyle of all flatpaks (remotes, packages)
# installed on the system.
# handeUnmanagedStateCmd is used to handle the case where the user wants nix-flatpak to manage
# the state of the system, and uninstall any packages or remotes that are not declared in its config.
handleUnmanagedStateCmd = installation: uninstallUnmanagedState:
lib.optionalString uninstallUnmanagedState ''
# Add all installed Flatpak packages to the old state, so only the managed ones (new state) will be kept
INSTALLED_PACKAGES=$(${pkgs.flatpak}/bin/flatpak --${installation} list --app --columns=application)
OLD_STATE=$(${pkgs.jq}/bin/jq -r -n \
--argjson old "$OLD_STATE" \
--arg installed_packages "$INSTALLED_PACKAGES" \
'$old + { "packages" : $installed_packages | split("\n") }')
# Add all configured remoted to the old state, so that only managed ones will be kept across generations.
MANAGED_REMOTES=$(${pkgs.flatpak}/bin/flatpak --${installation} remotes --columns=name)
OLD_STATE=$(${pkgs.jq}/bin/jq -r -n \
--argjson old "$OLD_STATE" \
--arg managed_remotes "$MANAGED_REMOTES" \
'$old + { "remotes": $managed_remotes | split("\n") }')
'';

flatpakUninstallCmd = installation: {}: ''
Expand Down Expand Up @@ -85,37 +112,55 @@ let
''}
'';

flatpakAddRemoteCmd = installation: { name, location, args ? null, ... }: ''
flatpakAddRemotesCmd = installation: { name, location, args ? null, ... }: ''
${pkgs.flatpak}/bin/flatpak remote-add --${installation} --if-not-exists ${if args == null then "" else args} ${name} ${location}
'';
flatpakAddRemote = installation: remotes: map (flatpakAddRemoteCmd installation) remotes;
flatpakAddRemote = installation: remotes: map (flatpakAddRemotesCmd installation) remotes;

flatpakDeleteRemotesCmd = installation: {}: ''
# Delete all remotes that are present in the old state but not the new one
# $OLD_STATE and $NEW_STATE are globals, declared in the output of pkgs.writeShellScript.
${pkgs.jq}/bin/jq -r -n \
--argjson old "$OLD_STATE" \
--argjson new "$NEW_STATE" \
'(($old.remotes // []) - ($new.remotes // []))[]' \
| while read -r REMOTE_NAME; do
${pkgs.flatpak}/bin/flatpak remote-delete --${installation} $REMOTE_NAME
done
'';


flatpakInstall = installation: update: packages: map (flatpakInstallCmd installation update) packages;

mkFlatpakInstallCmd = installation: update: packages: builtins.foldl' (x: y: x + y) '''' (flatpakInstall installation update packages);
mkFlatpakAddRemoteCmd = installation: remotes: builtins.foldl' (x: y: x + y) '''' (flatpakAddRemote installation remotes);
mkFlatpakAddRemotesCmd = installation: remotes: builtins.foldl' (x: y: x + y) '''' (flatpakAddRemote installation remotes);
in
pkgs.writeShellScript "flatpak-managed-install" ''
# This script is triggered at build time by a transient systemd unit.
set -eu
# Setup state variables
# Setup state variables for packages and remotes
NEW_STATE=$(${pkgs.coreutils}/bin/cat ${stateFile})
if [[ -f ${statePath} ]]; then
OLD_STATE=$(${pkgs.coreutils}/bin/cat ${statePath})
else
OLD_STATE={}
fi
# Handle unmanaged packages
${handleUnmanagedPackagesCmd installation cfg.uninstallUnmanagedPackages}
# Handle unmanaged packages and remotes.
${handleUnmanagedStateCmd installation cfg.uninstallUnmanaged}
# Configure remotes
${mkFlatpakAddRemoteCmd installation cfg.remotes}
${mkFlatpakAddRemotesCmd installation cfg.remotes}
# Uninstall packages that have been removed from services.flatpak.packages
# since the previous activation.
${flatpakUninstallCmd installation {}}
# Uninstall remotes that have been removed from services.flatpak.packages
# since the previous activation.
${flatpakDeleteRemotesCmd installation {}}
# Install packages
${mkFlatpakInstallCmd installation updateApplications cfg.packages}
Expand Down
6 changes: 4 additions & 2 deletions modules/nixos.nix
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.flatpak;
cfg = lib.warnIf (! isNull config.services.flatpak.uninstallUnmanagedPackages)
"uninstallUnmanagedPackages is deprecated since nix-flatpak 0.4.0 and will be removed in 1.0.0. Use uninstallUnamanged instead."
config.services.flatpak;
installation = "system";
in
{
options.services.flatpak = import ./options.nix { inherit lib pkgs; };
options.services.flatpak = import ./options.nix { inherit config lib pkgs; };

config = lib.mkIf config.services.flatpak.enable {
systemd.services."flatpak-managed-install" = {
Expand Down
17 changes: 12 additions & 5 deletions modules/options.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ lib, ... }:
{ config, lib, ... }:
with lib;
let
remoteOptions = _: {
Expand Down Expand Up @@ -124,7 +124,7 @@ in

overrides = mkOption {
type = with types; attrsOf (attrsOf (attrsOf (either str (listOf str))));
default = {};
default = { };
description = lib.mdDoc ''
Applies the provided attribute set into a Flatpak overrides file with the
same structure, keeping externally applied changes.
Expand Down Expand Up @@ -166,12 +166,19 @@ in
};

uninstallUnmanagedPackages = mkOption {
type = lib.types.nullOr (lib.types.bool);
default = null;
description = lib.mdDoc ''
uninstallUnmanagedPackages is deprecated. Use uninstallUnamanged instead.'';
};

uninstallUnmanaged = mkOption {
type = with types; bool;
default = false;
default = config.services.flatpak.uninstallUnmanagedPackages || false;
description = lib.mdDoc ''
If enabled, uninstall packages not managed by this module on activation.
If enabled, uninstall packages and delete remotes not managed by this module on activation.
I.e. if packages were installed via Flatpak directly instead of this module,
they would get uninstalled on the next activation.
they would get uninstalled on the next activation. The same applies to remotes manually setup via `flatpak remote-add`
'';
};
}
6 changes: 3 additions & 3 deletions testing-base/flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion testing-base/flatpak.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}];

services.flatpak.update.auto.enable = false;
services.flatpak.uninstallUnmanagedPackages = true;
services.flatpak.uninstallUnmanaged = false;
services.flatpak.packages = [
#{ appId = "com.brave.Browser"; origin = "flathub"; }
#"com.obsproject.Studio"
Expand Down

0 comments on commit 7c436bd

Please sign in to comment.