From f786f208ff9874b220f7f7bc69e28f398649b583 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Tue, 11 Nov 2025 21:40:44 +0000 Subject: [PATCH] import UBI flatpak-1.16.0-6.el10 --- .flatpak.metadata | 1 - .gitignore | 2 +- SOURCES/flatpak-1.12.x-CVE-2024-42472.patch | 330 -- ...selinux-Permit-using-systemd-userdbd.patch | 28 - SPECS/flatpak.spec | 588 --- ...d-support-for-preinstalling-flatpaks.patch | 1859 ++++++++ ...-direct-installation-from-oci-images.patch | 4160 +++++++++++++++++ ...nable-collection-ids-for-oci-remotes.patch | 53 + ...-containers-certs-for-oci-registries.patch | 2003 ++++++++ ...n-to-flatpak-image-source-new-remote.patch | 104 + ...ideload-repositories-for-oci-remotes.patch | 1676 +++++++ flatpak.spec | 933 ++++ flatpak.sysusers.conf | 1 + sources | 1 + 14 files changed, 10791 insertions(+), 948 deletions(-) delete mode 100644 .flatpak.metadata delete mode 100644 SOURCES/flatpak-1.12.x-CVE-2024-42472.patch delete mode 100644 SOURCES/flatpak-Revert-selinux-Permit-using-systemd-userdbd.patch delete mode 100644 SPECS/flatpak.spec create mode 100644 flatpak-add-support-for-preinstalling-flatpaks.patch create mode 100644 flatpak-allow-direct-installation-from-oci-images.patch create mode 100644 flatpak-enable-collection-ids-for-oci-remotes.patch create mode 100644 flatpak-implement-etc-containers-certs-for-oci-registries.patch create mode 100644 flatpak-pass-token-to-flatpak-image-source-new-remote.patch create mode 100644 flatpak-support-sideload-repositories-for-oci-remotes.patch create mode 100644 flatpak.spec create mode 100644 flatpak.sysusers.conf create mode 100644 sources diff --git a/.flatpak.metadata b/.flatpak.metadata deleted file mode 100644 index 56a0c50..0000000 --- a/.flatpak.metadata +++ /dev/null @@ -1 +0,0 @@ -41429400eab33868b6c6045fe235e86e1086a056 SOURCES/flatpak-1.12.9.tar.xz diff --git a/.gitignore b/.gitignore index 804ac25..f5bdf52 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/flatpak-1.12.9.tar.xz +flatpak-1.16.0.tar.xz diff --git a/SOURCES/flatpak-1.12.x-CVE-2024-42472.patch b/SOURCES/flatpak-1.12.x-CVE-2024-42472.patch deleted file mode 100644 index 0fc23a5..0000000 --- a/SOURCES/flatpak-1.12.x-CVE-2024-42472.patch +++ /dev/null @@ -1,330 +0,0 @@ -From 8451fa0ae30397b83705a193aa0d3f7752486dda Mon Sep 17 00:00:00 2001 -From: Alexander Larsson -Date: Mon, 3 Jun 2024 12:22:30 +0200 -Subject: [PATCH 1/4] Don't follow symlinks when mounting persisted directories - -These directories are in a location under application control, so we -can't trust them to not be a symlink outside of the files accessibe to -the application. - -Continue to treat --persist=/foo as --persist=foo for backwards compat, -since this is how it (accidentally) worked before, but print a warning. - -Don't allow ".." elements in persist paths: these would not be useful -anyway, and are unlikely to be in use, however they could potentially -be used to confuse the persist path handling. - -This partially addresses CVE-2024-42472. If only one instance of the -malicious or compromised app is run at a time, the vulnerability -is avoided. If two instances can run concurrently, there is a -time-of-check/time-of-use issue remaining, which can only be resolved -with changes to bubblewrap; this will be resolved in a separate commit, -because the bubblewrap dependency might be more difficult to provide in -LTS distributions. - -Helps: CVE-2024-42472, GHSA-7hgv-f2j8-xw87 -[smcv: Make whitespace consistent] -[smcv: Use g_warning() if unable to create --persist paths] -[smcv: Use stat() to detect symlinks and warn about them] -[smcv: Use glnx_steal_fd() for portability to older GLib] -Co-authored-by: Simon McVittie -Signed-off-by: Simon McVittie ---- - common/flatpak-context.c | 108 +++++++++++++++++++++++++++++++++++++-- - 1 file changed, 105 insertions(+), 3 deletions(-) - -diff --git a/common/flatpak-context.c b/common/flatpak-context.c -index 53b79807..8c784acf 100644 ---- a/common/flatpak-context.c -+++ b/common/flatpak-context.c -@@ -2686,6 +2686,90 @@ flatpak_context_get_exports_full (FlatpakContext *context, - return g_steal_pointer (&exports); - } - -+/* This creates zero or more directories unders base_fd+basedir, each -+ * being guaranteed to either exist and be a directory (no symlinks) -+ * or be created as a directory. The last directory is opened -+ * and the fd is returned. -+ */ -+static gboolean -+mkdir_p_open_nofollow_at (int base_fd, -+ const char *basedir, -+ int mode, -+ const char *subdir, -+ int *out_fd, -+ GError **error) -+{ -+ glnx_autofd int parent_fd = -1; -+ -+ if (g_path_is_absolute (subdir)) -+ { -+ const char *skipped_prefix = subdir; -+ -+ while (*skipped_prefix == '/') -+ skipped_prefix++; -+ -+ g_warning ("--persist=\"%s\" is deprecated, treating it as --persist=\"%s\"", subdir, skipped_prefix); -+ subdir = skipped_prefix; -+ } -+ -+ g_autofree char *subdir_dirname = g_path_get_dirname (subdir); -+ -+ if (strcmp (subdir_dirname, ".") == 0) -+ { -+ /* It is ok to open basedir with follow=true */ -+ if (!glnx_opendirat (base_fd, basedir, TRUE, &parent_fd, error)) -+ return FALSE; -+ } -+ else if (strcmp (subdir_dirname, "..") == 0) -+ { -+ return glnx_throw (error, "'..' not supported in --persist paths"); -+ } -+ else -+ { -+ if (!mkdir_p_open_nofollow_at (base_fd, basedir, mode, -+ subdir_dirname, &parent_fd, error)) -+ return FALSE; -+ } -+ -+ g_autofree char *subdir_basename = g_path_get_basename (subdir); -+ -+ if (strcmp (subdir_basename, ".") == 0) -+ { -+ *out_fd = glnx_steal_fd (&parent_fd); -+ return TRUE; -+ } -+ else if (strcmp (subdir_basename, "..") == 0) -+ { -+ return glnx_throw (error, "'..' not supported in --persist paths"); -+ } -+ -+ if (!glnx_shutil_mkdir_p_at (parent_fd, subdir_basename, mode, NULL, error)) -+ return FALSE; -+ -+ int fd = openat (parent_fd, subdir_basename, O_PATH | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); -+ if (fd == -1) -+ { -+ int saved_errno = errno; -+ struct stat stat_buf; -+ -+ /* If it's a symbolic link, that could be a user trying to offload -+ * large data to another filesystem, but it could equally well be -+ * a malicious or compromised app trying to exploit GHSA-7hgv-f2j8-xw87. -+ * Produce a clearer error message in this case. -+ * Unfortunately the errno we get in this case is ENOTDIR, so we have -+ * to ask again to find out whether it's really a symlink. */ -+ if (saved_errno == ENOTDIR && -+ fstatat (parent_fd, subdir_basename, &stat_buf, AT_SYMLINK_NOFOLLOW) == 0 && -+ S_ISLNK (stat_buf.st_mode)) -+ return glnx_throw (error, "Symbolic link \"%s\" not allowed to avoid sandbox escape", subdir_basename); -+ -+ return glnx_throw_errno_prefix (error, "openat(%s)", subdir_basename); -+ } -+ -+ *out_fd = fd; -+ return TRUE; -+} -+ - void - flatpak_context_append_bwrap_filesystem (FlatpakContext *context, - FlatpakBwrap *bwrap, -@@ -2709,12 +2793,30 @@ flatpak_context_append_bwrap_filesystem (FlatpakContext *context, - while (g_hash_table_iter_next (&iter, &key, NULL)) - { - const char *persist = key; -- g_autofree char *src = g_build_filename (g_get_home_dir (), ".var/app", app_id, persist, NULL); -+ g_autofree char *appdir = g_build_filename (g_get_home_dir (), ".var/app", app_id, NULL); - g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL); -+ g_autoptr(GError) local_error = NULL; -+ -+ if (g_mkdir_with_parents (appdir, 0755) != 0) -+ { -+ g_warning ("Unable to create directory %s", appdir); -+ continue; -+ } -+ -+ /* Don't follow symlinks from the persist directory, as it is under user control */ -+ glnx_autofd int src_fd = -1; -+ if (!mkdir_p_open_nofollow_at (AT_FDCWD, appdir, 0755, -+ persist, &src_fd, -+ &local_error)) -+ { -+ g_warning ("Failed to create persist path %s: %s", persist, local_error->message); -+ continue; -+ } - -- g_mkdir_with_parents (src, 0755); -+ g_autofree char *src_via_proc = g_strdup_printf ("/proc/self/fd/%d", src_fd); - -- flatpak_bwrap_add_bind_arg (bwrap, "--bind", src, dest); -+ flatpak_bwrap_add_fd (bwrap, glnx_steal_fd (&src_fd)); -+ flatpak_bwrap_add_bind_arg (bwrap, "--bind", src_via_proc, dest); - } - } - --- -2.46.0 - - -From 5462c9b1e1a34b1104c8a0843a10382e90c9bb6b Mon Sep 17 00:00:00 2001 -From: Alexander Larsson -Date: Mon, 3 Jun 2024 12:59:05 +0200 -Subject: [PATCH 2/4] Add test coverage for --persist - -This adds three "positive" tests: the common case --persist=.persist, the -deprecated spelling --persist=/.persist, and the less common special case ---persist=. as used by Steam. - -It also adds "negative" tests for CVE-2024-42472: if the --persist -directory is a symbolic link or contains path segment "..", we want that -to be rejected. - -Reproduces: CVE-2024-42472, GHSA-7hgv-f2j8-xw87 -[smcv: Add "positive" tests] -[smcv: Exercise --persist=..] -[smcv: Assert that --persist with a symlink produces expected message] -Co-authored-by: Simon McVittie -Signed-off-by: Simon McVittie ---- - tests/test-run.sh | 41 ++++++++++++++++++++++++++++++++++++++++- - 1 file changed, 40 insertions(+), 1 deletion(-) - -diff --git a/tests/test-run.sh b/tests/test-run.sh -index dd371df3..bca0845d 100644 ---- a/tests/test-run.sh -+++ b/tests/test-run.sh -@@ -24,7 +24,7 @@ set -euo pipefail - skip_without_bwrap - skip_revokefs_without_fuse - --echo "1..20" -+echo "1..21" - - # Use stable rather than master as the branch so we can test that the run - # command automatically finds the branch correctly -@@ -512,3 +512,42 @@ ${FLATPAK} ${U} info -m org.test.App > out - assert_file_has_content out "^sdk=org\.test\.Sdk/$(flatpak --default-arch)/stable$" - - ok "--sdk option" -+ -+rm -fr "$HOME/.var/app/org.test.Hello" -+mkdir -p "$HOME/.var/app/org.test.Hello" -+run --command=sh --persist=.persist org.test.Hello -c 'echo can-persist > .persist/rc' -+sed -e 's,^,#--persist=.persist# ,g' < "$HOME/.var/app/org.test.Hello/.persist/rc" >&2 -+assert_file_has_content "$HOME/.var/app/org.test.Hello/.persist/rc" "can-persist" -+ -+ok "--persist=.persist persists a directory" -+ -+rm -fr "$HOME/.var/app/org.test.Hello" -+mkdir -p "$HOME/.var/app/org.test.Hello" -+# G_DEBUG= to avoid the deprecation warning being fatal -+G_DEBUG= run --command=sh --persist=/.persist org.test.Hello -c 'echo can-persist > .persist/rc' -+sed -e 's,^,#--persist=/.persist# ,g' < "$HOME/.var/app/org.test.Hello/.persist/rc" >&2 -+assert_file_has_content "$HOME/.var/app/org.test.Hello/.persist/rc" "can-persist" -+ -+ok "--persist=/.persist is a deprecated form of --persist=.persist" -+ -+rm -fr "$HOME/.var/app/org.test.Hello" -+mkdir -p "$HOME/.var/app/org.test.Hello" -+run --command=sh --persist=. org.test.Hello -c 'echo can-persist > .persistrc' -+sed -e 's,^,#--persist=.# ,g' < "$HOME/.var/app/org.test.Hello/.persistrc" >&2 -+assert_file_has_content "$HOME/.var/app/org.test.Hello/.persistrc" "can-persist" -+ -+ok "--persist=. persists all files" -+ -+mkdir "${TEST_DATA_DIR}/inaccessible" -+echo FOO > ${TEST_DATA_DIR}/inaccessible/secret-file -+rm -fr "$HOME/.var/app/org.test.Hello" -+mkdir -p "$HOME/.var/app/org.test.Hello" -+ln -fns "${TEST_DATA_DIR}/inaccessible" "$HOME/.var/app/org.test.Hello/persist" -+# G_DEBUG= to avoid the warnings being fatal when we reject a --persist option. -+# LC_ALL=C so we get the expected non-localized string. -+LC_ALL=C G_DEBUG= run --command=ls --persist=persist --persist=relative/../escape org.test.Hello -la ~/persist &> hello_out || true -+sed -e 's,^,#--persist=symlink# ,g' < hello_out >&2 -+assert_file_has_content hello_out "not allowed to avoid sandbox escape" -+assert_not_file_has_content hello_out "secret-file" -+ -+ok "--persist doesn't allow sandbox escape via a symlink (CVE-2024-42472)" --- -2.46.0 - - -From 04d8ad3009cd8a4350fba6cf7cc6c7819ccdfd34 Mon Sep 17 00:00:00 2001 -From: Simon McVittie -Date: Mon, 12 Aug 2024 19:48:18 +0100 -Subject: [PATCH 3/4] build: Require a version of bubblewrap with the --bind-fd - option - -We need this for the --bind-fd option, which will close a race -condition in our solution to CVE-2024-42472. - -For this stable branch, check the --help output for a --bind-fd option -instead of requiring a specific version number, to accommodate possible -backports in LTS distributions. - -Signed-off-by: Simon McVittie ---- - configure.ac | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/configure.ac b/configure.ac -index 0a44e11a..0c8e2d0e 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -175,6 +175,9 @@ if test "x$BWRAP" != xfalse; then - BWRAP_VERSION=`$BWRAP --version | sed 's,.*\ \([0-9]*\.[0-9]*\.[0-9]*\)$,\1,'` - AX_COMPARE_VERSION([$SYSTEM_BWRAP_REQS],[gt],[$BWRAP_VERSION], - [AC_MSG_ERROR([You need at least version $SYSTEM_BWRAP_REQS of bubblewrap to use the system installed version])]) -+ AS_IF([$BWRAP --help | grep '@<:@-@:>@-bind-fd' >/dev/null], -+ [:], -+ [AC_MSG_ERROR([$BWRAP does not list required option --bind-fd in its --help])]) - AM_CONDITIONAL([WITH_SYSTEM_BWRAP], [true]) - else - AC_CHECK_LIB(cap, cap_from_text, CAP_LIB=-lcap) --- -2.46.0 - - -From 2772f19e50c0e809dde8cf3c105d90ee8baf4fa8 Mon Sep 17 00:00:00 2001 -From: Simon McVittie -Date: Wed, 14 Aug 2024 13:44:30 +0100 -Subject: [PATCH 4/4] persist directories: Pass using new bwrap --bind-fd - option - -Instead of passing a /proc/self/fd bind mount we use --bind-fd, which -has two advantages: - * bwrap closes the fd when used, so it doesn't leak into the started app - * bwrap ensures that what was mounted was the passed in fd (same dev/ino), - as there is a small (required) gap between symlink resolve and mount - where the target path could be replaced. - -Please note that this change requires an updated version of bubblewrap. - -Resolves: CVE-2024-42472, GHSA-7hgv-f2j8-xw87 -[smcv: Make whitespace consistent] -Co-authored-by: Simon McVittie -Signed-off-by: Simon McVittie ---- - common/flatpak-context.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/common/flatpak-context.c b/common/flatpak-context.c -index 8c784acf..baa62728 100644 ---- a/common/flatpak-context.c -+++ b/common/flatpak-context.c -@@ -2813,10 +2813,10 @@ flatpak_context_append_bwrap_filesystem (FlatpakContext *context, - continue; - } - -- g_autofree char *src_via_proc = g_strdup_printf ("/proc/self/fd/%d", src_fd); -+ g_autofree char *src_via_proc = g_strdup_printf ("%d", src_fd); - - flatpak_bwrap_add_fd (bwrap, glnx_steal_fd (&src_fd)); -- flatpak_bwrap_add_bind_arg (bwrap, "--bind", src_via_proc, dest); -+ flatpak_bwrap_add_bind_arg (bwrap, "--bind-fd", src_via_proc, dest); - } - } - --- -2.46.0 - diff --git a/SOURCES/flatpak-Revert-selinux-Permit-using-systemd-userdbd.patch b/SOURCES/flatpak-Revert-selinux-Permit-using-systemd-userdbd.patch deleted file mode 100644 index 8c9dd9f..0000000 --- a/SOURCES/flatpak-Revert-selinux-Permit-using-systemd-userdbd.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 1c73110795b865246ce3595042dcd2d5e7891359 Mon Sep 17 00:00:00 2001 -From: Debarshi Ray -Date: Mon, 6 Nov 2023 20:27:16 +0100 -Subject: [PATCH] Revert "selinux: Permit using systemd-userdbd" - -This reverts commit 399710ada185c1ee232bc3e6266a71688eb152b7. ---- - selinux/flatpak.te | 4 ---- - 1 file changed, 4 deletions(-) - -diff --git a/selinux/flatpak.te b/selinux/flatpak.te -index bb3d80e316eb..4cf895c44abe 100644 ---- a/selinux/flatpak.te -+++ b/selinux/flatpak.te -@@ -33,10 +33,6 @@ optional_policy(` - policykit_dbus_chat(flatpak_helper_t) - ') - --optional_policy(` -- systemd_userdbd_stream_connect(flatpak_helper_t) --') -- - optional_policy(` - unconfined_domain(flatpak_helper_t) - ') --- -2.41.0 - diff --git a/SPECS/flatpak.spec b/SPECS/flatpak.spec deleted file mode 100644 index 452dca7..0000000 --- a/SPECS/flatpak.spec +++ /dev/null @@ -1,588 +0,0 @@ -%global bubblewrap_version 0.4.0-2 -%global ostree_version 2020.8 - -Name: flatpak -Version: 1.12.9 -Release: 3%{?dist} -Summary: Application deployment framework for desktop apps - -License: LGPLv2+ -URL: http://flatpak.org/ -Source0: https://github.com/flatpak/flatpak/releases/download/%{version}/%{name}-%{version}.tar.xz - -%if 0%{?fedora} -# Add Fedora flatpak repositories -Source1: flatpak-add-fedora-repos.service -%endif - -# https://issues.redhat.com/browse/RHEL-4220 -Patch0: flatpak-Revert-selinux-Permit-using-systemd-userdbd.patch - -# Backported upstream patch for CVE-2024-42472 -Patch1: flatpak-1.12.x-CVE-2024-42472.patch - -BuildRequires: pkgconfig(appstream-glib) -BuildRequires: pkgconfig(dconf) -BuildRequires: pkgconfig(fuse) -BuildRequires: pkgconfig(gdk-pixbuf-2.0) -BuildRequires: pkgconfig(gio-unix-2.0) -BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.40.0 -BuildRequires: pkgconfig(gpgme) -BuildRequires: pkgconfig(json-glib-1.0) -BuildRequires: pkgconfig(libarchive) >= 2.8.0 -BuildRequires: pkgconfig(libseccomp) -BuildRequires: pkgconfig(libsoup-2.4) -BuildRequires: pkgconfig(libsystemd) -BuildRequires: pkgconfig(libxml-2.0) >= 2.4 -BuildRequires: pkgconfig(libzstd) >= 0.8.1 -BuildRequires: pkgconfig(ostree-1) >= %{ostree_version} -BuildRequires: pkgconfig(polkit-gobject-1) -BuildRequires: pkgconfig(xau) -BuildRequires: bison -BuildRequires: bubblewrap >= %{bubblewrap_version} -BuildRequires: docbook-dtds -BuildRequires: docbook-style-xsl -BuildRequires: gettext -BuildRequires: libassuan-devel -BuildRequires: libcap-devel -BuildRequires: python3-devel -BuildRequires: python3-pyparsing -BuildRequires: systemd -BuildRequires: /usr/bin/xmlto -BuildRequires: /usr/bin/xsltproc - -Requires: bubblewrap >= %{bubblewrap_version} -Requires: librsvg2%{?_isa} -Requires: ostree-libs%{?_isa} >= %{ostree_version} -# https://fedoraproject.org/wiki/SELinux/IndependentPolicy -Requires: (flatpak-selinux = %{?epoch:%{epoch}:}%{version}-%{release} if selinux-policy-targeted) -Requires: %{name}-session-helper%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} -Recommends: p11-kit-server - -# Make sure the document portal is installed -%if 0%{?fedora} || 0%{?rhel} > 7 -Recommends: xdg-desktop-portal > 0.10 -# Remove in F30. -Conflicts: xdg-desktop-portal < 0.10 -%else -Requires: xdg-desktop-portal > 0.10 -%endif - -%description -flatpak is a system for building, distributing and running sandboxed desktop -applications on Linux. See https://wiki.gnome.org/Projects/SandboxedApps for -more information. - -%package devel -Summary: Development files for %{name} -License: LGPLv2+ -Requires: %{name}%{?_isa} = %{version}-%{release} -Requires: %{name}-libs%{?_isa} = %{version}-%{release} - -%description devel -This package contains the pkg-config file and development headers for %{name}. - -%package libs -Summary: Libraries for %{name} -License: LGPLv2+ -Requires: bubblewrap >= %{bubblewrap_version} -Requires: ostree%{?_isa} >= %{ostree_version} -Requires(pre): /usr/sbin/useradd - -%description libs -This package contains libflatpak. - -%package selinux -Summary: SELinux policy module for %{name} -License: LGPLv2+ -BuildRequires: selinux-policy -BuildRequires: selinux-policy-devel -BuildArch: noarch -%{?selinux_requires} - -%description selinux -This package contains the SELinux policy module for %{name}. - -%package session-helper -Summary: User D-Bus service used by %{name} and others -License: LGPLv2+ -Conflicts: flatpak < 1.4.1-2 -Requires: systemd - -%description session-helper -This package contains the org.freedesktop.Flatpak user D-Bus service -that's used by %{name} and other packages. - -%package tests -Summary: Tests for %{name} -License: LGPLv2+ -Requires: %{name}%{?_isa} = %{version}-%{release} -Requires: %{name}-libs%{?_isa} = %{version}-%{release} -Requires: %{name}-session-helper%{?_isa} = %{version}-%{release} -Requires: bubblewrap >= %{bubblewrap_version} -Requires: ostree%{?_isa} >= %{ostree_version} - -%description tests -This package contains installed tests for %{name}. - - -%prep -%autosetup -p1 -# Make sure to use the RHEL-lifetime supported Python and no other -%py3_shebang_fix scripts/* subprojects/variant-schema-compiler/* tests/* - - -%build -(if ! test -x configure; then NOCONFIGURE=1 ./autogen.sh; CONFIGFLAGS=--enable-gtk-doc; fi; - # Generate consistent IDs between runs to avoid multilib problems. - export XMLTO_FLAGS="--stringparam generate.consistent.ids=1" - %configure \ - --enable-docbook-docs \ - --enable-installed-tests \ - --enable-selinux-module \ - --with-priv-mode=none \ - --with-system-bubblewrap \ - $CONFIGFLAGS) -%make_build V=1 - - -%install -%make_install -install -pm 644 NEWS README.md %{buildroot}/%{_pkgdocdir} -# The system repo is not installed by the flatpak build system. -install -d %{buildroot}%{_localstatedir}/lib/flatpak -install -d %{buildroot}%{_sysconfdir}/flatpak/remotes.d -rm -f %{buildroot}%{_libdir}/libflatpak.la - -%if 0%{?fedora} -install -D -t %{buildroot}%{_unitdir} %{SOURCE1} -%endif - -%find_lang %{name} - -# Work around selinux denials, see -# https://github.com/flatpak/flatpak/issues/4128 for details. Note that we are -# going to need the system env generator if we should enable malcontent support -# in the future. -rm %{buildroot}%{_systemd_system_env_generator_dir}/60-flatpak-system-only - -%pre -getent group flatpak >/dev/null || groupadd -r flatpak -getent passwd flatpak >/dev/null || \ - useradd -r -g flatpak -d / -s /sbin/nologin \ - -c "User for flatpak system helper" flatpak -exit 0 - - -%if 0%{?fedora} -%post -%systemd_post flatpak-add-fedora-repos.service -%endif - - -%post selinux -%selinux_modules_install %{_datadir}/selinux/packages/flatpak.pp.bz2 - - -%if 0%{?fedora} -%preun -%systemd_preun flatpak-add-fedora-repos.service -%endif - - -%if 0%{?fedora} -%postun -%systemd_postun_with_restart flatpak-add-fedora-repos.service -%endif - - -%postun selinux -if [ $1 -eq 0 ]; then - %selinux_modules_uninstall %{_datadir}/selinux/packages/flatpak.pp.bz2 -fi - - -%ldconfig_scriptlets libs - - -%files -f %{name}.lang -%license COPYING -# Comply with the packaging guidelines about not mixing relative and absolute -# paths in doc. -%doc %{_pkgdocdir} -%{_bindir}/flatpak -%{_bindir}/flatpak-bisect -%{_bindir}/flatpak-coredumpctl -%{_datadir}/bash-completion -%{_datadir}/dbus-1/interfaces/org.freedesktop.portal.Flatpak.xml -%{_datadir}/dbus-1/interfaces/org.freedesktop.Flatpak.Authenticator.xml -%{_datadir}/dbus-1/services/org.flatpak.Authenticator.Oci.service -%{_datadir}/dbus-1/services/org.freedesktop.portal.Flatpak.service -%{_datadir}/dbus-1/system-services/org.freedesktop.Flatpak.SystemHelper.service -%{_datadir}/fish -%{_datadir}/%{name} -%{_datadir}/polkit-1/actions/org.freedesktop.Flatpak.policy -%{_datadir}/polkit-1/rules.d/org.freedesktop.Flatpak.rules -%{_datadir}/zsh/site-functions -%{_libexecdir}/flatpak-dbus-proxy -%{_libexecdir}/flatpak-oci-authenticator -%{_libexecdir}/flatpak-portal -%{_libexecdir}/flatpak-system-helper -%{_libexecdir}/flatpak-validate-icon -%{_libexecdir}/revokefs-fuse -%dir %{_localstatedir}/lib/flatpak -%{_mandir}/man1/%{name}*.1* -%{_mandir}/man5/%{name}-metadata.5* -%{_mandir}/man5/flatpak-flatpakref.5* -%{_mandir}/man5/flatpak-flatpakrepo.5* -%{_mandir}/man5/flatpak-installation.5* -%{_mandir}/man5/flatpak-remote.5* -%{_sysconfdir}/dbus-1/system.d/org.freedesktop.Flatpak.SystemHelper.conf -%dir %{_sysconfdir}/flatpak -%{_sysconfdir}/flatpak/remotes.d -%{_sysconfdir}/profile.d/flatpak.sh -%{_sysusersdir}/flatpak.conf -%{_unitdir}/flatpak-system-helper.service -%{_userunitdir}/flatpak-oci-authenticator.service -%{_userunitdir}/flatpak-portal.service -%{_systemd_user_env_generator_dir}/60-flatpak - -%if 0%{?fedora} -%{_unitdir}/flatpak-add-fedora-repos.service -%endif - -%files devel -%{_datadir}/gir-1.0/Flatpak-1.0.gir -%{_datadir}/gtk-doc/ -%{_includedir}/%{name}/ -%{_libdir}/libflatpak.so -%{_libdir}/pkgconfig/%{name}.pc - -%files libs -%license COPYING -%{_libdir}/girepository-1.0/Flatpak-1.0.typelib -%{_libdir}/libflatpak.so.* - -%files selinux -%{_datadir}/selinux/packages/flatpak.pp.bz2 -%{_datadir}/selinux/devel/include/contrib/flatpak.if - -%files session-helper -%license COPYING -%{_datadir}/dbus-1/interfaces/org.freedesktop.Flatpak.xml -%{_datadir}/dbus-1/services/org.freedesktop.Flatpak.service -%{_libexecdir}/flatpak-session-helper -%{_userunitdir}/flatpak-session-helper.service - -%files tests -%{_datadir}/installed-tests -%{_libexecdir}/installed-tests - - -%changelog -* Wed Sep 04 2024 Kalev Lember - 1.12.9-3 -- Fix previous changelog entry - -* Mon Sep 02 2024 Kalev Lember - 1.12.9-2 -- Backport upstream patches for CVE-2024-42472 -- Require bubblewrap version that has new --bind-fd option backported for - addressing CVE-2024-42472 - -* Tue Apr 30 2024 Kalev Lember - 1.12.9-1 -- Update to 1.12.9 (CVE-2024-32462) - -* Mon Nov 06 2023 Debarshi Ray - 1.12.8-1 -- Rebase to 1.12.8 (RHEL-4220) - -* Mon Nov 06 2023 Debarshi Ray - 1.10.8-3 -- Let flatpak own %%{_sysconfdir}/flatpak (RHEL-15822) - -* Mon Sep 04 2023 Miro Hrončok - 1.10.8-2 -- Make sure to use the RHEL-lifetime supported Python and no other (RHEL-2225) - -* Tue Jul 11 2023 Debarshi Ray - 1.10.8-1 -- Rebase to 1.10.8 (#2222103) -- Fix CVE-2023-28100 and CVE-2023-28101 (#2180311) - -* Wed Mar 09 2022 Debarshi Ray - 1.10.7-1 -- Rebase to 1.10.7 (#2062417) - -* Thu Feb 03 2022 Debarshi Ray - 1.8.7-1 -- Rebase to 1.8.7 (#2041972) - -* Tue Jan 25 2022 Debarshi Ray - 1.8.6-1 -- Rebase to 1.8.6 (#2010533) - -* Tue Oct 26 2021 Debarshi Ray - 1.8.5-6 -- Fix CVE-2021-41133 (#2012869) - -* Tue Oct 05 2021 Debarshi Ray - 1.8.5-5 -- Disable gvfs plugins when listing flatpak installations (#1980438) - -* Wed Jul 28 2021 Tomas Popela - 1.8.5-4 -- Ship flatpak-devel in CRB (#1938064) - -* Mon Mar 22 2021 David King - 1.8.5-3 -- Fix CVE-2021-21381 (#1938064) - -* Mon Jan 25 2021 David King - 1.8.5-2 -- Apply post-release CVE fixes (#1918776) - -* Thu Jan 14 2021 David King - 1.8.5-1 -- Rebase to 1.8.5 (#1851958) - -* Tue Nov 17 2020 David King - 1.8.3-1 -- Rebase to 1.8.3 (#1851958) - -* Mon Oct 05 2020 David King - 1.8.2-1 -- Rebase to 1.8.2 (#1851958) - -* Mon Sep 14 2020 Kalev Lember - 1.6.2-4 -- OCI: extract appstream data for runtimes (#1878231) - -* Wed Jun 17 2020 David King - 1.6.2-3 -- Further fixes for OCI authenticator (#1847201) - -* Fri Mar 20 2020 David King - 1.6.2-2 -- Fixes for OCI authenticator (#1814045) - -* Thu Feb 13 2020 David King - 1.6.2-1 -- Rebase to 1.6.2 (#1775339) - -* Thu Jan 23 2020 David King - 1.6.1-1 -- Rebase to 1.6.1 (#1775339) - -* Fri Jan 17 2020 David King - 1.6.0-2 -- Remove broken python3 sed hack (#1775339) - -* Sat Dec 21 2019 David King - 1.6.0-1 -- Rebase to 1.6.0 (#1775339) - -* Fri Nov 08 2019 David King - 1.4.3-2 -- Use %%{?selinux_requires} for proper install ordering - -* Tue Oct 08 2019 David King - 1.4.3-1 -- Rebase to 1.4.3 (#1748276) - -* Fri Sep 20 2019 Kalev Lember - 1.0.9-1 -- Update to 1.0.9 (#1753613) - -* Tue May 14 2019 David King - 1.0.6-4 -- Bump release (#1700654) - -* Mon Apr 29 2019 David King - 1.0.6-3 -- Fix IOCSTI sandbox bypass (#1700654) - -* Wed Feb 13 2019 David King - 1.0.6-2 -- Do not mount /proc in root sandbox (#1675776) - -* Tue Dec 18 2018 Kalev Lember - 1.0.6-1 -- Update to 1.0.6 (#1630249) -- Recommend p11-kit-server instead of just p11-kit (#1649049) - -* Mon Dec 10 2018 David King - 1.0.4-2 -- Backport patches to improve OCI support (#1657306) - -* Fri Oct 12 2018 Kalev Lember - 1.0.4-1 -- Update to 1.0.4 (#1630249) - -* Thu Sep 13 2018 Kalev Lember - 1.0.2-1 -- Update to 1.0.2 (#1630249) - -* Tue Aug 28 2018 David King - 1.0.1-1 -- Update to 1.0.1 (#1621401) - -* Wed Aug 01 2018 David King - 0.99.3-1 -- Update to 0.99.3 - -* Wed May 23 2018 Adam Jackson - 0.11.7-2 -- Remove Requires: kernel >= 4.0.4-202, which corresponds to rawhide - somewhere before Fedora 22 which this spec file certainly no longer - supports. - -* Thu May 03 2018 Kalev Lember - 0.11.7-1 -- Update to 0.11.7 - -* Wed May 02 2018 Kalev Lember - 0.11.6-1 -- Update to 0.11.6 - -* Wed May 02 2018 Kalev Lember - 0.11.5-2 -- Backport a fix for a gnome-software crash installing .flatpakref files - -* Mon Apr 30 2018 David King - 0.11.5-1 -- Update to 0.11.5 - -* Thu Apr 26 2018 Kalev Lember - 0.11.4-1 -- Update to 0.11.4 - -* Mon Feb 19 2018 David King - 0.11.3-1 -- Update to 0.11.3 - -* Mon Feb 19 2018 David King - 0.11.2-1 -- Update to 0.11.2 - -* Wed Feb 14 2018 David King - 0.11.1-1 -- Update to 0.11.1 (#1545224) - -* Wed Feb 07 2018 Fedora Release Engineering - 0.10.3-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild - -* Fri Feb 02 2018 Igor Gnatenko - 0.10.3-2 -- Switch to %%ldconfig_scriptlets - -* Tue Jan 30 2018 Kalev Lember - 0.10.3-1 -- Update to 0.10.3 - -* Thu Dec 21 2017 David King - 0.10.2.1-1 -- Update to 0.10.2.1 - -* Fri Dec 15 2017 Kalev Lember - 0.10.2-1 -- Update to 0.10.2 - -* Fri Nov 24 2017 David King - 0.10.1-1 -- Update to 0.10.1 - -* Thu Oct 26 2017 Kalev Lember - 0.10.0-1 -- Update to 0.10.0 - -* Mon Oct 09 2017 Kalev Lember - 0.9.99-1 -- Update to 0.9.99 - -* Tue Sep 26 2017 Kalev Lember - 0.9.98.2-1 -- Update to 0.9.98.2 - -* Tue Sep 26 2017 Kalev Lember - 0.9.98.1-1 -- Update to 0.9.98.1 - -* Mon Sep 25 2017 Kalev Lember - 0.9.98-1 -- Update to 0.9.98 - -* Thu Sep 14 2017 Kalev Lember - 0.9.12-1 -- Update to 0.9.12 - -* Wed Sep 13 2017 Kalev Lember - 0.9.11-1 -- Update to 0.9.11 - -* Mon Sep 04 2017 Kalev Lember - 0.9.10-1 -- Update to 0.9.10 -- Split out flatpak-builder to a separate source package - -* Fri Aug 25 2017 Kalev Lember - 0.9.8-2 -- Backport a patch to fix regression in --devel - -* Mon Aug 21 2017 David King - 0.9.8-1 -- Update to 0.9.8 - -* Wed Aug 02 2017 Fedora Release Engineering - 0.9.7-5 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild - -* Sun Jul 30 2017 Florian Weimer - 0.9.7-4 -- Rebuild with binutils fix for ppc64le (#1475636) - -* Thu Jul 27 2017 Owen Taylor - 0.9.7-3 -- Add a patch to fix OCI refname annotation - -* Wed Jul 26 2017 Fedora Release Engineering - 0.9.7-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild - -* Sat Jul 01 2017 David King - 0.9.7-1 -- Update to 0.9.7 (#1466970) - -* Tue Jun 20 2017 David King - 0.9.6-1 -- Update to 0.9.6 - -* Sat Jun 10 2017 David King - 0.9.5-1 -- Update to 0.9.5 (#1460437) - -* Tue May 23 2017 David King - 0.9.4-1 -- Update to 0.9.4 (#1454750) - -* Mon Apr 24 2017 David King - 0.9.3-1 -- Update to 0.9.3 - -* Fri Apr 07 2017 David King - 0.9.2-2 -- Add eu-strip dependency for flatpak-builder - -* Wed Apr 05 2017 Kalev Lember - 0.9.2-1 -- Update to 0.9.2 - -* Wed Mar 15 2017 Kalev Lember - 0.9.1-1 -- Update to 0.9.1 - -* Fri Mar 10 2017 Kalev Lember - 0.8.4-1 -- Update to 0.8.4 - -* Sun Feb 19 2017 David King - 0.8.3-3 -- Make flatpak-builder require bzip2 (#1424857) - -* Wed Feb 15 2017 Kalev Lember - 0.8.3-2 -- Avoid pulling in all of ostree and only depend on ostree-libs subpackage - -* Tue Feb 14 2017 Kalev Lember - 0.8.3-1 -- Update to 0.8.3 - -* Fri Feb 10 2017 Fedora Release Engineering - 0.8.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild - -* Fri Jan 27 2017 Kalev Lember - 0.8.2-1 -- Update to 0.8.2 - -* Wed Jan 18 2017 David King - 0.8.1-1 -- Update to 0.8.1 - -* Tue Dec 20 2016 Kalev Lember - 0.8.0-1 -- Update to 0.8.0 - -* Tue Nov 29 2016 David King - 0.6.14-2 -- Add a patch to fix a GNOME Software crash -- Silence repository listing during post - -* Tue Nov 29 2016 Kalev Lember - 0.6.14-1 -- Update to 0.6.14 - -* Wed Oct 26 2016 David King - 0.6.13-2 -- Add empty /etc/flatpak/remotes.d - -* Tue Oct 25 2016 David King - 0.6.13-1 -- Update to 0.6.13 - -* Thu Oct 06 2016 David King - 0.6.12-1 -- Update to 0.6.12 - -* Tue Sep 20 2016 Kalev Lember - 0.6.11-1 -- Update to 0.6.11 -- Set minimum ostree and bubblewrap versions - -* Mon Sep 12 2016 David King - 0.6.10-1 -- Update to 0.6.10 - -* Tue Sep 06 2016 David King - 0.6.9-2 -- Look for bwrap in PATH - -* Thu Aug 25 2016 David King - 0.6.9-1 -- Update to 0.6.9 - -* Mon Aug 01 2016 David King - 0.6.8-1 -- Update to 0.6.8 (#1361823) - -* Thu Jul 21 2016 David King - 0.6.7-2 -- Use system bubblewrap - -* Fri Jul 01 2016 David King - 0.6.7-1 -- Update to 0.6.7 - -* Thu Jun 23 2016 David King - 0.6.6-1 -- Update to 0.6.6 - -* Fri Jun 10 2016 David King - 0.6.5-1 -- Update to 0.6.5 - -* Wed Jun 01 2016 David King - 0.6.4-1 -- Update to 0.6.4 - -* Tue May 31 2016 David King - 0.6.3-1 -- Update to 0.6.3 -- Move bwrap to main package - -* Tue May 24 2016 David King - 0.6.2-1 -- Rename from xdg-app to flatpak (#1337434) diff --git a/flatpak-add-support-for-preinstalling-flatpaks.patch b/flatpak-add-support-for-preinstalling-flatpaks.patch new file mode 100644 index 0000000..737ba6a --- /dev/null +++ b/flatpak-add-support-for-preinstalling-flatpaks.patch @@ -0,0 +1,1859 @@ +From 73393a90f18cf143d17bc671750d64d474a11f9d Mon Sep 17 00:00:00 2001 +From: Kalev Lember +Date: Tue, 16 Apr 2024 12:18:59 +0200 +Subject: [PATCH] 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 +--- + app/flatpak-builtins-preinstall.c | 156 ++++++++++ + app/flatpak-builtins.h | 1 + + app/flatpak-main.c | 1 + + app/meson.build | 1 + + common/flatpak-dir-private.h | 23 +- + common/flatpak-dir.c | 430 +++++++++++++++++++++++++- + common/flatpak-installation.c | 2 +- + common/flatpak-transaction.c | 187 +++++++++-- + common/flatpak-transaction.h | 3 + + doc/flatpak-docs.xml.in | 1 + + doc/flatpak-preinstall.xml | 276 +++++++++++++++++ + doc/meson.build | 1 + + po/POTFILES.in | 1 + + system-helper/flatpak-system-helper.c | 5 +- + tests/libtest.sh | 1 + + tests/test-basic.sh | 2 +- + tests/test-matrix/meson.build | 1 + + tests/test-preinstall.sh | 249 +++++++++++++++ + 18 files changed, 1311 insertions(+), 30 deletions(-) + create mode 100644 app/flatpak-builtins-preinstall.c + create mode 100644 doc/flatpak-preinstall.xml + create mode 100755 tests/test-preinstall.sh + +diff --git a/app/flatpak-builtins-preinstall.c b/app/flatpak-builtins-preinstall.c +new file mode 100644 +index 0000000000..3d52cb8bc2 +--- /dev/null ++++ b/app/flatpak-builtins-preinstall.c +@@ -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 . ++ * ++ * Authors: ++ * Kalev Lember ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#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; ++} +diff --git a/app/flatpak-builtins.h b/app/flatpak-builtins.h +index 32a9c9528c..20eb227206 100644 +--- a/app/flatpak-builtins.h ++++ b/app/flatpak-builtins.h +@@ -127,6 +127,7 @@ BUILTINPROTO (repair) + BUILTINPROTO (create_usb) + BUILTINPROTO (kill) + BUILTINPROTO (history) ++BUILTINPROTO (preinstall) + + #undef BUILTINPROTO + +diff --git a/app/flatpak-main.c b/app/flatpak-main.c +index 4549889566..d5794d9909 100644 +--- a/app/flatpak-main.c ++++ b/app/flatpak-main.c +@@ -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") }, +diff --git a/app/meson.build b/app/meson.build +index 258d582a93..8e6ef1dd1b 100644 +--- a/app/meson.build ++++ b/app/meson.build +@@ -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', +diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h +index ffcff5ff50..2c75890c2e 100644 +--- a/common/flatpak-dir-private.h ++++ b/common/flatpak-dir-private.h +@@ -187,6 +187,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 | \ +@@ -196,18 +197,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, +@@ -371,6 +375,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); +@@ -687,6 +704,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, +@@ -696,6 +714,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, +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 1c30452576..e2a6202db1 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -86,6 +86,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" +@@ -2043,6 +2053,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) ++ { ++ g_clear_pointer (&config->branch, g_free); ++ if (*branch != '\0') ++ config->branch = g_steal_pointer (&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); ++ continue; ++ } ++ g_clear_error (&local_error); ++ ++ collection_id = g_key_file_get_string (keyfile, ++ group_name, ++ FLATPAK_PREINSTALL_COLLECTION_ID_KEY, ++ NULL); ++ if (collection_id) ++ { ++ g_clear_pointer (&config->collection_id, g_free); ++ if (*collection_id != '\0') ++ config->collection_id = g_steal_pointer (&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); ++ continue; ++ } ++ 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; ++ const char *existing_preinstalls; ++ 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) +@@ -9255,6 +9664,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) + { +@@ -9360,6 +9770,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); +@@ -9960,6 +10378,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, +@@ -10174,6 +10593,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, +@@ -10212,6 +10634,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; + +@@ -10502,7 +10925,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; + } + +@@ -10933,6 +11356,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); + +@@ -11039,6 +11463,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); +diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c +index 3a8677e836..be7061748b 100644 +--- a/common/flatpak-installation.c ++++ b/common/flatpak-installation.c +@@ -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; +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index b498e4d432..01d3732f26 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -116,6 +116,7 @@ struct _FlatpakTransactionOperation + gboolean skip; + gboolean update_only_deploy; + gboolean pin_on_deploy; ++ gboolean update_preinstalled_on_deploy; + + gboolean resolved; + char *resolved_commit; +@@ -701,7 +702,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; + +@@ -716,6 +718,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; + } +@@ -2176,7 +2179,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; +@@ -2206,7 +2210,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); +@@ -2314,7 +2319,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); +@@ -2343,7 +2348,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); +@@ -2574,7 +2579,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 + { +@@ -2584,7 +2589,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; + } + } +@@ -2680,6 +2685,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) + { +@@ -2803,7 +2809,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); +@@ -2861,7 +2868,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; +@@ -2921,7 +2928,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); + } + + /** +@@ -2997,25 +3006,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 can’t be uninstalled because it’s 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 +@@ -3119,6 +3125,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 +@@ -3154,7 +3288,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); + } + + /** +@@ -3181,7 +3315,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 +@@ -3293,7 +3427,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); +@@ -4808,7 +4942,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; + } + +@@ -4875,7 +5009,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; + } + +@@ -4947,6 +5082,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, +@@ -4986,7 +5122,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; + } + } +@@ -5091,6 +5227,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, +@@ -5271,7 +5410,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); + } + } +diff --git a/common/flatpak-transaction.h b/common/flatpak-transaction.h +index dfa3d0392f..ae305c3411 100644 +--- a/common/flatpak-transaction.h ++++ b/common/flatpak-transaction.h +@@ -339,6 +339,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, +diff --git a/doc/flatpak-docs.xml.in b/doc/flatpak-docs.xml.in +index 581bf685db..4de8d1f9bf 100644 +--- a/doc/flatpak-docs.xml.in ++++ b/doc/flatpak-docs.xml.in +@@ -52,6 +52,7 @@ + + + ++ + + + +diff --git a/doc/flatpak-preinstall.xml b/doc/flatpak-preinstall.xml +new file mode 100644 +index 0000000000..b774c618ec +--- /dev/null ++++ b/doc/flatpak-preinstall.xml +@@ -0,0 +1,276 @@ ++ ++ ++ ++ ++ ++ ++ flatpak preinstall ++ flatpak ++ ++ ++ ++ Developer ++ Kalev ++ Lember ++ klember@redhat.com ++ ++ ++ ++ ++ ++ flatpak preinstall ++ 1 ++ ++ ++ ++ flatpak-preinstall ++ Install flatpaks that are part of the operating system ++ ++ ++ ++ ++ flatpak preinstall ++ OPTION ++ ++ ++ ++ ++ Description ++ ++ ++ This command manages flatpaks that are part of the operating system. If no options are given, running flatpak preinstall will synchronize (install and remove) flatpaks to match the set that the OS vendor has chosen. ++ ++ ++ ++ Preinstalled flatpaks are defined by dropping .preinstall files into the directories /usr/share/flatpak/preinstall.d/ and /etc/flatpak/preinstall.d/. The OS runs flatpak preinstall -y (or its GUI equivalent) on system startup, which then does the actual installation. ++ ++ ++ ++ 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 flatpak preinstall 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. ++ ++ ++ ++ ++ File format ++ ++ ++ The .preinstall file is using the same .ini file format that is used for systemd unit files or application .desktop files. ++ ++ ++ ++ [Flatpak Preinstall NAME] ++ ++ ++ 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. ++ ++ ++ The following keys can be present in this group: ++ ++ ++ ++ (boolean) ++ ++ Whether this group should be installed. If this key is not specified, the group will be installed. ++ ++ ++ ++ (string) ++ ++ The name of the branch from which to install the application or runtime. If this key is not specified, the "master" branch is used. ++ ++ ++ ++ (boolean) ++ ++ Whether this group refers to a runtime. If this key is not specified, the group is assumed to refer to an application. ++ ++ ++ ++ (string) ++ ++ The collection ID of the remote to use, if it has one. ++ ++ ++ ++ ++ ++ ++ ++ Example ++ ++[Flatpak Preinstall org.gnome.Loupe] ++Branch=stable ++IsRuntime=false ++ ++ ++ ++ ++ Options ++ ++ The following options are understood: ++ ++ ++ ++ ++ ++ ++ ++ Show help options and exit. ++ ++ ++ ++ ++ ++ ++ ++ Uninstall first if already installed. ++ ++ ++ ++ ++ ++ ++ ++ ++ Install the application or runtime in a per-user installation. ++ ++ ++ ++ ++ ++ ++ ++ Install the application or runtime in the default system-wide installation. ++ ++ ++ ++ ++ ++ ++ ++ Install the application or runtime in a system-wide installation ++ specified by NAME among those defined in ++ /etc/flatpak/installations.d/. Using ++ is equivalent to using ++ . ++ ++ ++ ++ ++ ++ ++ ++ Download the latest version, but don't deploy it. ++ ++ ++ ++ ++ ++ ++ ++ Don't download the latest version, deploy whatever is locally available. ++ ++ ++ ++ ++ ++ ++ ++ Don't download related extensions, such as the locale data. ++ ++ ++ ++ ++ ++ ++ ++ Don't verify runtime dependencies when installing. ++ ++ ++ ++ ++ ++ ++ ++ Adds an extra local ostree repo as a source for installation. This is equivalent ++ to using the sideload-repos directories (see ++ flatpak1), ++ but can be done on a per-command basis. Any path added here is used in addition ++ to ones in those directories. ++ ++ ++ ++ ++ ++ ++ ++ For each app being installed, also installs the SDK that was used to build it. ++ Implies ; incompatible with . ++ ++ ++ ++ ++ ++ ++ ++ For each ref being installed, as well as all dependencies, also installs its ++ debug info. Implies ; incompatible with ++ . ++ ++ ++ ++ ++ ++ ++ ++ Automatically answer yes to all questions (or pick the most prioritized answer). This is useful for automation. ++ ++ ++ ++ ++ ++ ++ Produce minimal output and avoid most questions. This is suitable for use in ++ non-interactive situations, e.g. in a build script. ++ ++ ++ ++ ++ ++ ++ ++ ++ Print debug information during command processing. ++ ++ ++ ++ ++ ++ ++ ++ Print OSTree debug information during command processing. ++ ++ ++ ++ ++ ++ ++ Examples ++ ++ ++ $ flatpak preinstall ++ ++ ++ ++ ++ See also ++ ++ ++ flatpak1, ++ ++ ++ ++ +diff --git a/doc/meson.build b/doc/meson.build +index a4cedfbe84..9ef8eee1fd 100644 +--- a/doc/meson.build ++++ b/doc/meson.build +@@ -26,6 +26,7 @@ man1 = [ + 'flatpak-config', + 'flatpak-update', + 'flatpak-uninstall', ++ 'flatpak-preinstall', + 'flatpak-mask', + 'flatpak-pin', + 'flatpak-list', +diff --git a/po/POTFILES.in b/po/POTFILES.in +index b3cb79c0e2..52600abd60 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -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 +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index 1f950b20e4..623c2fa04a 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -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; +diff --git a/tests/libtest.sh b/tests/libtest.sh +index 7dad594fd2..69ae0fefe5 100644 +--- a/tests/libtest.sh ++++ b/tests/libtest.sh +@@ -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 +diff --git a/tests/test-basic.sh b/tests/test-basic.sh +index 76369b358f..9f1cfb20a2 100755 +--- a/tests/test-basic.sh ++++ b/tests/test-basic.sh +@@ -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 +diff --git a/tests/test-matrix/meson.build b/tests/test-matrix/meson.build +index fd0b50345c..3c20abed3e 100644 +--- a/tests/test-matrix/meson.build ++++ b/tests/test-matrix/meson.build +@@ -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'} +diff --git a/tests/test-preinstall.sh b/tests/test-preinstall.sh +new file mode 100755 +index 0000000000..4c6a4ad966 +--- /dev/null ++++ b/tests/test-preinstall.sh +@@ -0,0 +1,249 @@ ++#!/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 ++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 &> /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$" ++ ++# Also make sure we installed the app from the right CollectionID ++${FLATPAK} ${U} run org.test.Hello2 > hello2-output ++assert_file_has_content hello2-output "HELLO2_MASTER$" ++ ++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 ++ ++# Make sure the app with the right CollectionID got installed ++${FLATPAK} ${U} run org.test.Hello2 > hello2-output ++assert_file_has_content hello2-output "HELLO2_MASTER_C2$" ++ ++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" +\ No newline at end of file diff --git a/flatpak-allow-direct-installation-from-oci-images.patch b/flatpak-allow-direct-installation-from-oci-images.patch new file mode 100644 index 0000000..76a0475 --- /dev/null +++ b/flatpak-allow-direct-installation-from-oci-images.patch @@ -0,0 +1,4160 @@ +From 7255a7ff96d9066c5c8e090f3a6d76de2f38ba50 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Mon, 14 Oct 2024 09:12:39 -0400 +Subject: [PATCH 01/12]image-source: Refactor - add FlatpakImageSource type + +To avoid passing around combinations of a FlaptakOciRegistry with +repository and digest, add a FlatpakImageSource type. + +This also reduces duplicated code where every place that did +this independently retrieved the repository and image config. +--- + app/flatpak-builtins-build-import-bundle.c | 65 +----- + common/flatpak-common-types-private.h | 1 + + common/flatpak-dir.c | 80 +++----- + common/flatpak-image-source-private.h | 57 ++++++ + common/flatpak-image-source.c | 227 +++++++++++++++++++++ + common/flatpak-oci-registry-private.h | 11 +- + common/flatpak-oci-registry.c | 46 ++--- + common/meson.build | 1 + + doc/reference/meson.build | 1 + + system-helper/flatpak-system-helper.c | 57 +----- + 10 files changed, 348 insertions(+), 198 deletions(-) + create mode 100644 common/flatpak-image-source-private.h + create mode 100644 common/flatpak-image-source.c + +diff --git a/app/flatpak-builtins-build-import-bundle.c b/app/flatpak-builtins-build-import-bundle.c +index d5d6fc26..f3508709 100644 +--- a/app/flatpak-builtins-build-import-bundle.c ++++ b/app/flatpak-builtins-build-import-bundle.c +@@ -30,9 +30,10 @@ + #include "libglnx.h" + + #include "flatpak-builtins.h" ++#include "flatpak-image-source-private.h" ++#include "flatpak-oci-registry-private.h" + #include "flatpak-repo-utils-private.h" + #include "flatpak-utils-private.h" +-#include "flatpak-oci-registry-private.h" + + static char *opt_ref; + static gboolean opt_oci = FALSE; +@@ -58,65 +59,17 @@ import_oci (OstreeRepo *repo, GFile *file, + GCancellable *cancellable, GError **error) + { + g_autofree char *commit_checksum = NULL; +- g_autofree char *dir_uri = NULL; + g_autofree char *target_ref = NULL; +- const char *oci_digest; +- g_autoptr(FlatpakOciRegistry) registry = NULL; +- g_autoptr(FlatpakOciVersioned) versioned = NULL; +- g_autoptr(FlatpakOciImage) image_config = NULL; +- FlatpakOciManifest *manifest = NULL; +- g_autoptr(FlatpakOciIndex) index = NULL; +- const FlatpakOciManifestDescriptor *desc; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + GHashTable *labels; + +- dir_uri = g_file_get_uri (file); +- registry = flatpak_oci_registry_new (dir_uri, FALSE, -1, cancellable, error); +- if (registry == NULL) +- return NULL; +- +- index = flatpak_oci_registry_load_index (registry, cancellable, error); +- if (index == NULL) +- return NULL; +- +- if (opt_ref) +- { +- desc = flatpak_oci_index_get_manifest (index, opt_ref); +- if (desc == NULL) +- { +- flatpak_fail (error, _("Ref '%s' not found in registry"), opt_ref); +- return NULL; +- } +- } +- else +- { +- desc = flatpak_oci_index_get_only_manifest (index); +- if (desc == NULL) +- { +- flatpak_fail (error, _("Multiple images in registry, specify a ref with --ref")); +- return NULL; +- } +- } +- +- oci_digest = desc->parent.digest; +- +- versioned = flatpak_oci_registry_load_versioned (registry, NULL, +- oci_digest, NULL, NULL, +- cancellable, error); +- if (versioned == NULL) ++ image_source = flatpak_image_source_new_local (file, opt_ref, cancellable, error); ++ if (image_source == NULL) + return NULL; + +- manifest = FLATPAK_OCI_MANIFEST (versioned); +- +- image_config = flatpak_oci_registry_load_image_config (registry, NULL, +- manifest->config.digest, NULL, +- NULL, cancellable, error); +- if (image_config == NULL) +- return FALSE; +- +- labels = flatpak_oci_image_get_labels (image_config); +- if (labels) +- flatpak_oci_parse_commit_labels (labels, NULL, NULL, NULL, +- &target_ref, NULL, NULL, NULL); ++ labels = flatpak_image_source_get_labels (image_source); ++ flatpak_oci_parse_commit_labels (labels, NULL, NULL, NULL, ++ &target_ref, NULL, NULL, NULL); + if (target_ref == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, +@@ -124,7 +77,7 @@ import_oci (OstreeRepo *repo, GFile *file, + return NULL; + } + +- commit_checksum = flatpak_pull_from_oci (repo, registry, NULL, oci_digest, NULL, manifest, image_config, ++ commit_checksum = flatpak_pull_from_oci (repo, image_source, NULL, + NULL, target_ref, FLATPAK_PULL_FLAGS_NONE, NULL, NULL, cancellable, error); + if (commit_checksum == NULL) + return NULL; +diff --git a/common/flatpak-common-types-private.h b/common/flatpak-common-types-private.h +index 05130144..d7f3913b 100644 +--- a/common/flatpak-common-types-private.h ++++ b/common/flatpak-common-types-private.h +@@ -53,6 +53,7 @@ typedef enum { + + typedef struct FlatpakDir FlatpakDir; + typedef struct FlatpakDeploy FlatpakDeploy; ++typedef struct _FlatpakImageSource FlatpakImageSource; + typedef struct FlatpakOciRegistry FlatpakOciRegistry; + typedef struct _FlatpakOciManifest FlatpakOciManifest; + typedef struct _FlatpakOciImage FlatpakOciImage; +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 6936d45f..b0c29a70 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -52,6 +52,7 @@ + #include "flatpak-dir-utils-private.h" + #include "flatpak-error.h" + #include "flatpak-locale-utils-private.h" ++#include "flatpak-image-source-private.h" + #include "flatpak-oci-registry-private.h" + #include "flatpak-ref.h" + #include "flatpak-repo-utils-private.h" +@@ -1049,14 +1050,16 @@ lookup_oci_registry_uri_from_summary (GVariant *summary, + return g_steal_pointer (®istry_uri); + } + +-static FlatpakOciRegistry * +-flatpak_remote_state_new_oci_registry (FlatpakRemoteState *self, ++static FlatpakImageSource * ++flatpak_remote_state_new_image_source (FlatpakRemoteState *self, ++ const char *oci_repository, ++ const char *digest, + const char *token, + GCancellable *cancellable, + GError **error) + { + g_autofree char *registry_uri = NULL; +- g_autoptr(FlatpakOciRegistry) registry = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + + if (!flatpak_remote_state_ensure_summary (self, error)) + return NULL; +@@ -1065,13 +1068,13 @@ flatpak_remote_state_new_oci_registry (FlatpakRemoteState *self, + if (registry_uri == NULL) + return NULL; + +- registry = flatpak_oci_registry_new (registry_uri, FALSE, -1, NULL, error); +- if (registry == NULL) ++ image_source = flatpak_image_source_new_remote (registry_uri, oci_repository, digest, NULL, error); ++ if (image_source == NULL) + return NULL; + +- flatpak_oci_registry_set_token (registry, token); ++ flatpak_image_source_set_token (image_source, token); + +- return g_steal_pointer (®istry); ++ return g_steal_pointer (&image_source); + } + + static GVariant * +@@ -1083,9 +1086,7 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + GCancellable *cancellable, + GError **error) + { +- g_autoptr(FlatpakOciRegistry) registry = NULL; +- g_autoptr(FlatpakOciVersioned) versioned = NULL; +- g_autoptr(FlatpakOciImage) image_config = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + g_autofree char *oci_digest = NULL; + g_autofree char *latest_rev = NULL; + VarRefInfoRef latest_rev_info; +@@ -1100,10 +1101,6 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autoptr(GVariant) metadata_v = NULL; + +- registry = flatpak_remote_state_new_oci_registry (self, token, cancellable, error); +- if (registry == NULL) +- return NULL; +- + /* We extract the rev info from the latest, even if we don't use the latest digest, assuming refs don't move */ + if (!flatpak_remote_state_lookup_ref (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) + return NULL; +@@ -1121,25 +1118,11 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + + oci_digest = g_strconcat ("sha256:", checksum, NULL); + +- versioned = flatpak_oci_registry_load_versioned (registry, oci_repository, oci_digest, +- NULL, NULL, cancellable, error); +- if (versioned == NULL) +- return NULL; +- +- if (!FLATPAK_IS_OCI_MANIFEST (versioned)) +- { +- flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest")); +- return NULL; +- } +- +- image_config = flatpak_oci_registry_load_image_config (registry, oci_repository, +- FLATPAK_OCI_MANIFEST (versioned)->config.digest, +- (const char **)FLATPAK_OCI_MANIFEST (versioned)->config.urls, +- NULL, cancellable, error); +- if (image_config == NULL) ++ image_source = flatpak_remote_state_new_image_source (self, oci_repository, oci_digest, token, cancellable, error); ++ if (image_source == NULL) + return NULL; + +- labels = flatpak_oci_image_get_labels (image_config); ++ labels = flatpak_image_source_get_labels (image_source); + if (labels) + flatpak_oci_parse_commit_labels (labels, ×tamp, + &subject, &body, +@@ -5935,7 +5918,7 @@ flatpak_dir_mirror_oci (FlatpakDir *self, + GCancellable *cancellable, + GError **error) + { +- g_autoptr(FlatpakOciRegistry) registry = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + g_autofree char *oci_digest = NULL; + g_autofree char *latest_rev = NULL; + VarRefInfoRef latest_rev_info; +@@ -5968,15 +5951,15 @@ flatpak_dir_mirror_oci (FlatpakDir *self, + + oci_digest = g_strconcat ("sha256:", rev, NULL); + +- registry = flatpak_remote_state_new_oci_registry (state, token, cancellable, error); +- if (registry == NULL) ++ image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); ++ if (image_source == NULL) + return FALSE; + + flatpak_progress_start_oci_pull (progress); + + g_info ("Mirroring OCI image %s", oci_digest); + +- res = flatpak_mirror_image_from_oci (dst_registry, registry, oci_repository, oci_digest, state->remote_name, ref, delta_url, self->repo, oci_pull_progress_cb, ++ res = flatpak_mirror_image_from_oci (dst_registry, image_source, state->remote_name, ref, delta_url, self->repo, oci_pull_progress_cb, + progress, cancellable, error); + + if (!res) +@@ -5998,9 +5981,8 @@ flatpak_dir_pull_oci (FlatpakDir *self, + GCancellable *cancellable, + GError **error) + { +- g_autoptr(FlatpakOciRegistry) registry = NULL; +- g_autoptr(FlatpakOciVersioned) versioned = NULL; +- g_autoptr(FlatpakOciImage) image_config = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; ++ FlatpakOciRegistry *registry = NULL; + const char *oci_repository = NULL; + const char *delta_url = NULL; + g_autofree char *oci_digest = NULL; +@@ -6031,23 +6013,8 @@ flatpak_dir_pull_oci (FlatpakDir *self, + if (latest_alt_commit != NULL && strcmp (oci_digest + strlen ("sha256:"), latest_alt_commit) == 0) + return TRUE; + +- registry = flatpak_remote_state_new_oci_registry (state, token, cancellable, error); +- if (registry == NULL) +- return FALSE; +- +- versioned = flatpak_oci_registry_load_versioned (registry, oci_repository, oci_digest, +- NULL, NULL, cancellable, error); +- if (versioned == NULL) +- return FALSE; +- +- if (!FLATPAK_IS_OCI_MANIFEST (versioned)) +- return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest")); +- +- image_config = flatpak_oci_registry_load_image_config (registry, oci_repository, +- FLATPAK_OCI_MANIFEST (versioned)->config.digest, +- (const char **)FLATPAK_OCI_MANIFEST (versioned)->config.urls, +- NULL, cancellable, error); +- if (image_config == NULL) ++ image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); ++ if (image_source == NULL) + return FALSE; + + if (repo == NULL) +@@ -6057,7 +6024,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, + + g_info ("Pulling OCI image %s", oci_digest); + +- checksum = flatpak_pull_from_oci (repo, registry, oci_repository, oci_digest, delta_url, FLATPAK_OCI_MANIFEST (versioned), image_config, ++ checksum = flatpak_pull_from_oci (repo, image_source, delta_url, + state->remote_name, ref, flatpak_flags, oci_pull_progress_cb, progress, cancellable, error); + + if (checksum == NULL) +@@ -6073,6 +6040,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, + name = g_file_get_path (file); + } + ++ registry = flatpak_image_source_get_registry (image_source); + (flatpak_dir_log) (self, __FILE__, __LINE__, __FUNCTION__, name, + "pull oci", flatpak_oci_registry_get_uri (registry), ref, NULL, NULL, NULL, + "Pulled %s from %s", ref, flatpak_oci_registry_get_uri (registry)); +diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h +new file mode 100644 +index 00000000..ebd856a4 +--- /dev/null ++++ b/common/flatpak-image-source-private.h +@@ -0,0 +1,57 @@ ++/* ++ * 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 . ++ * ++ * Authors: ++ * Owen Taylor ++ */ ++ ++#ifndef __FLATPAK_IMAGE_SOURCE_H__ ++#define __FLATPAK_IMAGE_SOURCE_H__ ++ ++#include ++#include ++ ++#include ++ ++#define FLATPAK_TYPE_IMAGE_SOURCE flatpak_image_source_get_type () ++G_DECLARE_FINAL_TYPE (FlatpakImageSource, ++ flatpak_image_source, ++ FLATPAK, IMAGE_SOURCE, ++ GObject) ++ ++FlatpakImageSource *flatpak_image_source_new_local (GFile *file, ++ const char *reference, ++ GCancellable *cancellable, ++ GError **error); ++FlatpakImageSource *flatpak_image_source_new_remote (const char *uri, ++ const char *oci_repository, ++ const char *digest, ++ GCancellable *cancellable, ++ GError **error); ++ ++void flatpak_image_source_set_token (FlatpakImageSource *self, ++ const char *token); ++ ++FlatpakOciRegistry *flatpak_image_source_get_registry (FlatpakImageSource *self); ++const char *flatpak_image_source_get_oci_repository (FlatpakImageSource *self); ++const char *flatpak_image_source_get_digest (FlatpakImageSource *self); ++FlatpakOciManifest *flatpak_image_source_get_manifest (FlatpakImageSource *self); ++size_t flatpak_image_source_get_manifest_size (FlatpakImageSource *self); ++FlatpakOciImage *flatpak_image_source_get_image_config (FlatpakImageSource *self); ++ ++GHashTable *flatpak_image_source_get_labels (FlatpakImageSource *self); ++ ++#endif /* __FLATPAK_IMAGE_SOURCE_H__ */ +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +new file mode 100644 +index 00000000..22497ccf +--- /dev/null ++++ b/common/flatpak-image-source.c +@@ -0,0 +1,227 @@ ++/* 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 . ++ * ++ * Authors: ++ * Owen Taylor ++ */ ++ ++#include ++ ++#include "flatpak-image-source-private.h" ++#include "flatpak-oci-registry-private.h" ++ ++struct _FlatpakImageSource ++{ ++ GObject parent; ++ ++ FlatpakOciRegistry *registry; ++ char *repository; ++ char *digest; ++ ++ FlatpakOciManifest *manifest; ++ size_t manifest_size; ++ FlatpakOciImage *image_config; ++}; ++ ++G_DEFINE_TYPE (FlatpakImageSource, flatpak_image_source, G_TYPE_OBJECT) ++ ++static void ++flatpak_image_source_finalize (GObject *object) ++{ ++ FlatpakImageSource *self = FLATPAK_IMAGE_SOURCE (object); ++ ++ g_clear_object (&self->registry); ++ g_clear_pointer (&self->repository, g_free); ++ g_clear_pointer (&self->digest, g_free); ++ g_clear_object (&self->manifest); ++ g_clear_object (&self->image_config); ++ ++ G_OBJECT_CLASS (flatpak_image_source_parent_class)->finalize (object); ++} ++ ++static void ++flatpak_image_source_class_init (FlatpakImageSourceClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = flatpak_image_source_finalize; ++} ++ ++static void ++flatpak_image_source_init (FlatpakImageSource *self) ++{ ++} ++ ++static FlatpakImageSource * ++flatpak_image_source_new (FlatpakOciRegistry *registry, ++ const char *repository, ++ const char *digest, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(FlatpakImageSource) self = NULL; ++ g_autoptr(FlatpakOciVersioned) versioned = NULL; ++ GHashTable *labels; ++ ++ self = g_object_new (FLATPAK_TYPE_IMAGE_SOURCE, NULL); ++ self->registry = g_object_ref (registry); ++ self->repository = g_strdup (repository); ++ self->digest = g_strdup (digest); ++ ++ versioned = flatpak_oci_registry_load_versioned (self->registry, self->repository, ++ self->digest, NULL, &self->manifest_size, ++ cancellable, error); ++ if (versioned == NULL) ++ return NULL; ++ ++ if (!FLATPAK_IS_OCI_MANIFEST (versioned)) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest")); ++ return NULL; ++ } ++ ++ self->manifest = FLATPAK_OCI_MANIFEST (g_steal_pointer (&versioned)); ++ ++ if (self->manifest->config.digest == NULL) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest")); ++ return NULL; ++ } ++ ++ self->image_config = flatpak_oci_registry_load_image_config (self->registry, self->repository, ++ self->manifest->config.digest, NULL, ++ NULL, cancellable, error); ++ if (self->image_config == NULL) ++ return NULL; ++ ++ labels = flatpak_image_source_get_labels (self); ++ if (!g_hash_table_contains (labels, "org.flatpak.ref")) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No org.flatpak.ref found in image")); ++ return NULL; ++ } ++ ++ return g_steal_pointer (&self); ++} ++ ++FlatpakImageSource * ++flatpak_image_source_new_local (GFile *file, ++ const char *reference, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autofree char *dir_uri = NULL; ++ g_autofree char *target_ref = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; ++ g_autoptr(FlatpakOciRegistry) registry = NULL; ++ g_autoptr(FlatpakOciIndex) index = NULL; ++ const FlatpakOciManifestDescriptor *desc; ++ ++ dir_uri = g_file_get_uri (file); ++ registry = flatpak_oci_registry_new (dir_uri, FALSE, -1, cancellable, error); ++ if (registry == NULL) ++ return NULL; ++ ++ index = flatpak_oci_registry_load_index (registry, cancellable, error); ++ if (index == NULL) ++ return NULL; ++ ++ if (reference) ++ { ++ desc = flatpak_oci_index_get_manifest (index, reference); ++ if (desc == NULL) ++ { ++ flatpak_fail (error, _("Ref '%s' not found in registry"), reference); ++ return NULL; ++ } ++ } ++ else ++ { ++ desc = flatpak_oci_index_get_only_manifest (index); ++ if (desc == NULL) ++ { ++ flatpak_fail (error, _("Multiple images in registry, specify a ref with --ref")); ++ return NULL; ++ } ++ } ++ ++ return flatpak_image_source_new (registry, NULL, desc->parent.digest, cancellable, error); ++} ++ ++FlatpakImageSource * ++flatpak_image_source_new_remote (const char *uri, ++ const char *oci_repository, ++ const char *digest, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(FlatpakOciRegistry) registry = NULL; ++ ++ registry = flatpak_oci_registry_new (uri, FALSE, -1, cancellable, error); ++ if (!registry) ++ return NULL; ++ ++ return flatpak_image_source_new (registry, oci_repository, digest, cancellable, error); ++} ++ ++void ++flatpak_image_source_set_token (FlatpakImageSource *self, ++ const char *token) ++{ ++ flatpak_oci_registry_set_token (self->registry, token); ++} ++ ++FlatpakOciRegistry * ++flatpak_image_source_get_registry (FlatpakImageSource *self) ++{ ++ return self->registry; ++} ++ ++const char * ++flatpak_image_source_get_oci_repository (FlatpakImageSource *self) ++{ ++ return self->repository; ++} ++ ++const char * ++flatpak_image_source_get_digest (FlatpakImageSource *self) ++{ ++ return self->digest; ++} ++ ++FlatpakOciManifest * ++flatpak_image_source_get_manifest (FlatpakImageSource *self) ++{ ++ return self->manifest; ++} ++ ++size_t ++flatpak_image_source_get_manifest_size (FlatpakImageSource *self) ++{ ++ return self->manifest_size; ++} ++ ++FlatpakOciImage * ++flatpak_image_source_get_image_config (FlatpakImageSource *self) ++{ ++ return self->image_config; ++} ++ ++GHashTable * ++flatpak_image_source_get_labels (FlatpakImageSource *self) ++{ ++ return flatpak_oci_image_get_labels (self->image_config); ++} +diff --git a/common/flatpak-oci-registry-private.h b/common/flatpak-oci-registry-private.h +index 2c0608dc..47ffc7be 100644 +--- a/common/flatpak-oci-registry-private.h ++++ b/common/flatpak-oci-registry-private.h +@@ -183,6 +183,7 @@ GBytes *flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + GCancellable *cancellable, + GError **error); + ++ + typedef void (*FlatpakOciPullProgress) (guint64 total_size, + guint64 pulled_size, + guint32 n_layers, +@@ -190,12 +191,8 @@ typedef void (*FlatpakOciPullProgress) (guint64 total_size, + gpointer data); + + char * flatpak_pull_from_oci (OstreeRepo *repo, +- FlatpakOciRegistry *registry, +- const char *oci_repository, +- const char *digest, ++ FlatpakImageSource *image_source, + const char *delta_url, +- FlatpakOciManifest *manifest, +- FlatpakOciImage *image_config, + const char *remote, + const char *ref, + FlatpakPullFlags flags, +@@ -205,9 +202,7 @@ char * flatpak_pull_from_oci (OstreeRepo *repo, + GError **error); + + gboolean flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, +- FlatpakOciRegistry *registry, +- const char *oci_repository, +- const char *digest, ++ FlatpakImageSource *image_source, + const char *remote, + const char *ref, + const char *delta_url, +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index 6d36de2a..11b5f98d 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -28,6 +28,7 @@ + + #include + #include ++#include "flatpak-image-source-private.h" + #include "flatpak-oci-registry-private.h" + #include "flatpak-repo-utils-private.h" + #include "flatpak-utils-base-private.h" +@@ -3454,9 +3455,7 @@ oci_layer_progress (guint64 downloaded_bytes, + + gboolean + flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, +- FlatpakOciRegistry *registry, +- const char *oci_repository, +- const char *digest, ++ FlatpakImageSource *image_source, + const char *remote, + const char *ref, + const char *delta_url, +@@ -3467,8 +3466,11 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + GError **error) + { + FlatpakOciPullProgressData progress_data = { progress_cb, progress_user_data }; +- g_autoptr(FlatpakOciVersioned) versioned = NULL; +- FlatpakOciManifest *manifest = NULL; ++ FlatpakOciRegistry *registry = flatpak_image_source_get_registry (image_source); ++ const char *oci_repository = flatpak_image_source_get_oci_repository (image_source); ++ const char *digest = flatpak_image_source_get_digest (image_source); ++ FlatpakOciManifest *manifest = flatpak_image_source_get_manifest (image_source); ++ FlatpakOciImage *image_config = flatpak_image_source_get_image_config (image_source); + g_autoptr(FlatpakOciDescriptor) manifest_desc = NULL; + g_autoptr(FlatpakOciManifest) delta_manifest = NULL; + g_autofree char *old_checksum = NULL; +@@ -3476,36 +3478,16 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + g_autoptr(GFile) old_root = NULL; + OstreeRepoCommitState old_state = 0; + g_autofree char *old_diffid = NULL; +- gsize versioned_size; + g_autoptr(FlatpakOciIndex) index = NULL; +- g_autoptr(FlatpakOciImage) image_config = NULL; + int n_layers; + int i; + + if (!flatpak_oci_registry_mirror_blob (dst_registry, registry, oci_repository, TRUE, digest, NULL, NULL, NULL, cancellable, error)) + return FALSE; + +- versioned = flatpak_oci_registry_load_versioned (dst_registry, NULL, digest, NULL, &versioned_size, cancellable, error); +- if (versioned == NULL) +- return FALSE; +- +- if (!FLATPAK_IS_OCI_MANIFEST (versioned)) +- return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest")); +- +- manifest = FLATPAK_OCI_MANIFEST (versioned); +- +- if (manifest->config.digest == NULL) +- return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest")); +- + if (!flatpak_oci_registry_mirror_blob (dst_registry, registry, oci_repository, FALSE, manifest->config.digest, (const char **)manifest->config.urls, NULL, NULL, cancellable, error)) + return FALSE; + +- image_config = flatpak_oci_registry_load_image_config (dst_registry, NULL, +- manifest->config.digest, NULL, +- NULL, cancellable, error); +- if (image_config == NULL) +- return FALSE; +- + /* For deltas we ensure that the diffid and regular layers exists and match up */ + n_layers = flatpak_oci_manifest_get_n_layers (manifest); + if (n_layers == 0 || n_layers != flatpak_oci_image_get_n_layers (image_config)) +@@ -3589,7 +3571,8 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + if (index == NULL) + index = flatpak_oci_index_new (); + +- manifest_desc = flatpak_oci_descriptor_new (versioned->mediatype, digest, versioned_size); ++ manifest_desc = flatpak_oci_descriptor_new (manifest->parent.mediatype, digest, ++ flatpak_image_source_get_manifest_size (image_source)); + + flatpak_oci_index_add_manifest (index, ref, manifest_desc); + +@@ -3601,12 +3584,8 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + + char * + flatpak_pull_from_oci (OstreeRepo *repo, +- FlatpakOciRegistry *registry, +- const char *oci_repository, +- const char *digest, ++ FlatpakImageSource *image_source, + const char *delta_url, +- FlatpakOciManifest *manifest, +- FlatpakOciImage *image_config, + const char *remote, + const char *ref, + FlatpakPullFlags flags, +@@ -3615,6 +3594,11 @@ flatpak_pull_from_oci (OstreeRepo *repo, + GCancellable *cancellable, + GError **error) + { ++ FlatpakOciRegistry *registry = flatpak_image_source_get_registry (image_source); ++ const char *oci_repository = flatpak_image_source_get_oci_repository (image_source); ++ const char *digest = flatpak_image_source_get_digest (image_source); ++ FlatpakOciManifest *manifest = flatpak_image_source_get_manifest (image_source); ++ FlatpakOciImage *image_config = flatpak_image_source_get_image_config (image_source); + gboolean force_disable_deltas = (flags & FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS) != 0; + g_autoptr(OstreeMutableTree) archive_mtree = NULL; + g_autoptr(GFile) archive_root = NULL; +diff --git a/common/meson.build b/common/meson.build +index bd5dbf3d..beed1f06 100644 +--- a/common/meson.build ++++ b/common/meson.build +@@ -172,6 +172,7 @@ sources = [ + 'flatpak-error.c', + 'flatpak-exports.c', + 'flatpak-glib-backports.c', ++ 'flatpak-image-source.c', + 'flatpak-installation.c', + 'flatpak-installed-ref.c', + 'flatpak-instance.c', +diff --git a/doc/reference/meson.build b/doc/reference/meson.build +index a881b0cb..92f1482e 100644 +--- a/doc/reference/meson.build ++++ b/doc/reference/meson.build +@@ -46,6 +46,7 @@ gnome.gtkdoc( + 'flatpak-document-dbus-generated.h', + 'flatpak-enum-types.h', + 'flatpak-exports-private.h', ++ 'flatpak-image-source-private.h', + 'flatpak-installed-ref-private.h', + 'flatpak-json-oci-private.h', + 'flatpak-json-private.h', +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index a58ab2c6..a4d9708b 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -39,6 +39,7 @@ + #include "flatpak-dbus-generated.h" + #include "flatpak-dir-private.h" + #include "flatpak-error.h" ++#include "flatpak-image-source-private.h" + #include "flatpak-oci-registry-private.h" + #include "flatpak-progress-private.h" + #include "flatpak-system-helper.h" +@@ -528,15 +529,11 @@ handle_deploy (FlatpakSystemHelper *object, + if (strlen (arg_repo_path) > 0 && is_oci) + { + g_autoptr(GFile) registry_file = g_file_new_for_path (arg_repo_path); +- g_autofree char *registry_uri = g_file_get_uri (registry_file); +- g_autoptr(FlatpakOciRegistry) registry = NULL; +- g_autoptr(FlatpakOciIndex) index = NULL; +- const FlatpakOciManifestDescriptor *desc; +- g_autoptr(FlatpakOciVersioned) versioned = NULL; +- g_autoptr(FlatpakOciImage) image_config = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + g_autoptr(FlatpakRemoteState) state = NULL; + g_autoptr(GHashTable) remote_refs = NULL; + g_autofree char *checksum = NULL; ++ const char *image_source_digest; + const char *verified_digest; + g_autofree char *upstream_url = NULL; + +@@ -552,50 +549,14 @@ handle_deploy (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- registry = flatpak_oci_registry_new (registry_uri, FALSE, -1, NULL, &error); +- if (registry == NULL) ++ image_source = flatpak_image_source_new_local (registry_file, arg_ref, NULL, &error); ++ if (image_source == NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "Can't open child OCI registry: %s", error->message); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- index = flatpak_oci_registry_load_index (registry, NULL, &error); +- if (index == NULL) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Can't open child OCI registry index: %s", error->message); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- +- desc = flatpak_oci_index_get_manifest (index, arg_ref); +- if (desc == NULL) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Can't find ref %s in child OCI registry index", arg_ref); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- +- versioned = flatpak_oci_registry_load_versioned (registry, NULL, desc->parent.digest, (const char **)desc->parent.urls, NULL, +- NULL, &error); +- if (versioned == NULL || !FLATPAK_IS_OCI_MANIFEST (versioned)) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Can't open child manifest"); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- +- image_config = flatpak_oci_registry_load_image_config (registry, NULL, +- FLATPAK_OCI_MANIFEST (versioned)->config.digest, +- (const char **)FLATPAK_OCI_MANIFEST (versioned)->config.urls, +- NULL, NULL, &error); +- if (image_config == NULL) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Can't open child image config"); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- + state = flatpak_dir_get_remote_state (system, arg_origin, FALSE, NULL, &error); + if (state == NULL) + { +@@ -622,15 +583,17 @@ handle_deploy (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (!g_str_has_prefix (desc->parent.digest, "sha256:") || +- strcmp (desc->parent.digest + strlen ("sha256:"), verified_digest) != 0) ++ image_source_digest = flatpak_image_source_get_digest (image_source); ++ ++ if (!g_str_has_prefix (image_source_digest, "sha256:") || ++ strcmp (image_source_digest + strlen ("sha256:"), verified_digest) != 0) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "%s: manifest hash in downloaded content does not match ref %s", arg_origin, arg_ref); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- checksum = flatpak_pull_from_oci (flatpak_dir_get_repo (system), registry, NULL, desc->parent.digest, NULL, FLATPAK_OCI_MANIFEST (versioned), image_config, ++ checksum = flatpak_pull_from_oci (flatpak_dir_get_repo (system), image_source, NULL, + arg_origin, arg_ref, FLATPAK_PULL_FLAGS_NONE, NULL, NULL, NULL, &error); + if (checksum == NULL) + { +-- +2.47.1 + +From 5e0cfa2e33b9d99992047a14650784e5fd4c0c05 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Mon, 28 Oct 2024 12:35:33 -0400 +Subject: [PATCH 02/12]image-source: Replace flatpak_oci_parse_commit_labels + with getters + +Instead of having one function with a pile of out arguments in +arbitrary order, add getters to FlatpakImageSource. +--- + app/flatpak-builtins-build-import-bundle.c | 18 ++-- + common/flatpak-dir.c | 25 ++---- + common/flatpak-image-source-private.h | 11 ++- + common/flatpak-image-source.c | 96 ++++++++++++++++++++-- + common/flatpak-json-oci-private.h | 8 -- + common/flatpak-json-oci.c | 66 --------------- + common/flatpak-oci-registry.c | 28 +++---- + 7 files changed, 125 insertions(+), 127 deletions(-) + +diff --git a/app/flatpak-builtins-build-import-bundle.c b/app/flatpak-builtins-build-import-bundle.c +index f3508709..d79fbbde 100644 +--- a/app/flatpak-builtins-build-import-bundle.c ++++ b/app/flatpak-builtins-build-import-bundle.c +@@ -59,30 +59,22 @@ import_oci (OstreeRepo *repo, GFile *file, + GCancellable *cancellable, GError **error) + { + g_autofree char *commit_checksum = NULL; +- g_autofree char *target_ref = NULL; + g_autoptr(FlatpakImageSource) image_source = NULL; +- GHashTable *labels; ++ const char *ref; + + image_source = flatpak_image_source_new_local (file, opt_ref, cancellable, error); + if (image_source == NULL) + return NULL; + +- labels = flatpak_image_source_get_labels (image_source); +- flatpak_oci_parse_commit_labels (labels, NULL, NULL, NULL, +- &target_ref, NULL, NULL, NULL); +- if (target_ref == NULL) +- { +- g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, +- "The OCI image didn't specify a ref, use --ref to specify one"); +- return NULL; +- } ++ ref = flatpak_image_source_get_ref (image_source); + + commit_checksum = flatpak_pull_from_oci (repo, image_source, NULL, +- NULL, target_ref, FLATPAK_PULL_FLAGS_NONE, NULL, NULL, cancellable, error); ++ NULL, ref, FLATPAK_PULL_FLAGS_NONE, ++ NULL, NULL, cancellable, error); + if (commit_checksum == NULL) + return NULL; + +- g_print (_("Importing %s (%s)\n"), target_ref, commit_checksum); ++ g_print (_("Importing %s (%s)\n"), ref, commit_checksum); + + return g_strdup (commit_checksum); + } +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index b0c29a70..e1224170 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -1092,12 +1092,7 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + VarRefInfoRef latest_rev_info; + VarMetadataRef metadata; + const char *oci_repository = NULL; +- GHashTable *labels; +- g_autofree char *subject = NULL; +- g_autofree char *body = NULL; +- g_autofree char *manifest_ref = NULL; +- g_autofree char *parent = NULL; +- guint64 timestamp = 0; ++ const char *parent = NULL; + g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autoptr(GVariant) metadata_v = NULL; + +@@ -1122,30 +1117,26 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + if (image_source == NULL) + return NULL; + +- labels = flatpak_image_source_get_labels (image_source); +- if (labels) +- flatpak_oci_parse_commit_labels (labels, ×tamp, +- &subject, &body, +- &manifest_ref, NULL, &parent, +- metadata_builder); +- +- +- if (g_strcmp0 (manifest_ref, ref) != 0) ++ if (g_strcmp0 (flatpak_image_source_get_ref (image_source), ref) != 0) + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit has no requested ref ‘%s’ in ref binding metadata"), ref); + return NULL; + } + ++ flatpak_image_source_build_commit_metadata (image_source, metadata_builder); + metadata_v = g_variant_ref_sink (g_variant_builder_end (metadata_builder)); + ++ parent = flatpak_image_source_get_parent_commit (image_source); ++ + /* This isn't going to be exactly the same as the reconstructed one from the pull, because we don't have the contents, but its useful to get metadata */ + return + g_variant_ref_sink (g_variant_new ("(@a{sv}@ay@a(say)sst@ay@ay)", + metadata_v, + parent ? ostree_checksum_to_bytes_v (parent) : g_variant_new_from_data (G_VARIANT_TYPE ("ay"), NULL, 0, FALSE, NULL, NULL), + g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0), +- subject, body, +- GUINT64_TO_BE (timestamp), ++ flatpak_image_source_get_commit_subject (image_source), ++ flatpak_image_source_get_commit_body (image_source), ++ GUINT64_TO_BE (flatpak_image_source_get_commit_timestamp (image_source)), + ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"), + ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"))); + } +diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h +index ebd856a4..fe8f02ba 100644 +--- a/common/flatpak-image-source-private.h ++++ b/common/flatpak-image-source-private.h +@@ -52,6 +52,15 @@ FlatpakOciManifest *flatpak_image_source_get_manifest (FlatpakImageSource + size_t flatpak_image_source_get_manifest_size (FlatpakImageSource *self); + FlatpakOciImage *flatpak_image_source_get_image_config (FlatpakImageSource *self); + +-GHashTable *flatpak_image_source_get_labels (FlatpakImageSource *self); ++const char *flatpak_image_source_get_ref (FlatpakImageSource *self); ++const char *flatpak_image_source_get_metadata (FlatpakImageSource *self); ++const char *flatpak_image_source_get_commit (FlatpakImageSource *self); ++const char *flatpak_image_source_get_parent_commit (FlatpakImageSource *self); ++guint64 flatpak_image_source_get_commit_timestamp (FlatpakImageSource *self); ++const char *flatpak_image_source_get_commit_subject (FlatpakImageSource *self); ++const char *flatpak_image_source_get_commit_body (FlatpakImageSource *self); ++ ++void flatpak_image_source_build_commit_metadata (FlatpakImageSource *self, ++ GVariantBuilder *metadata_builder); + + #endif /* __FLATPAK_IMAGE_SOURCE_H__ */ +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +index 22497ccf..feb4fe27 100644 +--- a/common/flatpak-image-source.c ++++ b/common/flatpak-image-source.c +@@ -74,7 +74,6 @@ flatpak_image_source_new (FlatpakOciRegistry *registry, + { + g_autoptr(FlatpakImageSource) self = NULL; + g_autoptr(FlatpakOciVersioned) versioned = NULL; +- GHashTable *labels; + + self = g_object_new (FLATPAK_TYPE_IMAGE_SOURCE, NULL); + self->registry = g_object_ref (registry); +@@ -107,8 +106,7 @@ flatpak_image_source_new (FlatpakOciRegistry *registry, + if (self->image_config == NULL) + return NULL; + +- labels = flatpak_image_source_get_labels (self); +- if (!g_hash_table_contains (labels, "org.flatpak.ref")) ++ if (flatpak_image_source_get_ref (self) == NULL) + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No org.flatpak.ref found in image")); + return NULL; +@@ -220,8 +218,94 @@ flatpak_image_source_get_image_config (FlatpakImageSource *self) + return self->image_config; + } + +-GHashTable * +-flatpak_image_source_get_labels (FlatpakImageSource *self) ++const char * ++flatpak_image_source_get_ref (FlatpakImageSource *self) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ ++ return g_hash_table_lookup (labels, "org.flatpak.ref"); ++} ++ ++const char * ++flatpak_image_source_get_metadata (FlatpakImageSource *self) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ ++ return g_hash_table_lookup (labels, "org.flatpak.metadata"); ++} ++ ++const char * ++flatpak_image_source_get_commit (FlatpakImageSource *self) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ ++ return g_hash_table_lookup (labels, "org.flatpak.commit"); ++} ++ ++const char * ++flatpak_image_source_get_parent_commit (FlatpakImageSource *self) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ ++ return g_hash_table_lookup (labels, "org.flatpak.parent-commit"); ++} ++ ++guint64 ++flatpak_image_source_get_commit_timestamp (FlatpakImageSource *self) + { +- return flatpak_oci_image_get_labels (self->image_config); ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ const char *oci_timestamp = g_hash_table_lookup (labels, "org.flatpak.timestamp"); ++ ++ if (oci_timestamp != NULL) ++ return g_ascii_strtoull (oci_timestamp, NULL, 10); ++ else ++ return 0; ++} ++ ++const char * ++flatpak_image_source_get_commit_subject (FlatpakImageSource *self) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ ++ return g_hash_table_lookup (labels, "org.flatpak.subject"); ++} ++ ++const char * ++flatpak_image_source_get_commit_body (FlatpakImageSource *self) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ ++ return g_hash_table_lookup (labels, "org.flatpak.body"); ++} ++ ++void ++flatpak_image_source_build_commit_metadata (FlatpakImageSource *self, ++ GVariantBuilder *metadata_builder) ++{ ++ GHashTable *labels = flatpak_oci_image_get_labels (self->image_config); ++ GHashTableIter iter; ++ const char *key; ++ const char *value; ++ ++ g_hash_table_iter_init (&iter, labels); ++ while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value)) ++ { ++ g_autoptr(GVariant) data = NULL; ++ uint8_t *bin; ++ size_t bin_len; ++ ++ if (!g_str_has_prefix (key, "org.flatpak.commit-metadata.")) ++ continue; ++ ++ key += strlen ("org.flatpak.commit-metadata."); ++ ++ bin = g_base64_decode (value, &bin_len); ++ data = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("v"), ++ bin, ++ bin_len, ++ FALSE, ++ g_free, ++ bin)); ++ g_variant_builder_add (metadata_builder, "{s@v}", key, data); ++ } + } +diff --git a/common/flatpak-json-oci-private.h b/common/flatpak-json-oci-private.h +index edb22eeb..87573c17 100644 +--- a/common/flatpak-json-oci-private.h ++++ b/common/flatpak-json-oci-private.h +@@ -246,14 +246,6 @@ void flatpak_oci_add_labels_for_commit (GHashTable *labels, + const char *ref, + const char *commit, + GVariant *commit_data); +-void flatpak_oci_parse_commit_labels (GHashTable *labels, +- guint64 *out_timestamp, +- char **out_subject, +- char **out_body, +- char **out_ref, +- char **out_commit, +- char **out_parent_commit, +- GVariantBuilder *metadata_builder); + + #define FLATPAK_TYPE_OCI_SIGNATURE flatpak_oci_signature_get_type () + G_DECLARE_FINAL_TYPE (FlatpakOciSignature, flatpak_oci_signature, FLATPAK, OCI_SIGNATURE, FlatpakJson) +diff --git a/common/flatpak-json-oci.c b/common/flatpak-json-oci.c +index 42647d11..2b55b9de 100644 +--- a/common/flatpak-json-oci.c ++++ b/common/flatpak-json-oci.c +@@ -927,72 +927,6 @@ flatpak_oci_add_labels_for_commit (GHashTable *labels, + } + } + +-void +-flatpak_oci_parse_commit_labels (GHashTable *labels, +- guint64 *out_timestamp, +- char **out_subject, +- char **out_body, +- char **out_ref, +- char **out_commit, +- char **out_parent_commit, +- GVariantBuilder *metadata_builder) +-{ +- const char *oci_timestamp, *oci_subject, *oci_body, *oci_parent_commit, *oci_commit, *oci_ref; +- GHashTableIter iter; +- gpointer _key, _value; +- +- oci_ref = g_hash_table_lookup (labels, "org.flatpak.ref"); +- +- /* Early return if this is not a flatpak manifest */ +- if (oci_ref == NULL) +- return; +- +- if (oci_ref != NULL && out_ref != NULL && *out_ref == NULL) +- *out_ref = g_strdup (oci_ref); +- +- oci_commit = g_hash_table_lookup (labels, "org.flatpak.commit"); +- if (oci_commit != NULL && out_commit != NULL && *out_commit == NULL) +- *out_commit = g_strdup (oci_commit); +- +- oci_parent_commit = g_hash_table_lookup (labels, "org.flatpak.parent-commit"); +- if (oci_parent_commit != NULL && out_parent_commit != NULL && *out_parent_commit == NULL) +- *out_parent_commit = g_strdup (oci_parent_commit); +- +- oci_timestamp = g_hash_table_lookup (labels, "org.flatpak.timestamp"); +- if (oci_timestamp != NULL && out_timestamp != NULL && *out_timestamp == 0) +- *out_timestamp = g_ascii_strtoull (oci_timestamp, NULL, 10); +- +- oci_subject = g_hash_table_lookup (labels, "org.flatpak.subject"); +- if (oci_subject != NULL && out_subject != NULL && *out_subject == NULL) +- *out_subject = g_strdup (oci_subject); +- +- oci_body = g_hash_table_lookup (labels, "org.flatpak.body"); +- if (oci_body != NULL && out_body != NULL && *out_body == NULL) +- *out_body = g_strdup (oci_body); +- +- if (metadata_builder) +- { +- g_hash_table_iter_init (&iter, labels); +- while (g_hash_table_iter_next (&iter, &_key, &_value)) +- { +- const char *key = _key; +- const char *value = _value; +- guchar *bin; +- gsize bin_len; +- g_autoptr(GVariant) data = NULL; +- +- if (!g_str_has_prefix (key, "org.flatpak.commit-metadata.")) +- continue; +- key += strlen ("org.flatpak.commit-metadata."); +- +- bin = g_base64_decode (value, &bin_len); +- data = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("v"), bin, bin_len, FALSE, +- g_free, bin)); +- g_variant_builder_add (metadata_builder, "{s@v}", key, data); +- } +- } +-} +- + + G_DEFINE_TYPE (FlatpakOciSignature, flatpak_oci_signature, FLATPAK_TYPE_JSON); + +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index 11b5f98d..ad05c9f0 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -3610,28 +3610,18 @@ flatpak_pull_from_oci (OstreeRepo *repo, + g_autofree char *old_diffid = NULL; + g_autofree char *commit_checksum = NULL; + const char *parent = NULL; +- g_autofree char *subject = NULL; +- g_autofree char *body = NULL; +- g_autofree char *manifest_ref = NULL; ++ const char *manifest_ref = NULL; + g_autofree char *full_ref = NULL; + const char *diffid; +- guint64 timestamp = 0; + FlatpakOciPullProgressData progress_data = { progress_cb, progress_user_data }; + g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autoptr(GVariant) metadata = NULL; +- GHashTable *labels; + int n_layers; + int i; + +- g_assert (ref != NULL); + g_assert (g_str_has_prefix (digest, "sha256:")); + +- labels = flatpak_oci_image_get_labels (image_config); +- if (labels) +- flatpak_oci_parse_commit_labels (labels, ×tamp, +- &subject, &body, +- &manifest_ref, NULL, NULL, +- metadata_builder); ++ manifest_ref = flatpak_image_source_get_ref (image_source); + + if (manifest_ref == NULL) + { +@@ -3639,12 +3629,18 @@ flatpak_pull_from_oci (OstreeRepo *repo, + return NULL; + } + +- if (strcmp (manifest_ref, ref) != 0) ++ if (ref == NULL) ++ { ++ ref = manifest_ref; ++ } ++ else if (g_strcmp0 (manifest_ref, ref) != 0) + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong ref (%s) specified for OCI image %s, expected %s"), manifest_ref, digest, ref); + return NULL; + } + ++ flatpak_image_source_build_commit_metadata (image_source, metadata_builder); ++ + g_variant_builder_add (metadata_builder, "{s@v}", "xa.alt-id", + g_variant_new_variant (g_variant_new_string (digest + strlen ("sha256:")))); + +@@ -3818,11 +3814,11 @@ flatpak_pull_from_oci (OstreeRepo *repo, + metadata = g_variant_ref_sink (g_variant_builder_end (metadata_builder)); + if (!ostree_repo_write_commit_with_time (repo, + parent, +- subject, +- body, ++ flatpak_image_source_get_commit_subject (image_source), ++ flatpak_image_source_get_commit_body (image_source), + metadata, + OSTREE_REPO_FILE (archive_root), +- timestamp, ++ flatpak_image_source_get_commit_timestamp (image_source), + &commit_checksum, + cancellable, error)) + goto error; +-- +2.47.1 + +From c13709a1e320b9f7e5ff31b2284e372ba6ee08f4 Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Wed, 18 Dec 2024 00:32:32 +0100 +Subject: [PATCH 03/12]transaction: Typedef structs directly + +--- + common/flatpak-transaction.c | 12 ++++-------- + 1 file changed, 4 insertions(+), 8 deletions(-) + +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index 7339acab..8a16cc1e 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -138,15 +138,11 @@ struct _FlatpakTransactionOperation + GPtrArray *related_to_ops; /* (element-type FlatpakTransactionOperation) (nullable) */ + }; + +-typedef struct _FlatpakTransactionPrivate FlatpakTransactionPrivate; +- +-typedef struct _BundleData BundleData; +- +-struct _BundleData ++typedef struct _BundleData + { + GFile *file; + GBytes *gpg_data; +-}; ++} BundleData; + + typedef struct { + FlatpakTransaction *transaction; +@@ -157,7 +153,7 @@ typedef struct { + GVariant *results; + } RequestData; + +-struct _FlatpakTransactionPrivate ++typedef struct _FlatpakTransactionPrivate + { + GObject parent; + +@@ -198,7 +194,7 @@ struct _FlatpakTransactionPrivate + + gboolean needs_resolve; + gboolean needs_tokens; +-}; ++} FlatpakTransactionPrivate; + + enum { + NEW_OPERATION, +-- +2.47.1 + +From 8d6959c27c5b992750cbcc2fefb016a3d58db58c Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Wed, 18 Dec 2024 00:38:15 +0100 +Subject: [PATCH 04/12]transaction: Use g_clear_pointer/object functions for + op finalize + +--- + common/flatpak-transaction.c | 45 +++++++++++++++--------------------- + 1 file changed, 18 insertions(+), 27 deletions(-) + +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index 8a16cc1e..7039d86e 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -593,34 +593,25 @@ flatpak_transaction_operation_finalize (GObject *object) + { + FlatpakTransactionOperation *self = (FlatpakTransactionOperation *) object; + +- g_free (self->remote); +- flatpak_decomposed_unref (self->ref); +- g_free (self->commit); +- g_strfreev (self->subpaths); ++ g_clear_pointer (&self->remote, g_free); ++ g_clear_pointer (&self->ref, flatpak_decomposed_unref); ++ g_clear_pointer (&self->commit, g_free); ++ g_clear_pointer (&self->subpaths, g_strfreev); + g_clear_object (&self->bundle); +- g_free (self->eol); +- g_free (self->eol_rebase); +- if (self->previous_ids) +- g_strfreev (self->previous_ids); +- if (self->external_metadata) +- g_bytes_unref (self->external_metadata); +- g_free (self->resolved_commit); +- if (self->resolved_sideload_path) +- g_object_unref (self->resolved_sideload_path); +- if (self->resolved_metadata) +- g_bytes_unref (self->resolved_metadata); +- if (self->resolved_metakey) +- g_key_file_unref (self->resolved_metakey); +- if (self->resolved_old_metadata) +- g_bytes_unref (self->resolved_old_metadata); +- if (self->resolved_old_metakey) +- g_key_file_unref (self->resolved_old_metakey); +- g_free (self->resolved_token); +- g_list_free (self->run_before_ops); +- if (self->related_to_ops) +- g_ptr_array_unref (self->related_to_ops); +- if (self->summary_metadata) +- g_variant_unref (self->summary_metadata); ++ g_clear_pointer (&self->eol, g_free); ++ g_clear_pointer (&self->eol_rebase, g_free); ++ g_clear_pointer (&self->previous_ids, g_strfreev); ++ g_clear_pointer (&self->external_metadata, g_bytes_unref); ++ g_clear_pointer (&self->resolved_commit, g_free); ++ g_clear_object (&self->resolved_sideload_path); ++ g_clear_pointer (&self->resolved_metadata, g_bytes_unref); ++ g_clear_pointer (&self->resolved_metakey, g_key_file_unref); ++ g_clear_pointer (&self->resolved_old_metadata, g_bytes_unref); ++ g_clear_pointer (&self->resolved_old_metakey, g_key_file_unref); ++ g_clear_pointer (&self->resolved_token, g_free); ++ g_clear_pointer (&self->run_before_ops, g_list_free); ++ g_clear_pointer (&self->related_to_ops, g_ptr_array_unref); ++ g_clear_pointer (&self->summary_metadata, g_variant_unref); + + G_OBJECT_CLASS (flatpak_transaction_operation_parent_class)->finalize (object); + } +-- +2.47.1 + +From b235bb419e87d2a9c13e29a55f9cd32a3089f15a Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Wed, 18 Dec 2024 00:46:33 +0100 +Subject: [PATCH 05/12]image-source: Add flatpak_image_source_new_for_location + +Which allows one to create an image source from a container location. + +It also adds a new FlatpakDockerReference to access different parts of a +docker reference and changes to FlatpakOciIndex to get a manifest for a +specific architecture. + +This will become useful in the next commit when we're going to add +support for installing OCI images. +--- + common/flatpak-docker-reference-private.h | 20 ++++ + common/flatpak-docker-reference.c | 140 ++++++++++++++++++++++ + common/flatpak-image-source-private.h | 3 + + common/flatpak-image-source.c | 122 +++++++++++++++++++ + common/flatpak-json-oci-private.h | 4 +- + common/flatpak-json-oci.c | 21 ++++ + common/meson.build | 1 + + 7 files changed, 310 insertions(+), 1 deletion(-) + create mode 100644 common/flatpak-docker-reference-private.h + create mode 100644 common/flatpak-docker-reference.c + +diff --git a/common/flatpak-docker-reference-private.h b/common/flatpak-docker-reference-private.h +new file mode 100644 +index 00000000..8205064c +--- /dev/null ++++ b/common/flatpak-docker-reference-private.h +@@ -0,0 +1,20 @@ ++#ifndef __FLATPAK_DOCKER_REFERENCE_H__ ++#define __FLATPAK_DOCKER_REFERENCE_H__ ++ ++#include ++ ++typedef struct _FlatpakDockerReference FlatpakDockerReference; ++ ++FlatpakDockerReference *flatpak_docker_reference_parse (const char *reference_str, ++ GError **error); ++ ++const char *flatpak_docker_reference_get_uri (FlatpakDockerReference *reference); ++const char *flatpak_docker_reference_get_repository (FlatpakDockerReference *reference); ++const char *flatpak_docker_reference_get_tag (FlatpakDockerReference *reference); ++const char *flatpak_docker_reference_get_digest (FlatpakDockerReference *reference); ++ ++void flatpak_docker_reference_free (FlatpakDockerReference *reference); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlatpakDockerReference, flatpak_docker_reference_free); ++ ++#endif /* __FLATPAK_DOCKER_REFERENCE_H__ */ +diff --git a/common/flatpak-docker-reference.c b/common/flatpak-docker-reference.c +new file mode 100644 +index 00000000..6df3011f +--- /dev/null ++++ b/common/flatpak-docker-reference.c +@@ -0,0 +1,140 @@ ++/* 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 . ++ * ++ * Authors: ++ * Owen Taylor ++ */ ++ ++#include "flatpak-docker-reference-private.h" ++#include "flatpak-utils-private.h" ++ ++struct _FlatpakDockerReference ++{ ++ char *uri; ++ char *repository; ++ char *tag; ++ char *digest; ++}; ++ ++/* ++ * Parsing here is loosely based off: ++ * ++ * https://github.com/containers/image/tree/main/docker/reference ++ * ++ * The major simplification is that we require a domain component, and ++ * don't have any default domain. This removes ambiguity between domains and paths ++ * and makes parsing much simpler. We also don't normalize single component ++ * paths (e.g. ubuntu => library/ubuntu.) ++ */ ++ ++#define TAG "[0-9A-Za-z_][0-9A-Za-z_-]{0,127}" ++#define DIGEST "[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}" ++#define REMAINDER_TAG_AND_DIGEST_RE "^(.*?)(:" TAG ")?" "(@" DIGEST ")?$" ++ ++static GRegex * ++get_remainder_tag_and_digest_regex (void) ++{ ++ static gsize regex = 0; ++ ++ if (g_once_init_enter (®ex)) ++ { ++ g_autoptr(GRegex) compiled = g_regex_new (REMAINDER_TAG_AND_DIGEST_RE, ++ G_REGEX_DEFAULT, ++ G_REGEX_MATCH_DEFAULT, ++ NULL); ++ g_once_init_leave (®ex, (gsize)g_steal_pointer (&compiled)); ++ } ++ ++ return (GRegex *)regex; ++} ++ ++FlatpakDockerReference * ++flatpak_docker_reference_parse (const char *reference_str, ++ GError **error) ++{ ++ g_autoptr(FlatpakDockerReference) reference = g_new0 (FlatpakDockerReference, 1); ++ GRegex *regex = get_remainder_tag_and_digest_regex (); ++ g_autoptr(GMatchInfo) match_info = NULL; ++ g_autofree char *tag_match = NULL; ++ g_autofree char *digest_match = NULL; ++ g_autofree char *remainder = NULL; ++ g_autofree char *domain = NULL; ++ gboolean matched; ++ const char *slash; ++ ++ matched = g_regex_match (regex, reference_str, G_REGEX_MATCH_DEFAULT, &match_info); ++ g_assert (matched); ++ ++ tag_match = g_match_info_fetch (match_info, 2); ++ if (tag_match[0] == '\0') ++ reference->tag = NULL; ++ else ++ reference->tag = g_strdup (tag_match + 1); ++ ++ digest_match = g_match_info_fetch (match_info, 3); ++ if (digest_match[0] == '\0') ++ reference->digest = NULL; ++ else ++ reference->digest = g_strdup (digest_match + 1); ++ ++ remainder = g_match_info_fetch (match_info, 1); ++ slash = strchr (remainder, '/'); ++ if (slash == NULL || slash == reference_str || *slash == '\0') ++ { ++ flatpak_fail(error, "Can't parse %s into /", remainder); ++ return NULL; ++ } ++ ++ domain = g_strndup (remainder, slash - remainder); ++ reference->uri = g_strconcat ("https://", domain, NULL); ++ reference->repository = g_strdup (slash + 1); ++ ++ return g_steal_pointer (&reference); ++} ++ ++const char * ++flatpak_docker_reference_get_uri (FlatpakDockerReference *reference) ++{ ++ return reference->uri; ++} ++ ++const char * ++flatpak_docker_reference_get_repository (FlatpakDockerReference *reference) ++{ ++ return reference->repository; ++} ++ ++const char * ++flatpak_docker_reference_get_tag (FlatpakDockerReference *reference) ++{ ++ return reference->tag; ++} ++ ++const char * ++flatpak_docker_reference_get_digest (FlatpakDockerReference *reference) ++{ ++ return reference->digest; ++} ++ ++void ++flatpak_docker_reference_free (FlatpakDockerReference *reference) ++{ ++ g_clear_pointer (&reference->uri, g_free); ++ g_clear_pointer (&reference->repository, g_free); ++ g_clear_pointer (&reference->tag, g_free); ++ g_clear_pointer (&reference->digest, g_free); ++ g_free (reference); ++} +diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h +index fe8f02ba..b1e5d2fa 100644 +--- a/common/flatpak-image-source-private.h ++++ b/common/flatpak-image-source-private.h +@@ -41,6 +41,9 @@ FlatpakImageSource *flatpak_image_source_new_remote (const char *uri, + const char *digest, + GCancellable *cancellable, + GError **error); ++FlatpakImageSource *flatpak_image_source_new_for_location (const char *location, ++ GCancellable *cancellable, ++ GError **error); + + void flatpak_image_source_set_token (FlatpakImageSource *self, + const char *token); +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +index feb4fe27..0503de5d 100644 +--- a/common/flatpak-image-source.c ++++ b/common/flatpak-image-source.c +@@ -20,6 +20,7 @@ + + #include + ++#include "flatpak-docker-reference-private.h" + #include "flatpak-image-source-private.h" + #include "flatpak-oci-registry-private.h" + +@@ -175,6 +176,127 @@ flatpak_image_source_new_remote (const char *uri, + return flatpak_image_source_new (registry, oci_repository, digest, cancellable, error); + } + ++/* Parse an oci: or oci-archive: image location into a path ++ * and an optional reference ++ */ ++static void ++get_path_and_reference (const char *image_location, ++ GFile **path, ++ char **reference) ++{ ++ g_autofree char *path_str = NULL; ++ const char *bare; ++ const char *colon; ++ ++ colon = strchr (image_location, ':'); ++ g_assert (colon != NULL); ++ ++ bare = colon + 1; ++ colon = strchr (bare, ':'); ++ if (colon) ++ { ++ path_str = g_strndup (bare, colon - bare); ++ *reference = g_strdup (colon + 1); ++ } ++ else ++ { ++ path_str = g_strdup (bare); ++ *reference = NULL; ++ } ++ ++ *path = g_file_new_for_path (path_str); ++} ++ ++FlatpakImageSource * ++flatpak_image_source_new_for_location (const char *location, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ if (g_str_has_prefix (location, "oci:")) ++ { ++ g_autoptr(GFile) path = NULL; ++ g_autofree char *reference = NULL; ++ ++ get_path_and_reference (location, &path, &reference); ++ ++ return flatpak_image_source_new_local (path, reference, cancellable, error); ++ } ++ else if (g_str_has_prefix (location, "docker:")) ++ { ++ g_autoptr(FlatpakOciRegistry) registry = NULL; ++ g_autoptr(FlatpakDockerReference) docker_reference = NULL; ++ g_autofree char *local_digest = NULL; ++ const char *repository = NULL; ++ ++ if (!g_str_has_prefix (location, "docker://")) ++ { ++ flatpak_fail (error, "docker: location must start docker://"); ++ return NULL; ++ } ++ ++ docker_reference = flatpak_docker_reference_parse (location + 9, error); ++ if (docker_reference == NULL) ++ return NULL; ++ ++ registry = flatpak_oci_registry_new (flatpak_docker_reference_get_uri (docker_reference), ++ FALSE, -1, cancellable, error); ++ if (registry == NULL) ++ return NULL; ++ ++ repository = flatpak_docker_reference_get_repository (docker_reference); ++ ++ local_digest = g_strdup (flatpak_docker_reference_get_digest (docker_reference)); ++ if (local_digest == NULL) ++ { ++ g_autoptr(GBytes) bytes = NULL; ++ g_autoptr(FlatpakOciVersioned) versioned = NULL; ++ const char *tag = flatpak_docker_reference_get_tag (docker_reference); ++ ++ if (tag == NULL) ++ tag = "latest"; ++ ++ bytes = flatpak_oci_registry_load_blob (registry, repository, TRUE, tag, ++ NULL, NULL, cancellable, error); ++ if (!bytes) ++ return NULL; ++ ++ versioned = flatpak_oci_versioned_from_json (bytes, NULL, error); ++ if (!versioned) ++ return NULL; ++ ++ if (FLATPAK_IS_OCI_MANIFEST (versioned)) ++ { ++ g_autofree char *checksum = NULL; ++ ++ checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, bytes); ++ local_digest = g_strconcat ("sha256:", checksum, NULL); ++ } ++ else if (FLATPAK_IS_OCI_INDEX (versioned)) ++ { ++ const char *oci_arch = flatpak_arch_to_oci_arch (flatpak_get_arch ()); ++ FlatpakOciManifestDescriptor *descriptor; ++ ++ descriptor = flatpak_oci_index_get_manifest_for_arch (FLATPAK_OCI_INDEX (versioned), oci_arch); ++ if (descriptor == NULL) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, ++ "Can't find manifest for %s in image index", oci_arch); ++ return NULL; ++ } ++ ++ local_digest = g_strdup (descriptor->parent.digest); ++ } ++ } ++ ++ return flatpak_image_source_new (registry, repository, local_digest, cancellable, error); ++ } ++ else ++ { ++ flatpak_fail (error, "unsupported image location: %s", location); ++ return NULL; ++ } ++} ++ + void + flatpak_image_source_set_token (FlatpakImageSource *self, + const char *token) +diff --git a/common/flatpak-json-oci-private.h b/common/flatpak-json-oci-private.h +index 87573c17..e8924ee4 100644 +--- a/common/flatpak-json-oci-private.h ++++ b/common/flatpak-json-oci-private.h +@@ -166,8 +166,10 @@ gboolean flatpak_oci_index_remove_manifest (FlatpakOciIndex + FlatpakOciManifestDescriptor *flatpak_oci_index_get_manifest (FlatpakOciIndex *self, + const char *ref); + FlatpakOciManifestDescriptor *flatpak_oci_index_get_only_manifest (FlatpakOciIndex *self); +-int flatpak_oci_index_get_n_manifests (FlatpakOciIndex *self); ++FlatpakOciManifestDescriptor *flatpak_oci_index_get_manifest_for_arch (FlatpakOciIndex *self, ++ const char *oci_arch); + ++int flatpak_oci_index_get_n_manifests (FlatpakOciIndex *self); + /* Only useful for delta index */ + FlatpakOciDescriptor *flatpak_oci_index_find_delta_for (FlatpakOciIndex *delta_index, + const char *for_digest); +diff --git a/common/flatpak-json-oci.c b/common/flatpak-json-oci.c +index 2b55b9de..3a339a02 100644 +--- a/common/flatpak-json-oci.c ++++ b/common/flatpak-json-oci.c +@@ -577,6 +577,27 @@ flatpak_oci_index_get_only_manifest (FlatpakOciIndex *self) + return NULL; + } + ++FlatpakOciManifestDescriptor * ++flatpak_oci_index_get_manifest_for_arch (FlatpakOciIndex *self, ++ const char *oci_arch) ++{ ++ int i, found = -1; ++ ++ if (self->manifests == NULL) ++ return NULL; ++ ++ for (i = 0; self->manifests[i] != NULL; i++) ++ { ++ if (strcmp (self->manifests[i]->platform.architecture, oci_arch) == 0) ++ return self->manifests[i]; ++ } ++ ++ if (found >= 0) ++ return self->manifests[found]; ++ ++ return NULL; ++} ++ + gboolean + flatpak_oci_index_remove_manifest (FlatpakOciIndex *self, + const char *ref) +diff --git a/common/meson.build b/common/meson.build +index beed1f06..c14337be 100644 +--- a/common/meson.build ++++ b/common/meson.build +@@ -169,6 +169,7 @@ sources = [ + 'flatpak-context.c', + 'flatpak-dir.c', + 'flatpak-dir-utils.c', ++ 'flatpak-docker-reference.c', + 'flatpak-error.c', + 'flatpak-exports.c', + 'flatpak-glib-backports.c', +-- +2.47.1 + +From c9b992fda4ac3cd53c2f315d135823ac6929ad17 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Wed, 18 Dec 2024 00:52:27 +0100 +Subject: [PATCH 06/12]common: Add OCI image installation support + +--- + common/flatpak-dir-private.h | 3 + + common/flatpak-dir.c | 131 ++++++++++-------- + common/flatpak-installation.c | 4 +- + common/flatpak-transaction.c | 183 +++++++++++++++++++++++--- + common/flatpak-transaction.h | 4 + + system-helper/flatpak-system-helper.c | 6 +- + 6 files changed, 252 insertions(+), 79 deletions(-) + +diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h +index 871f40b6..539476b8 100644 +--- a/common/flatpak-dir-private.h ++++ b/common/flatpak-dir-private.h +@@ -574,6 +574,7 @@ gboolean flatpak_dir_pull (Fla + const char *opt_rev, + const char **subpaths, + GFile *sideload_repo, ++ FlatpakImageSource *image_source, + GBytes *require_metadata, + const char *token, + OstreeRepo *repo, +@@ -694,6 +695,7 @@ gboolean flatpak_dir_install (Fla + const char **subpaths, + const char **previous_ids, + GFile *sideload_repo, ++ FlatpakImageSource *image_source, + GBytes *require_metadata, + const char *token, + FlatpakProgress *progress, +@@ -740,6 +742,7 @@ gboolean flatpak_dir_update (Fla + const char **opt_subpaths, + const char **opt_previous_ids, + GFile *sideload_repo, ++ FlatpakImageSource *image_source, + GBytes *require_metadata, + const char *token, + FlatpakProgress *progress, +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index e1224170..e8ff645b 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -123,7 +123,7 @@ static gboolean flatpak_dir_mirror_oci (FlatpakDir *self, + FlatpakRemoteState *state, + const char *ref, + const char *opt_rev, +- const char *skip_if_current_is, ++ FlatpakImageSource *opt_image_source, + const char *token, + FlatpakProgress *progress, + GCancellable *cancellable, +@@ -5307,7 +5307,7 @@ flatpak_dir_update_appstream (FlatpakDir *self, + if (child_repo == NULL) + return FALSE; + +- if (!flatpak_dir_pull (self, state, used_branch, appstream_commit, NULL, appstream_sideload_path, NULL, NULL, ++ if (!flatpak_dir_pull (self, state, used_branch, appstream_commit, NULL, appstream_sideload_path, NULL, NULL, NULL, + child_repo, FLATPAK_PULL_FLAGS_NONE, 0, + progress, cancellable, error)) + { +@@ -5351,7 +5351,7 @@ flatpak_dir_update_appstream (FlatpakDir *self, + } + + +- if (!flatpak_dir_pull (self, state, used_branch, appstream_commit, NULL, appstream_sideload_path, NULL, NULL, NULL, ++ if (!flatpak_dir_pull (self, state, used_branch, appstream_commit, NULL, appstream_sideload_path, NULL, NULL, NULL, NULL, + FLATPAK_PULL_FLAGS_NONE, OSTREE_REPO_PULL_FLAGS_NONE, progress, + cancellable, error)) + { +@@ -5903,7 +5903,7 @@ flatpak_dir_mirror_oci (FlatpakDir *self, + FlatpakRemoteState *state, + const char *ref, + const char *opt_rev, +- const char *skip_if_current_is, ++ FlatpakImageSource *opt_image_source, + const char *token, + FlatpakProgress *progress, + GCancellable *cancellable, +@@ -5911,40 +5911,42 @@ flatpak_dir_mirror_oci (FlatpakDir *self, + { + g_autoptr(FlatpakImageSource) image_source = NULL; + g_autofree char *oci_digest = NULL; +- g_autofree char *latest_rev = NULL; +- VarRefInfoRef latest_rev_info; +- VarMetadataRef metadata; +- const char *oci_repository = NULL; + const char *delta_url = NULL; +- const char *rev; + gboolean res; + +- /* We use the summary so that we can reuse any cached json */ +- if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) +- return FALSE; +- if (latest_rev == NULL) +- return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, +- _("Couldn't find latest checksum for ref %s in remote %s"), +- ref, state->remote_name); +- +- rev = opt_rev != NULL ? opt_rev : latest_rev; +- +- if (skip_if_current_is != NULL && strcmp (rev, skip_if_current_is) == 0) ++ if (opt_image_source) + { +- return flatpak_fail_error (error, FLATPAK_ERROR_ALREADY_INSTALLED, +- _("%s commit %s already installed"), +- ref, rev); ++ image_source = g_object_ref (opt_image_source); ++ oci_digest = g_strdup (flatpak_image_source_get_digest (image_source)); + } ++ else ++ { ++ g_autofree char *latest_rev = NULL; ++ VarRefInfoRef latest_rev_info; ++ VarMetadataRef metadata; ++ const char *oci_repository = NULL; ++ const char *rev; + +- metadata = var_ref_info_get_metadata (latest_rev_info); +- oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); +- delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); ++ /* We use the summary so that we can reuse any cached json */ ++ if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) ++ return FALSE; ++ if (latest_rev == NULL) ++ return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, ++ _("Couldn't find latest checksum for ref %s in remote %s"), ++ ref, state->remote_name); + +- oci_digest = g_strconcat ("sha256:", rev, NULL); ++ rev = opt_rev != NULL ? opt_rev : latest_rev; + +- image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); +- if (image_source == NULL) +- return FALSE; ++ metadata = var_ref_info_get_metadata (latest_rev_info); ++ oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); ++ delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); ++ ++ oci_digest = g_strconcat ("sha256:", rev, NULL); ++ ++ image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); ++ if (image_source == NULL) ++ return FALSE; ++ } + + flatpak_progress_start_oci_pull (progress); + +@@ -5964,6 +5966,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, + FlatpakRemoteState *state, + const char *ref, + const char *opt_rev, ++ FlatpakImageSource *opt_image_source, + OstreeRepo *repo, + FlatpakPullFlags flatpak_flags, + OstreeRepoPullFlags flags, +@@ -5974,40 +5977,49 @@ flatpak_dir_pull_oci (FlatpakDir *self, + { + g_autoptr(FlatpakImageSource) image_source = NULL; + FlatpakOciRegistry *registry = NULL; +- const char *oci_repository = NULL; + const char *delta_url = NULL; + g_autofree char *oci_digest = NULL; + g_autofree char *checksum = NULL; +- VarRefInfoRef latest_rev_info; + g_autofree char *latest_alt_commit = NULL; +- VarMetadataRef metadata; +- g_autofree char *latest_rev = NULL; + G_GNUC_UNUSED g_autofree char *latest_commit = + flatpak_dir_read_latest (self, state->remote_name, ref, &latest_alt_commit, cancellable, NULL); + g_autofree char *name = NULL; + +- /* We use the summary so that we can reuse any cached json */ +- if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) +- return FALSE; +- if (latest_rev == NULL) +- return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, +- _("Couldn't find latest checksum for ref %s in remote %s"), +- ref, state->remote_name); ++ if (opt_image_source) ++ { ++ image_source = g_object_ref (opt_image_source); ++ oci_digest = g_strdup (flatpak_image_source_get_digest (image_source)); ++ } ++ else ++ { ++ VarMetadataRef metadata; ++ VarRefInfoRef latest_rev_info; ++ const char *oci_repository = NULL; ++ g_autofree char *latest_rev = NULL; + +- metadata = var_ref_info_get_metadata (latest_rev_info); +- oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); +- delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); ++ /* We use the summary so that we can reuse any cached json */ ++ if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) ++ return FALSE; ++ if (latest_rev == NULL) ++ return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, ++ _("Couldn't find latest checksum for ref %s in remote %s"), ++ ref, state->remote_name); ++ ++ metadata = var_ref_info_get_metadata (latest_rev_info); ++ oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); ++ delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); ++ ++ oci_digest = g_strconcat ("sha256:", opt_rev != NULL ? opt_rev : latest_rev, NULL); + +- oci_digest = g_strconcat ("sha256:", opt_rev != NULL ? opt_rev : latest_rev, NULL); ++ image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); ++ if (image_source == NULL) ++ return FALSE; ++ } + + /* Short circuit if we've already got this commit */ + if (latest_alt_commit != NULL && strcmp (oci_digest + strlen ("sha256:"), latest_alt_commit) == 0) + return TRUE; + +- image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); +- if (image_source == NULL) +- return FALSE; +- + if (repo == NULL) + repo = self->repo; + +@@ -6046,6 +6058,7 @@ flatpak_dir_pull (FlatpakDir *self, + const char *opt_rev, + const char **subpaths, + GFile *sideload_repo, ++ FlatpakImageSource *image_source, + GBytes *require_metadata, + const char *token, + OstreeRepo *repo, +@@ -6076,8 +6089,8 @@ flatpak_dir_pull (FlatpakDir *self, + if (repo == NULL && !flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error)) + return FALSE; + +- if (flatpak_dir_get_remote_oci (self, state->remote_name)) +- return flatpak_dir_pull_oci (self, state, ref, opt_rev, repo, flatpak_flags, ++ if (image_source || flatpak_dir_get_remote_oci (self, state->remote_name)) ++ return flatpak_dir_pull_oci (self, state, ref, opt_rev, image_source, repo, flatpak_flags, + flags, token, progress, cancellable, error); + + if (!ostree_repo_remote_get_url (self->repo, +@@ -9838,6 +9851,7 @@ flatpak_dir_install (FlatpakDir *self, + const char **opt_subpaths, + const char **opt_previous_ids, + GFile *sideload_repo, ++ FlatpakImageSource *image_source, + GBytes *require_metadata, + const char *token, + FlatpakProgress *progress, +@@ -9902,7 +9916,8 @@ flatpak_dir_install (FlatpakDir *self, + + child_repo_path = g_file_get_path (registry_file); + +- if (!flatpak_dir_mirror_oci (self, registry, state, flatpak_decomposed_get_ref (ref), opt_commit, NULL, token, progress, cancellable, error)) ++ if (!flatpak_dir_mirror_oci (self, registry, state, flatpak_decomposed_get_ref (ref), ++ opt_commit, image_source, token, progress, cancellable, error)) + return FALSE; + } + else if (!gpg_verify_summary || !gpg_verify) +@@ -9996,7 +10011,7 @@ flatpak_dir_install (FlatpakDir *self, + + flatpak_flags |= FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA; + +- if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, subpaths, sideload_repo, require_metadata, token, ++ if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, subpaths, sideload_repo, NULL, require_metadata, token, + child_repo, + flatpak_flags, + 0, +@@ -10071,7 +10086,8 @@ flatpak_dir_install (FlatpakDir *self, + + if (!no_pull) + { +- if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, opt_subpaths, sideload_repo, require_metadata, token, NULL, ++ if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, opt_subpaths, ++ sideload_repo, image_source, require_metadata, token, NULL, + flatpak_flags, OSTREE_REPO_PULL_FLAGS_NONE, + progress, cancellable, error)) + return FALSE; +@@ -10518,6 +10534,7 @@ flatpak_dir_update (FlatpakDir *self, + const char **opt_subpaths, + const char **opt_previous_ids, + GFile *sideload_repo, ++ FlatpakImageSource *image_source, + GBytes *require_metadata, + const char *token, + FlatpakProgress *progress, +@@ -10605,7 +10622,7 @@ flatpak_dir_update (FlatpakDir *self, + child_repo_path = g_file_get_path (registry_file); + + if (!flatpak_dir_mirror_oci (self, registry, state, flatpak_decomposed_get_ref (ref), +- commit, NULL, token, progress, cancellable, error)) ++ commit, image_source, token, progress, cancellable, error)) + return FALSE; + } + else if (!gpg_verify_summary || !gpg_verify) +@@ -10685,7 +10702,7 @@ flatpak_dir_update (FlatpakDir *self, + + flatpak_flags |= FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA; + if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), +- commit, subpaths, sideload_repo, require_metadata, token, ++ commit, subpaths, sideload_repo, NULL, require_metadata, token, + child_repo, + flatpak_flags, 0, + progress, cancellable, error)) +@@ -10751,7 +10768,7 @@ flatpak_dir_update (FlatpakDir *self, + if (!no_pull) + { + if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), +- commit, subpaths, sideload_repo, require_metadata, token, ++ commit, subpaths, sideload_repo, image_source, require_metadata, token, + NULL, flatpak_flags, OSTREE_REPO_PULL_FLAGS_NONE, + progress, cancellable, error)) + return FALSE; +diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c +index c7d2e1dd..3a8677e8 100644 +--- a/common/flatpak-installation.c ++++ b/common/flatpak-installation.c +@@ -1930,7 +1930,7 @@ flatpak_installation_install_full (FlatpakInstallation *self, + (flags & FLATPAK_INSTALL_FLAGS_NO_DEPLOY) != 0, + (flags & FLATPAK_INSTALL_FLAGS_NO_STATIC_DELTAS) != 0, + FALSE, FALSE, FALSE, state, +- ref, NULL, (const char **) subpaths, NULL, NULL, NULL, NULL, ++ ref, NULL, (const char **) subpaths, NULL, NULL, NULL, NULL, NULL, + progress, cancellable, error)) + return NULL; + +@@ -2098,7 +2098,7 @@ flatpak_installation_update_full (FlatpakInstallation *self, + (flags & FLATPAK_UPDATE_FLAGS_NO_STATIC_DELTAS) != 0, + FALSE, FALSE, FALSE, state, + ref, target_commit, +- (const char **) subpaths, NULL, NULL, NULL, NULL, ++ (const char **) subpaths, NULL, NULL, NULL, NULL, NULL, + progress, cancellable, error)) + return NULL; + +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index 7039d86e..ffee385e 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -26,7 +26,9 @@ + #include "flatpak-auth-private.h" + #include "flatpak-dir-private.h" + #include "flatpak-error.h" ++#include "flatpak-image-source-private.h" + #include "flatpak-installation-private.h" ++#include "flatpak-oci-registry-private.h" + #include "flatpak-progress-private.h" + #include "flatpak-repo-utils-private.h" + #include "flatpak-transaction-private.h" +@@ -103,6 +105,7 @@ struct _FlatpakTransactionOperation + char **subpaths; + char **previous_ids; + char *commit; ++ FlatpakImageSource *image_source; + GFile *bundle; + GBytes *external_metadata; + FlatpakTransactionOperationType kind; +@@ -115,6 +118,7 @@ struct _FlatpakTransactionOperation + gboolean resolved; + char *resolved_commit; + GFile *resolved_sideload_path; ++ FlatpakImageSource *resolved_image_source; + GBytes *resolved_metadata; + GKeyFile *resolved_metakey; + GBytes *resolved_old_metadata; +@@ -144,6 +148,11 @@ typedef struct _BundleData + GBytes *gpg_data; + } BundleData; + ++typedef struct _ImageData ++{ ++ char *image_location; ++} ImageData; ++ + typedef struct { + FlatpakTransaction *transaction; + const char *remote; +@@ -168,6 +177,7 @@ typedef struct _FlatpakTransactionPrivate + + GList *flatpakrefs; /* GKeyFiles */ + GList *bundles; /* BundleData */ ++ GList *images; /* ImageData */ + + guint next_request_id; + guint active_request_id; +@@ -260,6 +270,23 @@ bundle_data_free (BundleData *data) + g_free (data); + } + ++static ImageData * ++image_data_new (const char *image_location) ++{ ++ ImageData *data = g_new0 (ImageData, 1); ++ ++ data->image_location = g_strdup (image_location); ++ ++ return data; ++} ++ ++static void ++image_data_free (ImageData *data) ++{ ++ g_clear_pointer (&data->image_location, g_free); ++ g_free (data); ++} ++ + static guint progress_signals[LAST_SIGNAL] = { 0 }; + + /** +@@ -612,6 +639,8 @@ flatpak_transaction_operation_finalize (GObject *object) + g_clear_pointer (&self->run_before_ops, g_list_free); + g_clear_pointer (&self->related_to_ops, g_ptr_array_unref); + g_clear_pointer (&self->summary_metadata, g_variant_unref); ++ g_clear_object (&self->image_source); ++ g_clear_object (&self->resolved_image_source); + + G_OBJECT_CLASS (flatpak_transaction_operation_parent_class)->finalize (object); + } +@@ -993,6 +1022,7 @@ flatpak_transaction_finalize (GObject *object) + g_free (priv->parent_window); + g_list_free_full (priv->flatpakrefs, (GDestroyNotify) g_key_file_unref); + g_list_free_full (priv->bundles, (GDestroyNotify) bundle_data_free); ++ g_list_free_full (priv->images, (GDestroyNotify) image_data_free); + g_free (priv->default_arch); + g_hash_table_unref (priv->last_op_for_ref); + g_hash_table_unref (priv->remote_states); +@@ -2603,6 +2633,7 @@ flatpak_transaction_add_ref (FlatpakTransaction *self, + const char *commit, + FlatpakTransactionOperationType kind, + GFile *bundle, ++ FlatpakImageSource *image_source, + const char *external_metadata, + gboolean pin_on_deploy, + FlatpakTransactionOperation **out_op, +@@ -2730,6 +2761,9 @@ flatpak_transaction_add_ref (FlatpakTransaction *self, + op = flatpak_transaction_add_op (self, remote, ref, subpaths, previous_ids, + commit, bundle, kind, pin_on_deploy); + ++ if (image_source) ++ op->image_source = g_object_ref (image_source); ++ + if (external_metadata) + op->external_metadata = g_bytes_new (external_metadata, strlen (external_metadata)); + +@@ -2783,7 +2817,7 @@ flatpak_transaction_add_install (FlatpakTransaction *self, + + if (!flatpak_transaction_add_ref (self, remote, decomposed, subpaths, NULL, NULL, + FLATPAK_TRANSACTION_OPERATION_INSTALL, +- NULL, NULL, pin_on_deploy, NULL, error)) ++ NULL, NULL, NULL, pin_on_deploy, NULL, error)) + return FALSE; + + return TRUE; +@@ -2843,7 +2877,7 @@ 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, 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, NULL, error); + } + + /** +@@ -2919,12 +2953,12 @@ 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, FALSE, &rebase_op, error)) ++ NULL, NULL, NULL, FALSE, &rebase_op, error)) + return FALSE; + + if (!flatpak_transaction_add_ref (self, NULL, old_decomposed, NULL, NULL, NULL, + FLATPAK_TRANSACTION_OPERATION_UNINSTALL, +- NULL, NULL, FALSE, &uninstall_op, &local_error)) ++ NULL, NULL, NULL, FALSE, &uninstall_op, &local_error)) + { + /* If the user is trying to install an eol-rebased app from scratch, the + * @old_ref can’t be uninstalled because it’s not installed already. +@@ -2980,6 +3014,36 @@ flatpak_transaction_add_install_bundle (FlatpakTransaction *self, + return TRUE; + } + ++/** ++ * flatpak_transaction_add_install_image: ++ * @self: a #FlatpakTransaction ++ * @image_location: (nullable): location string to install from. ++ * @error: return location for a #GError ++ * ++ * Install a Flatpak from a container image. The image is specified ++ * ++ * If the reference from the image was previously installed, then ++ * that remote will be used as the remote for the newly installed image. If the ++ * reference was not previously installed, then a remote will be created for the ++ * reference. ++ * ++ * @image_location is specified in containers-transports(5) form. Only a subset ++ * of transports are supported: oci: and docker:. ++ * ++ * Returns: %TRUE on success; %FALSE with @error set on failure. ++ */ ++gboolean ++flatpak_transaction_add_install_image (FlatpakTransaction *self, ++ const char *image_location, ++ GError **error) ++{ ++ FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); ++ ++ priv->images = g_list_append (priv->images, image_data_new (image_location)); ++ ++ return TRUE; ++} ++ + /** + * flatpak_transaction_add_install_flatpakref: + * @self: a #FlatpakTransaction +@@ -3046,7 +3110,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, FALSE, NULL, error); ++ return flatpak_transaction_add_ref (self, NULL, decomposed, subpaths, NULL, commit, FLATPAK_TRANSACTION_OPERATION_UPDATE, NULL, NULL, NULL, FALSE, NULL, error); + } + + /** +@@ -3073,7 +3137,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, FALSE, NULL, error); ++ return flatpak_transaction_add_ref (self, NULL, decomposed, NULL, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_UNINSTALL, NULL, NULL, NULL, FALSE, NULL, error); + } + + static gboolean +@@ -3185,7 +3249,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, FALSE, NULL, ++ NULL, NULL, NULL, FALSE, NULL, + &local_error)) + g_info ("Failed to add auto-install ref %s: %s", flatpak_decomposed_get_ref (auto_install_ref), + local_error->message); +@@ -3283,6 +3347,7 @@ static gboolean + mark_op_resolved (FlatpakTransactionOperation *op, + const char *commit, + GFile *sideload_path, ++ FlatpakImageSource *image_source, + GBytes *metadata, + GBytes *old_metadata, + GError **error) +@@ -3291,7 +3356,7 @@ mark_op_resolved (FlatpakTransactionOperation *op, + + g_assert (op != NULL); + +- g_assert (commit != NULL); ++ g_assert (commit != NULL || image_source != NULL); + + op->resolved = TRUE; + +@@ -3304,6 +3369,9 @@ mark_op_resolved (FlatpakTransactionOperation *op, + if (sideload_path) + op->resolved_sideload_path = g_object_ref (sideload_path); + ++ if (image_source) ++ op->resolved_image_source = g_object_ref (image_source); ++ + if (metadata) + { + g_autoptr(GKeyFile) metakey = g_key_file_new (); +@@ -3337,13 +3405,14 @@ resolve_op_end (FlatpakTransaction *self, + FlatpakTransactionOperation *op, + const char *checksum, + GFile *sideload_path, ++ FlatpakImageSource *image_source, + GBytes *metadata_bytes, + GError **error) + { + g_autoptr(GBytes) old_metadata_bytes = NULL; + + old_metadata_bytes = load_deployed_metadata (self, op->ref, NULL, NULL); +- if (!mark_op_resolved (op, checksum, sideload_path, metadata_bytes, old_metadata_bytes, error)) ++ if (!mark_op_resolved (op, checksum, sideload_path, image_source, metadata_bytes, old_metadata_bytes, error)) + return FALSE; + emit_eol_and_maybe_skip (self, op); + return TRUE; +@@ -3394,7 +3463,7 @@ resolve_op_from_commit (FlatpakTransaction *self, + flatpak_decomposed_get_ref (eolr_decomposed)); + } + +- return resolve_op_end (self, op, checksum, sideload_path, metadata_bytes, error); ++ return resolve_op_end (self, op, checksum, sideload_path, NULL, metadata_bytes, error); + } + + /* NOTE: In case of non-available summary this returns FALSE with a +@@ -3459,7 +3528,7 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, + } + } + +- return resolve_op_end (self, op, checksum, sideload_path, metadata_bytes, error); ++ return resolve_op_end (self, op, checksum, sideload_path, NULL, metadata_bytes, error); + } + + static gboolean +@@ -3502,7 +3571,7 @@ resolve_ops (FlatpakTransaction *self, + * checksum we got was the version already installed. + */ + g_assert (op->resolved_commit != NULL); +- if (!mark_op_resolved (op, op->resolved_commit, NULL, NULL, NULL, error)) ++ if (!mark_op_resolved (op, op->resolved_commit, NULL, NULL, NULL, NULL, error)) + return FALSE; + continue; + } +@@ -3517,7 +3586,7 @@ resolve_ops (FlatpakTransaction *self, + op->skip = TRUE; + continue; + } +- if (!mark_op_resolved (op, checksum, NULL, metadata_bytes, NULL, error)) ++ if (!mark_op_resolved (op, checksum, NULL, NULL, metadata_bytes, NULL, error)) + return FALSE; + continue; + } +@@ -3525,7 +3594,7 @@ resolve_ops (FlatpakTransaction *self, + if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) + { + g_assert (op->commit != NULL); +- if (!mark_op_resolved (op, op->commit, NULL, op->external_metadata, NULL, error)) ++ if (!mark_op_resolved (op, op->commit, NULL, NULL, op->external_metadata, NULL, error)) + return FALSE; + continue; + } +@@ -3549,8 +3618,13 @@ resolve_ops (FlatpakTransaction *self, + if (state == NULL) + return FALSE; + ++ if (op->image_source) ++ { ++ if (!mark_op_resolved (op, NULL, NULL, op->image_source, op->external_metadata, NULL, error)) ++ return FALSE; ++ } + /* Should we use local state */ +- if (transaction_is_local_only (self, op->kind)) ++ else if (transaction_is_local_only (self, op->kind)) + { + g_autoptr(GVariant) commit_data = flatpak_dir_read_latest_commit (priv->dir, op->remote, op->ref, + &checksum, NULL, error); +@@ -4688,7 +4762,74 @@ flatpak_transaction_resolve_bundles (FlatpakTransaction *self, + + if (!flatpak_transaction_add_ref (self, remote, ref, NULL, NULL, commit, + FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE, +- data->file, metadata, FALSE, NULL, error)) ++ data->file, NULL, metadata, FALSE, NULL, error)) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++flatpak_transaction_resolve_images (FlatpakTransaction *self, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); ++ GList *l; ++ ++ for (l = priv->images; l != NULL; l = l->next) ++ { ++ ImageData *data = l->data; ++ g_autoptr(FlatpakImageSource) image_source = NULL; ++ g_autofree char *remote = NULL; ++ g_autoptr(FlatpakDecomposed) ref = NULL; ++ const char *ref_label; ++ const char *metadata_label; ++ FlatpakTransactionOperation *op; ++ g_autoptr(GBytes) deploy_data = NULL; ++ ++ image_source = flatpak_image_source_new_for_location (data->image_location, ++ cancellable, error); ++ if (!image_source) ++ return FALSE; ++ ++ ref_label = flatpak_image_source_get_ref (image_source); ++ ref = flatpak_decomposed_new_from_ref (ref_label, error); ++ if (ref == NULL) ++ { ++ g_prefix_error (error, "Cannot parse org.flatpak.ref label: "); ++ return FALSE; ++ } ++ ++ metadata_label = flatpak_image_source_get_metadata (image_source); ++ if (metadata_label == NULL) ++ return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, ++ "Image does not have org.flatpak.metadata label"); ++ ++ deploy_data = flatpak_dir_get_deploy_data (priv->dir, ref, FLATPAK_DEPLOY_VERSION_ANY, cancellable, NULL); ++ if (deploy_data != NULL) ++ remote = g_strdup (flatpak_deploy_data_get_origin (deploy_data)); ++ ++ if (remote == NULL) ++ { ++ gboolean created_remote; ++ g_autofree char *id = flatpak_decomposed_dup_id (ref); ++ ++ remote = flatpak_dir_create_origin_remote (priv->dir, NULL /* url */, id, ++ NULL /* title */, ref_label, ++ NULL /* gpg_data */, NULL /* collection_id */, ++ &created_remote, ++ cancellable, error); ++ if (!remote) ++ return FALSE; ++ ++ if (created_remote) ++ flatpak_installation_drop_caches (priv->installation, NULL, NULL); ++ } ++ ++ if (!flatpak_transaction_add_ref (self, remote, ref, NULL, NULL, NULL, ++ FLATPAK_TRANSACTION_OPERATION_INSTALL, ++ NULL, image_source, metadata_label, FALSE, &op, error)) + return FALSE; + } + +@@ -4747,7 +4888,7 @@ _run_op_kind (FlatpakTransaction *self, + + emit_new_op (self, op, progress); + +- g_assert (op->resolved_commit != NULL); /* We resolved this before */ ++ g_assert (op->resolved_commit != NULL || op->resolved_image_source != NULL); /* We resolved this before */ + + if (op->resolved_metakey && !flatpak_check_required_version (flatpak_decomposed_get_ref (op->ref), + op->resolved_metakey, &local_error)) +@@ -4765,6 +4906,7 @@ _run_op_kind (FlatpakTransaction *self, + (const char **) op->subpaths, + (const char **) op->previous_ids, + op->resolved_sideload_path, ++ op->resolved_image_source, + op->resolved_metadata, + op->resolved_token, + progress->progress_obj, +@@ -4838,6 +4980,7 @@ _run_op_kind (FlatpakTransaction *self, + (const char **) op->subpaths, + (const char **) op->previous_ids, + op->resolved_sideload_path, ++ op->resolved_image_source, + op->resolved_metadata, + op->resolved_token, + progress->progress_obj, +@@ -5149,6 +5292,12 @@ flatpak_transaction_real_run (FlatpakTransaction *self, + return FALSE; + } + ++ if (!flatpak_transaction_resolve_images (self, cancellable, error)) ++ { ++ g_assert (error == NULL || *error != NULL); ++ return FALSE; ++ } ++ + /* Resolve initial ops */ + if (!resolve_all_ops (self, cancellable, error)) + { +diff --git a/common/flatpak-transaction.h b/common/flatpak-transaction.h +index 0b8f2de8..3d3f4ce8 100644 +--- a/common/flatpak-transaction.h ++++ b/common/flatpak-transaction.h +@@ -326,6 +326,10 @@ gboolean flatpak_transaction_add_install_bundle (FlatpakTransaction * + GBytes *gpg_data, + GError **error); + FLATPAK_EXTERN ++gboolean flatpak_transaction_add_install_image (FlatpakTransaction *self, ++ const char *image_location, ++ GError **error); ++FLATPAK_EXTERN + gboolean flatpak_transaction_add_install_flatpakref (FlatpakTransaction *self, + GBytes *flatpakref_data, + GError **error); +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index a4d9708b..2740b299 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -640,7 +640,7 @@ handle_deploy (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (!flatpak_dir_pull (system, state, arg_ref, NULL, (const char **) arg_subpaths, NULL, NULL, NULL, NULL, ++ if (!flatpak_dir_pull (system, state, arg_ref, NULL, (const char **) arg_subpaths, NULL, NULL, NULL, NULL, NULL, + FLATPAK_PULL_FLAGS_NONE, OSTREE_REPO_PULL_FLAGS_UNTRUSTED, NULL, + NULL, &error)) + { +@@ -874,11 +874,11 @@ handle_deploy_appstream (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (!flatpak_dir_pull (system, state, new_branch, NULL, NULL, NULL, NULL, NULL, NULL, ++ if (!flatpak_dir_pull (system, state, new_branch, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + FLATPAK_PULL_FLAGS_NONE, OSTREE_REPO_PULL_FLAGS_UNTRUSTED, NULL, + NULL, &first_error)) + { +- if (!flatpak_dir_pull (system, state, old_branch, NULL, NULL, NULL, NULL, NULL, NULL, ++ if (!flatpak_dir_pull (system, state, old_branch, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + FLATPAK_PULL_FLAGS_NONE, OSTREE_REPO_PULL_FLAGS_UNTRUSTED, NULL, + NULL, &second_error)) + { +-- +2.47.1 + +From 7ef02411fc7209bd1072c13d0e08b95a9c579b0f Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Wed, 18 Dec 2024 00:40:45 +0100 +Subject: [PATCH 07/12]builtins/install: Create install transaction in common + function + +--- + app/flatpak-builtins-install.c | 85 +++++++++++++--------------------- + 1 file changed, 33 insertions(+), 52 deletions(-) + +diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c +index d4d9ae30..c5132da6 100644 +--- a/app/flatpak-builtins-install.c ++++ b/app/flatpak-builtins-install.c +@@ -129,6 +129,36 @@ read_gpg_data (GCancellable *cancellable, + return flatpak_read_stream (source_stream, FALSE, error); + } + ++static FlatpakTransaction * ++create_install_transaction (FlatpakDir *dir, ++ GError **error) ++{ ++ g_autoptr(FlatpakTransaction) transaction = NULL; ++ ++ if (opt_noninteractive) ++ transaction = flatpak_quiet_transaction_new (dir, error); ++ else ++ transaction = flatpak_cli_transaction_new (dir, opt_yes, TRUE, opt_arch != NULL, error); ++ ++ if (transaction == NULL) ++ return NULL; ++ ++ 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_disable_auto_pin (transaction, opt_no_auto_pin); ++ 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]); ++ ++ return g_steal_pointer (&transaction); ++} ++ + static gboolean + install_bundle (FlatpakDir *dir, + GOptionContext *context, +@@ -162,26 +192,10 @@ install_bundle (FlatpakDir *dir, + return FALSE; + } + +- if (opt_noninteractive) +- transaction = flatpak_quiet_transaction_new (dir, error); +- else +- transaction = flatpak_cli_transaction_new (dir, opt_yes, TRUE, opt_arch != NULL, error); ++ transaction = create_install_transaction (dir, 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_disable_auto_pin (transaction, opt_no_auto_pin); +- 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_install_bundle (transaction, file, gpg_data, error)) + return FALSE; + +@@ -241,27 +255,10 @@ install_from (FlatpakDir *dir, + file_data = g_bytes_new_take (g_steal_pointer (&data), data_len); + } + +- if (opt_noninteractive) +- transaction = flatpak_quiet_transaction_new (dir, error); +- else +- transaction = flatpak_cli_transaction_new (dir, opt_yes, TRUE, opt_arch != NULL, error); ++ transaction = create_install_transaction (dir, 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_disable_auto_pin (transaction, opt_no_auto_pin); +- flatpak_transaction_set_reinstall (transaction, opt_reinstall); +- flatpak_transaction_set_default_arch (transaction, opt_arch); +- 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_install_flatpakref (transaction, file_data, error)) + return FALSE; + +@@ -485,26 +482,10 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro + + default_branch = flatpak_dir_get_remote_default_branch (dir, remote); + +- if (opt_noninteractive) +- transaction = flatpak_quiet_transaction_new (dir, error); +- else +- transaction = flatpak_cli_transaction_new (dir, opt_yes, TRUE, opt_arch != NULL, error); ++ transaction = create_install_transaction (dir, 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_disable_auto_pin (transaction, opt_no_auto_pin); +- 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 (i = 0; opt_sideload_repos != NULL && opt_sideload_repos[i] != NULL; i++) +- flatpak_transaction_add_sideload_repo (transaction, opt_sideload_repos[i]); +- + for (i = 0; i < n_prefs; i++) + { + const char *pref = prefs[i]; +-- +2.47.1 + +From 920b48c7a53b9bff543827309c974a9c7824068f Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Wed, 18 Dec 2024 00:53:59 +0100 +Subject: [PATCH 08/12]builtins/install: Allow direct installation from OCI + images + +Similar to bundle installs, add: + + flatpak install [--image] docker://registry.example.com/image:latest + flatpak install [--image] oci:/path/to/image + +These is useful for testing purposes and in certain cases when installing +Flatpaks on disconnected systems. +--- + app/flatpak-builtins-install.c | 60 ++++++++++++++++++++++++++++++++-- + app/flatpak-cli-transaction.c | 2 +- + doc/flatpak-install.xml | 24 ++++++++++---- + tests/test-oci-registry.sh | 33 ++++++++++++++++++- + tests/test-oci.sh | 22 ++++++++++++- + 5 files changed, 129 insertions(+), 12 deletions(-) + +diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c +index c5132da6..70474bbe 100644 +--- a/app/flatpak-builtins-install.c ++++ b/app/flatpak-builtins-install.c +@@ -56,6 +56,7 @@ static gboolean opt_include_sdk; + static gboolean opt_include_debug; + static gboolean opt_bundle; + static gboolean opt_from; ++static gboolean opt_image; + static gboolean opt_yes; + static gboolean opt_reinstall; + static gboolean opt_noninteractive; +@@ -76,6 +77,7 @@ static GOptionEntry options[] = { + { "include-debug", 0, 0, G_OPTION_ARG_NONE, &opt_include_debug, N_("Additionally install the debug info for the given refs and their dependencies") }, + { "bundle", 0, 0, G_OPTION_ARG_NONE, &opt_bundle, N_("Assume LOCATION is a .flatpak single-file bundle"), NULL }, + { "from", 0, 0, G_OPTION_ARG_NONE, &opt_from, N_("Assume LOCATION is a .flatpakref application description"), NULL }, ++ { "image", 0, 0, G_OPTION_ARG_NONE, &opt_image, N_("Assume LOCATION is containers-transports(5) reference to an OCI image"), NULL }, + { "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") }, + { "assumeyes", 'y', 0, G_OPTION_ARG_NONE, &opt_yes, N_("Automatically answer yes for all questions"), NULL }, +@@ -273,6 +275,52 @@ install_from (FlatpakDir *dir, + return TRUE; + } + ++static gboolean ++install_image (FlatpakDir *dir, ++ GOptionContext *context, ++ int argc, ++ char **argv, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ const char *location; ++ g_autoptr(GBytes) gpg_data = NULL; ++ g_autoptr(FlatpakTransaction) transaction = NULL; ++ ++ if (argc < 2) ++ return usage_error (context, _("Image location must be specified"), error); ++ ++ if (argc > 2) ++ return usage_error (context, _("Too many arguments"), error); ++ ++ location = argv[1]; ++ ++ if (opt_gpg_file != NULL) ++ { ++ /* Override gpg_data from file */ ++ gpg_data = read_gpg_data (cancellable, error); ++ if (gpg_data == NULL) ++ return FALSE; ++ } ++ ++ transaction = create_install_transaction (dir, error); ++ if (transaction == NULL) ++ return FALSE; ++ ++ if (!flatpak_transaction_add_install_image (transaction, location, error)) ++ return FALSE; ++ ++ 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_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error) + { +@@ -301,11 +349,14 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro + /* Start with the default or specified dir, this is fine for opt_bundle or opt_from */ + dir = g_object_ref (g_ptr_array_index (dirs, 0)); + +- if (!opt_bundle && !opt_from && argc >= 2) ++ if (!opt_bundle && !opt_from && !opt_image && argc >= 2) + { +- if (flatpak_file_arg_has_suffix (argv[1], ".flatpakref")) ++ if (g_str_has_prefix (argv[1], "oci:") || ++ g_str_has_prefix (argv[1], "docker:")) ++ opt_image = TRUE; ++ else if (flatpak_file_arg_has_suffix (argv[1], ".flatpakref")) + opt_from = TRUE; +- if (flatpak_file_arg_has_suffix (argv[1], ".flatpak")) ++ else if (flatpak_file_arg_has_suffix (argv[1], ".flatpak")) + opt_bundle = TRUE; + } + +@@ -315,6 +366,9 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro + if (opt_from) + return install_from (dir, context, argc, argv, cancellable, error); + ++ if (opt_image) ++ return install_image (dir, context, argc, argv, cancellable, error); ++ + if (argc < 2) + return usage_error (context, _("At least one REF must be specified"), error); + +diff --git a/app/flatpak-cli-transaction.c b/app/flatpak-cli-transaction.c +index e7c8c90f..8c02c552 100644 +--- a/app/flatpak-cli-transaction.c ++++ b/app/flatpak-cli-transaction.c +@@ -1350,7 +1350,7 @@ transaction_ready_pre_auth (FlatpakTransaction *transaction) + GList *l; + int i; + FlatpakTablePrinter *printer; +- const char *op_shorthand[] = { "i", "u", "i", "r" }; ++ const char *op_shorthand[] = { "i", "u", "i", "r", "i" }; + + /* These caches may no longer be valid once the transaction runs */ + g_clear_pointer (&self->runtime_app_map, g_hash_table_unref); +diff --git a/doc/flatpak-install.xml b/doc/flatpak-install.xml +index ab4bb1fb..f75dedae 100644 +--- a/doc/flatpak-install.xml ++++ b/doc/flatpak-install.xml +@@ -38,7 +38,7 @@ + + flatpak install + OPTION +- --from|--bundle ++ --from|--bundle|--image + LOCATION + + +@@ -85,11 +85,11 @@ + flatpak1). + + +- The alternative form of the command (with or +- ) allows to install directly from a source such as a +- .flatpak single-file bundle or a .flatpakref +- application description. The options are optional if the first argument has the expected +- filename extension. ++ The alternative form of the command (with , , ++ or ) allows to install directly from a source. The source can be a ++ .flatpak single-file bundle, .flatpakref ++ application description, or a reference to an OCI image. The options are optional if the ++ first argument has the expected filename extension or prefix. + + + Note that flatpak allows to have multiple branches of an application and runtimes +@@ -139,6 +139,18 @@ + + + ++ ++ ++ ++ Treat LOCATION as the location of a Flatpak in ++ OCI image format. LOCATION is in the format of ++ containers-transports5. ++ Supported schemes are docker:// and oci:. ++ This is assumed if the argument starts ++ with one of these schemes. ++ ++ ++ + + + +diff --git a/tests/test-oci-registry.sh b/tests/test-oci-registry.sh +index da234ded..9503656b 100755 +--- a/tests/test-oci-registry.sh ++++ b/tests/test-oci-registry.sh +@@ -23,7 +23,7 @@ set -euo pipefail + + skip_without_bwrap + +-echo "1..14" ++echo "1..16" + + # Start the fake registry server + +@@ -315,3 +315,34 @@ assert_file_has_content remotes-list "^hello-origin.*[ ]${scheme}://127\.0\.0\. + assert_not_has_file $base/oci/hello-origin.index.gz + + ok "change remote origin via bundle" ++ ++${FLATPAK} ${U} -y uninstall org.test.Hello >&2 ++${FLATPAK} ${U} -y uninstall org.test.Platform >&2 ++ ++${FLATPAK} ${U} list --columns=application,origin > flatpak-list ++assert_not_file_has_content flatpak-list 'org.test.Platform' ++assert_not_file_has_content flatpak-list 'org.test.Hello' ++ ++${FLATPAK} ${U} remotes --show-disabled > remotes-list ++assert_not_file_has_content remotes-list '^platform-origin' ++assert_not_file_has_content remotes-list '^hello-origin' ++ ++ok "clean up" ++ ++# Install from registry via a docker:// location ++# TODO: docker:// locations only support HTTPS ++# This needs https://github.com/flatpak/flatpak/pull/5916 ++ ++if false && [ x${USE_SYSTEMDIR-} != xyes ]; then ++ $FLATPAK --user -y install docker://127.0.0.1/platform-image:latest >&2 ++ ++ ${FLATPAK} ${U} list --columns=application,origin > flatpak-list ++ assert_file_has_content flatpak-list '^org.test.Platform *platform-origin$' ++ ++ ${FLATPAK} ${U} remotes --show-disabled > remotes-list ++ assert_file_has_content remotes-list '^platform-origin' ++ ++ ok "install image from registry" ++else ++ ok "install image from registry # skip Not supported" ++fi +diff --git a/tests/test-oci.sh b/tests/test-oci.sh +index a7baf887..431fa6d6 100755 +--- a/tests/test-oci.sh ++++ b/tests/test-oci.sh +@@ -23,7 +23,7 @@ set -euo pipefail + + skip_without_bwrap + +-echo "1..2" ++echo "1..3" + + setup_repo_no_add oci + +@@ -66,3 +66,23 @@ assert_has_file checked-out/files/bin/hello.sh + assert_has_file checked-out/metadata + + ok "import oci" ++ ++# Trying installing the bundle directly ++ ++${FLATPAK} build-bundle --runtime --oci $FL_GPGARGS repos/oci oci/platform-image org.test.Platform >&2 ++ ++${FLATPAK} --user list --columns=application,origin > flatpak-list ++assert_not_file_has_content flatpak-list 'org.test.Platform' ++ ++${FLATPAK} --user remotes --show-disabled > remotes-list ++assert_not_file_has_content remotes-list '^platform-origin' ++ ++$FLATPAK --user -y install oci:oci/platform-image >&2 ++ ++${FLATPAK} --user list --columns=application,origin > flatpak-list ++assert_file_has_content flatpak-list '^org.test.Platform *platform-origin$' ++ ++${FLATPAK} --user remotes --show-disabled > remotes-list ++assert_file_has_content remotes-list '^platform-origin' ++ ++ok "install oci" +-- +2.47.1 + +From 3996759b7553c41bea63fb282cbf3fa0f9a78e4e Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Fri, 20 Dec 2024 00:25:55 +0100 +Subject: [PATCH 09/12]oci-registry: Remove a bunch of double newlines + +--- + common/flatpak-oci-registry-private.h | 1 - + common/flatpak-oci-registry.c | 6 ------ + 2 files changed, 7 deletions(-) + +diff --git a/common/flatpak-oci-registry-private.h b/common/flatpak-oci-registry-private.h +index 47ffc7be..a051b0a6 100644 +--- a/common/flatpak-oci-registry-private.h ++++ b/common/flatpak-oci-registry-private.h +@@ -183,7 +183,6 @@ GBytes *flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + GCancellable *cancellable, + GError **error); + +- + typedef void (*FlatpakOciPullProgress) (guint64 total_size, + guint64 pulled_size, + guint32 n_layers, +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index ad05c9f0..e104ab40 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -255,7 +255,6 @@ flatpak_oci_registry_set_token (FlatpakOciRegistry *self, + 0, NULL, NULL); + } + +- + FlatpakOciRegistry * + flatpak_oci_registry_new (const char *uri, + gboolean for_write, +@@ -830,7 +829,6 @@ flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, + return -1; + } + +- + if (!flatpak_open_in_tmpdir_at (self->tmp_dfd, 0600, tmpfile_name, + &out_stream, cancellable, error)) + return -1; +@@ -1357,7 +1355,6 @@ flatpak_oci_layer_writer_reset (FlatpakOciLayerWriter *self) + g_clear_object (&self->compressor); + } + +- + static void + flatpak_oci_layer_writer_finalize (GObject *object) + { +@@ -1725,7 +1722,6 @@ delta_read_byte (GInputStream *in, + return TRUE; + } + +- + static gboolean + delta_read_varuint (GInputStream *in, + guint64 *out, +@@ -2731,7 +2727,6 @@ get_image_metadata (FlatpakOciIndexImage *img, const char *key) + return NULL; + } + +- + static const char * + get_image_ref (FlatpakOciIndexImage *img) + { +@@ -2838,7 +2833,6 @@ flatpak_oci_index_ensure_cached (FlatpakHttpSession *http_session, + + oci_arch = flatpak_arch_to_oci_arch (flatpak_get_arch ()); + +- + query = g_string_new (NULL); + flatpak_uri_encode_query_arg (query, "label:org.flatpak.ref:exists", "1"); + flatpak_uri_encode_query_arg (query, "architecture", oci_arch); +-- +2.47.1 + +From fcad7d86d287d164421d62589a70bb9699a66f08 Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Fri, 20 Dec 2024 00:27:29 +0100 +Subject: [PATCH 10/12]oci-registry: Allow passing a NULL URI + +--- + common/flatpak-oci-registry.c | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index e104ab40..a4b914a0 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -142,10 +142,13 @@ flatpak_oci_registry_set_property (GObject *object, + case PROP_URI: + /* Ensure the base uri ends with a / so relative urls work */ + uri = g_value_get_string (value); +- if (g_str_has_suffix (uri, "/")) +- self->uri = g_strdup (uri); +- else +- self->uri = g_strconcat (uri, "/", NULL); ++ if (uri) ++ { ++ if (g_str_has_suffix (uri, "/")) ++ self->uri = g_strdup (uri); ++ else ++ self->uri = g_strconcat (uri, "/", NULL); ++ } + break; + + case PROP_FOR_WRITE: +-- +2.47.1 + +From 96b50f2f632b57c9f6608a7820d282225afef46b Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Wed, 18 Dec 2024 00:53:59 +0100 +Subject: [PATCH 11/12]image-source: Support `oci-archive:` image sources + +Add support for `oci-archive:` image sources by temporarily +unpacking the archive using libarchive. + +Co-authored-by: Sebastian Wick +--- + app/flatpak-builtins-install.c | 1 + + common/flatpak-image-source.c | 50 ++++-- + common/flatpak-oci-registry-private.h | 14 +- + common/flatpak-oci-registry.c | 247 +++++++++++++++++++++++--- + common/flatpak-transaction.c | 2 +- + doc/flatpak-install.xml | 4 +- + tests/test-oci.sh | 24 ++- + 7 files changed, 297 insertions(+), 45 deletions(-) + +diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c +index 70474bbe..e1f7f312 100644 +--- a/app/flatpak-builtins-install.c ++++ b/app/flatpak-builtins-install.c +@@ -352,6 +352,7 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro + if (!opt_bundle && !opt_from && !opt_image && argc >= 2) + { + if (g_str_has_prefix (argv[1], "oci:") || ++ g_str_has_prefix (argv[1], "oci-archive:") || + g_str_has_prefix (argv[1], "docker:")) + opt_image = TRUE; + else if (flatpak_file_arg_has_suffix (argv[1], ".flatpakref")) +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +index 0503de5d..20c94b63 100644 +--- a/common/flatpak-image-source.c ++++ b/common/flatpak-image-source.c +@@ -116,24 +116,15 @@ flatpak_image_source_new (FlatpakOciRegistry *registry, + return g_steal_pointer (&self); + } + +-FlatpakImageSource * +-flatpak_image_source_new_local (GFile *file, +- const char *reference, +- GCancellable *cancellable, +- GError **error) ++static FlatpakImageSource * ++flatpak_image_source_new_local_for_registry (FlatpakOciRegistry *registry, ++ const char *reference, ++ GCancellable *cancellable, ++ GError **error) + { +- g_autofree char *dir_uri = NULL; +- g_autofree char *target_ref = NULL; +- g_autoptr(FlatpakImageSource) image_source = NULL; +- g_autoptr(FlatpakOciRegistry) registry = NULL; + g_autoptr(FlatpakOciIndex) index = NULL; + const FlatpakOciManifestDescriptor *desc; + +- dir_uri = g_file_get_uri (file); +- registry = flatpak_oci_registry_new (dir_uri, FALSE, -1, cancellable, error); +- if (registry == NULL) +- return NULL; +- + index = flatpak_oci_registry_load_index (registry, cancellable, error); + if (index == NULL) + return NULL; +@@ -160,6 +151,23 @@ flatpak_image_source_new_local (GFile *file, + return flatpak_image_source_new (registry, NULL, desc->parent.digest, cancellable, error); + } + ++FlatpakImageSource * ++flatpak_image_source_new_local (GFile *file, ++ const char *reference, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autofree char *dir_uri = NULL; ++ g_autoptr(FlatpakOciRegistry) registry = NULL; ++ ++ dir_uri = g_file_get_uri (file); ++ registry = flatpak_oci_registry_new (dir_uri, FALSE, -1, cancellable, error); ++ if (registry == NULL) ++ return NULL; ++ ++ return flatpak_image_source_new_local_for_registry (registry, reference, cancellable, error); ++} ++ + FlatpakImageSource * + flatpak_image_source_new_remote (const char *uri, + const char *oci_repository, +@@ -221,6 +229,20 @@ flatpak_image_source_new_for_location (const char *location, + + return flatpak_image_source_new_local (path, reference, cancellable, error); + } ++ else if (g_str_has_prefix (location, "oci-archive:")) ++ { ++ g_autoptr(FlatpakOciRegistry) registry = NULL; ++ g_autoptr(GFile) path = NULL; ++ g_autofree char *reference = NULL; ++ ++ get_path_and_reference (location, &path, &reference); ++ ++ registry = flatpak_oci_registry_new_for_archive (path, cancellable, error); ++ if (registry == NULL) ++ return NULL; ++ ++ return flatpak_image_source_new_local_for_registry (registry, reference, cancellable, error); ++ } + else if (g_str_has_prefix (location, "docker:")) + { + g_autoptr(FlatpakOciRegistry) registry = NULL; +diff --git a/common/flatpak-oci-registry-private.h b/common/flatpak-oci-registry-private.h +index a051b0a6..d48c8ed4 100644 +--- a/common/flatpak-oci-registry-private.h ++++ b/common/flatpak-oci-registry-private.h +@@ -50,11 +50,15 @@ typedef struct FlatpakOciLayerWriter FlatpakOciLayerWriter; + + G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakOciLayerWriter, g_object_unref) + +-FlatpakOciRegistry * flatpak_oci_registry_new (const char *uri, +- gboolean for_write, +- int tmp_dfd, +- GCancellable * cancellable, +- GError **error); ++ ++FlatpakOciRegistry * flatpak_oci_registry_new (const char *uri, ++ gboolean for_write, ++ int tmp_dfd, ++ GCancellable *cancellable, ++ GError **error); ++FlatpakOciRegistry * flatpak_oci_registry_new_for_archive (GFile *archive, ++ GCancellable *cancellable, ++ GError **error); + void flatpak_oci_registry_set_token (FlatpakOciRegistry *self, + const char *token); + gboolean flatpak_oci_registry_is_local (FlatpakOciRegistry *self); +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index a4b914a0..ebf000f8 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -27,6 +27,7 @@ + #include "libglnx.h" + + #include ++#include + #include + #include "flatpak-image-source-private.h" + #include "flatpak-oci-registry-private.h" +@@ -69,11 +70,13 @@ struct FlatpakOciRegistry + gboolean valid; + gboolean is_docker; + char *uri; ++ GFile *archive; + int tmp_dfd; + char *token; + + /* Local repos */ + int dfd; ++ GLnxTmpDir *tmp_dir; + + /* Remote repos */ + FlatpakHttpSession *http_session; +@@ -90,6 +93,7 @@ enum { + PROP_0, + + PROP_URI, ++ PROP_ARCHIVE, + PROP_FOR_WRITE, + PROP_TMP_DFD, + }; +@@ -98,6 +102,14 @@ G_DEFINE_TYPE_WITH_CODE (FlatpakOciRegistry, flatpak_oci_registry, G_TYPE_OBJECT + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + flatpak_oci_registry_initable_iface_init)) + ++static void ++glnx_tmpdir_free (GLnxTmpDir *tmpf) ++{ ++ (void)glnx_tmpdir_delete (tmpf, NULL, NULL); ++ g_free (tmpf); ++} ++G_DEFINE_AUTOPTR_CLEANUP_FUNC(GLnxTmpDir, glnx_tmpdir_free) ++ + static gchar * + parse_relative_uri (GUri *base_uri, + const char *subpath, +@@ -124,6 +136,8 @@ flatpak_oci_registry_finalize (GObject *object) + g_clear_pointer (&self->base_uri, g_uri_unref); + g_free (self->uri); + g_free (self->token); ++ g_clear_object (&self->archive); ++ g_clear_pointer (&self->tmp_dir, glnx_tmpdir_free); + + G_OBJECT_CLASS (flatpak_oci_registry_parent_class)->finalize (object); + } +@@ -151,6 +165,10 @@ flatpak_oci_registry_set_property (GObject *object, + } + break; + ++ case PROP_ARCHIVE: ++ self->archive = g_value_dup_object (value); ++ break; ++ + case PROP_FOR_WRITE: + self->for_write = g_value_get_boolean (value); + break; +@@ -179,6 +197,10 @@ flatpak_oci_registry_get_property (GObject *object, + g_value_set_string (value, self->uri); + break; + ++ case PROP_ARCHIVE: ++ g_value_set_object (value, self->archive); ++ break; ++ + case PROP_FOR_WRITE: + g_value_set_boolean (value, self->for_write); + break; +@@ -209,6 +231,13 @@ flatpak_oci_registry_class_init (FlatpakOciRegistryClass *klass) + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); ++ g_object_class_install_property (object_class, ++ PROP_ARCHIVE, ++ g_param_spec_object ("archive", ++ "", ++ "", ++ G_TYPE_FILE, ++ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_TMP_DFD, + g_param_spec_int ("tmp-dfd", +@@ -277,6 +306,21 @@ flatpak_oci_registry_new (const char *uri, + return oci_registry; + } + ++FlatpakOciRegistry * ++flatpak_oci_registry_new_for_archive (GFile *archive, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ FlatpakOciRegistry *oci_registry; ++ ++ oci_registry = g_initable_new (FLATPAK_TYPE_OCI_REGISTRY, ++ cancellable, error, ++ "archive", archive, ++ NULL); ++ ++ return oci_registry; ++} ++ + static int + local_open_file (int dfd, + const char *subpath, +@@ -457,13 +501,164 @@ verify_oci_version (GBytes *oci_layout_bytes, gboolean *not_json, GCancellable * + return TRUE; + } + ++/* ++ * Code to extract an archive such as a tarfile into a temporary directory ++ * ++ * Based on: https://github.com/libarchive/libarchive/wiki/Examples#A_Complete_Extractor ++ * ++ * We treat ARCHIVE_WARNING as fatal - while this might be too strict, it ++ * will avoid surprises. ++ */ ++ ++static gboolean ++propagate_libarchive_error (GError **error, ++ struct archive *a) ++{ ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "%s", archive_error_string (a)); ++ return FALSE; ++} ++ ++static gboolean ++copy_data (struct archive *ar, ++ struct archive *aw, ++ GError **error) ++{ ++ int r; ++ const void *buff; ++ size_t size; ++ gint64 offset; ++ ++ while (TRUE) ++ { ++ r = archive_read_data_block (ar, &buff, &size, &offset); ++ ++ if (r == ARCHIVE_EOF) ++ return TRUE; ++ ++ if (r == ARCHIVE_RETRY) ++ continue; ++ ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, ar); ++ ++ while (TRUE) ++ { ++ r = archive_write_data_block (aw, buff, size, offset); ++ ++ if (r == ARCHIVE_RETRY) ++ continue; ++ ++ if (r == ARCHIVE_OK) ++ break; ++ ++ return propagate_libarchive_error (error, aw); ++ } ++ } ++} ++ ++static gboolean ++unpack_archive (GFile *archive, ++ char *destination, ++ GError **error) ++{ ++ g_autoptr(FlatpakAutoArchiveRead) a = NULL; ++ g_autoptr(FlatpakAutoArchiveWrite) ext = NULL; ++ int flags; ++ int r; ++ ++ flags = 0; ++ flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT; ++ flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS; ++ ++ a = archive_read_new (); ++ archive_read_support_format_all (a); ++ archive_read_support_filter_all (a); ++ ++ ext = archive_write_disk_new (); ++ archive_write_disk_set_options (ext, flags); ++ archive_write_disk_set_standard_lookup (ext); ++ ++ r = archive_read_open_filename (a, g_file_get_path(archive), 10240); ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, a); ++ ++ while (TRUE) ++ { ++ g_autofree char *target_path = NULL; ++ struct archive_entry *entry; ++ ++ r = archive_read_next_header (a, &entry); ++ if (r == ARCHIVE_EOF) ++ break; ++ ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, a); ++ ++ target_path = g_build_filename (destination, archive_entry_pathname (entry), NULL); ++ archive_entry_set_pathname (entry, target_path); ++ ++ r = archive_write_header (ext, entry); ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, ext); ++ ++ if (archive_entry_size (entry) > 0) ++ { ++ if (!copy_data (a, ext, error)) ++ return FALSE; ++ } ++ ++ r = archive_write_finish_entry (ext); ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, ext); ++ } ++ ++ r = archive_read_close (a); ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, a); ++ ++ r = archive_write_close (ext); ++ if (r != ARCHIVE_OK) ++ return propagate_libarchive_error (error, ext); ++ ++ return TRUE; ++} ++ ++static const char * ++get_download_tmpdir (void) ++{ ++ /* We don't use TMPDIR because the downloaded artifacts can be ++ * very big, and we want to prefer /var/tmp to /tmp. ++ */ ++ const char *tmpdir = g_getenv ("FLATPAK_DOWNLOAD_TMPDIR"); ++ if (tmpdir) ++ return tmpdir; ++ ++ return "/var/tmp"; ++} ++ ++static GLnxTmpDir * ++download_tmpdir_new (GError **error) ++{ ++ g_autoptr(GLnxTmpDir) tmp_dir = g_new0 (GLnxTmpDir, 1); ++ glnx_autofd int base_dfd = -1; ++ ++ if (!glnx_opendirat (AT_FDCWD, get_download_tmpdir (), TRUE, &base_dfd, error)) ++ return NULL; ++ ++ if (!glnx_mkdtempat (base_dfd, "oci-XXXXXX", 0700, tmp_dir, error)) ++ return NULL; ++ ++ return g_steal_pointer (&tmp_dir); ++} ++ + static gboolean + flatpak_oci_registry_ensure_local (FlatpakOciRegistry *self, + gboolean for_write, + GCancellable *cancellable, + GError **error) + { +- g_autoptr(GFile) dir = g_file_new_for_uri (self->uri); ++ g_autoptr(GLnxTmpDir) local_tmp_dir = NULL; + glnx_autofd int local_dfd = -1; + int dfd; + g_autoptr(GError) local_error = NULL; +@@ -472,9 +667,28 @@ flatpak_oci_registry_ensure_local (FlatpakOciRegistry *self, + gboolean not_json; + + if (self->dfd != -1) +- dfd = self->dfd; ++ { ++ dfd = self->dfd; ++ } ++ else if (self->archive) ++ { ++ local_tmp_dir = download_tmpdir_new (error); ++ if (!local_tmp_dir) ++ return FALSE; ++ ++ if (!unpack_archive (self->archive, local_tmp_dir->path, error)) ++ return FALSE; ++ ++ if (!glnx_opendirat (AT_FDCWD, local_tmp_dir->path, ++ TRUE, &local_dfd, error)) ++ return FALSE; ++ ++ dfd = local_dfd; ++ } + else + { ++ g_autoptr(GFile) dir = g_file_new_for_uri (self->uri); ++ + if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dir), + TRUE, &local_dfd, &local_error)) + { +@@ -537,8 +751,11 @@ flatpak_oci_registry_ensure_local (FlatpakOciRegistry *self, + self->token = g_strndup (g_bytes_get_data (token_bytes, NULL), g_bytes_get_size (token_bytes)); + } + +- if (self->dfd == -1 && local_dfd != -1) +- self->dfd = g_steal_fd (&local_dfd); ++ if (self->dfd == -1) ++ { ++ self->dfd = g_steal_fd (&local_dfd); ++ self->tmp_dir = g_steal_pointer (&local_tmp_dir); ++ } + + return TRUE; + } +@@ -589,20 +806,15 @@ flatpak_oci_registry_initable_init (GInitable *initable, + FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (initable); + gboolean res; + ++ g_warn_if_fail (self->archive || self->uri); ++ + if (self->tmp_dfd == -1) + { +- /* We don't use TMPDIR because the downloaded artifacts can be +- * very big, and we want to prefer /var/tmp to /tmp. +- */ +- const char *tmpdir = g_getenv ("FLATPAK_DOWNLOAD_TMPDIR"); +- if (tmpdir == NULL) +- tmpdir = "/var/tmp"; +- +- if (!glnx_opendirat (AT_FDCWD, tmpdir, TRUE, &self->tmp_dfd, error)) ++ if (!glnx_opendirat (AT_FDCWD, get_download_tmpdir (), TRUE, &self->tmp_dfd, error)) + return FALSE; + } + +- if (g_str_has_prefix (self->uri, "file:/")) ++ if (self->archive || g_str_has_prefix (self->uri, "file:/")) + res = flatpak_oci_registry_ensure_local (self, self->for_write, cancellable, error); + else + res = flatpak_oci_registry_ensure_remote (self, self->for_write, cancellable, error); +@@ -1332,15 +1544,6 @@ typedef struct + + G_DEFINE_TYPE (FlatpakOciLayerWriter, flatpak_oci_layer_writer, G_TYPE_OBJECT) + +-static gboolean +-propagate_libarchive_error (GError **error, +- struct archive *a) +-{ +- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, +- "%s", archive_error_string (a)); +- return FALSE; +-} +- + static void + flatpak_oci_layer_writer_reset (FlatpakOciLayerWriter *self) + { +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index ffee385e..1aba1daa 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -3028,7 +3028,7 @@ flatpak_transaction_add_install_bundle (FlatpakTransaction *self, + * reference. + * + * @image_location is specified in containers-transports(5) form. Only a subset +- * of transports are supported: oci: and docker:. ++ * of transports are supported: oci:, oci-archive:, and docker:. + * + * Returns: %TRUE on success; %FALSE with @error set on failure. + */ +diff --git a/doc/flatpak-install.xml b/doc/flatpak-install.xml +index f75dedae..99aa5469 100644 +--- a/doc/flatpak-install.xml ++++ b/doc/flatpak-install.xml +@@ -145,8 +145,8 @@ + Treat LOCATION as the location of a Flatpak in + OCI image format. LOCATION is in the format of + containers-transports5. +- Supported schemes are docker:// and oci:. +- This is assumed if the argument starts ++ Supported schemes are docker://, oci:, ++ and oci-archive:. This is assumed if the argument starts + with one of these schemes. + + +diff --git a/tests/test-oci.sh b/tests/test-oci.sh +index 431fa6d6..861310a5 100755 +--- a/tests/test-oci.sh ++++ b/tests/test-oci.sh +@@ -23,7 +23,7 @@ set -euo pipefail + + skip_without_bwrap + +-echo "1..3" ++echo "1..4" + + setup_repo_no_add oci + +@@ -85,4 +85,26 @@ assert_file_has_content flatpak-list '^org.test.Platform *platform-origin$' + ${FLATPAK} --user remotes --show-disabled > remotes-list + assert_file_has_content remotes-list '^platform-origin' + ++${FLATPAK} ${U} -y uninstall org.test.Platform >&2 ++ + ok "install oci" ++ ++# Trying installing an OCI archive bundle ++ ++(cd oci/platform-image && tar cf - .) > oci/platform-image.tar ++ ++${FLATPAK} --user list --columns=application,origin > flatpak-list ++assert_not_file_has_content flatpak-list 'org.test.Platform' ++ ++${FLATPAK} --user remotes --show-disabled > remotes-list ++assert_not_file_has_content remotes-list '^platform-origin' ++ ++$FLATPAK --user -y install oci-archive:oci/platform-image.tar >&2 ++ ++${FLATPAK} --user list --columns=application,origin > flatpak-list ++assert_file_has_content flatpak-list '^org.test.Platform *platform-origin$' ++ ++${FLATPAK} --user remotes --show-disabled > remotes-list ++assert_file_has_content remotes-list '^platform-origin' ++ ++ok "install oci archive" +-- +2.47.1 + +From a2fbcf5d1db3ef69b04e7f0e4bc64c0e51dd7ffb Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 29 Oct 2024 16:59:40 -0400 +Subject: [PATCH 12/12]common: Move delta_url into the FlatpakImageSource + +Instead of passing the delta URL along with the image source, when +we create an image source for a remote registry, if we find a delta +URL in the metadata, set it on the FlatpakImageSource for later use. + +Centralize duplicated code for creating an image source for a remote +repository based on a summary lookup into one place. +--- + app/flatpak-builtins-build-import-bundle.c | 2 +- + common/flatpak-dir.c | 131 +++++++-------------- + common/flatpak-image-source-private.h | 4 + + common/flatpak-image-source.c | 41 +++++++ + common/flatpak-oci-registry-private.h | 2 - + common/flatpak-oci-registry.c | 4 +- + system-helper/flatpak-system-helper.c | 2 +- + 7 files changed, 89 insertions(+), 97 deletions(-) + +diff --git a/app/flatpak-builtins-build-import-bundle.c b/app/flatpak-builtins-build-import-bundle.c +index d79fbbde..d623386f 100644 +--- a/app/flatpak-builtins-build-import-bundle.c ++++ b/app/flatpak-builtins-build-import-bundle.c +@@ -69,7 +69,7 @@ import_oci (OstreeRepo *repo, GFile *file, + ref = flatpak_image_source_get_ref (image_source); + + commit_checksum = flatpak_pull_from_oci (repo, image_source, NULL, +- NULL, ref, FLATPAK_PULL_FLAGS_NONE, ++ ref, FLATPAK_PULL_FLAGS_NONE, + NULL, NULL, cancellable, error); + if (commit_checksum == NULL) + return NULL; +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index e8ff645b..9f398252 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -1077,14 +1077,14 @@ flatpak_remote_state_new_image_source (FlatpakRemoteState *self, + return g_steal_pointer (&image_source); + } + +-static GVariant * +-flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, +- FlatpakDir *dir, +- const char *ref, +- const char *checksum, +- const char *token, +- GCancellable *cancellable, +- GError **error) ++static FlatpakImageSource * ++flatpak_remote_state_fetch_image_source (FlatpakRemoteState *self, ++ FlatpakDir *dir, ++ const char *ref, ++ const char *opt_rev, ++ const char *token, ++ GCancellable *cancellable, ++ GError **error) + { + g_autoptr(FlatpakImageSource) image_source = NULL; + g_autofree char *oci_digest = NULL; +@@ -1092,9 +1092,7 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + VarRefInfoRef latest_rev_info; + VarMetadataRef metadata; + const char *oci_repository = NULL; +- const char *parent = NULL; +- g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); +- g_autoptr(GVariant) metadata_v = NULL; ++ const char *delta_url = NULL; + + /* We extract the rev info from the latest, even if we don't use the latest digest, assuming refs don't move */ + if (!flatpak_remote_state_lookup_ref (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) +@@ -1110,8 +1108,9 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + + metadata = var_ref_info_get_metadata (latest_rev_info); + oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); ++ delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); + +- oci_digest = g_strconcat ("sha256:", checksum, NULL); ++ oci_digest = g_strconcat ("sha256:", opt_rev ? opt_rev : latest_rev, NULL); + + image_source = flatpak_remote_state_new_image_source (self, oci_repository, oci_digest, token, cancellable, error); + if (image_source == NULL) +@@ -1123,22 +1122,28 @@ flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, + return NULL; + } + +- flatpak_image_source_build_commit_metadata (image_source, metadata_builder); +- metadata_v = g_variant_ref_sink (g_variant_builder_end (metadata_builder)); ++ flatpak_image_source_set_delta_url (image_source, delta_url); ++ ++ return g_steal_pointer (&image_source); ++} ++ + +- parent = flatpak_image_source_get_parent_commit (image_source); ++static GVariant * ++flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self, ++ FlatpakDir *dir, ++ const char *ref, ++ const char *checksum, ++ const char *token, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(FlatpakImageSource) image_source = NULL; ++ ++ image_source = flatpak_remote_state_fetch_image_source (self, dir, ref, checksum, token, cancellable, error); ++ if (image_source == NULL) ++ return NULL; + +- /* This isn't going to be exactly the same as the reconstructed one from the pull, because we don't have the contents, but its useful to get metadata */ +- return +- g_variant_ref_sink (g_variant_new ("(@a{sv}@ay@a(say)sst@ay@ay)", +- metadata_v, +- parent ? ostree_checksum_to_bytes_v (parent) : g_variant_new_from_data (G_VARIANT_TYPE ("ay"), NULL, 0, FALSE, NULL, NULL), +- g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0), +- flatpak_image_source_get_commit_subject (image_source), +- flatpak_image_source_get_commit_body (image_source), +- GUINT64_TO_BE (flatpak_image_source_get_commit_timestamp (image_source)), +- ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"), +- ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"))); ++ return flatpak_image_source_make_fake_commit (image_source); + } + + static GVariant * +@@ -5910,49 +5915,18 @@ flatpak_dir_mirror_oci (FlatpakDir *self, + GError **error) + { + g_autoptr(FlatpakImageSource) image_source = NULL; +- g_autofree char *oci_digest = NULL; +- const char *delta_url = NULL; + gboolean res; + + if (opt_image_source) +- { +- image_source = g_object_ref (opt_image_source); +- oci_digest = g_strdup (flatpak_image_source_get_digest (image_source)); +- } ++ image_source = g_object_ref (opt_image_source); + else +- { +- g_autofree char *latest_rev = NULL; +- VarRefInfoRef latest_rev_info; +- VarMetadataRef metadata; +- const char *oci_repository = NULL; +- const char *rev; +- +- /* We use the summary so that we can reuse any cached json */ +- if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) +- return FALSE; +- if (latest_rev == NULL) +- return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, +- _("Couldn't find latest checksum for ref %s in remote %s"), +- ref, state->remote_name); +- +- rev = opt_rev != NULL ? opt_rev : latest_rev; +- +- metadata = var_ref_info_get_metadata (latest_rev_info); +- oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); +- delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); +- +- oci_digest = g_strconcat ("sha256:", rev, NULL); +- +- image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); +- if (image_source == NULL) +- return FALSE; +- } ++ image_source = flatpak_remote_state_fetch_image_source (state, self, ref, opt_rev, token, cancellable, error); + + flatpak_progress_start_oci_pull (progress); + +- g_info ("Mirroring OCI image %s", oci_digest); ++ g_info ("Mirroring OCI image %s", flatpak_image_source_get_digest (image_source)); + +- res = flatpak_mirror_image_from_oci (dst_registry, image_source, state->remote_name, ref, delta_url, self->repo, oci_pull_progress_cb, ++ res = flatpak_mirror_image_from_oci (dst_registry, image_source, state->remote_name, ref, self->repo, oci_pull_progress_cb, + progress, cancellable, error); + + if (!res) +@@ -5977,8 +5951,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, + { + g_autoptr(FlatpakImageSource) image_source = NULL; + FlatpakOciRegistry *registry = NULL; +- const char *delta_url = NULL; +- g_autofree char *oci_digest = NULL; ++ const char *oci_digest = NULL; + g_autofree char *checksum = NULL; + g_autofree char *latest_alt_commit = NULL; + G_GNUC_UNUSED g_autofree char *latest_commit = +@@ -5986,35 +5959,11 @@ flatpak_dir_pull_oci (FlatpakDir *self, + g_autofree char *name = NULL; + + if (opt_image_source) +- { +- image_source = g_object_ref (opt_image_source); +- oci_digest = g_strdup (flatpak_image_source_get_digest (image_source)); +- } ++ image_source = g_object_ref (opt_image_source); + else +- { +- VarMetadataRef metadata; +- VarRefInfoRef latest_rev_info; +- const char *oci_repository = NULL; +- g_autofree char *latest_rev = NULL; ++ image_source = flatpak_remote_state_fetch_image_source (state, self, ref, opt_rev, token, cancellable, error); + +- /* We use the summary so that we can reuse any cached json */ +- if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) +- return FALSE; +- if (latest_rev == NULL) +- return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, +- _("Couldn't find latest checksum for ref %s in remote %s"), +- ref, state->remote_name); +- +- metadata = var_ref_info_get_metadata (latest_rev_info); +- oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); +- delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); +- +- oci_digest = g_strconcat ("sha256:", opt_rev != NULL ? opt_rev : latest_rev, NULL); +- +- image_source = flatpak_remote_state_new_image_source (state, oci_repository, oci_digest, token, cancellable, error); +- if (image_source == NULL) +- return FALSE; +- } ++ oci_digest = flatpak_image_source_get_digest (image_source); + + /* Short circuit if we've already got this commit */ + if (latest_alt_commit != NULL && strcmp (oci_digest + strlen ("sha256:"), latest_alt_commit) == 0) +@@ -6027,7 +5976,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, + + g_info ("Pulling OCI image %s", oci_digest); + +- checksum = flatpak_pull_from_oci (repo, image_source, delta_url, ++ checksum = flatpak_pull_from_oci (repo, image_source, + state->remote_name, ref, flatpak_flags, oci_pull_progress_cb, progress, cancellable, error); + + if (checksum == NULL) +diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h +index b1e5d2fa..94e4cc1a 100644 +--- a/common/flatpak-image-source-private.h ++++ b/common/flatpak-image-source-private.h +@@ -47,10 +47,13 @@ FlatpakImageSource *flatpak_image_source_new_for_location (const char *locatio + + void flatpak_image_source_set_token (FlatpakImageSource *self, + const char *token); ++void flatpak_image_source_set_delta_url (FlatpakImageSource *self, ++ const char *delta_url); + + FlatpakOciRegistry *flatpak_image_source_get_registry (FlatpakImageSource *self); + const char *flatpak_image_source_get_oci_repository (FlatpakImageSource *self); + const char *flatpak_image_source_get_digest (FlatpakImageSource *self); ++const char *flatpak_image_source_get_delta_url (FlatpakImageSource *self); + FlatpakOciManifest *flatpak_image_source_get_manifest (FlatpakImageSource *self); + size_t flatpak_image_source_get_manifest_size (FlatpakImageSource *self); + FlatpakOciImage *flatpak_image_source_get_image_config (FlatpakImageSource *self); +@@ -66,4 +69,5 @@ const char *flatpak_image_source_get_commit_body (FlatpakImageSource *self) + void flatpak_image_source_build_commit_metadata (FlatpakImageSource *self, + GVariantBuilder *metadata_builder); + ++GVariant *flatpak_image_source_make_fake_commit (FlatpakImageSource *image_source); + #endif /* __FLATPAK_IMAGE_SOURCE_H__ */ +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +index 20c94b63..9d13cf0d 100644 +--- a/common/flatpak-image-source.c ++++ b/common/flatpak-image-source.c +@@ -31,6 +31,7 @@ struct _FlatpakImageSource + FlatpakOciRegistry *registry; + char *repository; + char *digest; ++ char *delta_url; + + FlatpakOciManifest *manifest; + size_t manifest_size; +@@ -47,6 +48,7 @@ flatpak_image_source_finalize (GObject *object) + g_clear_object (&self->registry); + g_clear_pointer (&self->repository, g_free); + g_clear_pointer (&self->digest, g_free); ++ g_clear_pointer (&self->delta_url, g_free); + g_clear_object (&self->manifest); + g_clear_object (&self->image_config); + +@@ -326,6 +328,20 @@ flatpak_image_source_set_token (FlatpakImageSource *self, + flatpak_oci_registry_set_token (self->registry, token); + } + ++void ++flatpak_image_source_set_delta_url (FlatpakImageSource *self, ++ const char *delta_url) ++{ ++ g_free (self->delta_url); ++ self->delta_url = g_strdup (delta_url); ++} ++ ++const char * ++flatpak_image_source_get_delta_url (FlatpakImageSource *self) ++{ ++ return self->delta_url; ++} ++ + FlatpakOciRegistry * + flatpak_image_source_get_registry (FlatpakImageSource *self) + { +@@ -453,3 +469,28 @@ flatpak_image_source_build_commit_metadata (FlatpakImageSource *self, + g_variant_builder_add (metadata_builder, "{s@v}", key, data); + } + } ++ ++GVariant * ++flatpak_image_source_make_fake_commit (FlatpakImageSource *self) ++{ ++ const char *parent = NULL; ++ g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_autoptr(GVariant) metadata_v = NULL; ++ ++ flatpak_image_source_build_commit_metadata (self, metadata_builder); ++ metadata_v = g_variant_ref_sink (g_variant_builder_end (metadata_builder)); ++ ++ parent = flatpak_image_source_get_parent_commit (self); ++ ++ /* This isn't going to be exactly the same as the reconstructed one from the pull, because we don't have the contents, but its useful to get metadata */ ++ return ++ g_variant_ref_sink (g_variant_new ("(@a{sv}@ay@a(say)sst@ay@ay)", ++ metadata_v, ++ parent ? ostree_checksum_to_bytes_v (parent) : g_variant_new_from_data (G_VARIANT_TYPE ("ay"), NULL, 0, FALSE, NULL, NULL), ++ g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0), ++ flatpak_image_source_get_commit_subject (self), ++ flatpak_image_source_get_commit_body (self), ++ GUINT64_TO_BE (flatpak_image_source_get_commit_timestamp (self)), ++ ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"), ++ ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"))); ++} +diff --git a/common/flatpak-oci-registry-private.h b/common/flatpak-oci-registry-private.h +index d48c8ed4..7867c761 100644 +--- a/common/flatpak-oci-registry-private.h ++++ b/common/flatpak-oci-registry-private.h +@@ -195,7 +195,6 @@ typedef void (*FlatpakOciPullProgress) (guint64 total_size, + + char * flatpak_pull_from_oci (OstreeRepo *repo, + FlatpakImageSource *image_source, +- const char *delta_url, + const char *remote, + const char *ref, + FlatpakPullFlags flags, +@@ -208,7 +207,6 @@ gboolean flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + FlatpakImageSource *image_source, + const char *remote, + const char *ref, +- const char *delta_url, + OstreeRepo *repo, + FlatpakOciPullProgress progress_cb, + gpointer progress_data, +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index ebf000f8..c3dff5d6 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -3658,7 +3658,6 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + FlatpakImageSource *image_source, + const char *remote, + const char *ref, +- const char *delta_url, + OstreeRepo *repo, + FlatpakOciPullProgress progress_cb, + gpointer progress_user_data, +@@ -3670,6 +3669,7 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + const char *oci_repository = flatpak_image_source_get_oci_repository (image_source); + const char *digest = flatpak_image_source_get_digest (image_source); + FlatpakOciManifest *manifest = flatpak_image_source_get_manifest (image_source); ++ const char *delta_url = flatpak_image_source_get_delta_url (image_source); + FlatpakOciImage *image_config = flatpak_image_source_get_image_config (image_source); + g_autoptr(FlatpakOciDescriptor) manifest_desc = NULL; + g_autoptr(FlatpakOciManifest) delta_manifest = NULL; +@@ -3785,7 +3785,6 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, + char * + flatpak_pull_from_oci (OstreeRepo *repo, + FlatpakImageSource *image_source, +- const char *delta_url, + const char *remote, + const char *ref, + FlatpakPullFlags flags, +@@ -3798,6 +3797,7 @@ flatpak_pull_from_oci (OstreeRepo *repo, + const char *oci_repository = flatpak_image_source_get_oci_repository (image_source); + const char *digest = flatpak_image_source_get_digest (image_source); + FlatpakOciManifest *manifest = flatpak_image_source_get_manifest (image_source); ++ const char *delta_url = flatpak_image_source_get_delta_url (image_source); + FlatpakOciImage *image_config = flatpak_image_source_get_image_config (image_source); + gboolean force_disable_deltas = (flags & FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS) != 0; + g_autoptr(OstreeMutableTree) archive_mtree = NULL; +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index 2740b299..1f950b20 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -593,7 +593,7 @@ handle_deploy (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- checksum = flatpak_pull_from_oci (flatpak_dir_get_repo (system), image_source, NULL, ++ checksum = flatpak_pull_from_oci (flatpak_dir_get_repo (system), image_source, + arg_origin, arg_ref, FLATPAK_PULL_FLAGS_NONE, NULL, NULL, NULL, &error); + if (checksum == NULL) + { +-- +2.47.1 + diff --git a/flatpak-enable-collection-ids-for-oci-remotes.patch b/flatpak-enable-collection-ids-for-oci-remotes.patch new file mode 100644 index 0000000..f40a7ad --- /dev/null +++ b/flatpak-enable-collection-ids-for-oci-remotes.patch @@ -0,0 +1,53 @@ +From 5c84fa21cc590811936d36dd8b122025a4340f85 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Wed, 30 Oct 2024 14:27:44 -0400 +Subject: [PATCH] Enable collection IDs for OCI remotes + +We want to use collection IDs to specify what remote to install from +when processing /etc/flatpak/preinstall.d; in order for this to work +for OCI remotes, we need to permit collection IDs. + + - In flatpakrepo files, don't require a GPGKey for a OCI remote + with a collection - we don't have signature verification for GPG remotes. + - Don't validate that the collection ID appears in the summary - + the image index doesn't currently contain an image ID +--- + common/flatpak-dir.c | 6 +++++- + common/flatpak-repo-utils.c | 5 ++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 6936d45f89..b0937eaa65 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -12995,9 +12995,13 @@ _flatpak_dir_get_remote_state (FlatpakDir *self, + return NULL; + } + ++ /* For OCI remotes, the collection ID is local configuration only: ++ * In the future we could add it to the index format. ++ */ + if (state->collection_id != NULL && + state->summary != NULL && +- !_validate_summary_for_collection_id (state->summary, state->collection_id, error)) ++ !(flatpak_dir_get_remote_oci (self, state->remote_name) || ++ _validate_summary_for_collection_id (state->summary, state->collection_id, error))) + return NULL; + + if (flatpak_dir_get_remote_oci (self, remote_or_uri)) +diff --git a/common/flatpak-repo-utils.c b/common/flatpak-repo-utils.c +index 63dc9981e7..52508d2df1 100644 +--- a/common/flatpak-repo-utils.c ++++ b/common/flatpak-repo-utils.c +@@ -2929,7 +2929,10 @@ flatpak_parse_repofile (const char *remote_name, + FLATPAK_REPO_COLLECTION_ID_KEY); + if (collection_id != NULL) + { +- if (gpg_key == NULL) ++ /* We don't support signatures for OCI remotes, but Collection ID's are ++ * still useful for preinstallation. ++ */ ++ if (gpg_key == NULL && !g_str_has_prefix (uri, "oci+")) + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ID requires GPG key to be provided")); + return NULL; diff --git a/flatpak-implement-etc-containers-certs-for-oci-registries.patch b/flatpak-implement-etc-containers-certs-for-oci-registries.patch new file mode 100644 index 0000000..361ecb7 --- /dev/null +++ b/flatpak-implement-etc-containers-certs-for-oci-registries.patch @@ -0,0 +1,2003 @@ +From cec97aac1c9fad9b5bc18d1166b63edb13ca2bcc Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 17 Dec 2024 15:35:21 +0100 +Subject: [PATCH 01/09] tests/oci-registry-client.py: Drop python2 + compatibility + +--- + tests/oci-registry-client.py | 15 ++++++--------- + 1 file changed, 6 insertions(+), 9 deletions(-) + +diff --git a/tests/oci-registry-client.py b/tests/oci-registry-client.py +index 7f1eeb54..b9ca63d7 100644 +--- a/tests/oci-registry-client.py ++++ b/tests/oci-registry-client.py +@@ -2,12 +2,9 @@ + + import sys + +-if sys.version_info[0] >= 3: +- import http.client as http_client +- import urllib.parse as urllib_parse +-else: +- import http.client as http_client +- import urllib as urllib_parse ++import http.client ++import urllib.parse ++ + + if sys.argv[2] == 'add': + detach_icons = '--detach-icons' in sys.argv +@@ -16,8 +13,8 @@ if sys.argv[2] == 'add': + params = {'d': sys.argv[5]} + if detach_icons: + params['detach-icons'] = 1 +- query = urllib_parse.urlencode(params) +- conn = http_client.HTTPConnection(sys.argv[1]) ++ query = urllib.parse.urlencode(params) ++ conn = http.client.HTTPConnection(sys.argv[1]) + path = "/testing/{repo}/{tag}?{query}".format(repo=sys.argv[3], + tag=sys.argv[4], + query=query) +@@ -28,7 +25,7 @@ if sys.argv[2] == 'add': + print("Failed: status={}".format(response.status), file=sys.stderr) + sys.exit(1) + elif sys.argv[2] == 'delete': +- conn = http_client.HTTPConnection(sys.argv[1]) ++ conn = http.client.HTTPConnection(sys.argv[1]) + path = "/testing/{repo}/{ref}".format(repo=sys.argv[3], + ref=sys.argv[4]) + conn.request("DELETE", path) +-- +2.47.1 + +From d8ce35c9d1c0b1c83127b07abe9b3479170cc8f6 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 17 Dec 2024 15:41:53 +0100 +Subject: [PATCH 02/09] tests/oci-registry-client.py: Parse URL parameter + +--- + tests/oci-registry-client.py | 9 +++++++-- + tests/test-oci-registry.sh | 2 +- + 2 files changed, 8 insertions(+), 3 deletions(-) + +diff --git a/tests/oci-registry-client.py b/tests/oci-registry-client.py +index b9ca63d7..cd835609 100644 +--- a/tests/oci-registry-client.py ++++ b/tests/oci-registry-client.py +@@ -6,6 +6,11 @@ import http.client + import urllib.parse + + ++def get_conn(url): ++ parsed = urllib.parse.urlparse(url) ++ return http.client.HTTPConnection(host=parsed.hostname, port=parsed.port) ++ ++ + if sys.argv[2] == 'add': + detach_icons = '--detach-icons' in sys.argv + if detach_icons: +@@ -14,7 +19,7 @@ if sys.argv[2] == 'add': + if detach_icons: + params['detach-icons'] = 1 + query = urllib.parse.urlencode(params) +- conn = http.client.HTTPConnection(sys.argv[1]) ++ conn = get_conn(sys.argv[1]) + path = "/testing/{repo}/{tag}?{query}".format(repo=sys.argv[3], + tag=sys.argv[4], + query=query) +@@ -25,7 +30,7 @@ if sys.argv[2] == 'add': + print("Failed: status={}".format(response.status), file=sys.stderr) + sys.exit(1) + elif sys.argv[2] == 'delete': +- conn = http.client.HTTPConnection(sys.argv[1]) ++ conn = get_conn(sys.argv[1]) + path = "/testing/{repo}/{ref}".format(repo=sys.argv[3], + ref=sys.argv[4]) + conn.request("DELETE", path) +diff --git a/tests/test-oci-registry.sh b/tests/test-oci-registry.sh +index 8eb154f5..51a6142f 100755 +--- a/tests/test-oci-registry.sh ++++ b/tests/test-oci-registry.sh +@@ -29,7 +29,7 @@ echo "1..14" + + httpd oci-registry-server.py . + port=$(cat httpd-port) +-client="python3 $test_srcdir/oci-registry-client.py 127.0.0.1:$port" ++client="python3 $test_srcdir/oci-registry-client.py http://127.0.0.1:$port" + + setup_repo_no_add oci + +-- +2.47.1 + +From 0757171aa07f3b8d390881e0765de09ed77d4825 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 17 Dec 2024 15:47:07 +0100 +Subject: [PATCH 03/09] tests/oci-registry-client.py: Convert to argparse + +--- + tests/oci-registry-client.py | 57 +++++++++++++++++++++++------------- + tests/test-oci-registry.sh | 2 +- + 2 files changed, 38 insertions(+), 21 deletions(-) + mode change 100644 => 100755 tests/oci-registry-client.py + +diff --git a/tests/oci-registry-client.py b/tests/oci-registry-client.py +old mode 100644 +new mode 100755 +index cd835609..5654062b +--- a/tests/oci-registry-client.py ++++ b/tests/oci-registry-client.py +@@ -1,45 +1,62 @@ + #!/usr/bin/python3 + ++import argparse + import sys + + import http.client + import urllib.parse + + +-def get_conn(url): +- parsed = urllib.parse.urlparse(url) ++def get_conn(args): ++ parsed = urllib.parse.urlparse(args.url) + return http.client.HTTPConnection(host=parsed.hostname, port=parsed.port) + + +-if sys.argv[2] == 'add': +- detach_icons = '--detach-icons' in sys.argv +- if detach_icons: +- sys.argv.remove('--detach-icons') +- params = {'d': sys.argv[5]} +- if detach_icons: +- params['detach-icons'] = 1 ++def run_add(args): ++ params = {"d": args.oci_dir} ++ if args.detach_icons: ++ params["detach-icons"] = "1" + query = urllib.parse.urlencode(params) +- conn = get_conn(sys.argv[1]) +- path = "/testing/{repo}/{tag}?{query}".format(repo=sys.argv[3], +- tag=sys.argv[4], +- query=query) ++ conn = get_conn(args) ++ path = "/testing/{repo}/{tag}?{query}".format( ++ repo=args.repo, tag=args.tag, query=query ++ ) + conn.request("POST", path) + response = conn.getresponse() + if response.status != 200: + print(response.read(), file=sys.stderr) + print("Failed: status={}".format(response.status), file=sys.stderr) + sys.exit(1) +-elif sys.argv[2] == 'delete': +- conn = get_conn(sys.argv[1]) +- path = "/testing/{repo}/{ref}".format(repo=sys.argv[3], +- ref=sys.argv[4]) ++ ++ ++def run_delete(args): ++ conn = get_conn(args) ++ path = "/testing/{repo}/{ref}".format(repo=args.repo, ref=args.ref) + conn.request("DELETE", path) + response = conn.getresponse() + if response.status != 200: + print(response.read(), file=sys.stderr) + print("Failed: status={}".format(response.status), file=sys.stderr) + sys.exit(1) +-else: +- print("Usage: oci-registry-client.py [add|remove] ARGS", file=sys.stderr) +- sys.exit(1) + ++ ++parser = argparse.ArgumentParser() ++parser.add_argument("--url", required=True) ++ ++subparsers = parser.add_subparsers() ++subparsers.required = True ++ ++add_parser = subparsers.add_parser("add") ++add_parser.add_argument("repo") ++add_parser.add_argument("tag") ++add_parser.add_argument("oci_dir") ++add_parser.add_argument("--detach-icons", action="store_true", default=False) ++add_parser.set_defaults(func=run_add) ++ ++delete_parser = subparsers.add_parser("delete") ++delete_parser.add_argument("repo") ++delete_parser.add_argument("ref") ++delete_parser.set_defaults(func=run_delete) ++ ++args = parser.parse_args() ++args.func(args) +diff --git a/tests/test-oci-registry.sh b/tests/test-oci-registry.sh +index 51a6142f..bc2f138b 100755 +--- a/tests/test-oci-registry.sh ++++ b/tests/test-oci-registry.sh +@@ -29,7 +29,7 @@ echo "1..14" + + httpd oci-registry-server.py . + port=$(cat httpd-port) +-client="python3 $test_srcdir/oci-registry-client.py http://127.0.0.1:$port" ++client="python3 $test_srcdir/oci-registry-client.py --url=http://127.0.0.1:$port" + + setup_repo_no_add oci + +-- +2.47.1 + +From f38197c03dc77b9192c6dbc59e175b3cb640614a Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Thu, 22 Aug 2024 02:43:13 -0400 +Subject: [PATCH 04/09] tests/oci-registry-server.py: Clean up Python style + +--- + .flake8 | 2 + + tests/oci-registry-server.py | 170 ++++++++++++++++++----------------- + 2 files changed, 88 insertions(+), 84 deletions(-) + create mode 100644 .flake8 + +diff --git a/.flake8 b/.flake8 +new file mode 100644 +index 00000000..7da1f960 +--- /dev/null ++++ b/.flake8 +@@ -0,0 +1,2 @@ ++[flake8] ++max-line-length = 100 +diff --git a/tests/oci-registry-server.py b/tests/oci-registry-server.py +index 33c3b646..3050a883 100755 +--- a/tests/oci-registry-server.py ++++ b/tests/oci-registry-server.py +@@ -13,65 +13,61 @@ import http.server as http_server + repositories = {} + icons = {} + ++ + def get_index(): + results = [] + for repo_name in sorted(repositories.keys()): + repo = repositories[repo_name] +- results.append({ +- 'Name': repo_name, +- 'Images': repo['images'], +- 'Lists': [], +- }) ++ results.append( ++ { ++ "Name": repo_name, ++ "Images": repo["images"], ++ "Lists": [], ++ } ++ ) ++ ++ return json.dumps({"Registry": "/", "Results": results}, indent=4) + +- return json.dumps({ +- 'Registry': '/', +- 'Results': results +- }, indent=4) + + def cache_icon(data_uri): +- prefix = 'data:image/png;base64,' ++ prefix = "data:image/png;base64," + assert data_uri.startswith(prefix) +- data = base64.b64decode(data_uri[len(prefix):]) ++ data = base64.b64decode(data_uri[len(prefix) :]) + h = hashlib.sha256() + h.update(data) + digest = h.hexdigest() +- filename = digest + '.png' ++ filename = digest + ".png" + icons[filename] = data + +- return '/icons/' + filename ++ return "/icons/" + filename ++ + + serial = 0 + server_start_time = int(time.time()) + ++ + def get_etag(): +- return str(server_start_time) + '-' + str(serial) ++ return str(server_start_time) + "-" + str(serial) ++ + + def modified(): + global serial + serial += 1 + +-def parse_http_date(date): +- parsed = parsedate(date) +- if parsed is not None: +- return timegm(parsed) +- else: +- return None + + class RequestHandler(http_server.BaseHTTPRequestHandler): + def check_route(self, route): +- parts = self.path.split('?', 1) +- path = parts[0].split('/') +- +- result = [] ++ parts = self.path.split("?", 1) ++ path = parts[0].split("/") + +- route_path = route.split('/') ++ route_path = route.split("/") + print((route_path, path)) + if len(route_path) != len(path): + return False + + matches = {} + for i in range(1, len(route_path)): +- if route_path[i][0] == '@': ++ if route_path[i][0] == "@": + matches[route_path[i][1:]] = path[i] + elif route_path[i] != path[i]: + return False +@@ -92,24 +88,25 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + + add_headers = {} + +- if self.check_route('/v2/@repo_name/blobs/@digest'): +- repo_name = self.matches['repo_name'] +- digest = self.matches['digest'] +- response_file = repositories[repo_name]['blobs'][digest] +- elif self.check_route('/v2/@repo_name/manifests/@ref'): +- repo_name = self.matches['repo_name'] +- ref = self.matches['ref'] +- response_file = repositories[repo_name]['manifests'][ref] +- elif self.check_route('/index/static') or self.check_route('/index/dynamic'): ++ if self.check_route("/v2/@repo_name/blobs/@digest"): ++ repo_name = self.matches["repo_name"] ++ digest = self.matches["digest"] ++ response_file = repositories[repo_name]["blobs"][digest] ++ elif self.check_route("/v2/@repo_name/manifests/@ref"): ++ repo_name = self.matches["repo_name"] ++ ref = self.matches["ref"] ++ response_file = repositories[repo_name]["manifests"][ref] ++ elif self.check_route("/index/static") or self.check_route("/index/dynamic"): + etag = get_etag() + if self.headers.get("If-None-Match") == etag: + response = 304 + else: + response_string = get_index() +- add_headers['Etag'] = etag +- elif self.check_route('/icons/@filename') : +- response_string = icons[self.matches['filename']] +- response_content_type = 'image/png' ++ add_headers["Etag"] = etag ++ elif self.check_route("/icons/@filename"): ++ response_string = icons[self.matches["filename"]] ++ assert isinstance(response_string, bytes) ++ response_content_type = "image/png" + else: + response = 404 + +@@ -121,86 +118,89 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + self.send_header("Content-Type", response_content_type) + + if response == 200 or response == 304: +- self.send_header('Cache-Control', 'no-cache') ++ self.send_header("Cache-Control", "no-cache") + + self.end_headers() + + if response == 200: + if response_file: +- with open(response_file, 'rb') as f: ++ with open(response_file, "rb") as f: + response_string = f.read() + + if isinstance(response_string, bytes): + self.wfile.write(response_string) + else: +- self.wfile.write(response_string.encode('utf-8')) ++ assert isinstance(response_string, str) ++ self.wfile.write(response_string.encode("utf-8")) + + def do_HEAD(self): + return self.do_GET() + + def do_POST(self): +- if self.check_route('/testing/@repo_name/@tag'): +- repo_name = self.matches['repo_name'] +- tag = self.matches['tag'] +- d = self.query['d'][0] +- detach_icons = 'detach-icons' in self.query ++ if self.check_route("/testing/@repo_name/@tag"): ++ repo_name = self.matches["repo_name"] ++ tag = self.matches["tag"] ++ d = self.query["d"][0] ++ detach_icons = "detach-icons" in self.query + + repo = repositories.setdefault(repo_name, {}) +- blobs = repo.setdefault('blobs', {}) +- manifests = repo.setdefault('manifests', {}) +- images = repo.setdefault('images', []) ++ blobs = repo.setdefault("blobs", {}) ++ manifests = repo.setdefault("manifests", {}) ++ images = repo.setdefault("images", []) + +- with open(os.path.join(d, 'index.json')) as f: ++ with open(os.path.join(d, "index.json")) as f: + index = json.load(f) + +- manifest_digest = index['manifests'][0]['digest'] +- manifest_path = os.path.join(d, 'blobs', *manifest_digest.split(':')) ++ manifest_digest = index["manifests"][0]["digest"] ++ manifest_path = os.path.join(d, "blobs", *manifest_digest.split(":")) + manifests[manifest_digest] = manifest_path + manifests[tag] = manifest_path + + with open(manifest_path) as f: + manifest = json.load(f) + +- config_digest = manifest['config']['digest'] +- config_path = os.path.join(d, 'blobs', *config_digest.split(':')) ++ config_digest = manifest["config"]["digest"] ++ config_path = os.path.join(d, "blobs", *config_digest.split(":")) + + with open(config_path) as f: + config = json.load(f) + +- for dig in os.listdir(os.path.join(d, 'blobs', 'sha256')): +- digest = 'sha256:' + dig +- path = os.path.join(d, 'blobs', 'sha256', dig) ++ for dig in os.listdir(os.path.join(d, "blobs", "sha256")): ++ digest = "sha256:" + dig ++ path = os.path.join(d, "blobs", "sha256", dig) + if digest != manifest_digest: + blobs[digest] = path + + if detach_icons: + for size in (64, 128): +- annotation = 'org.freedesktop.appstream.icon-{}'.format(size) +- icon = manifest.get('annotations', {}).get(annotation) ++ annotation = "org.freedesktop.appstream.icon-{}".format(size) ++ icon = manifest.get("annotations", {}).get(annotation) + if icon: + path = cache_icon(icon) +- manifest['annotations'][annotation] = path ++ manifest["annotations"][annotation] = path + else: +- icon = config.get('config', {}).get('Labels', {}).get(annotation) ++ icon = ( ++ config.get("config", {}).get("Labels", {}).get(annotation) ++ ) + if icon: + path = cache_icon(icon) +- config['config']['Labels'][annotation] = path ++ config["config"]["Labels"][annotation] = path + + image = { + "Tags": [tag], + "Digest": manifest_digest, + "MediaType": "application/vnd.oci.image.manifest.v1+json", +- "OS": config['os'], +- "Architecture": config['architecture'], +- "Annotations": manifest.get('annotations', {}), +- "Labels": config.get('config', {}).get('Labels', {}), ++ "OS": config["os"], ++ "Architecture": config["architecture"], ++ "Annotations": manifest.get("annotations", {}), ++ "Labels": config.get("config", {}).get("Labels", {}), + } + + # Delete old versions + for i in images: +- if tag in i['Tags']: ++ if tag in i["Tags"]: + images.remove(i) +- del manifests[i['Digest']] ++ del manifests[i["Digest"]] + + images.append(image) + +@@ -214,26 +214,26 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + return + + def do_DELETE(self): +- if self.check_route('/testing/@repo_name/@ref'): +- repo_name = self.matches['repo_name'] +- ref = self.matches['ref'] ++ if self.check_route("/testing/@repo_name/@ref"): ++ repo_name = self.matches["repo_name"] ++ ref = self.matches["ref"] + + repo = repositories.setdefault(repo_name, {}) +- blobs = repo.setdefault('blobs', {}) +- manifests = repo.setdefault('manifests', {}) +- images = repo.setdefault('images', []) ++ repo.setdefault("blobs", {}) ++ manifests = repo.setdefault("manifests", {}) ++ images = repo.setdefault("images", []) + + image = None + for i in images: +- if i['Digest'] == ref or ref in i['Tags']: ++ if i["Digest"] == ref or ref in i["Tags"]: + image = i + break + + assert image + + images.remove(image) +- del manifests[image['Digest']] +- for t in image['Tags']: ++ del manifests[image["Digest"]] ++ for t in image["Tags"]: + del manifests[t] + + modified() +@@ -245,22 +245,24 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + self.end_headers() + return + ++ + def run(dir): + RequestHandler.protocol_version = "HTTP/1.0" +- httpd = http_server.HTTPServer( ("127.0.0.1", 0), RequestHandler) ++ httpd = http_server.HTTPServer(("127.0.0.1", 0), RequestHandler) + host, port = httpd.socket.getsockname()[:2] +- with open("httpd-port", 'w') as file: ++ with open("httpd-port", "w") as file: + file.write("%d" % port) + try: +- os.write(3, bytes("Started\n", 'utf-8')); +- except: ++ os.write(3, bytes("Started\n", "utf-8")) ++ except OSError: + pass +- print("Serving HTTP on port %d" % port); ++ print("Serving HTTP on port %d" % port) + if dir: + os.chdir(dir) + httpd.serve_forever() + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + dir = None + if len(sys.argv) >= 2 and len(sys.argv[1]) > 0: + dir = sys.argv[1] +-- +2.47.1 + +From 47ff75860097afe0b07758b2457a52f0e6b94bd7 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Thu, 22 Aug 2024 02:47:19 -0400 +Subject: [PATCH 05/09] tests/oci-registry-server.py: Convert to argparse + +--- + tests/oci-registry-server.py | 15 ++++++++------- + tests/test-oci-registry.sh | 2 +- + 2 files changed, 9 insertions(+), 8 deletions(-) + +diff --git a/tests/oci-registry-server.py b/tests/oci-registry-server.py +index 3050a883..65421f34 100755 +--- a/tests/oci-registry-server.py ++++ b/tests/oci-registry-server.py +@@ -1,5 +1,6 @@ + #!/usr/bin/python3 + ++import argparse + import base64 + import hashlib + import json +@@ -246,7 +247,7 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + return + + +-def run(dir): ++def run(args): + RequestHandler.protocol_version = "HTTP/1.0" + httpd = http_server.HTTPServer(("127.0.0.1", 0), RequestHandler) + host, port = httpd.socket.getsockname()[:2] +@@ -257,14 +258,14 @@ def run(dir): + except OSError: + pass + print("Serving HTTP on port %d" % port) +- if dir: +- os.chdir(dir) ++ if args.dir: ++ os.chdir(args.dir) + httpd.serve_forever() + + + if __name__ == "__main__": +- dir = None +- if len(sys.argv) >= 2 and len(sys.argv[1]) > 0: +- dir = sys.argv[1] ++ parser = argparse.ArgumentParser() ++ parser.add_argument("--dir") ++ args = parser.parse_args() + +- run(dir) ++ run(args) +diff --git a/tests/test-oci-registry.sh b/tests/test-oci-registry.sh +index bc2f138b..12036358 100755 +--- a/tests/test-oci-registry.sh ++++ b/tests/test-oci-registry.sh +@@ -27,7 +27,7 @@ echo "1..14" + + # Start the fake registry server + +-httpd oci-registry-server.py . ++httpd oci-registry-server.py --dir=. + port=$(cat httpd-port) + client="python3 $test_srcdir/oci-registry-client.py --url=http://127.0.0.1:$port" + +-- +2.47.1 + +From c2c2f3679608a32339dadb233dc11633f22b0793 Mon Sep 17 00:00:00 2001 +From: Sebastian Wick +Date: Tue, 17 Dec 2024 16:39:16 +0100 +Subject: [PATCH 06/09] tests/oci-registry-server.py: Always get bytes for the + response + +And sent the Content-Length header. +--- + tests/oci-registry-server.py | 46 +++++++++++++++++++----------------- + 1 file changed, 24 insertions(+), 22 deletions(-) + +diff --git a/tests/oci-registry-server.py b/tests/oci-registry-server.py +index 65421f34..13bf50b3 100755 +--- a/tests/oci-registry-server.py ++++ b/tests/oci-registry-server.py +@@ -5,7 +5,6 @@ import base64 + import hashlib + import json + import os +-import sys + import time + + from urllib.parse import parse_qs +@@ -27,7 +26,7 @@ def get_index(): + } + ) + +- return json.dumps({"Registry": "/", "Results": results}, indent=4) ++ return json.dumps({"Registry": "/", "Results": results}, indent=4).encode("UTF-8") + + + def cache_icon(data_uri): +@@ -62,7 +61,6 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + path = parts[0].split("/") + + route_path = route.split("/") +- print((route_path, path)) + if len(route_path) != len(path): + return False + +@@ -82,34 +80,47 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + return True + + def do_GET(self): +- response = 200 +- response_string = None ++ response = 404 ++ response_string = b"" + response_content_type = "application/octet-stream" +- response_file = None + + add_headers = {} + ++ def get_file_contents(repo_name, type, ref): ++ try: ++ path = repositories[repo_name][type][ref] ++ with open(path, "rb") as f: ++ return 200, f.read() ++ except KeyError: ++ return 404, b"" ++ + if self.check_route("/v2/@repo_name/blobs/@digest"): + repo_name = self.matches["repo_name"] + digest = self.matches["digest"] +- response_file = repositories[repo_name]["blobs"][digest] ++ response, response_string = get_file_contents(repo_name, "blobs", digest) + elif self.check_route("/v2/@repo_name/manifests/@ref"): + repo_name = self.matches["repo_name"] + ref = self.matches["ref"] +- response_file = repositories[repo_name]["manifests"][ref] ++ response, response_string = get_file_contents(repo_name, "manifests", ref) + elif self.check_route("/index/static") or self.check_route("/index/dynamic"): + etag = get_etag() ++ add_headers["Etag"] = etag + if self.headers.get("If-None-Match") == etag: + response = 304 ++ response_string = b"" + else: ++ response = 200 + response_string = get_index() +- add_headers["Etag"] = etag + elif self.check_route("/icons/@filename"): ++ response = 200 + response_string = icons[self.matches["filename"]] +- assert isinstance(response_string, bytes) + response_content_type = "image/png" +- else: +- response = 404 ++ ++ assert isinstance(response, int) ++ assert isinstance(response_string, bytes) ++ assert isinstance(response_content_type, str) ++ ++ add_headers["Content-Length"] = len(response_string) + + self.send_response(response) + for k, v in list(add_headers.items()): +@@ -123,16 +134,7 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + + self.end_headers() + +- if response == 200: +- if response_file: +- with open(response_file, "rb") as f: +- response_string = f.read() +- +- if isinstance(response_string, bytes): +- self.wfile.write(response_string) +- else: +- assert isinstance(response_string, str) +- self.wfile.write(response_string.encode("utf-8")) ++ self.wfile.write(response_string) + + def do_HEAD(self): + return self.do_GET() +-- +2.47.1 + +From 85379a0fe6b79d216a29497980ce2c8e0ab46997 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 17 Dec 2024 16:41:38 +0100 +Subject: [PATCH 07/09] tests/oci-registry: Add support for SSL to client and + server + +--- + tests/oci-registry-client.py | 19 ++++++++++++++++++- + tests/oci-registry-server.py | 22 +++++++++++++++++++++- + 2 files changed, 39 insertions(+), 2 deletions(-) + +diff --git a/tests/oci-registry-client.py b/tests/oci-registry-client.py +index 5654062b..c4707c07 100755 +--- a/tests/oci-registry-client.py ++++ b/tests/oci-registry-client.py +@@ -1,6 +1,7 @@ + #!/usr/bin/python3 + + import argparse ++import ssl + import sys + + import http.client +@@ -9,7 +10,20 @@ import urllib.parse + + def get_conn(args): + parsed = urllib.parse.urlparse(args.url) +- return http.client.HTTPConnection(host=parsed.hostname, port=parsed.port) ++ if parsed.scheme == "http": ++ return http.client.HTTPConnection(host=parsed.hostname, port=parsed.port) ++ elif parsed.scheme == "https": ++ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ++ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 ++ if args.cert: ++ context.load_cert_chain(certfile=args.cert, keyfile=args.key) ++ if args.cacert: ++ context.load_verify_locations(cafile=args.cacert) ++ return http.client.HTTPSConnection( ++ host=parsed.hostname, port=parsed.port, context=context ++ ) ++ else: ++ assert False, "Bad scheme: " + parsed.scheme + + + def run_add(args): +@@ -42,6 +56,9 @@ def run_delete(args): + + parser = argparse.ArgumentParser() + parser.add_argument("--url", required=True) ++parser.add_argument("--cacert") ++parser.add_argument("--cert") ++parser.add_argument("--key") + + subparsers = parser.add_subparsers() + subparsers.required = True +diff --git a/tests/oci-registry-server.py b/tests/oci-registry-server.py +index 13bf50b3..2bbe8c6e 100755 +--- a/tests/oci-registry-server.py ++++ b/tests/oci-registry-server.py +@@ -5,6 +5,7 @@ import base64 + import hashlib + import json + import os ++import ssl + import time + + from urllib.parse import parse_qs +@@ -252,6 +253,19 @@ class RequestHandler(http_server.BaseHTTPRequestHandler): + def run(args): + RequestHandler.protocol_version = "HTTP/1.0" + httpd = http_server.HTTPServer(("127.0.0.1", 0), RequestHandler) ++ ++ if args.cert: ++ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ++ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 ++ context.load_cert_chain(certfile=args.cert, keyfile=args.key) ++ ++ if args.mtls_cacert: ++ context.load_verify_locations(cafile=args.mtls_cacert) ++ # In a real application, we'd need to check the CN against authorized users ++ context.verify_mode = ssl.CERT_REQUIRED ++ ++ httpd.socket = context.wrap_socket(httpd.socket, server_side=True) ++ + host, port = httpd.socket.getsockname()[:2] + with open("httpd-port", "w") as file: + file.write("%d" % port) +@@ -259,7 +273,10 @@ def run(args): + os.write(3, bytes("Started\n", "utf-8")) + except OSError: + pass +- print("Serving HTTP on port %d" % port) ++ if args.cert: ++ print("Serving HTTPS on port %d" % port) ++ else: ++ print("Serving HTTP on port %d" % port) + if args.dir: + os.chdir(args.dir) + httpd.serve_forever() +@@ -268,6 +285,9 @@ def run(args): + if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dir") ++ parser.add_argument("--cert") ++ parser.add_argument("--key") ++ parser.add_argument("--mtls-cacert") + args = parser.parse_args() + + run(args) +-- +2.47.1 + +From 68b3fdcc0b00ee1080a1a3cd15dff33f1de6c0bf Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Fri, 23 Aug 2024 09:48:26 -0400 +Subject: [PATCH 08/09] common: Implement /etc/containers/certs.d for OCI + registries + +Docker and podman can be configured to use mutual TLS authentication +to the registry by dropping files into system-wide and user +directories. Implement this in a largely compatible way. + +(Because of the limitations of our underlying libraries, we +can't support multiple certificates within the same host config, +but I don't expect anybody actually needs that.) + +The certs.d handling is extended so that certificates are separately +looked up when downloading the look-aside index. This is mostly +to simplify our tests, so we can use one web server for both - +in actual operation, we expect the indexes to be unauthenticated. + +Also for testing purposes, FLATPAK_CONTAINER_CERTS_D is supported +to override the standard search path. + +Co-authored-by: Sebastian Wick +--- + .github/workflows/check.yml | 2 +- + common/flatpak-oci-registry.c | 86 +++++-- + common/flatpak-utils-http-private.h | 12 + + common/flatpak-utils-http.c | 368 +++++++++++++++++++++++++++- + doc/flatpak-remote.xml | 6 + + tests/httpcache.c | 2 +- + 6 files changed, 440 insertions(+), 36 deletions(-) + +diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml +index dbf44c1d..6cce2a6f 100644 +--- a/.github/workflows/check.yml ++++ b/.github/workflows/check.yml +@@ -52,7 +52,7 @@ jobs: + libjson-glib-dev shared-mime-info desktop-file-utils libpolkit-agent-1-dev libpolkit-gobject-1-dev \ + libseccomp-dev libsoup2.4-dev libcurl4-openssl-dev libsystemd-dev libxml2-utils libgpgme11-dev gobject-introspection \ + libgirepository1.0-dev libappstream-dev libdconf-dev clang socat meson libdbus-1-dev e2fslibs-dev bubblewrap xdg-dbus-proxy \ +- python3-pip meson ninja-build libyaml-dev libstemmer-dev gperf itstool libmalcontent-0-dev ++ python3-pip meson ninja-build libyaml-dev libstemmer-dev gperf itstool libmalcontent-0-dev openssl + # One of the tests wants this + sudo mkdir /tmp/flatpak-com.example.App-OwnedByRoot + - name: Check out flatpak +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index ad595e53..6d36de2a 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -76,7 +76,8 @@ struct FlatpakOciRegistry + + /* Remote repos */ + FlatpakHttpSession *http_session; +- GUri *base_uri; ++ GUri *base_uri; ++ FlatpakCertificates *certificates; + }; + + typedef struct +@@ -353,31 +354,29 @@ choose_alt_uri (GUri *base_uri, + } + + static GBytes * +-remote_load_file (FlatpakHttpSession *http_session, +- GUri *base, +- const char *subpath, +- const char **alt_uris, +- const char *token, +- char **out_content_type, +- GCancellable *cancellable, +- GError **error) ++remote_load_file (FlatpakOciRegistry *self, ++ const char *subpath, ++ const char **alt_uris, ++ char **out_content_type, ++ GCancellable *cancellable, ++ GError **error) + { + g_autoptr(GBytes) bytes = NULL; + g_autofree char *uri_s = NULL; + +- uri_s = choose_alt_uri (base, alt_uris); ++ uri_s = choose_alt_uri (self->base_uri, alt_uris); + if (uri_s == NULL) + { +- uri_s = parse_relative_uri (base, subpath, error); ++ uri_s = parse_relative_uri (self->base_uri, subpath, error); + if (uri_s == NULL) + return NULL; + } + +- bytes = flatpak_load_uri (http_session, +- uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, +- token, +- NULL, NULL, out_content_type, +- cancellable, error); ++ bytes = flatpak_load_uri_full (self->http_session, ++ uri_s, self->certificates, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, ++ NULL, self->token, ++ NULL, NULL, NULL, out_content_type, NULL, ++ cancellable, error); + if (bytes == NULL) + return NULL; + +@@ -395,7 +394,7 @@ flatpak_oci_registry_load_file (FlatpakOciRegistry *self, + if (self->dfd != -1) + return local_load_file (self->dfd, subpath, cancellable, error); + else +- return remote_load_file (self->http_session, self->base_uri, subpath, alt_uris, self->token, out_content_type, cancellable, error); ++ return remote_load_file (self, subpath, alt_uris, out_content_type, cancellable, error); + } + + static JsonNode * +@@ -548,6 +547,7 @@ flatpak_oci_registry_ensure_remote (FlatpakOciRegistry *self, + GError **error) + { + g_autoptr(GUri) baseuri = NULL; ++ g_autoptr(GError) local_error = NULL; + + if (for_write) + { +@@ -568,6 +568,13 @@ flatpak_oci_registry_ensure_remote (FlatpakOciRegistry *self, + self->is_docker = TRUE; + self->base_uri = g_steal_pointer (&baseuri); + ++ self->certificates = flatpak_get_certificates_for_uri (self->uri, &local_error); ++ if (local_error) ++ { ++ g_propagate_error (error, g_steal_pointer (&local_error)); ++ return FALSE; ++ } ++ + return TRUE; + } + +@@ -834,6 +841,7 @@ flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, + return -1; + + if (!flatpak_download_http_uri (self->http_session, uri_s, ++ self->certificates, + FLATPAK_HTTP_FLAGS_ACCEPT_OCI, + out_stream, + self->token, +@@ -925,7 +933,8 @@ flatpak_oci_registry_mirror_blob (FlatpakOciRegistry *self, + + out_stream = g_unix_output_stream_new (tmpf.fd, FALSE); + +- if (!flatpak_download_http_uri (source_registry->http_session, uri_s, ++ if (!flatpak_download_http_uri (source_registry->http_session, ++ uri_s, source_registry->certificates, + FLATPAK_HTTP_FLAGS_ACCEPT_OCI, out_stream, + self->token, + progress_cb, user_data, +@@ -1055,6 +1064,7 @@ get_token_for_www_auth (FlatpakOciRegistry *self, + + body = flatpak_load_uri_full (self->http_session, + auth_uri_s, ++ self->certificates, + FLATPAK_HTTP_FLAGS_NOCHECK_STATUS, + auth, NULL, + NULL, NULL, +@@ -1146,7 +1156,7 @@ flatpak_oci_registry_get_token (FlatpakOciRegistry *self, + if (uri_s == NULL) + return NULL; + +- body = flatpak_load_uri_full (self->http_session, uri_s, ++ body = flatpak_load_uri_full (self->http_session, uri_s, self->certificates, + FLATPAK_HTTP_FLAGS_ACCEPT_OCI | FLATPAK_HTTP_FLAGS_HEAD | FLATPAK_HTTP_FLAGS_NOCHECK_STATUS, + NULL, NULL, + NULL, NULL, +@@ -2080,11 +2090,11 @@ flatpak_oci_registry_find_delta_manifest (FlatpakOciRegistry *registry, + g_autofree char *uri_s = parse_relative_uri (registry->base_uri, delta_manifest_url, NULL); + + if (uri_s != NULL) +- bytes = flatpak_load_uri (registry->http_session, +- uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, +- registry->token, +- NULL, NULL, NULL, +- cancellable, NULL); ++ bytes = flatpak_load_uri_full (registry->http_session, ++ uri_s, registry->certificates, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, ++ NULL, registry->token, ++ NULL, NULL, NULL, NULL, NULL, ++ cancellable, NULL); + if (bytes != NULL) + { + g_autoptr(FlatpakOciVersioned) versioned = +@@ -2760,6 +2770,7 @@ flatpak_oci_index_ensure_cached (FlatpakHttpSession *http_session, + g_autofree char *tag = NULL; + const char *oci_arch = NULL; + gboolean success = FALSE; ++ g_autoptr(FlatpakCertificates) certificates = NULL; + g_autoptr(GError) local_error = NULL; + GUri *tmp_uri; + +@@ -2844,8 +2855,16 @@ flatpak_oci_index_ensure_cached (FlatpakHttpSession *http_session, + + query_uri_s = g_uri_to_string_partial (query_uri, G_URI_HIDE_PASSWORD); + ++ certificates = flatpak_get_certificates_for_uri (query_uri_s, &local_error); ++ if (local_error) ++ { ++ g_propagate_error (error, g_steal_pointer (&local_error)); ++ return FALSE; ++ } ++ + success = flatpak_cache_http_uri (http_session, + query_uri_s, ++ certificates, + FLATPAK_HTTP_FLAGS_STORE_COMPRESSED, + AT_FDCWD, index_path, + NULL, NULL, +@@ -3082,6 +3101,7 @@ flatpak_oci_index_make_summary (GFile *index, + static gboolean + add_icon_image (FlatpakHttpSession *http_session, + const char *index_uri, ++ FlatpakCertificates *certificates, + int icons_dfd, + GHashTable *used_icons, + const char *subdir, +@@ -3132,7 +3152,7 @@ add_icon_image (FlatpakHttpSession *http_session, + if (icon_uri_s == NULL) + return FALSE; + +- if (!flatpak_cache_http_uri (http_session, icon_uri_s, ++ if (!flatpak_cache_http_uri (http_session, icon_uri_s, certificates, + 0 /* flags */, + icons_dfd, icon_path, + NULL, NULL, +@@ -3152,6 +3172,7 @@ add_icon_image (FlatpakHttpSession *http_session, + static void + add_image_to_appstream (FlatpakHttpSession *http_session, + const char *index_uri, ++ FlatpakCertificates *certificates, + FlatpakXml *appstream_root, + int icons_dfd, + GHashTable *used_icons, +@@ -3243,6 +3264,7 @@ add_image_to_appstream (FlatpakHttpSession *http_session, + { + if (!add_icon_image (http_session, + index_uri, ++ certificates, + icons_dfd, + used_icons, + icon_sizes[i].subdir, id, icon_data, +@@ -3338,6 +3360,8 @@ flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + g_autoptr(FlatpakXml) appstream_root = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GHashTable) used_icons = NULL; ++ g_autoptr(FlatpakCertificates) certificates = NULL; ++ g_autoptr(GError) local_error = NULL; + int i; + + const char *oci_arch = flatpak_arch_to_oci_arch (arch); +@@ -3351,6 +3375,14 @@ flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + + appstream_root = flatpak_appstream_xml_new (); + ++ certificates = flatpak_get_certificates_for_uri (index_uri, &local_error); ++ if (local_error) ++ { ++ g_print ("Failed to load certificates for %s: %s", ++ index_uri, local_error->message); ++ g_clear_error (&local_error); ++ } ++ + for (i = 0; response->results != NULL && response->results[i] != NULL; i++) + { + FlatpakOciIndexRepository *r = response->results[i]; +@@ -3361,7 +3393,7 @@ flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + FlatpakOciIndexImage *image = r->images[j]; + if (g_strcmp0 (image->architecture, oci_arch) == 0) + add_image_to_appstream (http_session, +- index_uri, ++ index_uri, certificates, + appstream_root, icons_dfd, used_icons, + r, image, + cancellable); +@@ -3377,7 +3409,7 @@ flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + FlatpakOciIndexImage *image = list->images[k]; + if (g_strcmp0 (image->architecture, oci_arch) == 0) + add_image_to_appstream (http_session, +- index_uri, ++ index_uri, certificates, + appstream_root, icons_dfd, used_icons, + r, image, + cancellable); +diff --git a/common/flatpak-utils-http-private.h b/common/flatpak-utils-http-private.h +index 2c89ba40..a930ee79 100644 +--- a/common/flatpak-utils-http-private.h ++++ b/common/flatpak-utils-http-private.h +@@ -39,6 +39,15 @@ void flatpak_http_session_free (FlatpakHttpSession* http_session); + + G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlatpakHttpSession, flatpak_http_session_free) + ++typedef struct FlatpakCertificates FlatpakCertificates; ++ ++FlatpakCertificates* flatpak_get_certificates_for_uri (const char *uri, ++ GError **error); ++FlatpakCertificates * flatpak_certificates_copy (FlatpakCertificates *other); ++void flatpak_certificates_free (FlatpakCertificates *certificates); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlatpakCertificates, flatpak_certificates_free) ++ + typedef enum { + FLATPAK_HTTP_FLAGS_NONE = 0, + FLATPAK_HTTP_FLAGS_ACCEPT_OCI = 1 << 0, +@@ -52,6 +61,7 @@ typedef void (*FlatpakLoadUriProgress) (guint64 downloaded_bytes, + + GBytes * flatpak_load_uri_full (FlatpakHttpSession *http_session, + const char *uri, ++ FlatpakCertificates *certificates, + FlatpakHTTPFlags flags, + const char *auth, + const char *token, +@@ -73,6 +83,7 @@ GBytes * flatpak_load_uri (FlatpakHttpSession *http_session, + GError **error); + gboolean flatpak_download_http_uri (FlatpakHttpSession *http_session, + const char *uri, ++ FlatpakCertificates *certificates, + FlatpakHTTPFlags flags, + GOutputStream *out, + const char *token, +@@ -82,6 +93,7 @@ gboolean flatpak_download_http_uri (FlatpakHttpSession *http_session, + GError **error); + gboolean flatpak_cache_http_uri (FlatpakHttpSession *http_session, + const char *uri, ++ FlatpakCertificates *certificates, + FlatpakHTTPFlags flags, + int dest_dfd, + const char *dest_subpath, +diff --git a/common/flatpak-utils-http.c b/common/flatpak-utils-http.c +index 27de9c7a..e8d5d724 100644 +--- a/common/flatpak-utils-http.c ++++ b/common/flatpak-utils-http.c +@@ -73,6 +73,17 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupURI, soup_uri_free) + + G_DEFINE_QUARK (flatpak_http_error, flatpak_http_error) + ++/* Holds information about CA and client certificates found in ++ * system-wide and per-user certificate directories as documented ++ * in container-certs.d(5). ++ */ ++struct FlatpakCertificates ++{ ++ char *ca_cert_file; ++ char *client_cert_file; ++ char *client_key_file; ++}; ++ + /* Information about the cache status of a file. + Encoded in an xattr on the cached file, or a file on the side if xattrs don't work. + */ +@@ -95,6 +106,7 @@ typedef struct + FlatpakHTTPFlags flags; + const char *auth; + const char *token; ++ FlatpakCertificates *certificates; + FlatpakLoadUriProgress progress; + GCancellable *cancellable; + gpointer user_data; +@@ -231,6 +243,157 @@ check_http_status (guint status_code, + return FALSE; + } + ++FlatpakCertificates* ++flatpak_get_certificates_for_uri (const char *uri, ++ GError **error) ++{ ++ g_autoptr(FlatpakCertificates) certificates = NULL; ++ g_autoptr(GUri) parsed_uri = NULL; ++ g_autofree char *hostport = NULL; ++ const char *system_certs_d = NULL; ++ g_autofree char *certs_path_str = NULL; ++ g_auto(GStrv) certs_path = NULL; ++ ++ certificates = g_new0 (FlatpakCertificates, 1); ++ ++ parsed_uri = g_uri_parse (uri, G_URI_FLAGS_PARSE_RELAXED, error); ++ if (!parsed_uri) ++ return NULL; ++ ++ if (!g_uri_get_host (parsed_uri)) ++ return NULL; ++ ++ if (g_uri_get_port (parsed_uri) != -1) ++ hostport = g_strdup_printf ("%s:%d", g_uri_get_host (parsed_uri), g_uri_get_port (parsed_uri)); ++ else ++ hostport = g_strdup (g_uri_get_host (parsed_uri)); ++ ++ system_certs_d = g_getenv ("FLATPAK_SYSTEM_CERTS_D"); ++ if (system_certs_d == NULL || system_certs_d[0] == '\0') ++ system_certs_d = "/etc/containers/certs.d:/etc/docker/certs.d"; ++ ++ /* containers/image hardcodes ~/.config and doesn't honor XDG_CONFIG_HOME */ ++ certs_path_str = g_strconcat (g_get_user_config_dir(), "/containers/certs.d:", ++ system_certs_d, NULL); ++ certs_path = g_strsplit (certs_path_str, ":", -1); ++ ++ for (int i = 0; certs_path[i]; i++) ++ { ++ g_autoptr(GFile) certs_dir = g_file_new_for_path (certs_path[i]); ++ g_autoptr(GFile) host_dir = g_file_get_child (certs_dir, hostport); ++ g_autoptr(GFileEnumerator) enumerator; ++ g_autoptr(GError) local_error = NULL; ++ ++ enumerator = g_file_enumerate_children (host_dir, G_FILE_ATTRIBUTE_STANDARD_NAME, ++ G_FILE_QUERY_INFO_NONE, ++ NULL, &local_error); ++ if (enumerator == NULL) ++ { ++ /* This matches libpod - missing certificate directory or a permission ++ * error causes the directory to be skipped; any other error is fatal ++ */ ++ if (g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || ++ g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) ++ { ++ g_clear_error (&local_error); ++ continue; ++ } ++ else ++ { ++ g_propagate_error (error, g_steal_pointer (&local_error)); ++ return NULL; ++ } ++ } ++ ++ while (TRUE) ++ { ++ GFile *child; ++ g_autofree char *basename = NULL; ++ ++ if (!g_file_enumerator_iterate (enumerator, NULL, &child, NULL, error)) ++ return NULL; ++ ++ if (child == NULL) ++ break; ++ ++ basename = g_file_get_basename (child); ++ ++ /* In libpod, all CA certificates are added to the CA certificate ++ * database. We just use the first in readdir order. ++ */ ++ if (g_str_has_suffix (basename, ".crt") && certificates->ca_cert_file == NULL) ++ certificates->ca_cert_file = g_file_get_path (child); ++ ++ if (g_str_has_suffix (basename, ".cert")) ++ { ++ g_autofree char *nosuffix = g_strndup (basename, strlen (basename) - 5); ++ g_autofree char *key_basename = g_strconcat (nosuffix, ".key", NULL); ++ g_autoptr(GFile) key_file = g_file_get_child (host_dir, key_basename); ++ ++ if (!g_file_query_exists (key_file, NULL)) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "missing key %s for client cert %s. " ++ "Note that CA certificates should use the extension .crt", ++ g_file_peek_path (key_file), ++ g_file_peek_path (child)); ++ return NULL; ++ } ++ ++ /* In libpod, all client certificates are added, and then the go TLS ++ * code selects the best based on TLS negotation. We just pick the first ++ * in readdir order ++ * */ ++ if (certificates->client_cert_file == NULL) ++ { ++ certificates->client_cert_file = g_file_get_path (child); ++ certificates->client_key_file = g_file_get_path (key_file); ++ } ++ } ++ ++ if (g_str_has_suffix (basename, ".key")) ++ { ++ g_autofree char *nosuffix = g_strndup (basename, strlen (basename) - 4); ++ g_autofree char *cert_basename = g_strconcat (nosuffix, ".cert", NULL); ++ g_autoptr(GFile) cert_file = g_file_get_child (host_dir, cert_basename); ++ ++ if (!g_file_query_exists (cert_file, NULL)) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "missing client certificate %s for key %s", ++ g_file_peek_path (cert_file), ++ g_file_peek_path (child)); ++ return NULL; ++ } ++ } ++ } ++ } ++ ++ return g_steal_pointer (&certificates); ++} ++ ++FlatpakCertificates * ++flatpak_certificates_copy (FlatpakCertificates *other) ++{ ++ FlatpakCertificates *certificates = g_new0 (FlatpakCertificates, 1); ++ ++ certificates->ca_cert_file = g_strdup (other->ca_cert_file); ++ certificates->client_cert_file = g_strdup (other->client_cert_file); ++ certificates->client_key_file = g_strdup (other->client_key_file); ++ ++ return certificates; ++} ++ ++void ++flatpak_certificates_free (FlatpakCertificates *certificates) ++{ ++ g_clear_pointer (&certificates->ca_cert_file, g_free); ++ g_clear_pointer (&certificates->client_cert_file, g_free); ++ g_clear_pointer (&certificates->client_key_file, g_free); ++ ++ g_free (certificates); ++} ++ + #if defined(HAVE_CURL) + + /************************************************************************ +@@ -477,6 +640,18 @@ flatpak_download_http_uri_once (FlatpakHttpSession *session, + curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *)data); + curl_easy_setopt (curl, CURLOPT_HEADERDATA, (void *)data); + ++ if (data->certificates) ++ { ++ if (data->certificates->ca_cert_file) ++ curl_easy_setopt (curl, CURLOPT_CAINFO, data->certificates->ca_cert_file); ++ ++ if (data->certificates->client_cert_file) ++ { ++ curl_easy_setopt (curl, CURLOPT_SSLCERT, data->certificates->client_cert_file); ++ curl_easy_setopt (curl, CURLOPT_SSLKEY, data->certificates->client_key_file); ++ } ++ } ++ + if (data->flags & FLATPAK_HTTP_FLAGS_HEAD) + curl_easy_setopt (curl, CURLOPT_NOBODY, 1L); + else +@@ -576,6 +751,73 @@ flatpak_download_http_uri_once (FlatpakHttpSession *session, + * Soup implementation * + ***********************************************************************/ + ++/* ++ * The implementation of /etc/containers/certs.d for Soup is made tricky ++ * because the CA certificate database in Soup is global to the session, ++ * but we share a single sesssion between different hosts that might ++ * need different custom CA certs based on what's configured in certs.d. ++ * ++ * So what we do is make the FlatpakSoupSession multiplex multiple ++ * SoupSessions, depending on the certificates we use. The most common ++ * case, is of course, a single session with no custom certificates. ++ */ ++ ++typedef struct ++{ ++ char *user_agent; ++ GHashTable *soup_sessions; ++} FlatpakSoupSession; ++ ++static guint ++certificates_hash(const FlatpakCertificates *certificates) ++{ ++ guint hash = 0; ++ ++ if (certificates && certificates->ca_cert_file) ++ hash |= 13 * g_str_hash (certificates->ca_cert_file); ++ if (certificates && certificates->client_cert_file) ++ hash |= 17 * g_str_hash (certificates->client_cert_file); ++ if (certificates && certificates->client_key_file) ++ hash |= 23 * g_str_hash (certificates->client_key_file); ++ ++ return hash; ++} ++ ++static gboolean ++certificates_equal (const FlatpakCertificates *a, ++ const FlatpakCertificates *b) ++{ ++ if (a && b) ++ { ++ return (g_strcmp0(a->ca_cert_file, b->ca_cert_file) == 0 && ++ g_strcmp0(a->client_cert_file, b->client_cert_file) == 0 && ++ g_strcmp0(a->client_key_file, b->client_key_file) == 0); ++ } ++ else ++ return a == b; ++} ++ ++static void ++certificates_free (FlatpakCertificates *certificates) ++{ ++ if (certificates) ++ flatpak_certificates_free (certificates); ++} ++ ++static FlatpakSoupSession * ++flatpak_create_soup_session (const char *user_agent) ++{ ++ FlatpakSoupSession *session = g_new0 (FlatpakSoupSession, 1); ++ ++ session->user_agent = g_strdup (user_agent); ++ session->soup_sessions = g_hash_table_new_full ((GHashFunc)certificates_hash, ++ (GEqualFunc)certificates_equal, ++ (GDestroyNotify)certificates_free, ++ (GDestroyNotify)g_object_unref); ++ ++ return session; ++} ++ + static gboolean + check_soup_transfer_error (SoupMessage *msg, GError **error) + { +@@ -768,18 +1010,114 @@ load_uri_callback (GObject *source_object, + load_uri_read_cb, data); + } + ++/* Inline class for providing a pre-configured client certificate; from ++ * libsoup/examples/get.c. By Colin Walters. ++ */ ++struct _FlatpakTlsInteraction ++{ ++ GTlsInteraction parent_instance; ++ ++ GTlsCertificate *cert; ++}; ++ ++struct _FlatpakTlsInteractionClass ++{ ++ GTlsInteractionClass parent_class; ++}; ++ ++G_DECLARE_FINAL_TYPE (FlatpakTlsInteraction, ++ flatpak_tls_interaction, ++ FLATPAK, TLS_INTERACTION, ++ GTlsInteraction) ++ ++G_DEFINE_TYPE (FlatpakTlsInteraction, ++ flatpak_tls_interaction, ++ G_TYPE_TLS_INTERACTION) ++ ++static GTlsInteractionResult ++flatpak_tls_interaction_request_certificate (GTlsInteraction *interaction, ++ GTlsConnection *connection, ++ GTlsCertificateRequestFlags flags, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ FlatpakTlsInteraction *self = FLATPAK_TLS_INTERACTION (interaction); ++ ++ g_tls_connection_set_certificate (connection, self->cert); ++ ++ return G_TLS_INTERACTION_HANDLED; ++} ++ ++static void ++flatpak_tls_interaction_finalize (GObject *object) ++{ ++ FlatpakTlsInteraction *self = FLATPAK_TLS_INTERACTION (object); ++ ++ g_clear_object (&self->cert); ++ ++ G_OBJECT_CLASS (flatpak_tls_interaction_parent_class)->finalize (object); ++} ++ ++static void ++flatpak_tls_interaction_init (FlatpakTlsInteraction *interaction) ++{ ++} ++ ++static void ++flatpak_tls_interaction_class_init (FlatpakTlsInteractionClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass); ++ ++ object_class->finalize = flatpak_tls_interaction_finalize; ++ ++ interaction_class->request_certificate = flatpak_tls_interaction_request_certificate; ++} ++ ++static FlatpakTlsInteraction * ++flatpak_tls_interaction_new (GTlsCertificate *cert) ++{ ++ FlatpakTlsInteraction *self = g_object_new (flatpak_tls_interaction_get_type (), NULL); ++ ++ self->cert = g_object_ref (cert); ++ ++ return self; ++} ++ + static SoupSession * +-flatpak_create_soup_session (const char *user_agent) ++get_soup_session (FlatpakSoupSession *session, FlatpakCertificates *certificates, GError **error) + { + SoupSession *soup_session; + const char *http_proxy; + +- soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, +- SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, ++ soup_session = g_hash_table_lookup (session->soup_sessions, certificates); ++ if (soup_session) ++ return soup_session; ++ ++ soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, session->user_agent, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_TIMEOUT, FLATPAK_HTTP_TIMEOUT_SECS, + SOUP_SESSION_IDLE_TIMEOUT, FLATPAK_HTTP_TIMEOUT_SECS, + NULL); ++ if (certificates && certificates->ca_cert_file) ++ g_object_set (soup_session, SOUP_SESSION_SSL_CA_FILE, certificates->ca_cert_file, NULL); ++ else ++ g_object_set (soup_session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, NULL); ++ ++ if (certificates && certificates->client_cert_file) ++ { ++ g_autoptr(GTlsCertificate) client_cert = NULL; ++ g_autoptr(GTlsInteraction) interaction = NULL; ++ ++ client_cert = g_tls_certificate_new_from_files (certificates->client_cert_file, ++ certificates->client_key_file, error); ++ if (!client_cert) ++ return NULL; ++ ++ interaction = G_TLS_INTERACTION (flatpak_tls_interaction_new (client_cert)); ++ g_object_set (soup_session, SOUP_SESSION_TLS_INTERACTION, interaction, NULL); ++ } ++ + http_proxy = g_getenv ("http_proxy"); + if (http_proxy) + { +@@ -793,6 +1131,10 @@ flatpak_create_soup_session (const char *user_agent) + if (g_getenv ("OSTREE_DEBUG_HTTP")) + soup_session_add_feature (soup_session, (SoupSessionFeature *) soup_logger_new (SOUP_LOGGER_LOG_BODY, 500)); + ++ g_hash_table_replace (session->soup_sessions, ++ certificates ? flatpak_certificates_copy (certificates) : NULL, ++ soup_session); ++ + return soup_session; + } + +@@ -805,9 +1147,11 @@ flatpak_create_http_session (const char *user_agent) + void + flatpak_http_session_free (FlatpakHttpSession* http_session) + { +- SoupSession *soup_session = (SoupSession *)http_session; ++ FlatpakSoupSession *session = (FlatpakSoupSession *)http_session; + +- g_object_unref (soup_session); ++ g_hash_table_destroy (session->soup_sessions); ++ g_free (session->user_agent); ++ g_free (session); + } + + static gboolean +@@ -816,12 +1160,16 @@ flatpak_download_http_uri_once (FlatpakHttpSession *http_session, + const char *uri, + GError **error) + { +- SoupSession *soup_session = (SoupSession *)http_session; ++ SoupSession *soup_session; + g_autoptr(SoupRequestHTTP) request = NULL; + SoupMessage *m; + + g_info ("Loading %s using libsoup", uri); + ++ soup_session = get_soup_session ((FlatpakSoupSession *)http_session, data->certificates, error); ++ if (!soup_session) ++ return FALSE; ++ + request = soup_session_request_http (soup_session, + (data->flags & FLATPAK_HTTP_FLAGS_HEAD) != 0 ? "HEAD" : "GET", + uri, error); +@@ -927,6 +1275,7 @@ flatpak_http_should_retry_request (const GError *error, + GBytes * + flatpak_load_uri_full (FlatpakHttpSession *http_session, + const char *uri, ++ FlatpakCertificates *certificates, + FlatpakHTTPFlags flags, + const char *auth, + const char *token, +@@ -965,6 +1314,7 @@ flatpak_load_uri_full (FlatpakHttpSession *http_session, + data.last_progress_time = g_get_monotonic_time (); + data.cancellable = cancellable; + data.flags = flags; ++ data.certificates = certificates; + data.auth = auth; + data.token = token; + +@@ -1018,7 +1368,7 @@ flatpak_load_uri (FlatpakHttpSession *http_session, + GCancellable *cancellable, + GError **error) + { +- return flatpak_load_uri_full (http_session, uri, flags, NULL, token, ++ return flatpak_load_uri_full (http_session, uri, NULL, flags, NULL, token, + progress, user_data, NULL, out_content_type, NULL, + cancellable, error); + } +@@ -1026,6 +1376,7 @@ flatpak_load_uri (FlatpakHttpSession *http_session, + gboolean + flatpak_download_http_uri (FlatpakHttpSession *http_session, + const char *uri, ++ FlatpakCertificates *certificates, + FlatpakHTTPFlags flags, + GOutputStream *out, + const char *token, +@@ -1047,6 +1398,7 @@ flatpak_download_http_uri (FlatpakHttpSession *http_session, + data.user_data = user_data; + data.last_progress_time = g_get_monotonic_time (); + data.cancellable = cancellable; ++ data.certificates = certificates; + data.flags = flags; + data.token = token; + +@@ -1384,6 +1736,7 @@ set_cache_http_data_from_headers (CacheHttpData *cache_data, + gboolean + flatpak_cache_http_uri (FlatpakHttpSession *http_session, + const char *uri, ++ FlatpakCertificates *certificates, + FlatpakHTTPFlags flags, + int dest_dfd, + const char *dest_subpath, +@@ -1444,6 +1797,7 @@ flatpak_cache_http_uri (FlatpakHttpSession *http_session, + data.last_progress_time = g_get_monotonic_time (); + data.cancellable = cancellable; + data.flags = flags; ++ data.certificates = certificates; + + data.cache_data = cache_data; + +diff --git a/doc/flatpak-remote.xml b/doc/flatpak-remote.xml +index 798f5c39..47a5a42a 100644 +--- a/doc/flatpak-remote.xml ++++ b/doc/flatpak-remote.xml +@@ -80,6 +80,12 @@ + is a Flatpak extension that indicates that the remote is not an ostree + repository, but is rather an URL to an index of OCI images that are stored + within a container image registry. ++ ++ ++ For OCI remotes, client and CA certificates are read from ++ /etc/containers/certs.d and ++ ~/.config/containers/certs.d as documented in ++ containers-certs.d5. + + + +diff --git a/tests/httpcache.c b/tests/httpcache.c +index a4550fb0..f6f9de64 100644 +--- a/tests/httpcache.c ++++ b/tests/httpcache.c +@@ -32,7 +32,7 @@ main (int argc, char *argv[]) + + + if (!flatpak_cache_http_uri (session, +- url, ++ url, NULL, + flags, + AT_FDCWD, dest, + NULL, NULL, NULL, &error)) +-- +2.47.1 + +From cf555f02fcc1cf410fdad7607ff83a6764864a14 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 17 Dec 2024 17:47:35 +0100 +Subject: [PATCH 09/09] tests: Add tests for https OCI remotes + +--- + tests/libtest.sh | 17 +++++-- + tests/test-matrix/meson.build | 6 ++- + tests/test-oci-registry.sh | 90 ++++++++++++++++++++++++++++++----- + tests/test-wrapper.sh | 6 +++ + tests/update-test-matrix | 2 +- + 5 files changed, 101 insertions(+), 20 deletions(-) + +diff --git a/tests/libtest.sh b/tests/libtest.sh +index d63810e1..7dad594f 100644 +--- a/tests/libtest.sh ++++ b/tests/libtest.sh +@@ -332,12 +332,16 @@ make_runtime () { + } + + httpd () { +- COMMAND=${1:-web-server.py} +- DIR=${2:-repos} ++ if [ $# -eq 0 ] ; then ++ set web-server.py repos ++ fi ++ ++ COMMAND=$1 ++ shift + + rm -f httpd-pipe + mkfifo httpd-pipe +- PYTHONUNBUFFERED=1 $(dirname $0)/$COMMAND "$DIR" 3> httpd-pipe 2>&1 | tee -a httpd-log >&2 & ++ PYTHONUNBUFFERED=1 $(dirname $0)/$COMMAND "$@" 3> httpd-pipe 2>&1 | tee -a httpd-log >&2 & + read < httpd-pipe + } + +@@ -589,10 +593,15 @@ skip_without_libsystemd () { + fi + } + ++FLATPAK_SYSTEM_CERTS_D=$(pwd)/certs.d ++export FLATPAK_SYSTEM_CERTS_D ++ + sed s#@testdir@#${test_builddir}# ${test_srcdir}/session.conf.in > session.conf + dbus-daemon --fork --config-file=session.conf --print-address=3 --print-pid=4 \ + 3> dbus-session-bus-address 4> dbus-session-bus-pid +-export DBUS_SESSION_BUS_ADDRESS="$(cat dbus-session-bus-address)" ++ ++DBUS_SESSION_BUS_ADDRESS="$(cat dbus-session-bus-address)" ++export DBUS_SESSION_BUS_ADDRESS + DBUS_SESSION_BUS_PID="$(cat dbus-session-bus-pid)" + + if ! /bin/kill -0 "$DBUS_SESSION_BUS_PID"; then +diff --git a/tests/test-matrix/meson.build b/tests/test-matrix/meson.build +index 15176048..fd0b5034 100644 +--- a/tests/test-matrix/meson.build ++++ b/tests/test-matrix/meson.build +@@ -17,8 +17,10 @@ wrapped_tests += {'name' : 'test-sideload@system.wrap', 'script' : 'test-sideloa + wrapped_tests += {'name' : 'test-bundle@user.wrap', 'script' : 'test-bundle.sh'} + wrapped_tests += {'name' : 'test-bundle@system.wrap', 'script' : 'test-bundle.sh'} + wrapped_tests += {'name' : 'test-bundle@system-norevokefs.wrap', 'script' : 'test-bundle.sh'} +-wrapped_tests += {'name' : 'test-oci-registry@user.wrap', 'script' : 'test-oci-registry.sh'} +-wrapped_tests += {'name' : 'test-oci-registry@system.wrap', 'script' : 'test-oci-registry.sh'} ++wrapped_tests += {'name' : 'test-oci-registry@user,http.wrap', 'script' : 'test-oci-registry.sh'} ++wrapped_tests += {'name' : 'test-oci-registry@user,https.wrap', 'script' : 'test-oci-registry.sh'} ++wrapped_tests += {'name' : 'test-oci-registry@system,http.wrap', 'script' : 'test-oci-registry.sh'} ++wrapped_tests += {'name' : 'test-oci-registry@system,https.wrap', 'script' : 'test-oci-registry.sh'} + wrapped_tests += {'name' : 'test-update-remote-configuration@newsummary.wrap', 'script' : 'test-update-remote-configuration.sh'} + wrapped_tests += {'name' : 'test-update-remote-configuration@oldsummary.wrap', 'script' : 'test-update-remote-configuration.sh'} + wrapped_tests += {'name' : 'test-update-portal@user.wrap', 'script' : 'test-update-portal.sh'} +diff --git a/tests/test-oci-registry.sh b/tests/test-oci-registry.sh +index 12036358..da234ded 100755 +--- a/tests/test-oci-registry.sh ++++ b/tests/test-oci-registry.sh +@@ -27,9 +27,73 @@ echo "1..14" + + # Start the fake registry server + +-httpd oci-registry-server.py --dir=. ++if [ x${USE_HTTPS} = xyes ] ; then ++ cat > openssl.config <&2 ++${FLATPAK} remote-add ${U} oci-registry "oci+${scheme}://127.0.0.1:${port}" >&2 + + # Check that the images we expect are listed + +@@ -144,7 +208,7 @@ fi + assert_has_file $base/oci/oci-registry.index.gz + assert_has_file $base/oci/oci-registry.summary + assert_has_dir $base/appstream/oci-registry +-${FLATPAK} remote-modify ${U} --url=http://127.0.0.1:${port} oci-registry >&2 ++${FLATPAK} remote-modify ${U} --url=${scheme}://127.0.0.1:${port} oci-registry >&2 + assert_not_has_file $base/oci/oci-registry.index.gz + assert_not_has_file $base/oci/oci-registry.summary + assert_not_has_dir $base/appstream/oci-registry +@@ -153,7 +217,7 @@ ok "change remote to non-OCI" + + # Change it back and refetch + +-${FLATPAK} remote-modify ${U} --url=oci+http://127.0.0.1:${port} oci-registry >&2 ++${FLATPAK} remote-modify ${U} --url=oci+${scheme}://127.0.0.1:${port} oci-registry >&2 + ${FLATPAK} update ${U} --appstream oci-registry >&2 + + # Delete the remote, check that everything was removed +@@ -177,7 +241,7 @@ ok "delete remote" + cat << EOF > runtime-repo.flatpakrepo + [Flatpak Repo] + Version=1 +-Url=oci+http://localhost:${port} ++Url=oci+${scheme}://localhost:${port} + Title=The OCI Title + EOF + +@@ -186,7 +250,7 @@ cat << EOF > org.test.Platform.flatpakref + Title=Test Platform + Name=org.test.Platform + Branch=master +-Url=oci+http://127.0.0.1:${port} ++Url=oci+${scheme}://127.0.0.1:${port} + IsRuntime=true + RuntimeRepo=file://$(pwd)/runtime-repo.flatpakrepo + EOF +@@ -214,12 +278,12 @@ ok "prune origin remote" + + # Install from a (non-OCI) bundle, check that the repo-url is respected + +-${FLATPAK} build-bundle --runtime --repo-url "oci+http://127.0.0.1:${port}" $FL_GPGARGS repos/oci org.test.Platform.flatpak org.test.Platform >&2 ++${FLATPAK} build-bundle --runtime --repo-url "oci+${scheme}://127.0.0.1:${port}" $FL_GPGARGS repos/oci org.test.Platform.flatpak org.test.Platform >&2 + + ${FLATPAK} ${U} install -y --bundle org.test.Platform.flatpak >&2 + + ${FLATPAK} remotes -d > remotes-list +-assert_file_has_content remotes-list "^platform-origin.*[ ]oci+http://127\.0\.0\.1:${port}" ++assert_file_has_content remotes-list "^platform-origin.*[ ]oci+${scheme}://127\.0\.0\.1:${port}" + + assert_has_file $base/oci/platform-origin.index.gz + +@@ -227,12 +291,12 @@ ok "install via bundle" + + # Install an app from a bundle + +-${FLATPAK} build-bundle --repo-url "oci+http://127.0.0.1:${port}" $FL_GPGARGS repos/oci org.test.Hello.flatpak org.test.Hello >&2 ++${FLATPAK} build-bundle --repo-url "oci+${scheme}://127.0.0.1:${port}" $FL_GPGARGS repos/oci org.test.Hello.flatpak org.test.Hello >&2 + + ${FLATPAK} ${U} install -y --bundle org.test.Hello.flatpak >&2 + + ${FLATPAK} remotes -d > remotes-list +-assert_file_has_content remotes-list "^hello-origin.*[ ]oci+http://127\.0\.0\.1:${port}" ++assert_file_has_content remotes-list "^hello-origin.*[ ]oci+${scheme}://127\.0\.0\.1:${port}" + + assert_has_file $base/oci/hello-origin.index.gz + +@@ -241,12 +305,12 @@ ok "app install via bundle" + # Install an updated app bundle with a different origin + + make_updated_app oci +-${FLATPAK} build-bundle --repo-url "http://127.0.0.1:${port}" $FL_GPGARGS repos/oci org.test.Hello.flatpak org.test.Hello >&2 ++${FLATPAK} build-bundle --repo-url "${scheme}://127.0.0.1:${port}" $FL_GPGARGS repos/oci org.test.Hello.flatpak org.test.Hello >&2 + + ${FLATPAK} ${U} install -y --bundle org.test.Hello.flatpak >&2 + + ${FLATPAK} remotes -d > remotes-list +-assert_file_has_content remotes-list "^hello-origin.*[ ]http://127\.0\.0\.1:${port}" ++assert_file_has_content remotes-list "^hello-origin.*[ ]${scheme}://127\.0\.0\.1:${port}" + + assert_not_has_file $base/oci/hello-origin.index.gz + +diff --git a/tests/test-wrapper.sh b/tests/test-wrapper.sh +index be624256..2dacc1bc 100755 +--- a/tests/test-wrapper.sh ++++ b/tests/test-wrapper.sh +@@ -30,6 +30,12 @@ for feature in $(echo $1 | sed "s/^.*@\(.*\).wrap/\1/" | tr "," "\n"); do + annotations) + export USE_OCI_ANNOTATIONS=yes + ;; ++ https) ++ export USE_HTTPS=yes ++ ;; ++ http) ++ export USE_HTTPS=no ++ ;; + *) + echo unsupported test feature $feature + exit 1 +diff --git a/tests/update-test-matrix b/tests/update-test-matrix +index 2aff6f00..3a51d0ba 100755 +--- a/tests/update-test-matrix ++++ b/tests/update-test-matrix +@@ -23,7 +23,7 @@ TEST_MATRIX_SOURCE=( + 'tests/test-extensions.sh' \ + 'tests/test-bundle.sh{user+system+system-norevokefs}' \ + 'tests/test-oci.sh' \ +- 'tests/test-oci-registry.sh{user+system}' \ ++ 'tests/test-oci-registry.sh{{user+system},{http+https}}' \ + 'tests/test-update-remote-configuration.sh{newsummary+oldsummary}' \ + 'tests/test-override.sh' \ + 'tests/test-update-portal.sh{user+system}' \ +-- +2.47.1 + diff --git a/flatpak-pass-token-to-flatpak-image-source-new-remote.patch b/flatpak-pass-token-to-flatpak-image-source-new-remote.patch new file mode 100644 index 0000000..2eb9686 --- /dev/null +++ b/flatpak-pass-token-to-flatpak-image-source-new-remote.patch @@ -0,0 +1,104 @@ +commit f0bc60dc0b34669e64d48e723a5e84c0b90b281d +Author: Owen W. Taylor +Date: Wed Feb 5 12:29:43 2025 -0500 + + Pass token to flatpak_image_source_new_remote() + + Since flatpak_image_source_new_remote() already tries to load files + from the registry, having a separate flatpak_image_source_set_token() + doesn't work - when the token is set, it's already too late to + be passed along with the initial requests. + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 26ec176f..3621dd3b 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -1225,12 +1225,10 @@ flatpak_remote_state_new_image_source (FlatpakRemoteState *self, + if (registry_uri == NULL) + return NULL; + +- image_source = flatpak_image_source_new_remote (registry_uri, oci_repository, digest, NULL, error); ++ image_source = flatpak_image_source_new_remote (registry_uri, oci_repository, digest, token, NULL, error); + if (image_source == NULL) + return NULL; + +- flatpak_image_source_set_token (image_source, token); +- + return g_steal_pointer (&image_source); + } + +@@ -6473,6 +6471,9 @@ flatpak_dir_mirror_oci (FlatpakDir *self, + else + image_source = flatpak_remote_state_fetch_image_source (state, self, ref, opt_rev, token, cancellable, error); + ++ if (!image_source) ++ return FALSE; ++ + flatpak_progress_start_oci_pull (progress); + + g_info ("Mirroring OCI image %s", flatpak_image_source_get_digest (image_source)); +@@ -6514,6 +6515,9 @@ flatpak_dir_pull_oci (FlatpakDir *self, + else + image_source = flatpak_remote_state_fetch_image_source (state, self, ref, opt_rev, token, cancellable, error); + ++ if (!image_source) ++ return FALSE; ++ + oci_digest = flatpak_image_source_get_digest (image_source); + + /* Short circuit if we've already got this commit */ +diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h +index 597a8174..5f9604d8 100644 +--- a/common/flatpak-image-source-private.h ++++ b/common/flatpak-image-source-private.h +@@ -45,14 +45,13 @@ FlatpakImageSource *flatpak_image_source_new_local (GFile *file, + FlatpakImageSource *flatpak_image_source_new_remote (const char *uri, + const char *oci_repository, + const char *digest, ++ const char *token, + GCancellable *cancellable, + GError **error); + FlatpakImageSource *flatpak_image_source_new_for_location (const char *location, + GCancellable *cancellable, + GError **error); + +-void flatpak_image_source_set_token (FlatpakImageSource *self, +- const char *token); + void flatpak_image_source_set_delta_url (FlatpakImageSource *self, + const char *delta_url); + +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +index a31f1084..1fc0eeb0 100644 +--- a/common/flatpak-image-source.c ++++ b/common/flatpak-image-source.c +@@ -180,6 +180,7 @@ FlatpakImageSource * + flatpak_image_source_new_remote (const char *uri, + const char *oci_repository, + const char *digest, ++ const char *token, + GCancellable *cancellable, + GError **error) + { +@@ -189,6 +190,8 @@ flatpak_image_source_new_remote (const char *uri, + if (!registry) + return NULL; + ++ flatpak_oci_registry_set_token (registry, token); ++ + return flatpak_image_source_new (registry, oci_repository, digest, cancellable, error); + } + +@@ -327,13 +330,6 @@ flatpak_image_source_new_for_location (const char *location, + } + } + +-void +-flatpak_image_source_set_token (FlatpakImageSource *self, +- const char *token) +-{ +- flatpak_oci_registry_set_token (self->registry, token); +-} +- + void + flatpak_image_source_set_delta_url (FlatpakImageSource *self, + const char *delta_url) diff --git a/flatpak-support-sideload-repositories-for-oci-remotes.patch b/flatpak-support-sideload-repositories-for-oci-remotes.patch new file mode 100644 index 0000000..114007f --- /dev/null +++ b/flatpak-support-sideload-repositories-for-oci-remotes.patch @@ -0,0 +1,1676 @@ +From 74c8d6ee0bf170fbd4accda2d422afde14712408 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 29 Oct 2024 17:16:08 -0400 +Subject: [PATCH 01/02] Don't return a VarRefInfoRef from + flatpak_remote_state_lookup_ref() + +The memory management of returning a VarRefInfoRef is tricky - it +points to data owned by the summary or the sideload repo. External +consumers were always retrieving a copy of the summary metadata, +so make the public function do that. +--- + common/flatpak-dir-private.h | 14 ++++----- + common/flatpak-dir.c | 61 +++++++++++++++++++++++++++++------- + common/flatpak-transaction.c | 14 +++------ + 3 files changed, 61 insertions(+), 28 deletions(-) + +diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h +index 539476b8..3456660b 100644 +--- a/common/flatpak-dir-private.h ++++ b/common/flatpak-dir-private.h +@@ -126,13 +126,13 @@ gboolean flatpak_remote_state_ensure_subsummary_all_arches (FlatpakRemoteState * + GError **error); + gboolean flatpak_remote_state_allow_ref (FlatpakRemoteState *self, + const char *ref); +-gboolean flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, +- const char *ref, +- char **out_checksum, +- guint64 *out_timestamp, +- VarRefInfoRef *out_info, +- GFile **out_sideload_path, +- GError **error); ++gboolean flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, ++ const char *ref, ++ char **out_checksum, ++ guint64 *out_timestamp, ++ GVariant **out_summary_metadata, ++ GFile **out_sideload_path, ++ GError **error); + GPtrArray *flatpak_remote_state_match_subrefs (FlatpakRemoteState *self, + FlatpakDecomposed *ref); + GFile *flatpak_remote_state_lookup_sideload_checksum (FlatpakRemoteState *self, +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 9f398252..99889aa5 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -755,14 +755,14 @@ get_summary_for_ref (FlatpakRemoteState *self, + /* Returns TRUE if the ref is found in the summary or cache. + * out_checksum and out_variant are only set when the ref is found. + */ +-gboolean +-flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, +- const char *ref, +- char **out_checksum, +- guint64 *out_timestamp, +- VarRefInfoRef *out_info, +- GFile **out_sideload_path, +- GError **error) ++static gboolean ++flatpak_remote_state_lookup_ref_internal (FlatpakRemoteState *self, ++ const char *ref, ++ char **out_checksum, ++ guint64 *out_timestamp, ++ VarRefInfoRef *out_info, ++ GFile **out_sideload_path, ++ GError **error) + { + if (!flatpak_remote_state_allow_ref (self, ref)) + { +@@ -822,12 +822,50 @@ flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, + return FALSE; + + if (out_sideload_path) +- *out_sideload_path = g_object_ref (ostree_repo_get_path (ss->repo)); ++ { ++ if (ss) ++ *out_sideload_path = g_object_ref (ostree_repo_get_path (ss->repo)); ++ else ++ *out_sideload_path = NULL; ++ } + } + + return TRUE; + } + ++/* Returns TRUE if the ref is found in the summary or cache. ++ * out parameters are only set if the ref is found. ++ */ ++gboolean ++flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, ++ const char *ref, ++ char **out_checksum, ++ guint64 *out_timestamp, ++ GVariant **out_summary_metadata, ++ GFile **out_sideload_path, ++ GError **error) ++{ ++ ++ if (out_summary_metadata) ++ { ++ VarRefInfoRef info; ++ ++ if (flatpak_remote_state_lookup_ref_internal (self, ref, out_checksum, out_timestamp, ++ &info, out_sideload_path, error)) ++ { ++ *out_summary_metadata = var_metadata_dup_to_gvariant (var_ref_info_get_metadata (info)); ++ return TRUE; ++ } ++ else ++ return FALSE; ++ } ++ else ++ { ++ return flatpak_remote_state_lookup_ref_internal (self, ref, out_checksum, out_timestamp, ++ NULL, out_sideload_path, error); ++ } ++} ++ + GPtrArray * + flatpak_remote_state_match_subrefs (FlatpakRemoteState *self, + FlatpakDecomposed *ref) +@@ -995,7 +1033,6 @@ flatpak_remote_state_load_data (FlatpakRemoteState *self, + /* Look up from sideload */ + g_autofree char *checksum = NULL; + guint64 timestamp; +- VarRefInfoRef info; + FlatpakSideloadState *ss = NULL; + g_autoptr(GVariant) commit_data = NULL; + g_autoptr(GVariant) commit_metadata = NULL; +@@ -1006,7 +1043,7 @@ flatpak_remote_state_load_data (FlatpakRemoteState *self, + /* Use sideload refs if any */ + + if (!flatpak_remote_state_resolve_sideloaded_ref (self, ref, &checksum, ×tamp, +- &info, &ss, error)) ++ NULL, &ss, error)) + return FALSE; + + if (!ostree_repo_load_commit (ss->repo, checksum, &commit_data, NULL, error)) +@@ -1095,7 +1132,7 @@ flatpak_remote_state_fetch_image_source (FlatpakRemoteState *self, + const char *delta_url = NULL; + + /* We extract the rev info from the latest, even if we don't use the latest digest, assuming refs don't move */ +- if (!flatpak_remote_state_lookup_ref (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) ++ if (!flatpak_remote_state_lookup_ref_internal (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) + return NULL; + + if (latest_rev == NULL) +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index 1aba1daa..2dd35f53 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -3482,7 +3482,6 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, + guint64 installed_size = 0; + const char *metadata = NULL; + VarMetadataRef sparse_cache; +- VarRefInfoRef info; + g_autofree char *summary_checksum = NULL; + + /* Ref has to match the actual commit in the summary */ +@@ -3499,9 +3498,8 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, + + metadata_bytes = g_bytes_new (metadata, strlen (metadata)); + +- if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), +- NULL, NULL, &info, NULL, NULL)) +- op->summary_metadata = var_metadata_dup_to_gvariant (var_ref_info_get_metadata (info)); ++ flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), ++ NULL, NULL, &op->summary_metadata, NULL, NULL); + + op->installed_size = installed_size; + op->download_size = download_size; +@@ -3704,13 +3702,11 @@ resolve_ops (FlatpakTransaction *self, + * Note, we don't have a token here, so this will not work for authenticated apps. + * We handle this by catching the 401 http status and retrying. */ + g_autoptr(GVariant) commit_data = NULL; +- VarRefInfoRef ref_info; + + /* OCI needs this to get the oci repository for the ref to request the token, so lets always set it here */ +- if (op->summary_metadata == NULL && +- flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), +- NULL, NULL, &ref_info, NULL, NULL)) +- op->summary_metadata = var_metadata_dup_to_gvariant (var_ref_info_get_metadata (ref_info)); ++ if (op->summary_metadata == NULL) ++ flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), ++ NULL, NULL, &op->summary_metadata, NULL, NULL); + + commit_data = flatpak_remote_state_load_ref_commit (state, priv->dir, + flatpak_decomposed_get_ref (op->ref), +-- +2.47.1 + +From 751944405e027daf968741a3fbeec188ccf07ab1 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Tue, 29 Oct 2024 17:19:38 -0400 +Subject: [PATCH 02/02] Support sideload repositories for OCI remotes + +For OCI remotes, the existing sideload repository system doesn't +work: identity for OCI remotes is done by manifest digest (disguised +as a fake commit ID internally), instead of by ostree commit, so +we have no way of knowing whether a sideloaded image matches the +summary. + +Allow specifying a new form of sideload repository with: + + --sideload-repo=oci: + +The desired use case for this is preinstalling Flatpaks during OS +install, and for this, binding the entire repository to a single +collection ID is both inconvenient and not useful, so OCI sideload +repostories don't have a defined collection ID - they just apply to +all OCI remotes. (And, because of this, they are restricted to +the command line.) + +This is implemented in a straightforward way by adding, throughout +the FlatpakTransaction and FlatpakDir code, in parallel, +to GFile *sideload_path, FlatpakImageSource *image_source. + +The new FlatpakImageCollection type represents a set of +FlatpakImageSource loaded from the image sideload repository. +--- + app/flatpak-builtins-install.c | 17 +- + app/flatpak-builtins-remote-info.c | 2 +- + app/flatpak-builtins-update.c | 4 +- + app/flatpak-builtins-utils.c | 33 ++ + app/flatpak-builtins-utils.h | 6 + + common/flatpak-common-types-private.h | 13 +- + common/flatpak-dir-private.h | 12 +- + common/flatpak-dir.c | 362 +++++++++++++++++----- + common/flatpak-image-collection-private.h | 50 +++ + common/flatpak-image-collection.c | 154 +++++++++ + common/flatpak-image-source-private.h | 7 + + common/flatpak-image-source.c | 25 +- + common/flatpak-transaction.c | 74 ++++- + common/flatpak-transaction.h | 5 + + common/meson.build | 1 + + doc/flatpak-install.xml | 9 + + doc/flatpak-update.xml | 9 + + doc/reference/meson.build | 1 + + 18 files changed, 667 insertions(+), 117 deletions(-) + create mode 100644 common/flatpak-image-collection-private.h + create mode 100644 common/flatpak-image-collection.c + +diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c +index e1f7f312..80a43152 100644 +--- a/app/flatpak-builtins-install.c ++++ b/app/flatpak-builtins-install.c +@@ -132,8 +132,9 @@ read_gpg_data (GCancellable *cancellable, + } + + static FlatpakTransaction * +-create_install_transaction (FlatpakDir *dir, +- GError **error) ++create_install_transaction (FlatpakDir *dir, ++ GCancellable *cancellable, ++ GError **error) + { + g_autoptr(FlatpakTransaction) transaction = NULL; + +@@ -155,8 +156,8 @@ create_install_transaction (FlatpakDir *dir, + 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 (!setup_sideload_repositories (transaction, opt_sideload_repos, cancellable, error)) ++ return FALSE; + + return g_steal_pointer (&transaction); + } +@@ -194,7 +195,7 @@ install_bundle (FlatpakDir *dir, + return FALSE; + } + +- transaction = create_install_transaction (dir, error); ++ transaction = create_install_transaction (dir, cancellable, error); + if (transaction == NULL) + return FALSE; + +@@ -257,7 +258,7 @@ install_from (FlatpakDir *dir, + file_data = g_bytes_new_take (g_steal_pointer (&data), data_len); + } + +- transaction = create_install_transaction (dir, error); ++ transaction = create_install_transaction (dir, cancellable, error); + if (transaction == NULL) + return FALSE; + +@@ -303,7 +304,7 @@ install_image (FlatpakDir *dir, + return FALSE; + } + +- transaction = create_install_transaction (dir, error); ++ transaction = create_install_transaction (dir, cancellable, error); + if (transaction == NULL) + return FALSE; + +@@ -537,7 +538,7 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro + + default_branch = flatpak_dir_get_remote_default_branch (dir, remote); + +- transaction = create_install_transaction (dir, error); ++ transaction = create_install_transaction (dir, cancellable, error); + if (transaction == NULL) + return FALSE; + +diff --git a/app/flatpak-builtins-remote-info.c b/app/flatpak-builtins-remote-info.c +index 8dee2af7..89fd5c83 100644 +--- a/app/flatpak-builtins-remote-info.c ++++ b/app/flatpak-builtins-remote-info.c +@@ -151,7 +151,7 @@ flatpak_builtin_remote_info (int argc, char **argv, GCancellable *cancellable, G + if (opt_commit) + commit = g_strdup (opt_commit); + else if (!flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (ref), +- &commit, NULL, NULL, NULL, error)) ++ &commit, NULL, NULL, NULL, NULL, error)) + { + g_assert (error == NULL || *error != NULL); + return FALSE; +diff --git a/app/flatpak-builtins-update.c b/app/flatpak-builtins-update.c +index b5184a33..5465dc8c 100644 +--- a/app/flatpak-builtins-update.c ++++ b/app/flatpak-builtins-update.c +@@ -150,8 +150,8 @@ flatpak_builtin_update (int argc, + if (opt_arch) + flatpak_transaction_set_default_arch (transaction, opt_arch); + +- for (i = 0; opt_sideload_repos != NULL && opt_sideload_repos[i] != NULL; i++) +- flatpak_transaction_add_sideload_repo (transaction, opt_sideload_repos[i]); ++ if (!setup_sideload_repositories (transaction, opt_sideload_repos, cancellable, error)) ++ return FALSE; + + g_ptr_array_insert (transactions, 0, transaction); + } +diff --git a/app/flatpak-builtins-utils.c b/app/flatpak-builtins-utils.c +index e539a8c2..01c2cd05 100644 +--- a/app/flatpak-builtins-utils.c ++++ b/app/flatpak-builtins-utils.c +@@ -1450,3 +1450,36 @@ ensure_remote_state_all_arches (FlatpakDir *dir, + + return TRUE; + } ++ ++gboolean ++setup_sideload_repositories (FlatpakTransaction *transaction, ++ char **opt_sideload_repos, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ for (int i = 0; opt_sideload_repos != NULL && opt_sideload_repos[i] != NULL; i++) ++ { ++ const char *repo = opt_sideload_repos[i]; ++ if (g_str_has_prefix (repo, "oci:") || g_str_has_prefix (repo, "oci-archive:")) ++ { ++ if (!flatpak_transaction_add_sideload_image_collection (transaction, repo, cancellable, error)) ++ return FALSE; ++ } ++ else if (g_str_has_prefix (repo, "file:")) ++ { ++ g_autoptr(GFile) file = g_file_new_for_uri (repo); ++ const char *path = flatpak_file_get_path_cached (file); ++ flatpak_transaction_add_sideload_repo (transaction, path); ++ } ++ else ++ { ++ if (g_regex_match_simple ("^[A-Za-z][A-Za-z0-9+.-]*:", repo, ++ G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT)) ++ return flatpak_fail (error, _("Unknown scheme in sideload location %s"), repo); ++ ++ flatpak_transaction_add_sideload_repo (transaction, repo); ++ } ++ } ++ ++ return TRUE; ++} +diff --git a/app/flatpak-builtins-utils.h b/app/flatpak-builtins-utils.h +index 257a6b95..cd24abf7 100644 +--- a/app/flatpak-builtins-utils.h ++++ b/app/flatpak-builtins-utils.h +@@ -27,6 +27,7 @@ + #include "flatpak-utils-private.h" + #include "flatpak-dir-private.h" + #include "flatpak-permission-dbus-generated.h" ++#include "flatpak-transaction.h" + + /* AS_CHECK_VERSION was introduced in 0.14.0; we still support 0.12.0, so + * behave as though versions without this macro are arbitrarily old */ +@@ -209,4 +210,9 @@ gboolean ensure_remote_state_all_arches (FlatpakDir *dir, + GCancellable *cancellable, + GError **error); + ++gboolean setup_sideload_repositories (FlatpakTransaction *transaction, ++ char **opt_sideload_repos, ++ GCancellable *cancellable, ++ GError **error); ++ + #endif /* __FLATPAK_BUILTINS_UTILS_H__ */ +diff --git a/common/flatpak-common-types-private.h b/common/flatpak-common-types-private.h +index d7f3913b..6d41f12c 100644 +--- a/common/flatpak-common-types-private.h ++++ b/common/flatpak-common-types-private.h +@@ -51,11 +51,12 @@ typedef enum { + FLATPAK_RUN_FLAG_PARENT_SHARE_PIDS = (1 << 21), + } FlatpakRunFlags; + +-typedef struct FlatpakDir FlatpakDir; +-typedef struct FlatpakDeploy FlatpakDeploy; +-typedef struct _FlatpakImageSource FlatpakImageSource; +-typedef struct FlatpakOciRegistry FlatpakOciRegistry; +-typedef struct _FlatpakOciManifest FlatpakOciManifest; +-typedef struct _FlatpakOciImage FlatpakOciImage; ++typedef struct FlatpakDir FlatpakDir; ++typedef struct FlatpakDeploy FlatpakDeploy; ++typedef struct FlatpakImageCollection FlatpakImageCollection; ++typedef struct _FlatpakImageSource FlatpakImageSource; ++typedef struct FlatpakOciRegistry FlatpakOciRegistry; ++typedef struct _FlatpakOciManifest FlatpakOciManifest; ++typedef struct _FlatpakOciImage FlatpakOciImage; + + #endif /* __FLATPAK_COMMON_TYPES_H__ */ +diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h +index 3456660b..ffcff5ff 100644 +--- a/common/flatpak-dir-private.h ++++ b/common/flatpak-dir-private.h +@@ -87,6 +87,7 @@ typedef struct + { + char *remote_name; + gboolean is_file_uri; ++ gboolean is_oci; + char *collection_id; + + /* New format summary */ +@@ -107,6 +108,7 @@ typedef struct + int refcount; + gint32 default_token_type; + GPtrArray *sideload_repos; ++ GPtrArray *sideload_image_collections; + } FlatpakRemoteState; + + FlatpakRemoteState *flatpak_remote_state_ref (FlatpakRemoteState *remote_state); +@@ -132,11 +134,14 @@ gboolean flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, + guint64 *out_timestamp, + GVariant **out_summary_metadata, + GFile **out_sideload_path, ++ FlatpakImageSource **out_image_Source, + GError **error); + GPtrArray *flatpak_remote_state_match_subrefs (FlatpakRemoteState *self, + FlatpakDecomposed *ref); +-GFile *flatpak_remote_state_lookup_sideload_checksum (FlatpakRemoteState *self, +- char *checksum); ++void flatpak_remote_state_lookup_sideload_checksum (FlatpakRemoteState *self, ++ char *checksum, ++ GFile **out_sideload_path, ++ FlatpakImageSource **out_image_source); + gboolean flatpak_remote_state_lookup_cache (FlatpakRemoteState *self, + const char *ref, + guint64 *download_size, +@@ -163,6 +168,8 @@ GVariant *flatpak_remote_state_load_ref_commit (FlatpakRemoteState *self, + GError **error); + void flatpak_remote_state_add_sideload_dir (FlatpakRemoteState *self, + GFile *path); ++void flatpak_remote_state_add_sideload_image_collection (FlatpakRemoteState *self, ++ FlatpakImageCollection *image_collection); + + + G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDir, g_object_unref) +@@ -982,6 +989,7 @@ gboolean flatpak_dir_find_latest_rev (Fla + char **out_rev, + guint64 *out_timestamp, + GFile **out_sideload_path, ++ FlatpakImageSource **out_image_source, + GCancellable *cancellable, + GError **error); + FlatpakDecomposed * flatpak_dir_get_remote_auto_install_authenticator_ref (FlatpakDir *self, +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 99889aa5..1c304525 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -52,6 +52,7 @@ + #include "flatpak-dir-utils-private.h" + #include "flatpak-error.h" + #include "flatpak-locale-utils-private.h" ++#include "flatpak-image-collection-private.h" + #include "flatpak-image-source-private.h" + #include "flatpak-oci-registry-private.h" + #include "flatpak-ref.h" +@@ -375,6 +376,7 @@ flatpak_remote_state_new (void) + + state->refcount = 1; + state->sideload_repos = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_sideload_state_free); ++ state->sideload_image_collections = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); + state->subsummaries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)variant_maybe_unref); + return state; + } +@@ -408,6 +410,7 @@ flatpak_remote_state_unref (FlatpakRemoteState *remote_state) + g_clear_pointer (&remote_state->allow_refs, g_regex_unref); + g_clear_pointer (&remote_state->deny_refs, g_regex_unref); + g_clear_pointer (&remote_state->sideload_repos, g_ptr_array_unref); ++ g_clear_pointer (&remote_state->sideload_image_collections, g_ptr_array_unref); + + g_free (remote_state); + } +@@ -470,6 +473,13 @@ flatpak_remote_state_add_sideload_repo (FlatpakRemoteState *self, + } + } + ++void ++flatpak_remote_state_add_sideload_image_collection (FlatpakRemoteState *self, ++ FlatpakImageCollection *image_collection) ++{ ++ g_ptr_array_add (self->sideload_image_collections, g_object_ref (image_collection)); ++} ++ + static void add_sideload_subdirs (GPtrArray *res, + GFile *parent, + gboolean recurse); +@@ -656,31 +666,57 @@ get_timestamp_from_ref_info (VarRefInfoRef info) + } + + +-GFile * +-flatpak_remote_state_lookup_sideload_checksum (FlatpakRemoteState *self, +- char *checksum) ++void ++flatpak_remote_state_lookup_sideload_checksum (FlatpakRemoteState *self, ++ char *checksum, ++ GFile **out_sideload_path, ++ FlatpakImageSource **out_image_source) + { +- for (int i = 0; i < self->sideload_repos->len; i++) ++ if (out_sideload_path) ++ *out_sideload_path = NULL; ++ if (out_image_source) ++ *out_image_source = NULL; ++ ++ if (self->is_oci) + { +- FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i); +- OstreeRepoCommitState commit_state; ++ if (out_image_source) ++ { ++ const char *digest = g_strconcat ("sha256:", checksum, NULL); + +- if (ostree_repo_load_commit (ss->repo, checksum, NULL, &commit_state, NULL) && +- commit_state == OSTREE_REPO_COMMIT_STATE_NORMAL) +- return g_object_ref (ostree_repo_get_path (ss->repo)); ++ for (int i = 0; i < self->sideload_image_collections->len; i++) ++ { ++ FlatpakImageCollection *collection = g_ptr_array_index (self->sideload_image_collections, i); ++ g_autoptr(FlatpakImageSource) image_source = flatpak_image_collection_lookup_digest (collection, digest); ++ if (image_source) ++ *out_image_source = g_steal_pointer (&image_source); ++ } ++ } + } ++ else if (out_sideload_path) ++ { ++ for (int i = 0; i < self->sideload_repos->len; i++) ++ { ++ FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i); ++ OstreeRepoCommitState commit_state; + +- return NULL; ++ if (ostree_repo_load_commit (ss->repo, checksum, NULL, &commit_state, NULL) && ++ commit_state == OSTREE_REPO_COMMIT_STATE_NORMAL) ++ { ++ *out_sideload_path = g_object_ref (ostree_repo_get_path (ss->repo)); ++ return; ++ } ++ } ++ } + } + + static gboolean +-flatpak_remote_state_resolve_sideloaded_ref (FlatpakRemoteState *self, +- const char *ref, +- char **out_checksum, +- guint64 *out_timestamp, +- VarRefInfoRef *out_info, +- FlatpakSideloadState **out_sideload_state, +- GError **error) ++flatpak_remote_state_resolve_sideloaded_ref_repos (FlatpakRemoteState *self, ++ const char *ref, ++ char **out_checksum, ++ guint64 *out_timestamp, ++ VarRefInfoRef *out_info, ++ FlatpakSideloadState **out_sideload_state, ++ GError **error) + { + g_autofree char *latest_checksum = NULL; + guint64 latest_timestamp = 0; +@@ -725,6 +761,73 @@ flatpak_remote_state_resolve_sideloaded_ref (FlatpakRemoteState *self, + return TRUE; + } + ++static gboolean ++flatpak_remote_state_resolve_sideloaded_ref_images (FlatpakRemoteState *self, ++ const char *ref, ++ char **out_checksum, ++ guint64 *out_timestamp, ++ VarRefInfoRef *out_info, ++ FlatpakImageSource **out_image_source, ++ GError **error) ++{ ++ g_autoptr(FlatpakImageSource) image_source = NULL; ++ ++ for (int i = 0; i < self->sideload_image_collections->len; i++) ++ { ++ FlatpakImageCollection *collection = g_ptr_array_index (self->sideload_image_collections, i); ++ image_source = flatpak_image_collection_lookup_ref (collection, ref); ++ if (image_source) ++ break; ++ } ++ ++ if (image_source) ++ { ++ if (out_checksum) ++ { ++ const char *digest = flatpak_image_source_get_digest (image_source); ++ g_assert (g_str_has_prefix (digest, "sha256:")); ++ *out_checksum = g_strdup (digest + 7); ++ } ++ ++ if (out_timestamp) ++ *out_timestamp = flatpak_image_source_get_commit_timestamp (image_source); ++ } ++ ++ if (image_source == NULL) ++ return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, ++ _("No such ref '%s' in remote %s"), ++ ref, self->remote_name); ++ ++ if (out_image_source) ++ *out_image_source = g_steal_pointer (&image_source); ++ ++ return TRUE; ++} ++ ++static gboolean ++flatpak_remote_state_resolve_sideloaded_ref (FlatpakRemoteState *self, ++ const char *ref, ++ char **out_checksum, ++ guint64 *out_timestamp, ++ VarRefInfoRef *out_info, ++ FlatpakSideloadState **out_sideload_state, ++ FlatpakImageSource **out_image_source, ++ GError **error) ++{ ++ if (out_sideload_state) ++ *out_sideload_state = NULL; ++ if (out_image_source) ++ *out_image_source = NULL; ++ ++ if (self->is_oci) ++ return flatpak_remote_state_resolve_sideloaded_ref_images (self, ref, out_checksum, out_timestamp, out_info, ++ out_image_source, error); ++ else ++ return flatpak_remote_state_resolve_sideloaded_ref_repos (self, ref, out_checksum, out_timestamp, out_info, ++ out_sideload_state, error); ++ ++} ++ + static GVariant * + get_summary_for_ref (FlatpakRemoteState *self, + const char *ref) +@@ -754,6 +857,9 @@ get_summary_for_ref (FlatpakRemoteState *self, + + /* Returns TRUE if the ref is found in the summary or cache. + * out_checksum and out_variant are only set when the ref is found. ++ * ++ * NOTE: The _internal() variant has the odd constraint that *out_info is only ++ * valid if *out_image_source is NULL. + */ + static gboolean + flatpak_remote_state_lookup_ref_internal (FlatpakRemoteState *self, +@@ -762,8 +868,11 @@ flatpak_remote_state_lookup_ref_internal (FlatpakRemoteState *self, + guint64 *out_timestamp, + VarRefInfoRef *out_info, + GFile **out_sideload_path, ++ FlatpakImageSource **out_image_source, + GError **error) + { ++ g_assert (out_info == NULL || out_image_source != NULL); ++ + if (!flatpak_remote_state_allow_ref (self, ref)) + { + return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, +@@ -786,26 +895,7 @@ flatpak_remote_state_lookup_ref_internal (FlatpakRemoteState *self, + ref, self->remote_name); + + /* Even if its available in the summary we want to install it from a sideload repo if available */ +- +- if (out_sideload_path) +- { +- g_autoptr(GFile) found_sideload_path = NULL; +- +- for (int i = 0; i < self->sideload_repos->len; i++) +- { +- FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i); +- OstreeRepoCommitState commit_state; +- +- if (ostree_repo_load_commit (ss->repo, checksum, NULL, &commit_state, NULL) && +- commit_state == OSTREE_REPO_COMMIT_STATE_NORMAL) +- { +- found_sideload_path = g_object_ref (ostree_repo_get_path (ss->repo)); +- break; +- } +- } +- +- *out_sideload_path = g_steal_pointer (&found_sideload_path); +- } ++ flatpak_remote_state_lookup_sideload_checksum (self, checksum, out_sideload_path, out_image_source); + + if (out_info) + *out_info = info; +@@ -818,7 +908,7 @@ flatpak_remote_state_lookup_ref_internal (FlatpakRemoteState *self, + { + FlatpakSideloadState *ss = NULL; + +- if (!flatpak_remote_state_resolve_sideloaded_ref (self, ref, out_checksum, out_timestamp, out_info, &ss, error)) ++ if (!flatpak_remote_state_resolve_sideloaded_ref (self, ref, out_checksum, out_timestamp, out_info, &ss, out_image_source, error)) + return FALSE; + + if (out_sideload_path) +@@ -843,17 +933,26 @@ flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, + guint64 *out_timestamp, + GVariant **out_summary_metadata, + GFile **out_sideload_path, ++ FlatpakImageSource **out_image_source, + GError **error) + { + + if (out_summary_metadata) + { ++ g_autoptr(FlatpakImageSource) local_image_source; + VarRefInfoRef info; + + if (flatpak_remote_state_lookup_ref_internal (self, ref, out_checksum, out_timestamp, +- &info, out_sideload_path, error)) ++ &info, out_sideload_path, &local_image_source, error)) + { +- *out_summary_metadata = var_metadata_dup_to_gvariant (var_ref_info_get_metadata (info)); ++ if (local_image_source) ++ *out_summary_metadata = flatpak_image_source_make_summary_metadata (local_image_source); ++ else ++ *out_summary_metadata = var_metadata_dup_to_gvariant (var_ref_info_get_metadata (info)); ++ ++ if (out_image_source) ++ *out_image_source = g_steal_pointer (&local_image_source); ++ + return TRUE; + } + else +@@ -862,7 +961,7 @@ flatpak_remote_state_lookup_ref (FlatpakRemoteState *self, + else + { + return flatpak_remote_state_lookup_ref_internal (self, ref, out_checksum, out_timestamp, +- NULL, out_sideload_path, error); ++ NULL, out_sideload_path, out_image_source, error); + } + } + +@@ -1034,6 +1133,7 @@ flatpak_remote_state_load_data (FlatpakRemoteState *self, + g_autofree char *checksum = NULL; + guint64 timestamp; + FlatpakSideloadState *ss = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + g_autoptr(GVariant) commit_data = NULL; + g_autoptr(GVariant) commit_metadata = NULL; + const char *xa_metadata = NULL; +@@ -1043,13 +1143,23 @@ flatpak_remote_state_load_data (FlatpakRemoteState *self, + /* Use sideload refs if any */ + + if (!flatpak_remote_state_resolve_sideloaded_ref (self, ref, &checksum, ×tamp, +- NULL, &ss, error)) ++ NULL, &ss, &image_source, error)) + return FALSE; + +- if (!ostree_repo_load_commit (ss->repo, checksum, &commit_data, NULL, error)) +- return FALSE; ++ if (image_source) ++ { ++ g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ flatpak_image_source_build_commit_metadata (image_source, metadata_builder); ++ commit_metadata = g_variant_builder_end (metadata_builder); ++ } ++ else ++ { ++ if (!ostree_repo_load_commit (ss->repo, checksum, &commit_data, NULL, error)) ++ return FALSE; ++ ++ commit_metadata = g_variant_get_child_value (commit_data, 0); ++ } + +- commit_metadata = g_variant_get_child_value (commit_data, 0); + g_variant_lookup (commit_metadata, "xa.metadata", "&s", &xa_metadata); + if (xa_metadata == NULL) + return flatpak_fail (error, "No xa.metadata in sideload commit %s ref %s", checksum, ref); +@@ -1124,15 +1234,11 @@ flatpak_remote_state_fetch_image_source (FlatpakRemoteState *self, + GError **error) + { + g_autoptr(FlatpakImageSource) image_source = NULL; +- g_autofree char *oci_digest = NULL; + g_autofree char *latest_rev = NULL; + VarRefInfoRef latest_rev_info; +- VarMetadataRef metadata; +- const char *oci_repository = NULL; +- const char *delta_url = NULL; + + /* We extract the rev info from the latest, even if we don't use the latest digest, assuming refs don't move */ +- if (!flatpak_remote_state_lookup_ref_internal (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, error)) ++ if (!flatpak_remote_state_lookup_ref_internal (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, &image_source, error)) + return NULL; + + if (latest_rev == NULL) +@@ -1143,23 +1249,31 @@ flatpak_remote_state_fetch_image_source (FlatpakRemoteState *self, + return NULL; + } + +- metadata = var_ref_info_get_metadata (latest_rev_info); +- oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); +- delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); ++ if (image_source == NULL) ++ { ++ VarMetadataRef metadata; ++ g_autofree char *oci_digest = NULL; ++ const char *oci_repository = NULL; ++ const char *delta_url = NULL; + +- oci_digest = g_strconcat ("sha256:", opt_rev ? opt_rev : latest_rev, NULL); ++ metadata = var_ref_info_get_metadata (latest_rev_info); ++ oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL); ++ delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL); + +- image_source = flatpak_remote_state_new_image_source (self, oci_repository, oci_digest, token, cancellable, error); +- if (image_source == NULL) +- return NULL; ++ oci_digest = g_strconcat ("sha256:", opt_rev ? opt_rev : latest_rev, NULL); + +- if (g_strcmp0 (flatpak_image_source_get_ref (image_source), ref) != 0) +- { +- flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit has no requested ref ‘%s’ in ref binding metadata"), ref); +- return NULL; +- } ++ image_source = flatpak_remote_state_new_image_source (self, oci_repository, oci_digest, token, cancellable, error); ++ if (image_source == NULL) ++ return NULL; ++ ++ if (g_strcmp0 (flatpak_image_source_get_ref (image_source), ref) != 0) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit has no requested ref ‘%s’ in ref binding metadata"), ref); ++ return NULL; ++ } + +- flatpak_image_source_set_delta_url (image_source, delta_url); ++ flatpak_image_source_set_delta_url (image_source, delta_url); ++ } + + return g_steal_pointer (&image_source); + } +@@ -1299,7 +1413,7 @@ flatpak_remote_state_load_ref_commit (FlatpakRemoteState *self, + + if (opt_commit == NULL) + { +- if (!flatpak_remote_state_lookup_ref (self, ref, &commit, NULL, NULL, NULL, error)) ++ if (!flatpak_remote_state_lookup_ref (self, ref, &commit, NULL, NULL, NULL, NULL, error)) + return NULL; + + if (commit == NULL) +@@ -1317,12 +1431,26 @@ flatpak_remote_state_load_ref_commit (FlatpakRemoteState *self, + if (ostree_repo_load_commit (dir->repo, commit, &commit_data, NULL, NULL)) + goto out; + +- for (int i = 0; i < self->sideload_repos->len; i++) ++ if (self->is_oci) + { +- FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i); ++ g_autoptr(FlatpakImageSource) image_source; + +- if (ostree_repo_load_commit (ss->repo, commit, &commit_data, NULL, NULL)) +- goto out; ++ flatpak_remote_state_lookup_sideload_checksum (self, commit, NULL, &image_source); ++ if (image_source) ++ { ++ commit_data = flatpak_image_source_make_fake_commit (image_source); ++ goto out; ++ } ++ } ++ else ++ { ++ for (int i = 0; i < self->sideload_repos->len; i++) ++ { ++ FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i); ++ ++ if (ostree_repo_load_commit (ss->repo, commit, &commit_data, NULL, NULL)) ++ goto out; ++ } + } + + if (flatpak_dir_get_remote_oci (dir, self->remote_name)) +@@ -5034,6 +5162,7 @@ flatpak_dir_find_latest_rev (FlatpakDir *self, + char **out_rev, + guint64 *out_timestamp, + GFile **out_sideload_path, ++ FlatpakImageSource **out_image_source, + GCancellable *cancellable, + GError **error) + { +@@ -5041,7 +5170,7 @@ flatpak_dir_find_latest_rev (FlatpakDir *self, + + g_return_val_if_fail (out_rev != NULL, FALSE); + +- if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, out_timestamp, NULL, out_sideload_path, error)) ++ if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, out_timestamp, NULL, out_sideload_path, out_image_source, error)) + return FALSE; + if (latest_rev == NULL) + return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, +@@ -5286,10 +5415,10 @@ flatpak_dir_update_appstream (FlatpakDir *self, + used_branch = new_branch; + if (!is_oci) + { +- if (!flatpak_dir_find_latest_rev (self, state, used_branch, NULL, &appstream_commit, NULL, &appstream_sideload_path, cancellable, &first_error)) ++ if (!flatpak_dir_find_latest_rev (self, state, used_branch, NULL, &appstream_commit, NULL, &appstream_sideload_path, NULL, cancellable, &first_error)) + { + used_branch = old_branch; +- if (!flatpak_dir_find_latest_rev (self, state, used_branch, NULL, &appstream_commit, NULL, &appstream_sideload_path, cancellable, &second_error)) ++ if (!flatpak_dir_find_latest_rev (self, state, used_branch, NULL, &appstream_commit, NULL, &appstream_sideload_path, NULL, cancellable, &second_error)) + { + g_prefix_error (&first_error, "Error updating appstream2: "); + g_prefix_error (&second_error, "Error updating appstream: "); +@@ -6095,7 +6224,7 @@ flatpak_dir_pull (FlatpakDir *self, + { + rev = g_strdup (opt_rev); + } +- else if (!flatpak_remote_state_lookup_ref (state, ref, &rev, NULL, NULL, NULL, error)) ++ else if (!flatpak_remote_state_lookup_ref (state, ref, &rev, NULL, NULL, NULL, NULL, error)) + { + g_assert (error == NULL || *error != NULL); + return FALSE; +@@ -10489,7 +10618,7 @@ flatpak_dir_check_for_update (FlatpakDir *self, + else + { + if (!flatpak_dir_find_latest_rev (self, state, flatpak_decomposed_get_ref (ref), checksum_or_latest, &latest_rev, +- NULL, NULL, cancellable, error)) ++ NULL, NULL, NULL, cancellable, error)) + return NULL; + } + +@@ -12771,6 +12900,7 @@ _flatpak_dir_get_remote_state (FlatpakDir *self, + if (!ostree_repo_remote_get_url (self->repo, remote_or_uri, &url, error)) + return NULL; + ++ state->is_oci = flatpak_dir_get_remote_oci (self, remote_or_uri); + state->default_token_type = flatpak_dir_get_remote_default_token_type (self, remote_or_uri); + } + +@@ -13104,6 +13234,56 @@ populate_hash_table_from_refs_map (GHashTable *ret_all_refs, + } + + ++static void ++populate_hash_table_from_image_collection (GHashTable *ret_all_refs, ++ GHashTable *ref_timestamps, ++ FlatpakImageCollection *image_collection, ++ const char *opt_collection_id, ++ FlatpakRemoteState *state) ++{ ++ g_autoptr(GPtrArray) sources = flatpak_image_collection_get_sources (image_collection); ++ ++ for (guint i = 0; i < sources->len; i++) ++ { ++ FlatpakImageSource *image_source = g_ptr_array_index (sources, i); ++ const char *ref_name = flatpak_image_source_get_ref (image_source); ++ const char *digest = flatpak_image_source_get_digest (image_source); ++ const char *checksum; ++ guint64 *new_timestamp = NULL; ++ g_autoptr(FlatpakDecomposed) decomposed = NULL; ++ ++ if (!flatpak_remote_state_allow_ref (state, ref_name)) ++ continue; ++ ++ g_assert (g_str_has_prefix (digest, "sha256:")); ++ checksum = digest + 7; ++ ++ decomposed = flatpak_decomposed_new_from_col_ref (ref_name, opt_collection_id, NULL); ++ if (decomposed == NULL) ++ continue; ++ ++ if (ref_timestamps) ++ { ++ guint64 timestamp = flatpak_image_source_get_commit_timestamp (image_source); ++ gpointer value; ++ ++ if (g_hash_table_lookup_extended (ref_timestamps, ref_name, NULL, &value)) ++ { ++ guint64 *old_timestamp = value; ++ if (*old_timestamp >= timestamp) ++ continue; /* New timestamp is older, skip this commit */ ++ } ++ ++ new_timestamp = g_memdup2 (×tamp, sizeof (guint64)); ++ } ++ ++ g_hash_table_replace (ret_all_refs, g_steal_pointer (&decomposed), g_strdup (checksum)); ++ if (new_timestamp) ++ g_hash_table_replace (ref_timestamps, g_strdup (ref_name), new_timestamp); ++ } ++} ++ ++ + /* This tries to list all available remote refs but also tries to keep + * working when offline, so it looks in sideloaded repos. Also it uses + * in-memory cached summaries which ostree doesn't. */ +@@ -13175,25 +13355,37 @@ flatpak_dir_list_all_remote_refs (FlatpakDir *self, + ref_map = var_summary_get_ref_map (summary); + populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, main_collection_id, state); + } +- else if (state->collection_id) ++ else + { + g_autoptr(GHashTable) ref_mtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* No main summary, add just all sideloded refs, with the latest version of each checksum */ + +- for (int i = 0; i < state->sideload_repos->len; i++) ++ if (state->collection_id) + { +- FlatpakSideloadState *ss = g_ptr_array_index (state->sideload_repos, i); ++ for (int i = 0; i < state->sideload_repos->len; i++) ++ { ++ FlatpakSideloadState *ss = g_ptr_array_index (state->sideload_repos, i); + +- summary = var_summary_from_gvariant (ss->summary); +- exts = var_summary_get_metadata (summary); ++ summary = var_summary_from_gvariant (ss->summary); ++ exts = var_summary_get_metadata (summary); + +- if (var_metadata_lookup (exts, "ostree.summary.collection-map", NULL, &v)) +- { +- VarCollectionMapRef map = var_collection_map_from_variant (v); ++ if (var_metadata_lookup (exts, "ostree.summary.collection-map", NULL, &v)) ++ { ++ VarCollectionMapRef map = var_collection_map_from_variant (v); + +- if (var_collection_map_lookup (map, state->collection_id, NULL, &ref_map)) +- populate_hash_table_from_refs_map (ret_all_refs, ref_mtimes, ref_map, NULL, state); ++ if (var_collection_map_lookup (map, state->collection_id, NULL, &ref_map)) ++ populate_hash_table_from_refs_map (ret_all_refs, ref_mtimes, ref_map, NULL, state); ++ } ++ } ++ } ++ ++ if (state->is_oci) ++ { ++ for (int i = 0; i < state->sideload_image_collections->len; i++) ++ { ++ FlatpakImageCollection *collection = g_ptr_array_index (state->sideload_image_collections, i); ++ populate_hash_table_from_image_collection (ret_all_refs, ref_mtimes, collection, NULL, state); + } + } + } +@@ -15773,7 +15965,7 @@ flatpak_dir_find_remote_related_for_metadata (FlatpakDir *self, + if (extension_ref == NULL) + continue; + +- if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (extension_ref), &checksum, NULL, NULL, NULL, NULL)) ++ if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (extension_ref), &checksum, NULL, NULL, NULL, NULL, NULL)) + { + if (flatpak_filters_allow_ref (NULL, masked, flatpak_decomposed_get_ref (extension_ref))) + add_related (self, related, state->remote_name, extension, extension_ref, checksum, +@@ -15788,7 +15980,7 @@ flatpak_dir_find_remote_related_for_metadata (FlatpakDir *self, + g_autofree char *subref_checksum = NULL; + + if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (subref_ref), +- &subref_checksum, NULL, NULL, NULL, NULL) && ++ &subref_checksum, NULL, NULL, NULL, NULL, NULL) && + flatpak_filters_allow_ref (NULL, masked, flatpak_decomposed_get_ref (subref_ref))) + add_related (self, related, state->remote_name, extension, subref_ref, subref_checksum, + no_autodownload, download_if, autoprune_unless, autodelete, locale_subset); +diff --git a/common/flatpak-image-collection-private.h b/common/flatpak-image-collection-private.h +new file mode 100644 +index 00000000..2848f95f +--- /dev/null ++++ b/common/flatpak-image-collection-private.h +@@ -0,0 +1,50 @@ ++/* ++ * 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 . ++ * ++ * Authors: ++ * Owen Taylor ++ */ ++ ++#ifndef __FLATPAK_IMAGE_COLLECTION_H__ ++#define __FLATPAK_IMAGE_COLLECTION_H__ ++ ++#include ++#include ++ ++#include ++#include ++ ++#define FLATPAK_TYPE_IMAGE_COLLECTION flatpak_image_collection_get_type () ++#define FLATPAK_IMAGE_COLLECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_IMAGE_COLLECTION, FlatpakImageCollection)) ++#define FLATPAK_IS_IMAGE_COLLECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_IMAGE_COLLECTION)) ++ ++GType flatpak_image_collection_get_type (void); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakImageCollection, g_object_unref) ++ ++ ++FlatpakImageCollection *flatpak_image_collection_new (const char *location, ++ GCancellable *cancellable, ++ GError **error); ++ ++FlatpakImageSource *flatpak_image_collection_lookup_ref (FlatpakImageCollection *self, ++ const char *ref); ++FlatpakImageSource *flatpak_image_collection_lookup_digest (FlatpakImageCollection *self, ++ const char *digest); ++ ++GPtrArray *flatpak_image_collection_get_sources (FlatpakImageCollection *self); ++ ++#endif /* __FLATPAK_IMAGE_COLLECTION_H__ */ +diff --git a/common/flatpak-image-collection.c b/common/flatpak-image-collection.c +new file mode 100644 +index 00000000..bdb41838 +--- /dev/null ++++ b/common/flatpak-image-collection.c +@@ -0,0 +1,154 @@ ++/* 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 . ++ * ++ * Authors: ++ * Owen Taylor ++ */ ++ ++#include ++ ++#include "flatpak-image-collection-private.h" ++#include "flatpak-oci-registry-private.h" ++ ++struct FlatpakImageCollection ++{ ++ GObject parent; ++ ++ GPtrArray *sources; ++}; ++ ++typedef struct ++{ ++ GObjectClass parent_class; ++} FlatpakImageCollectionClass; ++ ++G_DEFINE_TYPE (FlatpakImageCollection, flatpak_image_collection, G_TYPE_OBJECT) ++ ++ ++static void ++flatpak_image_collection_finalize (GObject *object) ++{ ++ FlatpakImageCollection *self = FLATPAK_IMAGE_COLLECTION (object); ++ ++ g_ptr_array_free (self->sources, TRUE); ++ ++ G_OBJECT_CLASS (flatpak_image_collection_parent_class)->finalize (object); ++} ++ ++static void ++flatpak_image_collection_class_init (FlatpakImageCollectionClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = flatpak_image_collection_finalize; ++} ++ ++static void ++flatpak_image_collection_init (FlatpakImageCollection *self) ++{ ++ self->sources = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); ++} ++ ++FlatpakImageCollection * ++flatpak_image_collection_new (const char *location, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(FlatpakImageCollection) self = NULL; ++ g_autoptr(FlatpakOciRegistry) registry = NULL; ++ g_autoptr(FlatpakOciIndex) index = NULL; ++ gsize i; ++ ++ self = g_object_new (FLATPAK_TYPE_IMAGE_COLLECTION, NULL); ++ ++ if (g_str_has_prefix (location, "oci:")) ++ { ++ g_autoptr(GFile) dir = g_file_new_for_path (location + 4); ++ g_autofree char *uri = g_file_get_uri (dir); ++ ++ registry = flatpak_oci_registry_new (uri, FALSE, -1, cancellable, error); ++ if (registry == NULL) ++ return NULL; ++ } ++ else if (g_str_has_prefix (location, "oci-archive:")) ++ { ++ g_autoptr(GFile) file = g_file_new_for_path (location + 12); ++ registry = flatpak_oci_registry_new_for_archive (file, cancellable, error); ++ if (registry == NULL) ++ return NULL; ++ } ++ else ++ { ++ flatpak_fail (error, "Can't parse image collection location %s", location); ++ return NULL; ++ } ++ ++ index = flatpak_oci_registry_load_index (registry, cancellable, error); ++ if (index == NULL) ++ return NULL; ++ ++ for (i = 0; index->manifests[i] != NULL; i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ FlatpakOciManifestDescriptor *descriptor = index->manifests[i]; ++ g_autoptr(FlatpakImageSource) image_source = flatpak_image_source_new (registry, NULL, ++ descriptor->parent.digest, ++ cancellable, &local_error); ++ if (image_source == NULL) ++ { ++ g_info ("Can't load manifest in image collection: %s", local_error->message); ++ continue; ++ } ++ ++ g_ptr_array_add (self->sources, g_steal_pointer (&image_source)); ++ } ++ ++ return g_steal_pointer (&self); ++} ++ ++FlatpakImageSource * ++flatpak_image_collection_lookup_ref (FlatpakImageCollection *self, ++ const char *ref) ++{ ++ for (guint i = 0; i < self->sources->len; i++) ++ { ++ FlatpakImageSource *source = g_ptr_array_index (self->sources, i); ++ if (strcmp (flatpak_image_source_get_ref (source), ref) == 0) ++ return g_object_ref (source); ++ } ++ ++ return NULL; ++} ++ ++FlatpakImageSource * ++flatpak_image_collection_lookup_digest (FlatpakImageCollection *self, ++ const char *digest) ++{ ++ for (guint i = 0; i < self->sources->len; i++) ++ { ++ FlatpakImageSource *source = g_ptr_array_index (self->sources, i); ++ if (strcmp (flatpak_image_source_get_digest (source), digest) == 0) ++ return g_object_ref (source); ++ } ++ ++ return NULL; ++} ++ ++GPtrArray * ++flatpak_image_collection_get_sources (FlatpakImageCollection *self) ++{ ++ return g_ptr_array_ref (self->sources); ++} +diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h +index 94e4cc1a..597a8174 100644 +--- a/common/flatpak-image-source-private.h ++++ b/common/flatpak-image-source-private.h +@@ -32,6 +32,12 @@ G_DECLARE_FINAL_TYPE (FlatpakImageSource, + FLATPAK, IMAGE_SOURCE, + GObject) + ++FlatpakImageSource *flatpak_image_source_new (FlatpakOciRegistry *registry, ++ const char *repository, ++ const char *digest, ++ GCancellable *cancellable, ++ GError **error); ++ + FlatpakImageSource *flatpak_image_source_new_local (GFile *file, + const char *reference, + GCancellable *cancellable, +@@ -70,4 +76,5 @@ void flatpak_image_source_build_commit_metadata (FlatpakImageSource *self, + GVariantBuilder *metadata_builder); + + GVariant *flatpak_image_source_make_fake_commit (FlatpakImageSource *image_source); ++GVariant *flatpak_image_source_make_summary_metadata (FlatpakImageSource *self); + #endif /* __FLATPAK_IMAGE_SOURCE_H__ */ +diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c +index 9d13cf0d..a31f1084 100644 +--- a/common/flatpak-image-source.c ++++ b/common/flatpak-image-source.c +@@ -68,7 +68,7 @@ flatpak_image_source_init (FlatpakImageSource *self) + { + } + +-static FlatpakImageSource * ++FlatpakImageSource * + flatpak_image_source_new (FlatpakOciRegistry *registry, + const char *repository, + const char *digest, +@@ -78,6 +78,12 @@ flatpak_image_source_new (FlatpakOciRegistry *registry, + g_autoptr(FlatpakImageSource) self = NULL; + g_autoptr(FlatpakOciVersioned) versioned = NULL; + ++ if (!g_str_has_prefix (digest, "sha256:")) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Only sha256 image checksums are supported")); ++ return NULL; ++ } ++ + self = g_object_new (FLATPAK_TYPE_IMAGE_SOURCE, NULL); + self->registry = g_object_ref (registry); + self->repository = g_strdup (repository); +@@ -494,3 +500,20 @@ flatpak_image_source_make_fake_commit (FlatpakImageSource *self) + ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"), + ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"))); + } ++ ++GVariant * ++flatpak_image_source_make_summary_metadata (FlatpakImageSource *self) ++{ ++ g_autoptr(GVariantBuilder) ref_metadata_builder = NULL; ++ ++ ref_metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ ++ if (self->repository) ++ g_variant_builder_add (ref_metadata_builder, "{sv}", "xa.oci-repository", ++ g_variant_new_string (self->repository)); ++ if (self->delta_url) ++ g_variant_builder_add (ref_metadata_builder, "{sv}", "xa.delta-url", ++ g_variant_new_string (self->delta_url)); ++ ++ return g_variant_ref_sink (g_variant_builder_end (ref_metadata_builder)); ++} +diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c +index 2dd35f53..b498e4d4 100644 +--- a/common/flatpak-transaction.c ++++ b/common/flatpak-transaction.c +@@ -26,6 +26,8 @@ + #include "flatpak-auth-private.h" + #include "flatpak-dir-private.h" + #include "flatpak-error.h" ++#include "flatpak-image-collection-private.h" ++#include "flatpak-image-collection-private.h" + #include "flatpak-image-source-private.h" + #include "flatpak-installation-private.h" + #include "flatpak-oci-registry-private.h" +@@ -172,6 +174,7 @@ typedef struct _FlatpakTransactionPrivate + GHashTable *remote_states; /* (element-type utf8 FlatpakRemoteState) */ + GPtrArray *extra_dependency_dirs; + GPtrArray *extra_sideload_repos; ++ GPtrArray *sideload_image_collections; + GList *ops; + GPtrArray *added_origin_remotes; + +@@ -524,6 +527,38 @@ flatpak_transaction_add_sideload_repo (FlatpakTransaction *self, + g_strdup (path)); + } + ++/** ++ * flatpak_transaction_add_sideload_image_collection: ++ * @self: a #FlatpakTransaction ++ * @location: source of images for installation ++ * ++ * Adds a set of images to be used as source for installation. This is similar ++ * to flatpak_transaction_add_sideload_repo(), but the Flatpaks are stored ++ * as OCI images rather than ostree commits, and the images are used for ++ * all OCI remotes without regard to collection ID. ++ * ++ * Currently @location should be either 'oci:' or 'oci-archive:'. ++ * Additional schemes may be added in the future. ++ * ++ * Since: 1.7.1 ++ */ ++gboolean ++flatpak_transaction_add_sideload_image_collection (FlatpakTransaction *self, ++ const char *location, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); ++ g_autoptr(FlatpakImageCollection) collection = NULL; ++ ++ collection = flatpak_image_collection_new (location, cancellable, error); ++ if (collection == NULL) ++ return FALSE; ++ ++ g_ptr_array_add (priv->sideload_image_collections, g_steal_pointer (&collection)); ++ return TRUE; ++} ++ + /** + * flatpak_transaction_add_default_dependency_sources: + * @self: a #FlatpakTransaction +@@ -1033,6 +1068,7 @@ flatpak_transaction_finalize (GObject *object) + + g_ptr_array_free (priv->extra_dependency_dirs, TRUE); + g_ptr_array_free (priv->extra_sideload_repos, TRUE); ++ g_ptr_array_free (priv->sideload_image_collections, TRUE); + + G_OBJECT_CLASS (flatpak_transaction_parent_class)->finalize (object); + } +@@ -1508,6 +1544,7 @@ flatpak_transaction_init (FlatpakTransaction *self) + priv->added_origin_remotes = g_ptr_array_new_with_free_func (g_free); + priv->extra_dependency_dirs = g_ptr_array_new_with_free_func (g_object_unref); + priv->extra_sideload_repos = g_ptr_array_new_with_free_func (g_free); ++ priv->sideload_image_collections = g_ptr_array_new_with_free_func (g_object_unref); + priv->can_run = TRUE; + } + +@@ -2085,6 +2122,13 @@ flatpak_transaction_ensure_remote_state (FlatpakTransaction *self, + g_autoptr(GFile) f = g_file_new_for_path (path); + flatpak_remote_state_add_sideload_dir (state, f); + } ++ ++ for (int i = 0; i < priv->sideload_image_collections->len; i++) ++ { ++ FlatpakImageCollection *collection = g_ptr_array_index (priv->sideload_image_collections, i); ++ flatpak_remote_state_add_sideload_image_collection (state, collection); ++ } ++ + } + + if (opt_arch != NULL && +@@ -2368,7 +2412,7 @@ search_for_dependency (FlatpakTransaction *self, + continue; + } + +- if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (runtime_ref), NULL, NULL, NULL, NULL, NULL)) ++ if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (runtime_ref), NULL, NULL, NULL, NULL, NULL, NULL)) + g_ptr_array_add (found, g_strdup (remote)); + } + +@@ -3243,7 +3287,7 @@ flatpak_transaction_add_auto_install (FlatpakTransaction *self, + g_autoptr(FlatpakRemoteState) state = flatpak_transaction_ensure_remote_state (self, FLATPAK_TRANSACTION_OPERATION_UPDATE, remote, NULL, NULL); + + if (state != NULL && +- flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (auto_install_ref), NULL, NULL, NULL, NULL, NULL)) ++ flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (auto_install_ref), NULL, NULL, NULL, NULL, NULL, NULL)) + { + g_info ("Auto adding install of %s from remote %s", flatpak_decomposed_get_ref (auto_install_ref), remote); + +@@ -3424,6 +3468,7 @@ resolve_op_from_commit (FlatpakTransaction *self, + FlatpakTransactionOperation *op, + const char *checksum, + GFile *sideload_path, ++ FlatpakImageSource *image_source, + GVariant *commit_data, + GError **error) + { +@@ -3463,7 +3508,7 @@ resolve_op_from_commit (FlatpakTransaction *self, + flatpak_decomposed_get_ref (eolr_decomposed)); + } + +- return resolve_op_end (self, op, checksum, sideload_path, NULL, metadata_bytes, error); ++ return resolve_op_end (self, op, checksum, sideload_path, image_source, metadata_bytes, error); + } + + /* NOTE: In case of non-available summary this returns FALSE with a +@@ -3474,6 +3519,7 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, + FlatpakTransactionOperation *op, + const char *checksum, + GFile *sideload_path, ++ FlatpakImageSource *image_source, + FlatpakRemoteState *state, + GError **error) + { +@@ -3487,7 +3533,7 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, + /* Ref has to match the actual commit in the summary */ + if ((state->summary == NULL && state->index == NULL) || + !flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), +- &summary_checksum, NULL, NULL, NULL, NULL) || ++ &summary_checksum, NULL, NULL, NULL, NULL, NULL) || + strcmp (summary_checksum, checksum) != 0) + return FALSE; + +@@ -3499,7 +3545,7 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, + metadata_bytes = g_bytes_new (metadata, strlen (metadata)); + + flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), +- NULL, NULL, &op->summary_metadata, NULL, NULL); ++ NULL, NULL, &op->summary_metadata, NULL, NULL, NULL); + + op->installed_size = installed_size; + op->download_size = download_size; +@@ -3629,36 +3675,39 @@ resolve_ops (FlatpakTransaction *self, + if (commit_data == NULL) + return FALSE; + +- if (!resolve_op_from_commit (self, op, checksum, NULL, commit_data, error)) ++ if (!resolve_op_from_commit (self, op, checksum, NULL, NULL, commit_data, error)) + return FALSE; + } + else + { + g_autoptr(GError) local_error = NULL; + g_autoptr(GFile) sideload_path = NULL; ++ g_autoptr(FlatpakImageSource) image_source = NULL; + + if (op->commit != NULL) + { + checksum = g_strdup (op->commit); + /* Check if this is available offline and if so, use that */ +- sideload_path = flatpak_remote_state_lookup_sideload_checksum (state, op->commit); ++ flatpak_remote_state_lookup_sideload_checksum (state, op->commit, &sideload_path, &image_source); + } + else + { + g_autofree char *latest_checksum = NULL; + g_autoptr(GFile) latest_sideload_path = NULL; ++ g_autoptr(FlatpakImageSource) latest_image_source = NULL; + g_autofree char *local_checksum = NULL; + guint64 latest_timestamp; + g_autoptr(GVariant) local_commit_data = flatpak_dir_read_latest_commit (priv->dir, op->remote, op->ref, + &local_checksum, NULL, NULL); + + if (flatpak_dir_find_latest_rev (priv->dir, state, flatpak_decomposed_get_ref (op->ref), op->commit, +- &latest_checksum, &latest_timestamp, &latest_sideload_path, ++ &latest_checksum, &latest_timestamp, &latest_sideload_path, &latest_image_source, + cancellable, &local_error)) + { + /* If we found the latest in a sideload repo, it may be older that what is locally available, check timestamps. + * Note: If the timestamps are equal (timestamp granularity issue), assume we want to update */ +- if (latest_sideload_path != NULL && local_commit_data && latest_timestamp != 0 && ++ if ((latest_sideload_path != NULL || latest_image_source != NULL) && ++ local_commit_data && latest_timestamp != 0 && + ostree_commit_get_timestamp (local_commit_data) > latest_timestamp) + { + g_info ("Installed commit %s newer than sideloaded %s, ignoring", local_checksum, latest_checksum); +@@ -3669,6 +3718,7 @@ resolve_ops (FlatpakTransaction *self, + /* Otherwise, use whatever we found */ + checksum = g_steal_pointer (&latest_checksum); + sideload_path = g_steal_pointer (&latest_sideload_path); ++ image_source = g_steal_pointer (&latest_image_source); + } + } + else +@@ -3689,7 +3739,7 @@ resolve_ops (FlatpakTransaction *self, + } + + /* First try to resolve via metadata (if remote is available and its metadata matches the commit version) */ +- if (!try_resolve_op_from_metadata (self, op, checksum, sideload_path, state, &local_error)) ++ if (!try_resolve_op_from_metadata (self, op, checksum, sideload_path, image_source, state, &local_error)) + { + if (local_error) + { +@@ -3706,7 +3756,7 @@ resolve_ops (FlatpakTransaction *self, + /* OCI needs this to get the oci repository for the ref to request the token, so lets always set it here */ + if (op->summary_metadata == NULL) + flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (op->ref), +- NULL, NULL, &op->summary_metadata, NULL, NULL); ++ NULL, NULL, &op->summary_metadata, NULL, NULL, NULL); + + commit_data = flatpak_remote_state_load_ref_commit (state, priv->dir, + flatpak_decomposed_get_ref (op->ref), +@@ -3732,7 +3782,7 @@ resolve_ops (FlatpakTransaction *self, + return FALSE; + } + +- if (!resolve_op_from_commit (self, op, checksum, sideload_path, commit_data, error)) ++ if (!resolve_op_from_commit (self, op, checksum, sideload_path, image_source, commit_data, error)) + return FALSE; + } + } +diff --git a/common/flatpak-transaction.h b/common/flatpak-transaction.h +index 3d3f4ce8..dfa3d039 100644 +--- a/common/flatpak-transaction.h ++++ b/common/flatpak-transaction.h +@@ -272,6 +272,11 @@ FLATPAK_EXTERN + void flatpak_transaction_add_sideload_repo (FlatpakTransaction *self, + const char *path); + FLATPAK_EXTERN ++gboolean flatpak_transaction_add_sideload_image_collection (FlatpakTransaction *self, ++ const char *location, ++ GCancellable *cancellable, ++ GError **error); ++FLATPAK_EXTERN + void flatpak_transaction_add_default_dependency_sources (FlatpakTransaction *self); + FLATPAK_EXTERN + gboolean flatpak_transaction_run (FlatpakTransaction *transaction, +diff --git a/common/meson.build b/common/meson.build +index c14337be..37911c4c 100644 +--- a/common/meson.build ++++ b/common/meson.build +@@ -173,6 +173,7 @@ sources = [ + 'flatpak-error.c', + 'flatpak-exports.c', + 'flatpak-glib-backports.c', ++ 'flatpak-image-collection.c', + 'flatpak-image-source.c', + 'flatpak-installation.c', + 'flatpak-installed-ref.c', +diff --git a/doc/flatpak-install.xml b/doc/flatpak-install.xml +index 99aa5469..985b94b2 100644 +--- a/doc/flatpak-install.xml ++++ b/doc/flatpak-install.xml +@@ -84,6 +84,15 @@ + system-wide basis (see + flatpak1). + ++ ++ For OCI remotes, sideload repositories can also be specified as ++ or , ++ where path points to an OCI image layout or archive of an OCI image layout. ++ (See containers-transports5.) ++ When specified in this fashion, the images found in the image layout are available for all OCI remotes, ++ without regard to collection ID. For this reason, there is no directory equivalent - ++ it can only be specified on the command line. ++ + + The alternative form of the command (with , , + or ) allows to install directly from a source. The source can be a +diff --git a/doc/flatpak-update.xml b/doc/flatpak-update.xml +index feaaf278..096ee310 100644 +--- a/doc/flatpak-update.xml ++++ b/doc/flatpak-update.xml +@@ -75,6 +75,15 @@ + system-wide basis (see + flatpak1). + ++ ++ For OCI remotes, sideload repositories can also be specified as ++ or , ++ where path points to an OCI image layout or archive of an OCI image layout. ++ (See containers-transports5.) ++ When specified in this fashion, the images found in the image layout are available for all OCI remotes, ++ without regard to collection ID. For this reason, there is no directory equivalent - ++ it can only be specified on the command line. ++ + + Note that updating a runtime is different from installing + a different branch, and runtime updates are expected to keep +diff --git a/doc/reference/meson.build b/doc/reference/meson.build +index 92f1482e..0854030e 100644 +--- a/doc/reference/meson.build ++++ b/doc/reference/meson.build +@@ -46,6 +46,7 @@ gnome.gtkdoc( + 'flatpak-document-dbus-generated.h', + 'flatpak-enum-types.h', + 'flatpak-exports-private.h', ++ 'flatpak-image-collection-private.h', + 'flatpak-image-source-private.h', + 'flatpak-installed-ref-private.h', + 'flatpak-json-oci-private.h', +-- +2.47.1 + diff --git a/flatpak.spec b/flatpak.spec new file mode 100644 index 0000000..dc1c275 --- /dev/null +++ b/flatpak.spec @@ -0,0 +1,933 @@ +%global appstream_version 1.0.0~ +%global bubblewrap_version 0.10.0 +%global glib_version 2.46.0 +%global gpgme_version 1.8.0 +%global libcurl_version 7.29.0 +%global ostree_version 2020.8 +%global wayland_protocols_version 1.32 +%global wayland_scanner_version 1.15 + +# Disable parental control for RHEL builds +%bcond malcontent %[!0%{?rhel}] + +Name: flatpak +Version: 1.16.0 +Release: 6%{?dist} +Summary: Application deployment framework for desktop apps + +License: LGPL-2.1-or-later +URL: https://flatpak.org/ +Source0: https://github.com/flatpak/flatpak/releases/download/%{version}/%{name}-%{version}.tar.xz + +%if 0%{?fedora} +# Add Fedora flatpak repositories +Source1: flatpak-add-fedora-repos.service +%endif + +# systemd-sysusers config. Only used for the %%pre macro. Must be kept in sync +# with the config from upstream sources. +Source2: flatpak.sysusers.conf + +# Implement /etc/containers/certs.d for OCI registries +# https://github.com/flatpak/flatpak/pull/5916 +Patch0: flatpak-implement-etc-containers-certs-for-oci-registries.patch +# Allow direct installation from OCI images +# https://github.com/flatpak/flatpak/pull/5972 +Patch1: flatpak-allow-direct-installation-from-oci-images.patch +# Support sideload repositories for OCI remotes +# https://github.com/owtaylor/flatpak/commits/oci-sideload +Patch2: flatpak-support-sideload-repositories-for-oci-remotes.patch +# Add support for preinstalling flatpaks +# https://github.com/flatpak/flatpak/pull/6116 +Patch3: flatpak-add-support-for-preinstalling-flatpaks.patch +# Enable collection IDs for OCI remotes +# https://github.com/flatpak/flatpak/pull/6083 +Patch4: flatpak-enable-collection-ids-for-oci-remotes.patch +# Fix crash and installatcion of OCI images +Patch5: flatpak-pass-token-to-flatpak-image-source-new-remote.patch + +# ostree not on i686 for RHEL 10 +# https://github.com/containers/composefs/pull/229#issuecomment-1838735764 +%if 0%{?rhel} >= 10 +ExcludeArch: %{ix86} +%endif + +BuildRequires: pkgconfig(appstream) >= %{appstream_version} +BuildRequires: pkgconfig(dconf) +BuildRequires: pkgconfig(fuse3) +BuildRequires: pkgconfig(gdk-pixbuf-2.0) +BuildRequires: pkgconfig(gio-unix-2.0) >= %{glib_version} +BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.40.0 +BuildRequires: pkgconfig(gpgme) >= %{gpgme_version} +BuildRequires: pkgconfig(json-glib-1.0) +BuildRequires: pkgconfig(libarchive) >= 2.8.0 +BuildRequires: pkgconfig(libseccomp) +BuildRequires: pkgconfig(libcurl) >= %{libcurl_version} +BuildRequires: pkgconfig(libsystemd) +BuildRequires: pkgconfig(libxml-2.0) >= 2.4 +BuildRequires: pkgconfig(libzstd) >= 0.8.1 +%if %{with malcontent} +BuildRequires: pkgconfig(malcontent-0) +%endif +BuildRequires: pkgconfig(ostree-1) >= %{ostree_version} +BuildRequires: pkgconfig(polkit-gobject-1) +BuildRequires: pkgconfig(wayland-client) +BuildRequires: pkgconfig(wayland-protocols) >= %{wayland_protocols_version} +BuildRequires: pkgconfig(wayland-scanner) >= %{wayland_scanner_version} +BuildRequires: pkgconfig(xau) +BuildRequires: bison +BuildRequires: bubblewrap >= %{bubblewrap_version} +BuildRequires: docbook-dtds +BuildRequires: docbook-style-xsl +BuildRequires: gettext-devel +BuildRequires: gtk-doc +BuildRequires: libcap-devel +BuildRequires: meson +BuildRequires: python3-pyparsing +BuildRequires: systemd +BuildRequires: systemd-rpm-macros +BuildRequires: /usr/bin/fusermount3 +BuildRequires: /usr/bin/pkcheck +BuildRequires: /usr/bin/socat +BuildRequires: /usr/bin/xdg-dbus-proxy +BuildRequires: /usr/bin/xmlto +BuildRequires: /usr/bin/xsltproc + +%{?sysusers_requires_compat} + +Requires: appstream%{?_isa} >= %{appstream_version} +Requires: bubblewrap >= %{bubblewrap_version} +Requires: glib2%{?_isa} >= %{glib_version} +Requires: libcurl%{?_isa} >= %{libcurl_version} +Requires: librsvg2%{?_isa} +Requires: ostree-libs%{?_isa} >= %{ostree_version} +Requires: /usr/bin/fusermount3 +Requires: /usr/bin/xdg-dbus-proxy +# https://fedoraproject.org/wiki/SELinux/IndependentPolicy +Requires: (flatpak-selinux = %{?epoch:%{epoch}:}%{version}-%{release} if selinux-policy-targeted) +Requires: %{name}-session-helper%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} +Recommends: p11-kit-server + +# Make sure the document portal is installed +%if 0%{?fedora} || 0%{?rhel} > 7 +Recommends: xdg-desktop-portal > 0.10 +%else +Requires: xdg-desktop-portal > 0.10 +%endif + +%description +flatpak is a system for building, distributing and running sandboxed desktop +applications on Linux. See https://wiki.gnome.org/Projects/SandboxedApps for +more information. + +%package devel +Summary: Development files for %{name} +Requires: %{name}%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} +Requires: %{name}-libs%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} + +%description devel +This package contains the pkg-config file and development headers for %{name}. + +%package libs +Summary: Libraries for %{name} +Requires: bubblewrap >= %{bubblewrap_version} +# We can assume ostree is installed on ostree systems +# So do not enforce it on non-ostree ones +Requires: ostree-libs%{?_isa} >= %{ostree_version} + +%description libs +This package contains libflatpak. + +%package selinux +Summary: SELinux policy module for %{name} +BuildRequires: selinux-policy +BuildRequires: selinux-policy-devel +BuildRequires: make +BuildArch: noarch +%{?selinux_requires} + +%description selinux +This package contains the SELinux policy module for %{name}. + +%package session-helper +Summary: User D-Bus service used by %{name} and others +Conflicts: flatpak < 1.4.1-2 +Requires: systemd + +%description session-helper +This package contains the org.freedesktop.Flatpak user D-Bus service +that's used by %{name} and other packages. + +%package tests +Summary: Tests for %{name} +Requires: %{name}%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} +Requires: %{name}-libs%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} +Requires: %{name}-session-helper%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} +Requires: bubblewrap >= %{bubblewrap_version} +Requires: ostree%{?_isa} >= %{ostree_version} + +%description tests +This package contains installed tests for %{name}. + + +%prep +%autosetup -p1 + + +%build +%meson \ + -Dinstalled_tests=true \ + -Dsystem_bubblewrap=/usr/bin/bwrap \ + -Dsystem_dbus_proxy=/usr/bin/xdg-dbus-proxy \ + -Dtmpfilesdir=%{_tmpfilesdir} \ +%if %{with malcontent} + -Dmalcontent=enabled \ +%else + -Dmalcontent=disabled \ +%endif + -Dwayland_security_context=enabled \ + %{nil} +%meson_build + + +%install +%meson_install +install -pm 644 NEWS README.md %{buildroot}/%{_pkgdocdir} +# The system repo is not installed by the flatpak build system. +install -d %{buildroot}%{_localstatedir}/lib/flatpak +install -d %{buildroot}%{_sysconfdir}/flatpak/remotes.d + +%if 0%{?fedora} +install -D -t %{buildroot}%{_unitdir} %{SOURCE1} +%endif + +%find_lang %{name} + +%pre +%sysusers_create_compat %{SOURCE2} + + +%if 0%{?fedora} +%post +%systemd_post flatpak-add-fedora-repos.service +%endif + + +%post selinux +%selinux_modules_install %{_datadir}/selinux/packages/flatpak.pp.bz2 + + +%if 0%{?fedora} +%preun +%systemd_preun flatpak-add-fedora-repos.service +%endif + + +%if 0%{?fedora} +%postun +%systemd_postun_with_restart flatpak-add-fedora-repos.service +%endif + + +%postun selinux +if [ $1 -eq 0 ]; then + %selinux_modules_uninstall %{_datadir}/selinux/packages/flatpak.pp.bz2 +fi + + +%files -f %{name}.lang +%license COPYING +# Comply with the packaging guidelines about not mixing relative and absolute +# paths in doc. +%doc %{_pkgdocdir} +%{_bindir}/flatpak +%{_bindir}/flatpak-bisect +%{_bindir}/flatpak-coredumpctl +%{_datadir}/bash-completion +%{_datadir}/dbus-1/interfaces/org.freedesktop.portal.Flatpak.xml +%{_datadir}/dbus-1/interfaces/org.freedesktop.Flatpak.Authenticator.xml +%{_datadir}/dbus-1/services/org.flatpak.Authenticator.Oci.service +%{_datadir}/dbus-1/services/org.freedesktop.portal.Flatpak.service +%{_datadir}/dbus-1/system-services/org.freedesktop.Flatpak.SystemHelper.service +%{_datadir}/fish/ +%{_datadir}/%{name} +%{_datadir}/polkit-1/actions/org.freedesktop.Flatpak.policy +%{_datadir}/polkit-1/rules.d/org.freedesktop.Flatpak.rules +%{_datadir}/zsh/site-functions +%{_libexecdir}/flatpak-oci-authenticator +%{_libexecdir}/flatpak-portal +%{_libexecdir}/flatpak-system-helper +%{_libexecdir}/flatpak-validate-icon +%{_libexecdir}/revokefs-fuse +%dir %{_localstatedir}/lib/flatpak +%{_mandir}/man1/%{name}*.1* +%{_mandir}/man5/%{name}-metadata.5* +%{_mandir}/man5/flatpak-flatpakref.5* +%{_mandir}/man5/flatpak-flatpakrepo.5* +%{_mandir}/man5/flatpak-installation.5* +%{_mandir}/man5/flatpak-remote.5* +%{_mandir}/man5/flatpakref.5* +%{_mandir}/man5/flatpakrepo.5* +%{_sysconfdir}/dbus-1/system.d/org.freedesktop.Flatpak.SystemHelper.conf +%dir %{_sysconfdir}/flatpak +%{_sysconfdir}/flatpak/remotes.d +%{_sysconfdir}/profile.d/flatpak.csh +%{_sysconfdir}/profile.d/flatpak.sh +%{_sysusersdir}/%{name}.conf +%{_unitdir}/flatpak-system-helper.service +%{_userunitdir}/flatpak-oci-authenticator.service +%{_userunitdir}/flatpak-portal.service +%{_systemd_system_env_generator_dir}/60-flatpak-system-only +%{_systemd_user_env_generator_dir}/60-flatpak +%{_tmpfilesdir}/%{name}.conf + +%if 0%{?fedora} +%{_unitdir}/flatpak-add-fedora-repos.service +%endif + +%files devel +%{_datadir}/gir-1.0/Flatpak-1.0.gir +%{_datadir}/gtk-doc/ +%{_includedir}/%{name}/ +%{_libdir}/libflatpak.so +%{_libdir}/pkgconfig/%{name}.pc + +%files libs +%license COPYING +%{_libdir}/girepository-1.0/Flatpak-1.0.typelib +%{_libdir}/libflatpak.so.* + +%files selinux +%{_datadir}/selinux/packages/flatpak.pp.bz2 +%{_datadir}/selinux/devel/include/contrib/flatpak.if + +%files session-helper +%license COPYING +%{_datadir}/dbus-1/interfaces/org.freedesktop.Flatpak.xml +%{_datadir}/dbus-1/services/org.freedesktop.Flatpak.service +%{_libexecdir}/flatpak-session-helper +%{_userunitdir}/flatpak-session-helper.service + +%files tests +%{_datadir}/installed-tests +%{_libexecdir}/installed-tests + + +%changelog +* Mon Aug 04 2025 Jan Grulich - 1.16.0-6 +- Fix wrongly marked failed installs as pre-installed + Resolves: RHEL-89989 + +* Wed Feb 05 2025 Jan Grulich - 1.16.0-5 +- Fix crash and installatcion of OCI images + Resolves: RHEL-76958 + +* Thu Jan 23 2025 Zdenek Pytela - 1.16.0-4 +- Rebuild with selinux-policy-40.13.23-1 + Resolves: RHEL-36741 + +* Fri Jan 17 2025 Jan Grulich - 1.16.0-3 +- Backport: Enable collection IDs for OCI remotes + Resolves: RHEL-72779 + +* Fri Jan 17 2025 Jan Grulich - 1.16.0-2 +- Rebase backported upstream patches to updated versions + Resolves: RHEL-26066 + +* Mon Jan 13 2025 Jan Grulich - 1.16.0-1 +- Update to 1.16.0 + Resolves: RHEL-72779 +- Add support for preinstalling Flatpaks + Resolves: RHEL-26066 + +* Tue Oct 29 2024 Troy Dawson - 1.15.10-2 +- Bump release for October 2024 mass rebuild: + Resolves: RHEL-64018 + +* Fri Aug 30 2024 Kalev Lember - 1.15.10-1 +- Update to 1.15.10 (CVE-2024-42472) + +* Mon Jun 24 2024 Troy Dawson - 1.15.8-2 +- Bump release for June 2024 mass rebuild + +* Fri Jun 07 2024 Kalev Lember - 1.15.8-1 +- Update to 1.15.8 (CVE-2024-32462) + +* Wed Jan 24 2024 Fedora Release Engineering - 1.15.6-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jan 19 2024 Fedora Release Engineering - 1.15.6-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Thu Nov 16 2023 Debarshi Ray - 1.15.6-1 +- Update to 1.15.6 (#2249763) + +* Tue Nov 07 2023 Neal Gompa - 1.15.4-5 +- Fix appstream_version macro for prerelease appstream 1.0 package + +* Tue Nov 07 2023 Debarshi Ray - 1.15.4-4 +- Adjust to Appstream 1.0 API changes +- Fix Appstream regression in 'remote-info' + +* Wed Jul 19 2023 Fedora Release Engineering - 1.15.4-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Thu Jun 22 2023 Tomas Popela - 1.15.4-2 +- Disable parental control support (through malcontent) on RHEL + +* Fri Mar 17 2023 David King - 1.15.4-1 +- Update to 1.15.4 + +* Thu Feb 23 2023 David King - 1.15.3-1 +- Update to 1.15.3 (#2120890) + +* Thu Jan 19 2023 Fedora Release Engineering - 1.15.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Fri Jan 06 2023 David King - 1.15.1-2 +- Require fusermount (#2158474) + +* Tue Dec 13 2022 David King - 1.15.1-1 +- Update to 1.15.1 + +* Thu Dec 08 2022 David King - 1.14.1-1 +- Update to 1.14.1 (#2151850) + +* Thu Sep 15 2022 Michael Catanzaro - 1.14.0-2 +- Refresh gssproxy patch to use new socket path + +* Wed Sep 07 2022 Kalev Lember - 1.14.0-1 +- Update to 1.14.0 + +* Fri Aug 19 2022 Debarshi Ray - 1.13.3-6 +- Use %%sysusers_requires_compat to match %%sysusers_create_compat + +* Thu Jul 21 2022 Fedora Release Engineering - 1.13.3-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Thu Jul 14 2022 Debarshi Ray - 1.13.3-4 +- Avoid SELinux denials caused by reading symbolic links in /var/lib/flatpak + +* Sun Jun 26 2022 Ralf Corsépius - 1.13.3-3 +- Let flatpak own %%{_sysconfdir}/flatpak (RHBZ#2101073). + +* Fri Jun 17 2022 David King - 1.13.3-2 +- Add gssproxy support + +* Fri Jun 17 2022 Debarshi Ray - 1.13.3-1 +- Update to 1.13.3 +- Remove downstream patch for gssproxy support until it gets rebased + +* Tue Jun 07 2022 David King - 1.13.2-4 +- Add gssproxy support + +* Tue May 17 2022 Timothée Ravier - 1.13.2-3 +- Use sysusers_create_compat macro to create user & group. + +* Tue Apr 12 2022 Debarshi Ray - 1.13.2-2 +- Avoid SELinux denials caused by read access to /etc/passwd, watching files + inside /usr/libexec and read access to /var/lib/flatpak + +* Thu Mar 17 2022 Debarshi Ray - 1.13.2-1 +- Update to 1.13.2 (#2064038) + +* Sat Mar 12 2022 Debarshi Ray - 1.13.1-1 +- Update to 1.13.1 (#2059784) + +* Wed Mar 02 2022 Debarshi Ray - 1.12.6-2 +- Specify the %%{epoch} consistently + +* Fri Feb 25 2022 Debarshi Ray - 1.12.6-1 +- Update to 1.12.6 (#2053655) + +* Mon Feb 14 2022 Debarshi Ray - 1.12.5-1 +- Update to 1.12.5 (#2032528) + +* Tue Feb 08 2022 Debarshi Ray - 1.12.4-2 +- Don't try to add Fedora's OCI Flatpak repository on RHEL +- Remove an obsolete Fedora-specific update path + +* Thu Jan 20 2022 Fedora Release Engineering - 1.12.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Tue Jan 18 2022 Debarshi Ray - 1.12.4-1 +- Update to 1.12.4 (#2042071) + +* Fri Jan 14 2022 Debarshi Ray - 1.12.3-1 +- Update to 1.12.3 (#2040094) + +* Wed Oct 13 2021 David King - 1.12.2-1 +- Update to 1.12.2 (#2013492) + +* Fri Oct 08 2021 David King - 1.12.1-1 +- Update to 1.12.1 (#2012273) + +* Fri Oct 08 2021 David King - 1.12.0-1 +- Update to 1.12.0 (#2012246) + +* Thu Sep 09 2021 Kalev Lember - 1.11.3-2 +- Enable malcontent support + +* Wed Aug 25 2021 Kalev Lember - 1.11.3-1 +- Update to 1.11.3 + +* Wed Jul 21 2021 Fedora Release Engineering - 1.11.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Wed Jul 14 2021 David King - 1.11.2-1 +- Update to 1.11.2 (#1973591) + +* Thu May 13 2021 Jeff Law - 1.11.1-2 +- Re-enable LTO + +* Tue Apr 27 2021 David King - 1.11.1-1 +- Update to 1.11.1 (#1953833) + +* Wed Apr 14 2021 Kalev Lember - 1.10.2-3 +- Disable system env generator to work around selinux denials (#1947214) + +* Mon Apr 05 2021 Kalev Lember - 1.10.2-2 +- OCI: Switch to pax format for tar archives + +* Wed Mar 10 2021 Kalev Lember - 1.10.2-1 +- Update to 1.10.2 + +* Tue Mar 02 2021 Zbigniew Jędrzejewski-Szmek - 1.10.1-4 +- Rebuilt for updated systemd-rpm-macros + See https://pagure.io/fesco/issue/2583. + +* Fri Feb 12 2021 Kalev Lember - 1.10.1-3 +- Add G_BEGIN_DECLS/G_END_DECLS to public headers (#1927439) +- Drop unneeded ldconfig_scriptlets macro call + +* Tue Jan 26 2021 Fedora Release Engineering - 1.10.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Thu Jan 21 2021 Kalev Lember - 1.10.1-1 +- Update to 1.10.1 + +* Thu Jan 14 2021 Kalev Lember - 1.10.0-1 +- Update to 1.10.0 + +* Mon Jan 11 2021 Kalev Lember - 1.9.3-2 +- Use "Fedora Flatpaks" as the visible repo name + +* Tue Dec 22 2020 David King - 1.9.3-1 +- Update to 1.9.3 (#1910054) + +* Fri Nov 20 2020 Kalev Lember - 1.9.2-1 +- Update to 1.9.2 + +* Thu Nov 19 2020 Kalev Lember - 1.9.1-1 +- Update to 1.9.1 + +* Wed Nov 18 2020 David King - 1.8.3-2 +- Drop obsolete Requires on system-release + +* Tue Nov 17 2020 Kalev Lember - 1.8.3-1 +- Update to 1.8.3 + +* Sat Oct 31 2020 Jeff Law - 1.8.2-3 +- Fix bogus volatiles caught by gcc-11 + +* Fri Sep 11 2020 Kalev Lember - 1.8.2-2 +- Backport various OCI fixes from upstream + +* Fri Aug 21 2020 Kalev Lember - 1.8.2-1 +- Update to 1.8.2 + +* Mon Jul 27 2020 Fedora Release Engineering - 1.8.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Fri Jul 03 2020 David King - 1.8.1-1 +- Update to 1.8.1 (#1853667) + +* Tue Jun 30 2020 Jeff Law - 1.8.0-2 +- Disable LTO + +* Wed Jun 24 2020 David King - 1.8.0-1 +- Update to 1.8.0 (#1850676) + +* Wed Jun 10 2020 David King - 1.7.3-1 +- Update to 1.7.3 (#1820762) + +* Fri Apr 03 2020 Kalev Lember - 1.7.2-1 +- Update to 1.7.2 + +* Mon Mar 30 2020 David King - 1.7.1-1 +- Update to 1.7.1 (#1818882) + +* Mon Mar 30 2020 Kalev Lember - 1.6.3-1 +- Update to 1.6.3 + +* Thu Feb 13 2020 David King - 1.6.2-1 +- Update to 1.6.2 (#1802609) + +* Tue Jan 28 2020 Fedora Release Engineering - 1.6.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Thu Jan 23 2020 David King - 1.6.1-1 +- Update to 1.6.1 + +* Fri Jan 17 2020 David King - 1.6.0-2 +- Remove broken python3 sed hack + +* Fri Dec 20 2019 David King - 1.6.0-1 +- Update to 1.6.0 + +* Mon Dec 16 2019 David King - 1.5.2-1 +- Update to 1.5.2 + +* Thu Nov 28 2019 David King - 1.5.1-1 +- Update to 1.5.1 + +* Fri Nov 01 2019 Orion Poplawski - 1.5.0-2 +- Use %%{?selinux_requires} for proper install ordering + +* Thu Oct 03 2019 David King - 1.5.0-1 +- Update to 1.5.0 + +* Thu Sep 19 2019 Kalev Lember - 1.4.3-1 +- Update to 1.4.3 + +* Wed Sep 18 2019 Debarshi Ray - 1.4.2-6 +- Trim unused shared library linkages from the session helper + +* Wed Aug 7 2019 Owen Taylor - 1.4.2-5 +- Add patch fixing problem with downloading icons for OCI remotes (#1683375) + +* Thu Jul 25 2019 Tim Zabel - 1.4.2-4 +- SELinux needs additional Requires (#1732132) + +* Thu Jul 25 2019 Fedora Release Engineering - 1.4.2-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Tue Jul 09 2019 Kalev Lember - 1.4.2-2 +- Backport a patch that fixes a fairly large memory leak in gnome-software + +* Fri Jun 28 2019 David King - 1.4.2-1 +- Update to 1.4.2 (#1725071) + +* Tue Jun 25 2019 David King - 1.4.1-3 +- Use Requires(post) for selinux-policy (#1723118) + +* Tue Jun 25 2019 Debarshi Ray - 1.4.1-2 +- Split the session helper into a separate sub-package + +* Thu Jun 13 2019 Kalev Lember - 1.4.1-1 +- Update to 1.4.1 + +* Wed Jun 12 2019 Kalev Lember - 1.4.0-2 +- Backport an upstream patch to fix gnome-software CI + +* Tue May 28 2019 Kalev Lember - 1.4.0-1 +- Update to 1.4.0 + +* Fri May 10 2019 Kalev Lember - 1.3.4-1 +- Update to 1.3.4 + +* Tue Apr 30 2019 David King - 1.3.3-2 +- Generate consistent anchor IDs + +* Fri Apr 26 2019 David King - 1.3.3-1 +- Update to 1.3.3 (#1699338) + +* Wed Apr 17 2019 Igor Gnatenko - 1.3.2-2 +- Fixup selinux requires + +* Fri Apr 12 2019 David King - 1.3.2-1 +- Update to 1.3.2 (#1699338) + +* Wed Apr 03 2019 Kalev Lember - 1.3.1-2 +- Add a oneshot systemd service to add Fedora flatpak repos +- Remove the post script to create system repo now that we have the service + +* Wed Mar 27 2019 David King - 1.3.1-1 +- Update to 1.3.1 (#1693207) + +* Tue Mar 12 2019 David King - 1.3.0-1 +- Update to 1.3.0 + +* Thu Feb 14 2019 David King - 1.2.3-2 +- Remove an obsolete Conflicts +- Use xdg-dbus-proxy + +* Mon Feb 11 2019 David King - 1.2.3-1 +- Update to 1.2.3 + +* Wed Feb 06 2019 David King - 1.2.2-1 +- Update to 1.2.2 + +* Tue Feb 05 2019 Kalev Lember - 1.2.1-1 +- Update to 1.2.1 + +* Mon Feb 4 2019 fedora-toolbox - 1.2.0-4 +- Add an upstream patch to add flatpak build-export --disable-sandbox + +* Thu Jan 31 2019 Bastien Nocera - 1.2.0-3 +- Require librsvg2 so SVG icons can be exported + +* Tue Jan 29 2019 Kalev Lember - 1.2.0-2 +- Enable libsystemd support + +* Mon Jan 28 2019 David King - 1.2.0-1 +- Update to 1.2.0 + +* Tue Jan 15 2019 Kalev Lember - 1.1.3-1 +- Update to 1.1.3 + +* Fri Dec 21 2018 David King - 1.1.2-1 +- Update to 1.1.2 + +* Mon Dec 17 2018 David King - 1.1.1-2 +- Enable installed tests and add to tests subpackage + +* Mon Dec 10 2018 Kalev Lember - 1.1.1-1 +- Update to 1.1.1 + +* Fri Nov 30 2018 fedora-toolbox - 1.0.6-3 +- Add a patch to fix OCI system remotes +- Add patch fixing permissions on icons downloaded from an OCI registry + +* Fri Nov 16 2018 Kalev Lember - 1.0.6-1 +- Update to 1.0.6 + +* Mon Nov 12 2018 Kalev Lember - 1.0.5-2 +- Recommend p11-kit-server instead of just p11-kit (#1649049) + +* Mon Nov 12 2018 Kalev Lember - 1.0.5-1 +- Update to 1.0.5 + +* Fri Oct 12 2018 Kalev Lember - 1.0.4-1 +- Update to 1.0.4 + +* Thu Oct 04 2018 Kalev Lember - 1.0.3-1 +- Update to 1.0.3 + +* Thu Sep 13 2018 Kalev Lember - 1.0.2-1 +- Update to 1.0.2 + +* Tue Aug 28 2018 David King - 1.0.1-1 +- Update to 1.0.1 + +* Mon Aug 20 2018 David King - 1.0.0-2 +- Fix double dash in XML documentation + +* Mon Aug 20 2018 David King - 1.0.0-1 +- Update to 1.0.0 + +* Fri Jul 13 2018 Fedora Release Engineering - 0.99.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Tue Jul 10 2018 Kalev Lember - 0.99.3-1 +- Update to 0.99.3 + +* Wed Jun 27 2018 Kalev Lember - 0.99.2-1 +- Update to 0.99.2 + +* Thu Jun 21 2018 David King - 0.99.1-1 +- Update to 0.99.1 + +* Wed Jun 13 2018 David King - 0.11.8.3-1 +- Update to 0.11.8.3 (#1590808) + +* Mon Jun 11 2018 David King - 0.11.8.2-1 +- Update to 0.11.8.2 (#1589810) + +* Fri Jun 08 2018 David King - 0.11.8.1-1 +- Update to 0.11.8.1 (#1588868) + +* Fri Jun 08 2018 David King - 0.11.8-1 +- Update to 0.11.8 (#1588868) + +* Wed May 23 2018 Adam Jackson - 0.11.7-2 +- Remove Requires: kernel >= 4.0.4-202, which corresponds to rawhide + somewhere before Fedora 22 which this spec file certainly no longer + supports. + +* Thu May 03 2018 Kalev Lember - 0.11.7-1 +- Update to 0.11.7 + +* Wed May 02 2018 Kalev Lember - 0.11.6-1 +- Update to 0.11.6 + +* Wed May 02 2018 Kalev Lember - 0.11.5-2 +- Backport a fix for a gnome-software crash installing .flatpakref files + +* Mon Apr 30 2018 David King - 0.11.5-1 +- Update to 0.11.5 + +* Thu Apr 26 2018 Kalev Lember - 0.11.4-1 +- Update to 0.11.4 + +* Mon Feb 19 2018 David King - 0.11.3-1 +- Update to 0.11.3 + +* Mon Feb 19 2018 David King - 0.11.2-1 +- Update to 0.11.2 + +* Wed Feb 14 2018 David King - 0.11.1-1 +- Update to 0.11.1 (#1545224) + +* Wed Feb 07 2018 Fedora Release Engineering - 0.10.3-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Fri Feb 02 2018 Igor Gnatenko - 0.10.3-2 +- Switch to %%ldconfig_scriptlets + +* Tue Jan 30 2018 Kalev Lember - 0.10.3-1 +- Update to 0.10.3 + +* Thu Dec 21 2017 David King - 0.10.2.1-1 +- Update to 0.10.2.1 + +* Fri Dec 15 2017 Kalev Lember - 0.10.2-1 +- Update to 0.10.2 + +* Fri Nov 24 2017 David King - 0.10.1-1 +- Update to 0.10.1 + +* Thu Oct 26 2017 Kalev Lember - 0.10.0-1 +- Update to 0.10.0 + +* Mon Oct 09 2017 Kalev Lember - 0.9.99-1 +- Update to 0.9.99 + +* Tue Sep 26 2017 Kalev Lember - 0.9.98.2-1 +- Update to 0.9.98.2 + +* Tue Sep 26 2017 Kalev Lember - 0.9.98.1-1 +- Update to 0.9.98.1 + +* Mon Sep 25 2017 Kalev Lember - 0.9.98-1 +- Update to 0.9.98 + +* Thu Sep 14 2017 Kalev Lember - 0.9.12-1 +- Update to 0.9.12 + +* Wed Sep 13 2017 Kalev Lember - 0.9.11-1 +- Update to 0.9.11 + +* Mon Sep 04 2017 Kalev Lember - 0.9.10-1 +- Update to 0.9.10 +- Split out flatpak-builder to a separate source package + +* Fri Aug 25 2017 Kalev Lember - 0.9.8-2 +- Backport a patch to fix regression in --devel + +* Mon Aug 21 2017 David King - 0.9.8-1 +- Update to 0.9.8 + +* Wed Aug 02 2017 Fedora Release Engineering - 0.9.7-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Sun Jul 30 2017 Florian Weimer - 0.9.7-4 +- Rebuild with binutils fix for ppc64le (#1475636) + +* Thu Jul 27 2017 Owen Taylor - 0.9.7-3 +- Add a patch to fix OCI refname annotation + +* Wed Jul 26 2017 Fedora Release Engineering - 0.9.7-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Sat Jul 01 2017 David King - 0.9.7-1 +- Update to 0.9.7 (#1466970) + +* Tue Jun 20 2017 David King - 0.9.6-1 +- Update to 0.9.6 + +* Sat Jun 10 2017 David King - 0.9.5-1 +- Update to 0.9.5 (#1460437) + +* Tue May 23 2017 David King - 0.9.4-1 +- Update to 0.9.4 (#1454750) + +* Mon Apr 24 2017 David King - 0.9.3-1 +- Update to 0.9.3 + +* Fri Apr 07 2017 David King - 0.9.2-2 +- Add eu-strip dependency for flatpak-builder + +* Wed Apr 05 2017 Kalev Lember - 0.9.2-1 +- Update to 0.9.2 + +* Wed Mar 15 2017 Kalev Lember - 0.9.1-1 +- Update to 0.9.1 + +* Fri Mar 10 2017 Kalev Lember - 0.8.4-1 +- Update to 0.8.4 + +* Sun Feb 19 2017 David King - 0.8.3-3 +- Make flatpak-builder require bzip2 (#1424857) + +* Wed Feb 15 2017 Kalev Lember - 0.8.3-2 +- Avoid pulling in all of ostree and only depend on ostree-libs subpackage + +* Tue Feb 14 2017 Kalev Lember - 0.8.3-1 +- Update to 0.8.3 + +* Fri Feb 10 2017 Fedora Release Engineering - 0.8.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Fri Jan 27 2017 Kalev Lember - 0.8.2-1 +- Update to 0.8.2 + +* Wed Jan 18 2017 David King - 0.8.1-1 +- Update to 0.8.1 + +* Tue Dec 20 2016 Kalev Lember - 0.8.0-1 +- Update to 0.8.0 + +* Tue Nov 29 2016 David King - 0.6.14-2 +- Add a patch to fix a GNOME Software crash +- Silence repository listing during post + +* Tue Nov 29 2016 Kalev Lember - 0.6.14-1 +- Update to 0.6.14 + +* Wed Oct 26 2016 David King - 0.6.13-2 +- Add empty /etc/flatpak/remotes.d + +* Tue Oct 25 2016 David King - 0.6.13-1 +- Update to 0.6.13 + +* Thu Oct 06 2016 David King - 0.6.12-1 +- Update to 0.6.12 + +* Tue Sep 20 2016 Kalev Lember - 0.6.11-1 +- Update to 0.6.11 +- Set minimum ostree and bubblewrap versions + +* Mon Sep 12 2016 David King - 0.6.10-1 +- Update to 0.6.10 + +* Tue Sep 06 2016 David King - 0.6.9-2 +- Look for bwrap in PATH + +* Thu Aug 25 2016 David King - 0.6.9-1 +- Update to 0.6.9 + +* Mon Aug 01 2016 David King - 0.6.8-1 +- Update to 0.6.8 (#1361823) + +* Thu Jul 21 2016 David King - 0.6.7-2 +- Use system bubblewrap + +* Fri Jul 01 2016 David King - 0.6.7-1 +- Update to 0.6.7 + +* Thu Jun 23 2016 David King - 0.6.6-1 +- Update to 0.6.6 + +* Fri Jun 10 2016 David King - 0.6.5-1 +- Update to 0.6.5 + +* Wed Jun 01 2016 David King - 0.6.4-1 +- Update to 0.6.4 + +* Tue May 31 2016 David King - 0.6.3-1 +- Update to 0.6.3 +- Move bwrap to main package + +* Tue May 24 2016 David King - 0.6.2-1 +- Rename from xdg-app to flatpak (#1337434) diff --git a/flatpak.sysusers.conf b/flatpak.sysusers.conf new file mode 100644 index 0000000..8203ed5 --- /dev/null +++ b/flatpak.sysusers.conf @@ -0,0 +1 @@ +u flatpak - "Flatpak system helper" - diff --git a/sources b/sources new file mode 100644 index 0000000..593d603 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +SHA512 (flatpak-1.16.0.tar.xz) = 57a8b660193ef1f9724718533963d854fa8bb0eb823470261f3f0a685f8ddbd209d6a1ae8378411c131e9c298cba605863d394f43c8d9eccda608001aadbb68d