Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scheduled updates won't trigger on activation. #136

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions modules/common.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{ lib, config }:
rec {
mkExponentialBackoff = cfg:
if cfg.restartOnFailure.exponentialBackoff.enable then {
RestartSteps = cfg.restartOnFailure.exponentialBackoff.steps;
RestartMaxDelaySec = cfg.restartOnFailure.exponentialBackoff.maxDelay;
} else { };

mkRestartOptions = cfg:
if cfg.restartOnFailure.enable then {
Restart = "on-failure";
RestartSec = cfg.restartOnFailure.restartDelay;
} // (mkExponentialBackoff cfg) else { };

mkCommonServiceConfig = { cfg, pkgs, lib, installation, executionContext ? "service-start" }: {
Type = "oneshot";
ExecStart = import ./script/flatpak-managed-install.nix { inherit cfg pkgs lib installation executionContext; };
};

mkCommonTimerConfig = cfg: {
Unit = "flatpak-managed-install";
OnCalendar = cfg.update.auto.onCalendar;
Persistent = "true";
};

warnDeprecated = cfg:
lib.warnIf (! isNull cfg.uninstallUnmanagedPackages)
"uninstallUnmanagedPackages is deprecated since nix-flatpak 0.4.0 and will be removed in 1.0.0. Use uninstallUnmanaged instead."
cfg;
}
141 changes: 99 additions & 42 deletions modules/installer.nix → modules/flatpak/install.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ cfg, pkgs, lib, installation ? "system", ... }:
{ cfg, pkgs, lib, installation ? "system", executionContext ? "service-start", ... }:

let
utils = import ./ref.nix { inherit lib; };
Expand Down Expand Up @@ -75,8 +75,6 @@ let

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

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

# 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
Expand Down Expand Up @@ -186,48 +184,107 @@ let

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

mkFlatpakInstallCmd = installation: update: packages: builtins.foldl' (x: y: x + y) '''' (flatpakInstall installation update packages);

flatpakDeleteRemotesCmd = remotes.flatpakDeleteRemotesCmd;
mkFlatpakAddRemotesCmd = remotes.mkFlatpakAddRemotesCmd;
in
pkgs.writeShellScript "flatpak-managed-install" ''
# This script is triggered at build time by a transient systemd unit.
set -eu

# 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 and remotes.
${handleUnmanagedStateCmd installation cfg.uninstallUnmanaged}

# Configure 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 cfg.uninstallUnmanaged {}}

# Install packages
${mkFlatpakInstallCmd installation updateApplications cfg.packages}

# Configure overrides
${flatpakOverridesCmd installation {}}
updateTrigger =
if executionContext == "service-start" then cfg.update.onActivation
else if executionContext == "timer" then cfg.update.auto.enable
else lib.warn ("executionContext=${executionContext}: invalid arugment.") false;

gmodena marked this conversation as resolved.
Show resolved Hide resolved
# Initializes state variables for managing Flatpak packages and remotes.
# NEW_STATE is set to the content of `stateFile`. If `statePath` exists,
# OLD_STATE is set to its content; otherwise, OLD_STATE is an empty dictionary.
#
# Inputs:
# - stateFile: Path to the file containing the desired state.
# - statePath: Path to the file representing the current state.
# - pkgs.coreutils: Used for accessing the `cat` utility.
mkLoadStateCmd = ''
# 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
'';

# Clean up installation
${if cfg.uninstallUnused
# Generates a command to install Flatpak packages defined in `cfg.packages`.
# Combines installation commands using `flatpakInstall` for each package.
#
# Inputs:
# - installation: Installation context for Flatpak.
# - updateTrigger: Triggers updates if enabled.
# - cfg.packages: List of Flatpak packages to install.
mkInstallCmd = builtins.foldl' (x: y: x + y) '''' (flatpakInstall installation updateTrigger cfg.packages);

# Generates a command to uninstall Flatpak packages.
# Uses `flatpakUninstallCmd` to produce the necessary command.
#
# Inputs:
# - installation: Installation context for Flatpak.
mkUninstallCmd = flatpakUninstallCmd installation { };

# Creates a command to manage unmanaged Flatpak states based on configuration.
# Uses `handleUnmanagedStateCmd` with the current installation context and
# the `cfg.uninstallUnmanaged` option.
#
# Inputs:
# - installation: Installation context for Flatpak.
# - cfg.uninstallUnmanaged: Boolean indicating whether to handle unmanaged packages.
mkHandleUnmanagedStateCmd = handleUnmanagedStateCmd installation cfg.uninstallUnmanaged;

# Produces a command to delete Flatpak remotes if `cfg.uninstallUnmanaged` is enabled.
# Uses `flatpakDeleteRemotesCmd` to build the command.
#
# Inputs:
# - installation: Installation context for Flatpak.
# - cfg.uninstallUnmanaged: Boolean to control removal of unmanaged remotes.
mkDeleteRemotesCmd = flatpakDeleteRemotesCmd installation cfg.uninstallUnmanaged { };

# Generates a command to set Flatpak overrides based on the current installation context.
# Uses `flatpakOverridesCmd` to build the command.
#
# Inputs:
# - installation: Installation context for Flatpak.
mkOverridesCmd = flatpakOverridesCmd installation { };

# Generates a command to uninstall unused Flatpak packages if `cfg.uninstallUnused` is enabled.
# Otherwise, outputs a comment indicating the feature is not enabled.
#
# Inputs:
# - installation: Installation context for Flatpak.
# - cfg.uninstallUnused: Boolean indicating whether to uninstall unused packages.
mkUninstallUnusedCmd =
if cfg.uninstallUnused
then flatpakUninstallUnusedCmd installation
else "# services.flatpak.uninstallUnused is not enabled "}
else "# services.flatpak.uninstallUnused is not enabled ";

# Links the current state file (`stateFile`) to the state path (`statePath`).
# This ensures that the current state is saved persistently.
#
# Inputs:
# - stateFile: Path to the current state file.
# - statePath: Path to save the state file.
# - pkgs.coreutils: Used for accessing the `ln` utility.
mkSaveStateCmd = ''
${pkgs.coreutils}/bin/ln -sf ${stateFile} ${statePath}
'';

# Generates a command to add Flatpak remotes based on the provided configuration and installation context.
# Delegates to `remotes.mkFlatpakAddRemotesCmd` to construct the necessary commands.
#
# Inputs:
# - installation: Installation context for Flatpak.
# - cfg.remotes: Configuration specifying the remotes to be added.
# - remotes: Module providing the `mkFlatpakAddRemotesCmd` function.
#
# Output:
# - Command to add the specified Flatpak remotes.
mkAddRemotesCmd = remotes.mkFlatpakAddRemotesCmd installation cfg.remotes;

# Save state
${pkgs.coreutils}/bin/ln -sf ${stateFile} ${statePath}
''
in
{
inherit mkLoadStateCmd mkInstallCmd mkUninstallCmd mkHandleUnmanagedStateCmd mkAddRemotesCmd mkDeleteRemotesCmd mkOverridesCmd mkUninstallUnusedCmd mkSaveStateCmd;
}
42 changes: 42 additions & 0 deletions modules/flatpak/overrides.jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Convert entry value into array
def values ($value): if ($value | type) == "string" then [$value] else ($value // []) end;

# State aliases
($old_state.overrides[$app_id] // {}) as $old
| ($new_state.overrides[$app_id] // {}) as $new

# Map sections that exist in either active or new state (ignore old)
| $active + $new | keys | map (
. as $section | {"section_key": $section, "section_value": (

# Map entries that exist in either active or new state (ignore old)
($active[$section] // {}) + ($new[$section] // {}) | keys | map (
. as $entry | { "entry_key": $entry, "entry_value": (

# Entry value aliases
$active[$section][$entry] as $active_value
| $new[$section][$entry] as $new_value
| $old[$section][$entry] as $old_value

# Use new value if it is a string
| if ($new_value | type) == "string" then $new_value
else
# Otherwise remove old values from the active ones, and add the new ones
values($active_value) - values($old_value) + values($new_value)

# Remove empty arrays and duplicate values
| select(. != []) | unique

# Convert array into Flatpak string array format
| join(";")
end
)}
)

# Remove empty arrays
| select(. != [])
)}
)[]

# Generate the final overrides file
| "[\(.section_key)]", (.section_value[] | "\(.entry_key)=\(.entry_value)"), ""
File renamed without changes.
File renamed without changes.
59 changes: 24 additions & 35 deletions modules/home-manager.nix
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
{ config, lib, pkgs, ... }@args:
let
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 uninstallUnmanaged instead."
config.services.flatpak;
helpers = import ./common.nix { inherit lib config; };
cfg = helpers.warnDeprecated config.services.flatpak;
installation = "user";
in
{

options.services.flatpak = (import ./options.nix { inherit config lib pkgs; })
// {
enable = with lib; mkOption {
Expand All @@ -16,41 +14,32 @@ in
};
};


config = lib.mkIf config.services.flatpak.enable {
systemd.user.services."flatpak-managed-install" = let
exponentialBackoff = if config.services.flatpak.restartOnFailure.exponentialBackoff.enable then {
RestartSteps = config.services.flatpak.restartOnFailure.exponentialBackoff.steps;
RestartMaxDelaySec = config.services.flatpak.restartOnFailure.exponentialBackoff.maxDelay;
} else {};
restartOptions = if config.services.flatpak.restartOnFailure.enable then {
Restart = "on-failure";
RestartSec = config.services.flatpak.restartOnFailure.restartDelay;
} // exponentialBackoff else {};
in {
Unit = {
After = [
"multi-user.target" # ensures that network & connectivity have been setup.
];
};
Install = {
WantedBy = [
"default.target" # multi-user target with a GUI. For a desktop, this is typically going to be the graphical.target
];
};
Service = {
Type = "oneshot"; # TODO: should this be an async startup, to avoid blocking on network at boot ?
ExecStart = import ./installer.nix { inherit cfg pkgs lib installation; };
} // restartOptions;
systemd.user.services."flatpak-managed-install" = {
Unit.After = [ "multi-user.target" ];
Install.WantedBy = [ "default.target" ];
Service = helpers.mkCommonServiceConfig
{
inherit cfg pkgs lib installation;
executionContext = "service-start";
} // helpers.mkRestartOptions cfg;
};

# Create a service that will only be started by a timer.
# We need a separate service to provide a custom Enviroment
# that installer used to determine if certain action (e.g. updates)
# should be performed at activation or not.
systemd.user.services."flatpak-managed-install-timer" = lib.mkIf config.services.flatpak.update.auto.enable {
Service = helpers.mkCommonServiceConfig
{
inherit cfg pkgs lib installation;
executionContext = "timer";
} // helpers.mkRestartOptions cfg;
};

systemd.user.timers."flatpak-managed-install" = lib.mkIf config.services.flatpak.update.auto.enable {
systemd.user.timers."flatpak-managed-install-timer" = lib.mkIf config.services.flatpak.update.auto.enable {
Unit.Description = "flatpak update schedule";
Timer = {
Unit = "flatpak-managed-install";
OnCalendar = config.services.flatpak.update.auto.onCalendar;
Persistent = "true";
};
Timer = helpers.mkCommonTimerConfig cfg;
Install.WantedBy = [ "timers.target" ];
};

Expand Down
51 changes: 24 additions & 27 deletions modules/nixos.nix
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
{ config, lib, pkgs, ... }:
let
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 uninstallUnmanaged instead."
config.services.flatpak;
helpers = import ./common.nix { inherit lib config; };
cfg = helpers.warnDeprecated config.services.flatpak;
installation = "system";
exponentialBackoff = if config.services.flatpak.restartOnFailure.exponentialBackoff.enable then {
RestartSteps = config.services.flatpak.restartOnFailure.exponentialBackoff.steps;
RestartMaxDelaySec = config.services.flatpak.restartOnFailure.exponentialBackoff.maxDelay;
} else {};
restartOptions = if config.services.flatpak.restartOnFailure.enable then {
Restart = "on-failure";
RestartSec = config.services.flatpak.restartOnFailure.restartDelay;
} // exponentialBackoff else {};
in
{
options.services.flatpak = import ./options.nix { inherit config lib pkgs; };

config = lib.mkIf config.services.flatpak.enable {
systemd.services."flatpak-managed-install" = {
wantedBy = [
"default.target" # multi-user target with a GUI. For a desktop, this is typically going to be the graphical.target
];
after = [
"multi-user.target" # ensures that network & connectivity have been setup.
];
serviceConfig = {
Type = "oneshot"; # TODO: should this be an async startup, to avoid blocking on network at boot ?
ExecStart = import ./installer.nix { inherit cfg pkgs lib installation; };
} // restartOptions;
wantedBy = [ "default.target" ];
after = [ "multi-user.target" ];
serviceConfig = helpers.mkCommonServiceConfig
{
inherit cfg pkgs lib installation;
invokedFrom = "service-start";
} // helpers.mkRestartOptions cfg;
};
systemd.timers."flatpak-managed-install" = lib.mkIf config.services.flatpak.update.auto.enable {
timerConfig = {
Unit = "flatpak-managed-install";
OnCalendar = config.services.flatpak.update.auto.onCalendar;
Persistent = "true";
};

# Create a service that will only be started by a timer.
# We need a separate service to provide a custom Enviroment
# that installer used to determine if certain action (e.g. updates)
# should be performed at activation or not.
systemd.services."flatpak-managed-install-timer" = lib.mkIf config.services.flatpak.update.auto.enable {
serviceConfig = helpers.mkCommonServiceConfig
{
inherit cfg pkgs lib installation;
invokedFrom = "timer";
} // helpers.mkRestartOptions cfg;
};

systemd.timers."flatpak-managed-install-timer" = lib.mkIf config.services.flatpak.update.auto.enable {
timerConfig = helpers.mkCommonTimerConfig cfg;
wantedBy = [ "timers.target" ];
};
};
Expand Down
Loading
Loading