Add initial support for preinstalling flatpaks

This adds new FlatpakTransaction API, and a new top level CLI command to
preinstall flatpaks, that is to install flatpaks that are considered
part of the operating system.

A new drop-in directory /etc/flatpak/preinstall.d/ allows configuring
what apps should be preinstalled, and a new flatpak preinstall command
installs and removes apps based on the current configuration.

A drop-in loupe.preinstall file can look something like this:

[Flatpak Preinstall org.gnome.Loupe]
Branch=stable
IsRuntime=false

The corresponding API is flatpak_transaction_add_sync_preinstalled()
which can be implemented by GUI clients to drive the actual installs
on system startup.

Resolves: https://github.com/flatpak/flatpak/issues/5579
Co-authored-by: Sebastian Wick <sebastian.wick@redhat.com>
This commit is contained in:
Kalev Lember
2024-04-16 12:18:59 +02:00
committed by Sebastian Wick
parent 555e9200d9
commit d10e11482d
18 changed files with 1316 additions and 30 deletions

View File

@@ -0,0 +1,156 @@
/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 2024 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Kalev Lember <klember@redhat.com>
*/
#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gio/gunixinputstream.h>
#include "libglnx.h"
#include "flatpak-builtins.h"
#include "flatpak-builtins-utils.h"
#include "flatpak-transaction-private.h"
#include "flatpak-cli-transaction.h"
#include "flatpak-quiet-transaction.h"
#include "flatpak-utils-http-private.h"
#include "flatpak-utils-private.h"
#include "flatpak-error.h"
#include "flatpak-chain-input-stream-private.h"
static char **opt_sideload_repos;
static gboolean opt_no_pull;
static gboolean opt_no_deploy;
static gboolean opt_no_related;
static gboolean opt_no_deps;
static gboolean opt_no_static_deltas;
static gboolean opt_include_sdk;
static gboolean opt_include_debug;
static gboolean opt_yes;
static gboolean opt_reinstall;
static gboolean opt_noninteractive;
static GOptionEntry options[] = {
{ "no-pull", 0, 0, G_OPTION_ARG_NONE, &opt_no_pull, N_("Don't pull, only install from local cache"), NULL },
{ "no-deploy", 0, 0, G_OPTION_ARG_NONE, &opt_no_deploy, N_("Don't deploy, only download to local cache"), NULL },
{ "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't install related refs"), NULL },
{ "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify/install runtime dependencies"), NULL },
{ "no-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_no_static_deltas, N_("Don't use static deltas"), NULL },
{ "include-sdk", 0, 0, G_OPTION_ARG_NONE, &opt_include_sdk, N_("Additionally install the SDK used to build the given refs") },
{ "include-debug", 0, 0, G_OPTION_ARG_NONE, &opt_include_debug, N_("Additionally install the debug info for the given refs and their dependencies") },
{ "assumeyes", 'y', 0, G_OPTION_ARG_NONE, &opt_yes, N_("Automatically answer yes for all questions"), NULL },
{ "reinstall", 0, 0, G_OPTION_ARG_NONE, &opt_reinstall, N_("Uninstall first if already installed"), NULL },
{ "noninteractive", 0, 0, G_OPTION_ARG_NONE, &opt_noninteractive, N_("Produce minimal output and don't ask questions"), NULL },
/* Translators: A sideload is when you install from a local USB drive rather than the Internet. */
{ "sideload-repo", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_sideload_repos, N_("Use this local repo for sideloads"), N_("PATH") },
{ NULL }
};
gboolean
flatpak_builtin_preinstall (int argc, char **argv, GCancellable *cancellable, GError **error)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GPtrArray) dirs = NULL;
g_autoptr(FlatpakDir) dir = NULL;
g_autoptr(FlatpakTransaction) transaction = NULL;
context = g_option_context_new (_("- Install flatpaks that are part of the operating system"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!flatpak_option_context_parse (context, options, &argc, &argv,
FLATPAK_BUILTIN_FLAG_ALL_DIRS | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO,
&dirs, cancellable, error))
return FALSE;
/* Use the default dir */
dir = g_object_ref (g_ptr_array_index (dirs, 0));
if (opt_noninteractive)
opt_yes = TRUE; /* Implied */
if (opt_noninteractive)
transaction = flatpak_quiet_transaction_new (dir, error);
else
transaction = flatpak_cli_transaction_new (dir, opt_yes, TRUE, FALSE, error);
if (transaction == NULL)
return FALSE;
flatpak_transaction_set_no_pull (transaction, opt_no_pull);
flatpak_transaction_set_no_deploy (transaction, opt_no_deploy);
flatpak_transaction_set_disable_static_deltas (transaction, opt_no_static_deltas);
flatpak_transaction_set_disable_dependencies (transaction, opt_no_deps);
flatpak_transaction_set_disable_related (transaction, opt_no_related);
flatpak_transaction_set_reinstall (transaction, opt_reinstall);
flatpak_transaction_set_auto_install_sdk (transaction, opt_include_sdk);
flatpak_transaction_set_auto_install_debug (transaction, opt_include_debug);
for (int i = 0; opt_sideload_repos != NULL && opt_sideload_repos[i] != NULL; i++)
flatpak_transaction_add_sideload_repo (transaction, opt_sideload_repos[i]);
if (!flatpak_transaction_add_sync_preinstalled (transaction, error))
return FALSE;
if (flatpak_transaction_is_empty (transaction))
{
g_print (_("Nothing to do.\n"));
return TRUE;
}
if (!flatpak_transaction_run (transaction, cancellable, error))
{
if (g_error_matches (*error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED))
g_clear_error (error); /* Don't report on stderr */
return FALSE;
}
return TRUE;
}
gboolean
flatpak_complete_preinstall (FlatpakCompletion *completion)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GPtrArray) dirs = NULL;
context = g_option_context_new ("");
if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv,
FLATPAK_BUILTIN_FLAG_ONE_DIR | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO,
&dirs, NULL, NULL))
return FALSE;
switch (completion->argc)
{
default: /* REF */
flatpak_complete_options (completion, global_entries);
flatpak_complete_options (completion, options);
flatpak_complete_options (completion, user_entries);
break;
}
return TRUE;
}

View File

@@ -127,6 +127,7 @@ BUILTINPROTO (repair)
BUILTINPROTO (create_usb)
BUILTINPROTO (kill)
BUILTINPROTO (history)
BUILTINPROTO (preinstall)
#undef BUILTINPROTO

View File

@@ -89,6 +89,7 @@ static FlatpakCommand commands[] = {
{ "config", N_("Configure flatpak"), flatpak_builtin_config, flatpak_complete_config },
{ "repair", N_("Repair flatpak installation"), flatpak_builtin_repair, flatpak_complete_repair },
{ "create-usb", N_("Put applications or runtimes onto removable media"), flatpak_builtin_create_usb, flatpak_complete_create_usb },
{ "preinstall", N_("Install flatpaks that are part of the operating system"), flatpak_builtin_preinstall, flatpak_complete_preinstall },
/* translators: please keep the leading newline and space */
{ N_("\n Find applications and runtimes") },

View File

@@ -100,6 +100,7 @@ sources = [
'flatpak-builtins-permission-set.c',
'flatpak-builtins-permission-show.c',
'flatpak-builtins-pin.c',
'flatpak-builtins-preinstall.c',
'flatpak-builtins-ps.c',
'flatpak-builtins-remote-add.c',
'flatpak-builtins-remote-delete.c',

View File

@@ -180,6 +180,7 @@ typedef enum {
FLATPAK_HELPER_DEPLOY_FLAGS_APP_HINT = 1 << 5,
FLATPAK_HELPER_DEPLOY_FLAGS_INSTALL_HINT = 1 << 6,
FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PINNED = 1 << 7,
FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PREINSTALLED = 1 << 8,
} FlatpakHelperDeployFlags;
#define FLATPAK_HELPER_DEPLOY_FLAGS_ALL (FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE | \
@@ -189,18 +190,21 @@ typedef enum {
FLATPAK_HELPER_DEPLOY_FLAGS_NO_INTERACTION | \
FLATPAK_HELPER_DEPLOY_FLAGS_APP_HINT | \
FLATPAK_HELPER_DEPLOY_FLAGS_INSTALL_HINT | \
FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PINNED)
FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PINNED | \
FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PREINSTALLED)
typedef enum {
FLATPAK_HELPER_UNINSTALL_FLAGS_NONE = 0,
FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF = 1 << 0,
FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE = 1 << 1,
FLATPAK_HELPER_UNINSTALL_FLAGS_NO_INTERACTION = 1 << 2,
FLATPAK_HELPER_UNINSTALL_FLAGS_UPDATE_PREINSTALLED = 1 << 3,
} FlatpakHelperUninstallFlags;
#define FLATPAK_HELPER_UNINSTALL_FLAGS_ALL (FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF | \
FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE | \
FLATPAK_HELPER_UNINSTALL_FLAGS_NO_INTERACTION)
FLATPAK_HELPER_UNINSTALL_FLAGS_NO_INTERACTION | \
FLATPAK_HELPER_UNINSTALL_FLAGS_UPDATE_PREINSTALLED)
typedef enum {
FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_NONE = 0,
@@ -364,6 +368,19 @@ gboolean flatpak_remove_override_keyfile (const char *app_id,
gboolean user,
GError **error);
typedef struct
{
FlatpakDecomposed *ref;
char *collection_id;
} FlatpakPreinstallConfig;
GPtrArray * flatpak_get_preinstall_config (const char *default_arch,
GCancellable *cancellable,
GError **error);
gboolean flatpak_dir_uninitialized_mark_preinstalled (FlatpakDir *self,
const GPtrArray *preinstall_config,
GError **error);
int flatpak_deploy_data_get_version (GBytes *deploy_data);
const char * flatpak_deploy_data_get_origin (GBytes *deploy_data);
const char * flatpak_deploy_data_get_commit (GBytes *deploy_data);
@@ -681,6 +698,7 @@ gboolean flatpak_dir_deploy_install (Fla
const char **previous_ids,
gboolean reinstall,
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy,
GCancellable *cancellable,
GError **error);
gboolean flatpak_dir_install (FlatpakDir *self,
@@ -690,6 +708,7 @@ gboolean flatpak_dir_install (Fla
gboolean reinstall,
gboolean app_hint,
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy,
FlatpakRemoteState *state,
FlatpakDecomposed *ref,
const char *opt_commit,

View File

@@ -85,6 +85,16 @@
#define FLATPAK_REMOTES_DIR "remotes.d"
#define FLATPAK_REMOTES_FILE_EXT ".flatpakrepo"
#define FLATPAK_PREINSTALL_DIR "preinstall.d"
#define FLATPAK_PREINSTALL_FILE_EXT ".preinstall"
#define FLATPAK_PREINSTALL_GROUP_PREFIX "Flatpak Preinstall "
#define FLATPAK_PREINSTALL_IS_RUNTIME_KEY "IsRuntime"
#define FLATPAK_PREINSTALL_NAME_KEY "Name"
#define FLATPAK_PREINSTALL_BRANCH_KEY "Branch"
#define FLATPAK_PREINSTALL_COLLECTION_ID_KEY "CollectionID"
#define FLATPAK_PREINSTALL_INSTALL_KEY "Install"
#define SIDELOAD_REPOS_DIR_NAME "sideload-repos"
#define FLATPAK_TRIGGERS_DIR "triggers"
@@ -1878,6 +1888,405 @@ get_system_locations (GCancellable *cancellable,
return g_steal_pointer (&locations);
}
typedef struct
{
char *name;
char *branch;
gboolean is_runtime;
char *collection_id;
gboolean install;
} PreinstallConfig;
static PreinstallConfig *
preinstall_config_new (const char *name)
{
PreinstallConfig *config = g_new0 (PreinstallConfig, 1);
config->name = g_strdup (name);
config->branch = g_strdup ("master");
config->install = TRUE;
return config;
}
static void
preinstall_config_free (PreinstallConfig *config)
{
g_clear_pointer (&config->name, g_free);
g_clear_pointer (&config->branch, g_free);
g_clear_pointer (&config->collection_id, g_free);
g_free (config);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PreinstallConfig, preinstall_config_free)
static void
flatpak_preinstall_config_free (FlatpakPreinstallConfig *preinstall)
{
g_clear_pointer (&preinstall->ref, flatpak_decomposed_unref);
g_clear_pointer (&preinstall->collection_id, g_free);
g_free (preinstall);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakPreinstallConfig, flatpak_preinstall_config_free)
static void
flatpak_parse_preinstall_config_file (GKeyFile *keyfile,
GHashTable *configs)
{
g_auto(GStrv) groups = NULL;
groups = g_key_file_get_groups (keyfile, NULL);
for (int i = 0; groups[i] != NULL; i++)
{
const char *group_name = groups[i];
const char *name;
g_autoptr(PreinstallConfig) config = NULL;
g_autoptr(GError) local_error = NULL;
g_autofree char *owned_name = NULL;
g_autofree char *branch = NULL;
gboolean is_runtime = FALSE;
g_autofree char *collection_id = NULL;
gboolean install = TRUE;
if (!g_str_has_prefix (group_name, FLATPAK_PREINSTALL_GROUP_PREFIX) ||
*(group_name + strlen (FLATPAK_PREINSTALL_GROUP_PREFIX)) == '\0')
{
g_info ("Skipping unknown group %s", group_name);
continue;
}
name = group_name + strlen (FLATPAK_PREINSTALL_GROUP_PREFIX);
if (!g_hash_table_steal_extended (configs, name,
(gpointer *)&owned_name,
(gpointer *)&config))
{
config = preinstall_config_new (name);
owned_name = g_strdup (name);
}
branch = g_key_file_get_string (keyfile,
group_name,
FLATPAK_PREINSTALL_BRANCH_KEY,
NULL);
if (branch)
{
if (*branch == '\0')
g_clear_pointer (&branch, g_free);
g_set_str (&config->branch, branch);
}
is_runtime = g_key_file_get_boolean (keyfile,
group_name,
FLATPAK_PREINSTALL_IS_RUNTIME_KEY,
&local_error);
if (!local_error)
{
config->is_runtime = is_runtime;
}
else if (!g_error_matches (local_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND))
{
g_info ("Invalid file format, %s is not a boolean",
FLATPAK_PREINSTALL_IS_RUNTIME_KEY);
}
g_clear_error (&local_error);
collection_id = g_key_file_get_string (keyfile,
group_name,
FLATPAK_PREINSTALL_COLLECTION_ID_KEY,
NULL);
if (collection_id)
{
if (*collection_id == '\0')
g_clear_pointer (&collection_id, g_free);
g_set_str (&config->collection_id, collection_id);
}
install = g_key_file_get_boolean (keyfile,
group_name,
FLATPAK_PREINSTALL_INSTALL_KEY,
&local_error);
if (!local_error)
{
config->install = install;
}
else if (!g_error_matches (local_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND))
{
g_info ("Invalid file format, %s is not a boolean",
FLATPAK_PREINSTALL_INSTALL_KEY);
}
g_clear_error (&local_error);
g_hash_table_insert (configs,
g_steal_pointer (&owned_name),
g_steal_pointer (&config));
}
}
typedef struct
{
char *name;
GFile *file;
} PreinstallConfigFile;
static gint
preinstall_config_file_sort (gconstpointer a,
gconstpointer b)
{
const PreinstallConfigFile *ca = a;
const PreinstallConfigFile *cb = b;
return g_strcmp0 (ca->name, cb->name);
}
static void
preinstall_config_file_free (PreinstallConfigFile *preinstall_config_file)
{
g_clear_pointer (&preinstall_config_file->name, g_free);
g_clear_object (&preinstall_config_file->file);
g_free (preinstall_config_file);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PreinstallConfigFile, preinstall_config_file_free);
static gboolean
scan_preinstall_config_files (const char *config_dir,
GHashTable *configs,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFile) conf_dir = NULL;
g_autoptr(GFileEnumerator) dir_enum = NULL;
g_autoptr(GPtrArray) config_files = NULL;
g_autoptr(GError) local_error = NULL;
if (!g_file_test (config_dir, G_FILE_TEST_IS_DIR))
{
g_info ("Skipping missing preinstall config directory %s", config_dir);
return TRUE;
}
conf_dir = g_file_new_for_path (config_dir);
dir_enum = g_file_enumerate_children (conf_dir,
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NONE,
cancellable,
&local_error);
if (local_error != NULL)
{
g_info ("Unexpected error retrieving preinstalls from %s: %s",
config_dir,
local_error->message);
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
config_files = g_ptr_array_new_with_free_func ((GDestroyNotify)preinstall_config_file_free);
while (TRUE)
{
GFileInfo *file_info;
GFile *path;
const char *name;
guint32 type;
g_autoptr(PreinstallConfigFile) config_file = NULL;
if (!g_file_enumerator_iterate (dir_enum,
&file_info,
&path,
cancellable,
&local_error))
{
g_info ("Unexpected error reading file in %s: %s",
config_dir,
local_error->message);
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
if (file_info == NULL)
break;
name = g_file_info_get_name (file_info);
type = g_file_info_get_file_type (file_info);
if (type != G_FILE_TYPE_REGULAR ||
!g_str_has_suffix (name, FLATPAK_PREINSTALL_FILE_EXT))
continue;
config_file = g_new0 (PreinstallConfigFile, 1);
config_file->name = g_strdup (name);
config_file->file = g_object_ref (path);
g_ptr_array_add (config_files, g_steal_pointer (&config_file));
}
g_ptr_array_sort (config_files, preinstall_config_file_sort);
for (int i = 0; i < config_files->len; i++)
{
PreinstallConfigFile *config_file = g_ptr_array_index (config_files, i);
g_autofree char *path = NULL;
g_autoptr(GKeyFile) keyfile = NULL;
g_autoptr(GError) load_error = NULL;
path = g_file_get_path (config_file->file);
g_info ("Parsing config file %s", path);
keyfile = g_key_file_new ();
if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &load_error))
g_info ("Parsing config file %s failed: %s", path, load_error->message);
flatpak_parse_preinstall_config_file (keyfile, configs);
}
return TRUE;
}
GPtrArray *
flatpak_get_preinstall_config (const char *default_arch,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GHashTable) configs = NULL;
g_autoptr(GPtrArray) preinstalls = NULL;
g_autofree char *config_dir = NULL;
g_autofree char *data_dir = NULL;
GHashTableIter iter;
PreinstallConfig *config;
configs = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)preinstall_config_free);
/* scan directories in reverse priority order */
data_dir = g_build_filename (get_data_dir_location (), FLATPAK_PREINSTALL_DIR, NULL);
if (!scan_preinstall_config_files (data_dir, configs, cancellable, error))
return NULL;
config_dir = g_build_filename (get_config_dir_location (), FLATPAK_PREINSTALL_DIR, NULL);
if (!scan_preinstall_config_files (config_dir, configs, cancellable, error))
return NULL;
preinstalls = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_preinstall_config_free);
g_hash_table_iter_init (&iter, configs);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&config))
{
g_autoptr(FlatpakPreinstallConfig) preinstall = NULL;
g_autoptr(FlatpakDecomposed) ref = NULL;
g_autoptr(GError) local_error = NULL;
if (!config->install)
{
g_info ("Skipping preinstall of %s because it is configured to not install",
config->name);
continue;
}
ref = flatpak_decomposed_new_from_parts (config->is_runtime ?
FLATPAK_KINDS_RUNTIME :
FLATPAK_KINDS_APP,
config->name,
default_arch,
config->branch,
&local_error);
if (ref == NULL)
{
g_info ("Skipping preinstall of %s because of problems in the configuration: %s",
config->name,
local_error->message);
continue;
}
preinstall = g_new0 (FlatpakPreinstallConfig, 1);
preinstall->ref = g_steal_pointer (&ref);
preinstall->collection_id = g_strdup (config->collection_id);
g_info ("Found preinstall ref %s",
flatpak_decomposed_get_ref (preinstall->ref));
g_ptr_array_add (preinstalls, g_steal_pointer (&preinstall));
}
return g_steal_pointer (&preinstalls);
}
static gboolean
flatpak_is_ref_in_list (FlatpakDecomposed *needle,
GPtrArray *refs)
{
for (size_t i = 0; i < refs->len; i++)
{
FlatpakDecomposed *ref = g_ptr_array_index (refs, i);
if (flatpak_decomposed_equal (ref, needle))
return TRUE;
}
return FALSE;
}
gboolean
flatpak_dir_uninitialized_mark_preinstalled (FlatpakDir *self,
const GPtrArray *preinstall_config,
GError **error)
{
g_autoptr(GPtrArray) installed_refs = NULL;
g_autofree char *existing_preinstalls = NULL;
g_autoptr(GError) local_error = NULL;
existing_preinstalls = flatpak_dir_get_config (self,
"preinstalled",
&local_error);
if (existing_preinstalls != NULL)
return TRUE;
if (!g_error_matches (local_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND))
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
g_clear_error (&local_error);
installed_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_RUNTIME | FLATPAK_KINDS_APP,
NULL, error);
if (installed_refs == NULL)
return FALSE;
for (int i = 0; i < preinstall_config->len; i++)
{
const FlatpakPreinstallConfig *config = g_ptr_array_index (preinstall_config, i);
GError **append_error = local_error == NULL ? &local_error : NULL;
if (!flatpak_is_ref_in_list (config->ref, installed_refs))
continue;
flatpak_dir_config_append_pattern (self,
"preinstalled",
flatpak_decomposed_get_ref (config->ref),
FALSE,
NULL,
append_error);
}
if (local_error)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
return TRUE;
}
GPtrArray *
flatpak_get_system_base_dir_locations (GCancellable *cancellable,
GError **error)
@@ -9097,6 +9506,7 @@ flatpak_dir_deploy_install (FlatpakDir *self,
const char **previous_ids,
gboolean reinstall,
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy,
GCancellable *cancellable,
GError **error)
{
@@ -9204,6 +9614,14 @@ flatpak_dir_deploy_install (FlatpakDir *self,
TRUE, NULL, error))
goto out;
/* Save preinstalled refs to keep the data on what is user installed and what
* is automatically installed. */
if (update_preinstalled_on_deploy &&
!flatpak_dir_config_append_pattern (self, "preinstalled",
flatpak_decomposed_get_ref (ref),
FALSE, NULL, error))
goto out;
ret = TRUE;
commit = flatpak_dir_read_active (self, ref, cancellable);
@@ -9805,6 +10223,7 @@ flatpak_dir_install (FlatpakDir *self,
gboolean reinstall,
gboolean app_hint,
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy,
FlatpakRemoteState *state,
FlatpakDecomposed *ref,
const char *opt_commit,
@@ -10019,6 +10438,9 @@ flatpak_dir_install (FlatpakDir *self,
if (pin_on_deploy)
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PINNED;
if (update_preinstalled_on_deploy)
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PREINSTALLED;
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_INSTALL_HINT;
if (!flatpak_dir_system_helper_call_deploy (self,
@@ -10057,6 +10479,7 @@ flatpak_dir_install (FlatpakDir *self,
{
if (!flatpak_dir_deploy_install (self, ref, state->remote_name, opt_subpaths,
opt_previous_ids, reinstall, pin_on_deploy,
update_preinstalled_on_deploy,
cancellable, error))
return FALSE;
@@ -10347,7 +10770,7 @@ flatpak_dir_install_bundle (FlatpakDir *self,
}
else
{
if (!flatpak_dir_deploy_install (self, ref, remote, NULL, NULL, FALSE, FALSE, cancellable, error))
if (!flatpak_dir_deploy_install (self, ref, remote, NULL, NULL, FALSE, FALSE, FALSE, cancellable, error))
return FALSE;
}
@@ -10778,6 +11201,7 @@ flatpak_dir_uninstall (FlatpakDir *self,
g_autoptr(GBytes) deploy_data = NULL;
gboolean keep_ref = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF;
gboolean force_remove = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE;
gboolean update_preinstalled = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_UPDATE_PREINSTALLED;
name = flatpak_decomposed_dup_id (ref);
@@ -10884,6 +11308,10 @@ flatpak_dir_uninstall (FlatpakDir *self,
if (!flatpak_dir_mark_changed (self, error))
return FALSE;
if (update_preinstalled &&
!flatpak_dir_config_remove_pattern (self, "preinstalled", flatpak_decomposed_get_ref (ref), error))
return FALSE;
if (!was_deployed)
{
const char *branch = flatpak_decomposed_get_branch (ref);

View File

@@ -1929,7 +1929,7 @@ flatpak_installation_install_full (FlatpakInstallation *self,
(flags & FLATPAK_INSTALL_FLAGS_NO_PULL) != 0,
(flags & FLATPAK_INSTALL_FLAGS_NO_DEPLOY) != 0,
(flags & FLATPAK_INSTALL_FLAGS_NO_STATIC_DELTAS) != 0,
FALSE, FALSE, FALSE, state,
FALSE, FALSE, FALSE, FALSE, state,
ref, NULL, (const char **) subpaths, NULL, NULL, NULL, NULL, NULL,
progress, cancellable, error))
return NULL;

View File

@@ -114,6 +114,7 @@ struct _FlatpakTransactionOperation
gboolean skip;
gboolean update_only_deploy;
gboolean pin_on_deploy;
gboolean update_preinstalled_on_deploy;
gboolean resolved;
char *resolved_commit;
@@ -666,7 +667,8 @@ flatpak_transaction_operation_new (const char *remote,
const char *commit,
GFile *bundle,
FlatpakTransactionOperationType kind,
gboolean pin_on_deploy)
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy)
{
FlatpakTransactionOperation *self;
@@ -681,6 +683,7 @@ flatpak_transaction_operation_new (const char *remote,
self->bundle = g_object_ref (bundle);
self->kind = kind;
self->pin_on_deploy = pin_on_deploy;
self->update_preinstalled_on_deploy = update_preinstalled_on_deploy;
return self;
}
@@ -2132,7 +2135,8 @@ flatpak_transaction_add_op (FlatpakTransaction *self,
const char *commit,
GFile *bundle,
FlatpakTransactionOperationType kind,
gboolean pin_on_deploy)
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
FlatpakTransactionOperation *op;
@@ -2162,7 +2166,8 @@ flatpak_transaction_add_op (FlatpakTransaction *self,
}
op = flatpak_transaction_operation_new (remote, ref, subpaths, previous_ids,
commit, bundle, kind, pin_on_deploy);
commit, bundle, kind, pin_on_deploy,
update_preinstalled_on_deploy);
g_hash_table_insert (priv->last_op_for_ref, flatpak_decomposed_ref (ref), op);
priv->ops = g_list_prepend (priv->ops, op);
@@ -2270,7 +2275,7 @@ add_related (FlatpakTransaction *self,
related_op = flatpak_transaction_add_op (self, rel->remote, rel->ref,
NULL, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UNINSTALL,
FALSE);
FALSE, FALSE);
related_op->non_fatal = TRUE;
related_op->fail_if_op_fails = op;
flatpak_transaction_operation_add_related_to_op (related_op, op);
@@ -2299,7 +2304,7 @@ add_related (FlatpakTransaction *self,
(const char **) rel->subpaths,
NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE,
FALSE);
FALSE, FALSE);
related_op->non_fatal = TRUE;
related_op->fail_if_op_fails = op;
flatpak_transaction_operation_add_related_to_op (related_op, op);
@@ -2530,7 +2535,7 @@ add_new_dep_op (FlatpakTransaction *self,
return FALSE;
*dep_op = flatpak_transaction_add_op (self, dep_remote, dep_ref, NULL, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE, FALSE);
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE, FALSE, FALSE);
}
else
{
@@ -2540,7 +2545,7 @@ add_new_dep_op (FlatpakTransaction *self,
g_info ("Updating dependency %s of %s", flatpak_decomposed_get_pref (dep_ref),
flatpak_decomposed_get_pref (op->ref));
*dep_op = flatpak_transaction_add_op (self, dep_remote, dep_ref, NULL, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UPDATE, FALSE);
FLATPAK_TRANSACTION_OPERATION_UPDATE, FALSE, FALSE);
(*dep_op)->non_fatal = TRUE;
}
}
@@ -2636,6 +2641,7 @@ flatpak_transaction_add_ref (FlatpakTransaction *self,
FlatpakImageSource *image_source,
const char *external_metadata,
gboolean pin_on_deploy,
gboolean update_preinstalled_on_deploy,
FlatpakTransactionOperation **out_op,
GError **error)
{
@@ -2759,7 +2765,8 @@ flatpak_transaction_add_ref (FlatpakTransaction *self,
}
op = flatpak_transaction_add_op (self, remote, ref, subpaths, previous_ids,
commit, bundle, kind, pin_on_deploy);
commit, bundle, kind, pin_on_deploy,
update_preinstalled_on_deploy);
if (image_source)
op->image_source = g_object_ref (image_source);
@@ -2817,7 +2824,7 @@ flatpak_transaction_add_install (FlatpakTransaction *self,
if (!flatpak_transaction_add_ref (self, remote, decomposed, subpaths, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL,
NULL, NULL, NULL, pin_on_deploy, NULL, error))
NULL, NULL, NULL, pin_on_deploy, FALSE, NULL, error))
return FALSE;
return TRUE;
@@ -2877,7 +2884,9 @@ flatpak_transaction_add_rebase (FlatpakTransaction *self,
if (dir_ref_is_installed (priv->dir, decomposed, &installed_origin, NULL))
remote = installed_origin;
return flatpak_transaction_add_ref (self, remote, decomposed, subpaths, previous_ids, NULL, FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE, NULL, NULL, NULL, FALSE, NULL, error);
return flatpak_transaction_add_ref (self, remote, decomposed, subpaths, previous_ids, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE,
NULL, NULL, NULL, FALSE, FALSE, NULL, error);
}
/**
@@ -2953,25 +2962,22 @@ flatpak_transaction_add_rebase_and_uninstall (FlatpakTransaction *self,
if (!flatpak_transaction_add_ref (self, remote, new_decomposed, subpaths,
previous_ids, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE,
NULL, NULL, NULL, FALSE, &rebase_op, error))
NULL, NULL, NULL, FALSE, FALSE, &rebase_op, error))
return FALSE;
if (!flatpak_transaction_add_ref (self, NULL, old_decomposed, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UNINSTALL,
NULL, NULL, NULL, FALSE, &uninstall_op, &local_error))
NULL, NULL, NULL, FALSE, FALSE, &uninstall_op, &local_error))
{
/* If the user is trying to install an eol-rebased app from scratch, the
* @old_ref cant be uninstalled because its not installed already.
* Silently ignore that. */
if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
{
g_clear_error (&local_error);
}
else
if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
g_clear_error (&local_error);
}
/* Link the ops together so that the install/update is done first, and if
@@ -3075,6 +3081,134 @@ flatpak_transaction_add_install_flatpakref (FlatpakTransaction *self,
return TRUE;
}
/**
* flatpak_transaction_add_sync_preinstalled:
* @self: a #FlatpakTransaction
* @error: return location for a #GError
*
* Adds preinstall operations to this transaction. This can involve both
* installing and removing refs, based on /etc/preinstall.d contents and what
* the system had preinstalled before.
*
* Returns: %TRUE on success; %FALSE with @error set on failure.
*/
gboolean
flatpak_transaction_add_sync_preinstalled (FlatpakTransaction *self,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GPtrArray) install_refs = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) preinstalled_refs = NULL;
g_auto(GStrv) remotes = NULL;
g_autoptr(GPtrArray) configs = NULL;
remotes = flatpak_dir_list_remotes (priv->dir, NULL, error);
if (remotes == NULL)
return FALSE;
configs = flatpak_get_preinstall_config (priv->default_arch, NULL, error);
if (configs == NULL)
return FALSE;
/* If the system has not had any apps pre-installed (i.e. the key in the
* config is missing) we mark all installed apps we would pre-install as
* pre-installed. This makes sure we will uninstall them when the config says
* that they no longer should be installed. */
if (!flatpak_dir_uninitialized_mark_preinstalled (priv->dir, configs, NULL))
g_message (_("Warning: Could not mark already installed apps as preinstalled"));
preinstalled_refs = flatpak_dir_get_config_patterns (priv->dir, "preinstalled");
/* Find preinstalls that should get installed */
for (int i = 0; i < configs->len; i++)
{
const FlatpakPreinstallConfig *config = g_ptr_array_index (configs, i);
/* Store for later */
g_ptr_array_add (install_refs, flatpak_decomposed_dup_ref (config->ref));
/* Skip over if it's listed as previously preinstalled - it's now under
* user's control and we no longer install it again, even if the user
* manually removes it. */
if (!priv->reinstall &&
flatpak_g_ptr_array_contains_string (preinstalled_refs,
flatpak_decomposed_get_ref (config->ref)))
{
g_info ("Preinstall ref %s is marked as already preinstalled; skipping",
flatpak_decomposed_get_ref (config->ref));
continue;
}
for (int j = 0; remotes[j] != NULL; j++)
{
const char *remote = remotes[j];
g_autoptr(GError) local_error = NULL;
g_autofree char *remote_collection_id = NULL;
if (flatpak_dir_get_remote_disabled (priv->dir, remote))
continue;
remote_collection_id = flatpak_dir_get_remote_collection_id (priv->dir,
remote);
/* Choose the first match if the collection ID was not specified */
if (config->collection_id != NULL &&
g_strcmp0 (remote_collection_id, config->collection_id) != 0)
continue;
g_info ("Adding preinstall of %s from remote %s",
flatpak_decomposed_get_ref (config->ref),
remote);
if (!flatpak_transaction_add_ref (self, remote, config->ref, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL,
NULL, NULL, NULL, FALSE, TRUE, NULL,
&local_error))
{
g_info ("Failed to add preinstall ref %s: %s",
flatpak_decomposed_get_ref (config->ref),
local_error->message);
}
}
}
/* Find previously preinstalled refs that are no longer in the preinstall
* list and should now get uninstalled */
for (int i = 0; i < preinstalled_refs->len; i++)
{
const char *ref = g_ptr_array_index (preinstalled_refs, i);
/* No longer in the preinstall.d list, so uninstall */
if (!flatpak_g_ptr_array_contains_string (install_refs, ref))
{
g_autoptr(GError) local_error = NULL;
g_autoptr(FlatpakDecomposed) decomposed = NULL;
decomposed = flatpak_decomposed_new_from_ref (ref, error);
if (decomposed == NULL)
return FALSE;
g_info ("Preinstalled ref %s is no longer listed as wanted in preinstall.d config; uninstalling",
flatpak_decomposed_get_ref (decomposed));
if (!flatpak_transaction_add_ref (self, NULL, decomposed, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UNINSTALL,
NULL, NULL, NULL, FALSE, TRUE, NULL,
&local_error))
{
if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
g_clear_error (&local_error);
}
}
}
return TRUE;
}
/**
* flatpak_transaction_add_update:
* @self: a #FlatpakTransaction
@@ -3110,7 +3244,7 @@ flatpak_transaction_add_update (FlatpakTransaction *self,
return FALSE;
/* Note: we implement the merge when subpaths == NULL in flatpak_transaction_add_ref() */
return flatpak_transaction_add_ref (self, NULL, decomposed, subpaths, NULL, commit, FLATPAK_TRANSACTION_OPERATION_UPDATE, NULL, NULL, NULL, FALSE, NULL, error);
return flatpak_transaction_add_ref (self, NULL, decomposed, subpaths, NULL, commit, FLATPAK_TRANSACTION_OPERATION_UPDATE, NULL, NULL, NULL, FALSE, FALSE, NULL, error);
}
/**
@@ -3137,7 +3271,7 @@ flatpak_transaction_add_uninstall (FlatpakTransaction *self,
if (decomposed == NULL)
return FALSE;
return flatpak_transaction_add_ref (self, NULL, decomposed, NULL, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_UNINSTALL, NULL, NULL, NULL, FALSE, NULL, error);
return flatpak_transaction_add_ref (self, NULL, decomposed, NULL, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_UNINSTALL, NULL, NULL, NULL, FALSE, FALSE, NULL, error);
}
static gboolean
@@ -3249,7 +3383,7 @@ flatpak_transaction_add_auto_install (FlatpakTransaction *self,
if (!flatpak_transaction_add_ref (self, remote, auto_install_ref, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE,
NULL, NULL, NULL, FALSE, NULL,
NULL, NULL, NULL, FALSE, FALSE, NULL,
&local_error))
g_info ("Failed to add auto-install ref %s: %s", flatpak_decomposed_get_ref (auto_install_ref),
local_error->message);
@@ -4762,7 +4896,7 @@ flatpak_transaction_resolve_bundles (FlatpakTransaction *self,
if (!flatpak_transaction_add_ref (self, remote, ref, NULL, NULL, commit,
FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE,
data->file, NULL, metadata, FALSE, NULL, error))
data->file, NULL, metadata, FALSE, FALSE, NULL, error))
return FALSE;
}
@@ -4829,7 +4963,8 @@ flatpak_transaction_resolve_images (FlatpakTransaction *self,
if (!flatpak_transaction_add_ref (self, remote, ref, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL,
NULL, image_source, metadata_label, FALSE, &op, error))
NULL, image_source, metadata_label, FALSE, FALSE,
&op, error))
return FALSE;
}
@@ -4901,6 +5036,7 @@ _run_op_kind (FlatpakTransaction *self,
priv->reinstall,
priv->max_op >= APP_UPDATE,
op->pin_on_deploy,
op->update_preinstalled_on_deploy,
remote_state, op->ref,
op->resolved_commit,
(const char **) op->subpaths,
@@ -4940,7 +5076,7 @@ _run_op_kind (FlatpakTransaction *self,
if (flatpak_decomposed_is_app (op->ref))
*out_needs_triggers = TRUE;
if (op->pin_on_deploy)
if (op->pin_on_deploy|| op->update_preinstalled_on_deploy)
*out_needs_cache_drop = TRUE;
}
}
@@ -5045,6 +5181,9 @@ _run_op_kind (FlatpakTransaction *self,
if (priv->force_uninstall)
flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE;
if (op->update_preinstalled_on_deploy)
flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_UPDATE_PREINSTALLED;
emit_new_op (self, op, progress);
res = flatpak_dir_uninstall (priv->dir, op->ref, flags,
@@ -5225,7 +5364,7 @@ add_uninstall_unused_ops (FlatpakTransaction *self,
uninstall_op = flatpak_transaction_add_op (self, origin, unused_ref,
NULL, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UNINSTALL,
FALSE);
FALSE, FALSE);
run_operation_last (uninstall_op);
}
}

View File

@@ -334,6 +334,9 @@ gboolean flatpak_transaction_add_install_flatpakref (FlatpakTransacti
GBytes *flatpakref_data,
GError **error);
FLATPAK_EXTERN
gboolean flatpak_transaction_add_sync_preinstalled (FlatpakTransaction *self,
GError **error);
FLATPAK_EXTERN
gboolean flatpak_transaction_add_update (FlatpakTransaction *self,
const char *ref,
const char **subpaths,

View File

@@ -52,6 +52,7 @@
<xi:include href="@srcdir@/flatpak-permission-show.xml"/>
<xi:include href="@srcdir@/flatpak-permission-reset.xml"/>
<xi:include href="@srcdir@/flatpak-permission-set.xml"/>
<xi:include href="@srcdir@/flatpak-preinstall.xml"/>
<xi:include href="@srcdir@/flatpak-ps.xml"/>
<xi:include href="@srcdir@/flatpak-remote-add.xml"/>
<xi:include href="@srcdir@/flatpak-remote-delete.xml"/>

276
doc/flatpak-preinstall.xml Normal file
View File

@@ -0,0 +1,276 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<refentry id="flatpak-preinstall">
<refentryinfo>
<title>flatpak preinstall</title>
<productname>flatpak</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Kalev</firstname>
<surname>Lember</surname>
<email>klember@redhat.com</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>flatpak preinstall</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>flatpak-preinstall</refname>
<refpurpose>Install flatpaks that are part of the operating system</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>flatpak preinstall</command>
<arg choice="opt" rep="repeat">OPTION</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
This command manages flatpaks that are part of the operating system. If no options are given, running <command>flatpak preinstall</command> will synchronize (install and remove) flatpaks to match the set that the OS vendor has chosen.
</para>
<para>
Preinstalled flatpaks are defined by dropping .preinstall files into the directories <filename>/usr/share/flatpak/preinstall.d/</filename> and <filename>/etc/flatpak/preinstall.d/</filename>. The OS runs <command>flatpak preinstall -y</command> (or its GUI equivalent) on system startup, which then does the actual installation.
</para>
<para>
This system allows the OS vendor to define the list of flatpaks that are installed together with the OS, and also makes it possible for the OS vendor to make changes to the list in the future, which is then applied once <command>flatpak preinstall</command> is run next time.
Users can opt out of preinstalled flatpaks by simply uninstalling them, at which point they won't get automatically reinstalled again.
</para>
</refsect1>
<refsect1>
<title>File format</title>
<para>
The .preinstall file is using the same .ini file format that is used for systemd unit files or application .desktop files.
</para>
<refsect2>
<title>[Flatpak Preinstall NAME]</title>
<para>
The NAME is the fully qualified name of the runtime or application. All the information for a single runtime or application is contained in one [Flatpak Preinstall NAME] group. Multiple groups can be defined in a single file.
</para>
<para>
The following keys can be present in this group:
</para>
<variablelist>
<varlistentry>
<term><option>Install</option> (boolean)</term>
<listitem><para>
Whether this group should be installed. If this key is not specified, the group will be installed.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>Branch</option> (string)</term>
<listitem><para>
The name of the branch from which to install the application or runtime. If this key is not specified, the "master" branch is used.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>IsRuntime</option> (boolean)</term>
<listitem><para>
Whether this group refers to a runtime. If this key is not specified, the group is assumed to refer to an application.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>CollectionID</option> (string)</term>
<listitem><para>
The collection ID of the remote to use, if it has one.
</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
<refsect1>
<title>Example</title>
<programlisting>
[Flatpak Preinstall org.gnome.Loupe]
Branch=stable
IsRuntime=false
</programlisting>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>-h</option></term>
<term><option>--help</option></term>
<listitem><para>
Show help options and exit.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--reinstall</option></term>
<listitem><para>
Uninstall first if already installed.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-u</option></term>
<term><option>--user</option></term>
<listitem><para>
Install the application or runtime in a per-user installation.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--system</option></term>
<listitem><para>
Install the application or runtime in the default system-wide installation.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--installation=NAME</option></term>
<listitem><para>
Install the application or runtime in a system-wide installation
specified by <arg choice="plain">NAME</arg> among those defined in
<filename>/etc/flatpak/installations.d/</filename>. Using
<option>--installation=default</option> is equivalent to using
<option>--system</option>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-deploy</option></term>
<listitem><para>
Download the latest version, but don't deploy it.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-pull</option></term>
<listitem><para>
Don't download the latest version, deploy whatever is locally available.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-related</option></term>
<listitem><para>
Don't download related extensions, such as the locale data.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-deps</option></term>
<listitem><para>
Don't verify runtime dependencies when installing.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--sideload-repo=PATH</option></term>
<listitem><para>
Adds an extra local ostree repo as a source for installation. This is equivalent
to using the <filename>sideload-repos</filename> directories (see
<citerefentry><refentrytitle>flatpak</refentrytitle><manvolnum>1</manvolnum></citerefentry>),
but can be done on a per-command basis. Any path added here is used in addition
to ones in those directories.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--include-sdk</option></term>
<listitem><para>
For each app being installed, also installs the SDK that was used to build it.
Implies <option>--or-update</option>; incompatible with <option>--no-deps</option>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--include-debug</option></term>
<listitem><para>
For each ref being installed, as well as all dependencies, also installs its
debug info. Implies <option>--or-update</option>; incompatible with
<option>--no-deps</option>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-y</option></term>
<term><option>--assumeyes</option></term>
<listitem><para>
Automatically answer yes to all questions (or pick the most prioritized answer). This is useful for automation.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--noninteractive</option></term>
<listitem><para>
Produce minimal output and avoid most questions. This is suitable for use in
non-interactive situations, e.g. in a build script.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-v</option></term>
<term><option>--verbose</option></term>
<listitem><para>
Print debug information during command processing.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--ostree-verbose</option></term>
<listitem><para>
Print OSTree debug information during command processing.
</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
<command>$ flatpak preinstall</command>
</para>
</refsect1>
<refsect1>
<title>See also</title>
<para>
<citerefentry><refentrytitle>flatpak</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
</para>
</refsect1>
</refentry>

View File

@@ -26,6 +26,7 @@ man1 = [
'flatpak-config',
'flatpak-update',
'flatpak-uninstall',
'flatpak-preinstall',
'flatpak-mask',
'flatpak-pin',
'flatpak-list',

View File

@@ -30,6 +30,7 @@ app/flatpak-builtins-permission-reset.c
app/flatpak-builtins-permission-set.c
app/flatpak-builtins-permission-show.c
app/flatpak-builtins-pin.c
app/flatpak-builtins-preinstall.c
app/flatpak-builtins-ps.c
app/flatpak-builtins-remote-add.c
app/flatpak-builtins-remote-delete.c

View File

@@ -404,6 +404,7 @@ handle_deploy (FlatpakSystemHelper *object,
gboolean local_pull;
gboolean reinstall;
gboolean update_pinned;
gboolean update_preinstalled;
g_autofree char *url = NULL;
g_autoptr(OngoingPull) ongoing_pull = NULL;
g_autofree gchar *src_dir = NULL;
@@ -489,6 +490,7 @@ handle_deploy (FlatpakSystemHelper *object,
local_pull = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL) != 0;
reinstall = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_REINSTALL) != 0;
update_pinned = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PINNED) != 0;
update_preinstalled = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PREINSTALLED) != 0;
deploy_dir = flatpak_dir_get_if_deployed (system, ref, NULL, NULL);
@@ -667,7 +669,8 @@ handle_deploy (FlatpakSystemHelper *object,
if (!flatpak_dir_deploy_install (system, ref, arg_origin,
(const char **) arg_subpaths,
(const char **) arg_previous_ids,
reinstall, update_pinned, NULL, &error))
reinstall, update_pinned, update_preinstalled,
NULL, &error))
{
flatpak_invocation_return_error (invocation, error, "Error deploying");
return G_DBUS_METHOD_INVOCATION_HANDLED;

View File

@@ -105,6 +105,7 @@ export FLATPAK_SYSTEM_DIR=${TEST_DATA_DIR}/system
export FLATPAK_SYSTEM_CACHE_DIR=${TEST_DATA_DIR}/system-cache
export FLATPAK_SYSTEM_HELPER_ON_SESSION=1
export FLATPAK_CONFIG_DIR=${TEST_DATA_DIR}/config
export FLATPAK_DATA_DIR=${TEST_DATA_DIR}/datadir
export FLATPAK_RUN_DIR=${TEST_DATA_DIR}/run
export FLATPAK_FANCY_OUTPUT=0
export FLATPAK_FORCE_ALLOW_FUZZY_MATCHING=1

View File

@@ -71,7 +71,7 @@ for cmd in install update uninstall list info config repair create-usb \
remote-modify remote-delete remote-ls remote-info build-init \
build build-finish build-export build-bundle build-import-bundle \
build-sign build-update-repo build-commit-from repo kill history \
mask;
mask preinstall;
do
${FLATPAK} $cmd --help > help_out
head -2 help_out > help_out2

View File

@@ -45,3 +45,4 @@ wrapped_tests += {'name' : 'test-unused.sh', 'script' : 'test-unused.sh'}
wrapped_tests += {'name' : 'test-prune.sh', 'script' : 'test-prune.sh'}
wrapped_tests += {'name' : 'test-seccomp.sh', 'script' : 'test-seccomp.sh'}
wrapped_tests += {'name' : 'test-repair.sh', 'script' : 'test-repair.sh'}
wrapped_tests += {'name' : 'test-preinstall.sh', 'script' : 'test-preinstall.sh'}

254
tests/test-preinstall.sh Executable file
View File

@@ -0,0 +1,254 @@
#!/bin/bash
#
# Copyright (C) 2025 Red Hat, Inc
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
set -euo pipefail
USE_COLLECTIONS_IN_SERVER=yes
USE_COLLECTIONS_IN_CLIENT=yes
. $(dirname $0)/libtest.sh
mkdir -p $FLATPAK_DATA_DIR/preinstall.d
mkdir -p $FLATPAK_CONFIG_DIR/preinstall.d
cat << EOF > hello-install.preinstall
[Flatpak Preinstall org.test.Hello]
EOF
cat << EOF > hello-not-install.preinstall
[Flatpak Preinstall org.test.Hello]
Install=false
EOF
cat << EOF > hello-install-multi.preinstall
[Flatpak Preinstall org.test.Hello]
[Flatpak Preinstall org.test.Hello2]
CollectionID=org.test.Collection.test
EOF
cat << EOF > hello-install-devel.preinstall
[Flatpak Preinstall org.test.Hello]
Branch=devel
EOF
cat << EOF > hello-install-collection.preinstall
[Flatpak Preinstall org.test.Hello2]
CollectionID=org.test.Collection.test2
EOF
cat << EOF > bad.preinstall
[Wrong Group]
a=b
[Flatpak Preinstall ]
Install=false
[Flatpak Preinstall]
Install=true
EOF
# Set up the runtimes
# org.test.Platform//master and org.test.Platform//devel
# and the apps
# org.test.Hello//master, org.test.Hello//devel,
# org.test.Hello2//master, org.test.Hello2//devel
setup_repo test
make_updated_runtime test org.test.Collection.test devel HELLO_DEVEL org.test.Hello
make_updated_app test org.test.Collection.test devel HELLO_DEVEL org.test.Hello
make_updated_app test org.test.Collection.test master HELLO2_MASTER org.test.Hello2
make_updated_app test org.test.Collection.test devel HELLO2_DEVEL org.test.Hello2
setup_repo test2
make_updated_app test2 org.test.Collection.test2 master HELLO2_MASTER_C2 org.test.Hello2
echo "1..10"
# just checking that the test remote got added
port=$(cat httpd-port)
assert_remote_has_config test-repo url "http://127.0.0.1:${port}/test"
assert_remote_has_config test2-repo url "http://127.0.0.1:${port}/test2"
ok "setup"
# if we have nothing configured and nothing is marked as preinstalled
# calling preinstall should be a no-op
${FLATPAK} ${U} preinstall -y > nothingtodo
assert_file_has_content nothingtodo "Nothing to do"
ok "no config"
# make sure nothing is installed
${FLATPAK} ${U} list --columns=ref > list-log
assert_file_empty list-log
! ostree config --repo=$XDG_DATA_HOME/flatpak/repo get --group "core" xa.preinstalled &> /dev/null
# The preinstall config wants org.test.Hello.
cp hello-install.preinstall $FLATPAK_DATA_DIR/preinstall.d/
${FLATPAK} ${U} preinstall -y >&2
# Make sure it and the runtime were installed
${FLATPAK} ${U} list --columns=ref > list-log
assert_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
ostree config --repo=$XDG_DATA_HOME/flatpak/repo get --group "core" xa.preinstalled > marked-preinstalled
assert_file_has_content marked-preinstalled "^app/org\.test\.Hello/.*/master$"
ok "simple preinstall"
# Make sure calling preinstall with the same config again is a no-op...
${FLATPAK} ${U} preinstall -y > nothingtodo
assert_file_has_content nothingtodo "Nothing to do"
# ...and everything is still installed
${FLATPAK} ${U} list --columns=ref > list-log
assert_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
ok "simple preinstall no op"
${FLATPAK} ${U} uninstall -y org.test.Hello >&2
${FLATPAK} ${U} list --columns=ref > list-log
assert_not_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
# Make sure calling preinstall with the same config again is a no-op
# Even if the user uninstalled the app (it is marked as preinstalled)
${FLATPAK} ${U} preinstall -y > nothingtodo
assert_file_has_content nothingtodo "Nothing to do"
# Make sure nothing has changed
${FLATPAK} ${U} list --columns=ref > list-log
assert_not_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
ok "uninstall preinstall"
${FLATPAK} ${U} install test-repo -y org.test.Hello master >&2
${FLATPAK} ${U} list --columns=ref > list-log
assert_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
# Add a config to /etc which overwrites the config in /usr ($FLATPAK_DATA_DIR)
# It has the Install=false setting which means it shall not be installed.
cp hello-not-install.preinstall $FLATPAK_CONFIG_DIR/preinstall.d/
${FLATPAK} ${U} preinstall -y >&2
# Make sure preinstall removed org.test.Hello as indicated by the config
${FLATPAK} ${U} list --columns=ref > list-log
assert_not_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
ok "preinstall install false"
# Remove the existing configs
rm -rf $FLATPAK_CONFIG_DIR/preinstall.d/*
rm -rf $FLATPAK_DATA_DIR/preinstall.d/*
# Add a config file which wants org.test.Hello and org.test.Hello2 installed
cp hello-install-multi.preinstall $FLATPAK_DATA_DIR/preinstall.d/
${FLATPAK} ${U} preinstall -y >&2
# Make sure both apps got installed
${FLATPAK} ${U} list --columns=ref > list-log
assert_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Platform/.*/devel$"
if have_working_bwrap; then
# Also make sure we installed the app from the right CollectionID
${FLATPAK} run org.test.Hello2 > hello2-output
assert_file_has_content hello2-output "HELLO2_MASTER$"
fi
ok "install multi"
# Overwrite the branch of org.test.Hello from master to devel
cp hello-install-devel.preinstall $FLATPAK_CONFIG_DIR/preinstall.d/
${FLATPAK} ${U} preinstall -y >&2
# Make sure org.test.Hello//devel replaced org.test.Hello//master
${FLATPAK} ${U} list --columns=ref > list-log
assert_not_file_has_content list-log "^org\.test\.Hello/.*/master$"
assert_file_has_content list-log "^org\.test\.Hello/.*/devel$"
assert_file_has_content list-log "^org\.test\.Hello2/.*/master$"
assert_not_file_has_content list-log "^org\.test\.Hello2/.*/devel$"
assert_file_has_content list-log "^org\.test\.Platform/.*/master$"
assert_file_has_content list-log "^org\.test\.Platform/.*/devel$"
ok "overwrite branch"
# Overwrite the CollectionID we're installing org.test.Hello2 from
cp hello-install-collection.preinstall $FLATPAK_CONFIG_DIR/preinstall.d/
# Changing the collection id doesn't automatically change apps over so we need
# to uninstall and mark it as not pre-installed
${FLATPAK} ${U} uninstall -y org.test.Hello2 >&2
ostree config --repo=$XDG_DATA_HOME/flatpak/repo unset --group "core" xa.preinstalled
${FLATPAK} ${U} preinstall -y >&2
if have_working_bwrap; then
# Make sure the app with the right CollectionID got installed
${FLATPAK} run org.test.Hello2 > hello2-output
assert_file_has_content hello2-output "HELLO2_MASTER_C2$"
fi
ok "change collection id"
# Make sure some config file parsing edge cases don't blow up
cp bad.preinstall $FLATPAK_CONFIG_DIR/preinstall.d/
${FLATPAK} ${U} preinstall -y > nothingtodo
assert_file_has_content nothingtodo "Nothing to do"
ok "bad config"