diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c index f29f1e16..a8948224 100644 --- a/app/flatpak-builtins-install.c +++ b/app/flatpak-builtins-install.c @@ -45,6 +45,7 @@ static gboolean opt_no_related; static gboolean opt_runtime; static gboolean opt_app; static gboolean opt_bundle; +static gboolean opt_from; static GOptionEntry options[] = { { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to install for"), N_("ARCH") }, @@ -54,6 +55,7 @@ static GOptionEntry options[] = { { "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL }, { "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL }, { "bundle", 0, 0, G_OPTION_ARG_NONE, &opt_bundle, N_("Install from local bundle file"), NULL }, + { "from", 0, 0, G_OPTION_ARG_NONE, &opt_from, N_("Load options from file"), N_("FILE") }, { "gpg-file", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_gpg_file, N_("Check bundle signatures with GPG key from FILE (- for stdin)"), N_("FILE") }, { "subpath", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_subpaths, N_("Only install this subpath"), N_("PATH") }, { NULL } @@ -101,7 +103,7 @@ read_gpg_data (GCancellable *cancellable, return flatpak_read_stream (source_stream, FALSE, error); } -gboolean +static gboolean install_bundle (FlatpakDir *dir, GOptionContext *context, int argc, char **argv, @@ -135,51 +137,24 @@ install_bundle (FlatpakDir *dir, return TRUE; } -gboolean -flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error) +static gboolean +do_install (FlatpakDir *dir, + gboolean no_pull, + gboolean no_deploy, + const char *ref, + const char *remote_name, + const char **opt_subpaths, + GCancellable *cancellable, + GError **error) { - g_autoptr(GOptionContext) context = NULL; - g_autoptr(FlatpakDir) dir = NULL; - const char *repository; - char *name; - char *branch = NULL; - g_autofree char *ref = NULL; - gboolean is_app; g_autoptr(GPtrArray) related = NULL; int i; - context = g_option_context_new (_("REPOSITORY NAME [BRANCH] - Install an application or runtime")); - g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); - - if (!flatpak_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error)) - return FALSE; - - if (opt_bundle) - return install_bundle (dir, context, argc, argv, cancellable, error); - - if (argc < 3) - return usage_error (context, _("REPOSITORY and NAME must be specified"), error); - - repository = argv[1]; - name = argv[2]; - if (argc >= 4) - branch = argv[3]; - - if (!flatpak_split_partial_ref_arg (name, &opt_arch, &branch, error)) - return FALSE; - - if (!opt_app && !opt_runtime) - opt_app = opt_runtime = TRUE; - - ref = flatpak_dir_find_remote_ref (dir, repository, name, branch, opt_arch, - opt_app, opt_runtime, &is_app, cancellable, error); - if (ref == NULL) - return FALSE; - if (!flatpak_dir_install (dir, opt_no_pull, opt_no_deploy, - ref, repository, (const char **)opt_subpaths, + ref, remote_name, + (const char **)opt_subpaths, NULL, cancellable, error)) return FALSE; @@ -189,9 +164,9 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro g_autoptr(GError) local_error = NULL; if (opt_no_pull) - related = flatpak_dir_find_local_related (dir, ref, repository, NULL, &local_error); + related = flatpak_dir_find_local_related (dir, ref, remote_name, NULL, &local_error); else - related = flatpak_dir_find_remote_related (dir, ref, repository, NULL, &local_error); + related = flatpak_dir_find_remote_related (dir, ref, remote_name, NULL, &local_error); if (related == NULL) { g_printerr (_("Warning: Problem looking for related refs: %s\n"), local_error->message); @@ -214,7 +189,7 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro if (!flatpak_dir_install_or_update (dir, opt_no_pull, opt_no_deploy, - rel->ref, repository, + rel->ref, remote_name, (const char **)rel->subpaths, NULL, cancellable, &local_error)) @@ -230,6 +205,110 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro return TRUE; } +static gboolean +install_from (FlatpakDir *dir, + GOptionContext *context, + int argc, char **argv, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) file = NULL; + g_autoptr(GBytes) file_data = NULL; + g_autofree char *data = NULL; + gsize data_len; + const char *filename; + g_autofree char *remote = NULL; + g_autofree char *ref = NULL; + g_auto(GStrv) parts = NULL; + FlatpakDir *clone; + + if (argc < 2) + return usage_error (context, _("Filename must be specified"), error); + + filename = argv[1]; + + file = g_file_new_for_commandline_arg (filename); + + if (!g_file_load_contents (file, cancellable, &data, &data_len, NULL, error)) + return FALSE; + + file_data = g_bytes_new_take (g_steal_pointer (&data), data_len); + + if (!flatpak_dir_create_remote_for_ref_file (dir, file_data, &remote, &ref, error)) + return FALSE; + + /* Need to pick up the new config, in case it was applied in the system helper. */ + clone = flatpak_dir_clone (dir); + if (!flatpak_dir_ensure_repo (clone, cancellable, error)) + return FALSE; + + parts = g_strsplit (ref, "/", 0); + g_print (_("Installing: %s\n"), parts[1]); + + if (!do_install (clone, + opt_no_pull, + opt_no_deploy, + ref, remote, + (const char **)opt_subpaths, + cancellable, error)) + return FALSE; + + return TRUE; +} + +gboolean +flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error) +{ + g_autoptr(GOptionContext) context = NULL; + g_autoptr(FlatpakDir) dir = NULL; + const char *repository; + char *name; + char *branch = NULL; + g_autofree char *ref = NULL; + gboolean is_app; + + context = g_option_context_new (_("REPOSITORY NAME [BRANCH] - Install an application or runtime")); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + + if (!flatpak_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error)) + return FALSE; + + if (opt_bundle) + return install_bundle (dir, context, argc, argv, cancellable, error); + + if (opt_from) + return install_from (dir, context, argc, argv, cancellable, error); + + if (argc < 3) + return usage_error (context, _("REPOSITORY and NAME must be specified"), error); + + repository = argv[1]; + name = argv[2]; + if (argc >= 4) + branch = argv[3]; + + if (!flatpak_split_partial_ref_arg (name, &opt_arch, &branch, error)) + return FALSE; + + if (!opt_app && !opt_runtime) + opt_app = opt_runtime = TRUE; + + ref = flatpak_dir_find_remote_ref (dir, repository, name, branch, opt_arch, + opt_app, opt_runtime, &is_app, cancellable, error); + if (ref == NULL) + return FALSE; + + if (!do_install (dir, + opt_no_pull, + opt_no_deploy, + ref, repository, + (const char **)opt_subpaths, + cancellable, error)) + return FALSE; + + return TRUE; +} + gboolean flatpak_complete_install (FlatpakCompletion *completion) { diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index 3afcbe3a..96bad3c8 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -4721,25 +4721,20 @@ cmp_remote (gconstpointer a, return prio_b - prio_a; } -char * -flatpak_dir_create_origin_remote (FlatpakDir *self, - const char *url, - const char *id, - const char *title, - GBytes *gpg_data, - GCancellable *cancellable, - GError **error) +static char * +create_origin_remote_config (OstreeRepo *repo, + const char *url, + const char *id, + const char *title, + GKeyFile *new_config) { g_autofree char *remote = NULL; - g_auto(GStrv) remotes = NULL; int version = 0; g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autofree char *group = NULL; - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - remotes = ostree_repo_remote_list (self->repo, NULL); + remotes = ostree_repo_remote_list (repo, NULL); do { @@ -4756,38 +4751,161 @@ flatpak_dir_create_origin_remote (FlatpakDir *self, } while (remote == NULL); - g_variant_builder_add (optbuilder, "{s@v}", - "xa.title", - g_variant_new_variant (g_variant_new_string (title))); + group = g_strdup_printf ("remote \"%s\"", remote); - g_variant_builder_add (optbuilder, "{s@v}", - "xa.noenumerate", - g_variant_new_variant (g_variant_new_boolean (TRUE))); - - g_variant_builder_add (optbuilder, "{s@v}", - "xa.prio", - g_variant_new_variant (g_variant_new_string ("0"))); - - if (!ostree_repo_remote_add (self->repo, - remote, url ? url : "", g_variant_builder_end (optbuilder), cancellable, error)) - return NULL; - - if (gpg_data) - { - g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data); - - if (!ostree_repo_remote_gpg_import (self->repo, remote, gpg_data_as_stream, - NULL, NULL, cancellable, error)) - { - ostree_repo_remote_delete (self->repo, remote, - NULL, NULL); - return NULL; - } - } + g_key_file_set_string (new_config, group, "url", url ? url : ""); + g_key_file_set_string (new_config, group, "xa.title", title); + g_key_file_set_string (new_config, group, "xa.noenumerate", "true"); + g_key_file_set_string (new_config, group, "xa.prio", "0"); + g_key_file_set_string (new_config, group, "gpg-verify", "true"); + g_key_file_set_string (new_config, group, "gpg-verify-summary", "true"); return g_steal_pointer (&remote); } +char * +flatpak_dir_create_origin_remote (FlatpakDir *self, + const char *url, + const char *id, + const char *title, + GBytes *gpg_data, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GKeyFile) new_config = g_key_file_new (); + g_autofree char *remote = NULL; + + remote = create_origin_remote_config (self->repo, url, id, title, new_config); + + if (!flatpak_dir_modify_remote (self, remote, new_config, + gpg_data, cancellable, error)) + return NULL; + + return g_steal_pointer (&remote); +} + +static gboolean +parse_ref_file (GBytes *data, + char **name_out, + char **branch_out, + char **url_out, + char **title_out, + GBytes **gpg_data_out, + gboolean *is_runtime_out, + GError **error) +{ + g_autoptr(GKeyFile) keyfile = g_key_file_new (); + g_autofree char *url = NULL; + g_autofree char *title = NULL; + g_autofree char *name = NULL; + g_autofree char *branch = NULL; + g_autoptr(GBytes) gpg_data = NULL; + gboolean is_runtime = FALSE; + char *str; + + *name_out = NULL; + *branch_out = NULL; + *url_out = NULL; + *title_out = NULL; + *gpg_data_out = NULL; + *is_runtime_out = FALSE; + + if (!g_key_file_load_from_data (keyfile, g_bytes_get_data (data, NULL), g_bytes_get_size (data), + 0, error)) + return FALSE; + + if (!g_key_file_has_group (keyfile, FLATPAK_REF_GROUP)) + return flatpak_fail (error, "Invalid file format, no %s group", FLATPAK_REF_GROUP); + + url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_URL_KEY, NULL); + if (url == NULL) + return flatpak_fail (error, "Invalid file format, no Url specified"); + + name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_NAME_KEY, NULL); + if (name == NULL) + return flatpak_fail (error, "Invalid file format, no Name specified"); + + branch = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_BRANCH_KEY, NULL); + if (branch == NULL) + branch = g_strdup ("master"); + + title = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_TITLE_KEY, NULL); + + is_runtime = g_key_file_get_boolean (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_IS_RUNTIME_KEY, NULL); + + str = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_GPGKEY_KEY, NULL); + if (str != NULL) + { + guchar *decoded; + gsize decoded_len; + + str = g_strstrip (str); + decoded = g_base64_decode (str, &decoded_len); + if (decoded_len < 10) /* Check some minimal size so we don't get crap */ + return flatpak_fail (error, "Invalid file format, gpg key invalid"); + + gpg_data = g_bytes_new_take (decoded, decoded_len); + } + + *name_out = g_steal_pointer (&name); + *branch_out = g_steal_pointer (&branch); + *url_out = g_steal_pointer (&url); + *title_out = g_steal_pointer (&title); + *gpg_data_out = g_steal_pointer (&gpg_data); + *is_runtime_out = is_runtime; + + return TRUE; +} + +gboolean +flatpak_dir_create_remote_for_ref_file (FlatpakDir *self, + GBytes *data, + char **remote_name_out, + char **ref_out, + GError **error) +{ + g_autoptr(GBytes) gpg_data = NULL; + g_autofree char *name = NULL; + g_autofree char *branch = NULL; + g_autofree char *url = NULL; + g_autofree char *title = NULL; + g_autofree char *ref = NULL; + g_autofree char *remote = NULL; + gboolean is_runtime = FALSE; + g_autoptr(GFile) deploy_dir = NULL; + + if (!parse_ref_file (data, &name, &branch, &url, &title, &gpg_data, &is_runtime, error)) + return FALSE; + + ref = flatpak_compose_ref (!is_runtime, name, branch, NULL, error); + if (ref == NULL) + return FALSE; + + deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, NULL); + if (deploy_dir != NULL) + { + g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED, + is_runtime ? _("Runtime %s, branch %s is already installed") + : _("App %s, branch %s is already installed"), + name, branch); + return FALSE; + } + + remote = flatpak_dir_create_origin_remote (self, url, name, title, + gpg_data, NULL, error); + if (remote == NULL) + return FALSE; + + *remote_name_out = g_steal_pointer (&remote); + *ref_out = (char *)g_steal_pointer (&ref); + return TRUE; +} char ** flatpak_dir_list_remotes (FlatpakDir *self, diff --git a/common/flatpak-dir.h b/common/flatpak-dir.h index 342676e1..a0b1b629 100644 --- a/common/flatpak-dir.h +++ b/common/flatpak-dir.h @@ -37,6 +37,14 @@ GType flatpak_dir_get_type (void); GType flatpak_deploy_get_type (void); +#define FLATPAK_REF_GROUP "Flatpak Ref" +#define FLATPAK_REF_URL_KEY "Url" +#define FLATPAK_REF_TITLE_KEY "Title" +#define FLATPAK_REF_GPGKEY_KEY "GPGKey" +#define FLATPAK_REF_IS_RUNTIME_KEY "IsRuntime" +#define FLATPAK_REF_NAME_KEY "Name" +#define FLATPAK_REF_BRANCH_KEY "Branch" + typedef struct { char *ref; @@ -380,6 +388,12 @@ char *flatpak_dir_create_origin_remote (FlatpakDir *self, GBytes *gpg_data, GCancellable *cancellable, GError **error); +gboolean flatpak_dir_create_remote_for_ref_file (FlatpakDir *self, + GBytes *data, + char **remote_name_out, + char **ref_out, + GError **error); + char **flatpak_dir_list_remotes (FlatpakDir *self, GCancellable *cancellable, GError **error);