diff --git a/.gitignore b/.gitignore
index 869130b..52ddd23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-SOURCES/libostree-2022.1.tar.xz
+SOURCES/libostree-2022.2.tar.xz
diff --git a/.ostree.metadata b/.ostree.metadata
index 7b8dd7f..56ab50b 100644
--- a/.ostree.metadata
+++ b/.ostree.metadata
@@ -1 +1 @@
-31380c30eeb93de7d9850fa8a071b3fbc3f3acee SOURCES/libostree-2022.1.tar.xz
+9f1cc3796da8b7892a8ef930a5086d4ff42c475f SOURCES/libostree-2022.2.tar.xz
diff --git a/SOURCES/0001-Add-an-ostree-boot-complete.service-to-propagate-sta.patch b/SOURCES/0001-Add-an-ostree-boot-complete.service-to-propagate-sta.patch
new file mode 100644
index 0000000..2cf14d7
--- /dev/null
+++ b/SOURCES/0001-Add-an-ostree-boot-complete.service-to-propagate-sta.patch
@@ -0,0 +1,374 @@
+From a6d45dc165e48e2a463880ebb90f34c2b9d3c4ce Mon Sep 17 00:00:00 2001
+From: Colin Walters <walters@verbum.org>
+Date: Fri, 22 Apr 2022 18:46:28 -0400
+Subject: [PATCH 1/6] Add an `ostree-boot-complete.service` to propagate
+ staging failures
+
+Quite a while ago we added staged deployments, which solved
+a bunch of issues around the `/etc` merge.  However...a persistent
+problem since then is that any failures in that process that
+happened in the *previous* boot are not very visible.
+
+We ship custom code in `rpm-ostree status` to query the previous
+journal.  But that has a few problems - one is that on systems
+that have been up a while, that failure message may even get
+rotated out.  And second, some systems may not even have a persistent
+journal at all.
+
+A general thing we do in e.g. Fedora CoreOS testing is to check
+for systemd unit failures.  We do that both in our automated tests,
+and we even ship code that displays them on ssh logins.  And beyond
+that obviously a lot of other projects do the same; it's easy via
+`systemctl --failed`.
+
+So to make failures more visible, change our `ostree-finalize-staged.service`
+to have an internal wrapper around the process that "catches" any
+errors, and copies the error message into a file in `/boot/ostree`.
+
+Then, a new `ostree-boot-complete.service` looks for this file on
+startup and re-emits the error message, and fails.
+
+It also deletes the file.  The rationale is to avoid *continually*
+warning.  For example we need to handle the case when an upgrade
+process creates a new staged deployment.  Now, we could change the
+ostree core code to delete the warning file when that happens instead,
+but this is trying to be a conservative change.
+
+This should make failures here much more visible as is.
+---
+ Makefile-boot.am                             |  2 +
+ Makefile-ostree.am                           |  1 +
+ src/boot/ostree-boot-complete.service        | 33 +++++++++++
+ src/libostree/ostree-cmdprivate.c            |  1 +
+ src/libostree/ostree-cmdprivate.h            |  1 +
+ src/libostree/ostree-impl-system-generator.c |  2 +
+ src/libostree/ostree-sysroot-deploy.c        | 62 ++++++++++++++++++--
+ src/libostree/ostree-sysroot-private.h       |  7 +++
+ src/libostree/ostree-sysroot.c               |  2 +
+ src/ostree/ot-admin-builtin-boot-complete.c  | 58 ++++++++++++++++++
+ src/ostree/ot-admin-builtins.h               |  1 +
+ src/ostree/ot-builtin-admin.c                |  3 +
+ tests/kolainst/destructive/staged-deploy.sh  | 12 ++++
+ 13 files changed, 181 insertions(+), 4 deletions(-)
+ create mode 100644 src/boot/ostree-boot-complete.service
+ create mode 100644 src/ostree/ot-admin-builtin-boot-complete.c
+
+diff --git a/Makefile-boot.am b/Makefile-boot.am
+index ec10a0d6..e42e5180 100644
+--- a/Makefile-boot.am
++++ b/Makefile-boot.am
+@@ -38,6 +38,7 @@ endif
+ if BUILDOPT_SYSTEMD
+ systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \
+ 	src/boot/ostree-remount.service \
++	src/boot/ostree-boot-complete.service \
+ 	src/boot/ostree-finalize-staged.service \
+ 	src/boot/ostree-finalize-staged.path \
+ 	$(NULL)
+@@ -64,6 +65,7 @@ endif
+ EXTRA_DIST += src/boot/dracut/module-setup.sh \
+ 	src/boot/dracut/ostree.conf \
+ 	src/boot/mkinitcpio \
++	src/boot/ostree-boot-complete.service \
+ 	src/boot/ostree-prepare-root.service \
+ 	src/boot/ostree-finalize-staged.path \
+ 	src/boot/ostree-remount.service \
+diff --git a/Makefile-ostree.am b/Makefile-ostree.am
+index 82af1681..0fe2c5f8 100644
+--- a/Makefile-ostree.am
++++ b/Makefile-ostree.am
+@@ -70,6 +70,7 @@ ostree_SOURCES += \
+ 	src/ostree/ot-admin-builtin-diff.c \
+ 	src/ostree/ot-admin-builtin-deploy.c \
+ 	src/ostree/ot-admin-builtin-finalize-staged.c \
++	src/ostree/ot-admin-builtin-boot-complete.c \
+ 	src/ostree/ot-admin-builtin-undeploy.c \
+ 	src/ostree/ot-admin-builtin-instutil.c \
+ 	src/ostree/ot-admin-builtin-cleanup.c \
+diff --git a/src/boot/ostree-boot-complete.service b/src/boot/ostree-boot-complete.service
+new file mode 100644
+index 00000000..5c09fdc9
+--- /dev/null
++++ b/src/boot/ostree-boot-complete.service
+@@ -0,0 +1,33 @@
++# Copyright (C) 2022 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, see <https://www.gnu.org/licenses/>.
++
++[Unit]
++Description=OSTree Complete Boot
++Documentation=man:ostree(1)
++# For now, this is the only condition on which we start, but it's
++# marked as a triggering condition in case in the future we want
++# to do something else.
++ConditionPathExists=|/boot/ostree/finalize-failure.stamp
++RequiresMountsFor=/boot
++# Ensure that we propagate the failure into the current boot before
++# any further finalization attempts.
++Before=ostree-finalize-staged.service
++
++[Service]
++Type=oneshot
++# To write to /boot while keeping it read-only
++MountFlags=slave
++RemainAfterExit=yes
++ExecStart=/usr/bin/ostree admin boot-complete
+diff --git a/src/libostree/ostree-cmdprivate.c b/src/libostree/ostree-cmdprivate.c
+index c9a6e2e1..f6c114f4 100644
+--- a/src/libostree/ostree-cmdprivate.c
++++ b/src/libostree/ostree-cmdprivate.c
+@@ -51,6 +51,7 @@ ostree_cmd__private__ (void)
+     _ostree_repo_static_delta_delete,
+     _ostree_repo_verify_bindings,
+     _ostree_sysroot_finalize_staged,
++    _ostree_sysroot_boot_complete,
+   };
+ 
+   return &table;
+diff --git a/src/libostree/ostree-cmdprivate.h b/src/libostree/ostree-cmdprivate.h
+index 46452ebd..17f943c8 100644
+--- a/src/libostree/ostree-cmdprivate.h
++++ b/src/libostree/ostree-cmdprivate.h
+@@ -33,6 +33,7 @@ typedef struct {
+   gboolean (* ostree_static_delta_delete) (OstreeRepo *repo, const char *delta_id, GCancellable *cancellable, GError **error);
+   gboolean (* ostree_repo_verify_bindings) (const char *collection_id, const char *ref_name, GVariant *commit, GError **error);
+   gboolean (* ostree_finalize_staged) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error);
++  gboolean (* ostree_boot_complete) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error);
+ } OstreeCmdPrivateVTable;
+ 
+ /* Note this not really "public", we just export the symbol, but not the header */
+diff --git a/src/libostree/ostree-impl-system-generator.c b/src/libostree/ostree-impl-system-generator.c
+index 769f0cbd..92d71605 100644
+--- a/src/libostree/ostree-impl-system-generator.c
++++ b/src/libostree/ostree-impl-system-generator.c
+@@ -134,6 +134,8 @@ require_internal_units (const char *normal_dir,
+     return FALSE;
+   if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-finalize-staged.path", normal_dir_dfd, "multi-user.target.wants/ostree-finalize-staged.path") < 0)
+     return glnx_throw_errno_prefix (error, "symlinkat");
++  if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-boot-complete.service", normal_dir_dfd, "multi-user.target.wants/ostree-boot-complete.service") < 0)
++    return glnx_throw_errno_prefix (error, "symlinkat");
+ 
+   return TRUE;
+ #else
+diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
+index b7cc232f..fc5916d8 100644
+--- a/src/libostree/ostree-sysroot-deploy.c
++++ b/src/libostree/ostree-sysroot-deploy.c
+@@ -3255,10 +3255,10 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot     *self,
+ }
+ 
+ /* Invoked at shutdown time by ostree-finalize-staged.service */
+-gboolean
+-_ostree_sysroot_finalize_staged (OstreeSysroot *self,
+-                                 GCancellable  *cancellable,
+-                                 GError       **error)
++static gboolean
++_ostree_sysroot_finalize_staged_inner (OstreeSysroot *self,
++                                       GCancellable  *cancellable,
++                                       GError       **error)
+ {
+   /* It's totally fine if there's no staged deployment; perhaps down the line
+    * though we could teach the ostree cmdline to tell systemd to activate the
+@@ -3355,9 +3355,63 @@ _ostree_sysroot_finalize_staged (OstreeSysroot *self,
+   if (!ostree_sysroot_prepare_cleanup (self, cancellable, error))
+     return FALSE;
+ 
++  // Cleanup will have closed some FDs, re-ensure writability
++  if (!_ostree_sysroot_ensure_writable (self, error))
++    return FALSE;
++
+   return TRUE;
+ }
+ 
++/* Invoked at shutdown time by ostree-finalize-staged.service */
++gboolean
++_ostree_sysroot_finalize_staged (OstreeSysroot *self,
++                                 GCancellable  *cancellable,
++                                 GError       **error)
++{
++  g_autoptr(GError) finalization_error = NULL;
++  if (!_ostree_sysroot_ensure_boot_fd (self, error))
++    return FALSE;
++  if (!_ostree_sysroot_finalize_staged_inner (self, cancellable, &finalization_error))
++    {
++      g_autoptr(GError) writing_error = NULL;
++      g_assert_cmpint (self->boot_fd, !=, -1);
++      if (!glnx_file_replace_contents_at (self->boot_fd, _OSTREE_FINALIZE_STAGED_FAILURE_PATH, 
++                                           (guint8*)finalization_error->message, -1,
++                                           0, cancellable, &writing_error))
++        {
++          // We somehow failed to write the failure message...that's not great.  Maybe ENOSPC on /boot.
++          g_printerr ("Failed to write %s: %s\n", _OSTREE_FINALIZE_STAGED_FAILURE_PATH, writing_error->message);
++        }
++      g_propagate_error (error, g_steal_pointer (&finalization_error));
++      return FALSE;
++    }
++  return TRUE;
++}
++
++/* Invoked at bootup time by ostree-boot-complete.service */
++gboolean
++_ostree_sysroot_boot_complete (OstreeSysroot *self,
++                               GCancellable  *cancellable,
++                               GError       **error)
++{
++  if (!_ostree_sysroot_ensure_boot_fd (self, error))
++    return FALSE;
++
++  glnx_autofd int failure_fd = -1;
++  if (!ot_openat_ignore_enoent (self->boot_fd, _OSTREE_FINALIZE_STAGED_FAILURE_PATH, &failure_fd, error))
++    return FALSE;
++  // If we didn't find a failure log, then there's nothing to do right now.
++  // (Actually this unit shouldn't even be invoked, but we may do more in the future)
++  if (failure_fd == -1)
++    return TRUE;
++  g_autofree char *failure_data = glnx_fd_readall_utf8 (failure_fd, NULL, cancellable, error);
++  if (failure_data == NULL)
++    return glnx_prefix_error (error, "Reading from %s", _OSTREE_FINALIZE_STAGED_FAILURE_PATH);
++  // Remove the file; we don't want to continually error out.
++  (void) unlinkat (self->boot_fd, _OSTREE_FINALIZE_STAGED_FAILURE_PATH, 0);
++  return glnx_throw (error, "ostree-finalize-staged.service failed on previous boot: %s", failure_data);
++}
++
+ /**
+  * ostree_sysroot_deployment_set_kargs:
+  * @self: Sysroot
+diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h
+index cb34eeb3..a49a406c 100644
+--- a/src/libostree/ostree-sysroot-private.h
++++ b/src/libostree/ostree-sysroot-private.h
+@@ -96,6 +96,9 @@ struct OstreeSysroot {
+ #define _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "ostree/initramfs-overlays"
+ #define _OSTREE_SYSROOT_INITRAMFS_OVERLAYS "boot/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS
+ 
++// Relative to /boot, consumed by ostree-boot-complete.service
++#define _OSTREE_FINALIZE_STAGED_FAILURE_PATH "ostree/finalize-failure.stamp"
++
+ gboolean
+ _ostree_sysroot_ensure_writable (OstreeSysroot      *self,
+                                  GError            **error);
+@@ -142,6 +145,10 @@ gboolean
+ _ostree_sysroot_finalize_staged (OstreeSysroot *self,
+                                  GCancellable  *cancellable,
+                                  GError       **error);
++gboolean
++_ostree_sysroot_boot_complete (OstreeSysroot *self,
++                               GCancellable  *cancellable,
++                               GError       **error);
+ 
+ OstreeDeployment *
+ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
+diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c
+index 266a2975..f083f950 100644
+--- a/src/libostree/ostree-sysroot.c
++++ b/src/libostree/ostree-sysroot.c
+@@ -356,6 +356,8 @@ _ostree_sysroot_ensure_writable (OstreeSysroot      *self,
+   ostree_sysroot_unload (self);
+   if (!ensure_sysroot_fd (self, error))
+     return FALSE;
++  if (!_ostree_sysroot_ensure_boot_fd (self, error))
++    return FALSE;
+ 
+   return TRUE;
+ }
+diff --git a/src/ostree/ot-admin-builtin-boot-complete.c b/src/ostree/ot-admin-builtin-boot-complete.c
+new file mode 100644
+index 00000000..6e1052f5
+--- /dev/null
++++ b/src/ostree/ot-admin-builtin-boot-complete.c
+@@ -0,0 +1,58 @@
++/*
++ * Copyright (C) 2022 Red Hat, Inc.
++ *
++ * SPDX-License-Identifier: LGPL-2.0+
++ *
++ * 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, see <https://www.gnu.org/licenses/>.
++ */
++
++#include "config.h"
++
++#include <stdlib.h>
++
++#include "ot-main.h"
++#include "ot-admin-builtins.h"
++#include "ot-admin-functions.h"
++#include "ostree.h"
++#include "otutil.h"
++
++#include "ostree-cmdprivate.h"
++
++static GOptionEntry options[] = {
++  { NULL }
++};
++
++gboolean
++ot_admin_builtin_boot_complete (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
++{
++  /* Just a sanity check; we shouldn't be called outside of the service though.
++   */
++  struct stat stbuf;
++  if (fstatat (AT_FDCWD, OSTREE_PATH_BOOTED, &stbuf, 0) < 0)
++    return TRUE;
++  // We must have been invoked via systemd which should have set up a mount namespace.
++  g_assert (getenv ("INVOCATION_ID"));
++
++  g_autoptr(GOptionContext) context = g_option_context_new ("");
++  g_autoptr(OstreeSysroot) sysroot = NULL;
++  if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
++                                          OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER,
++                                          invocation, &sysroot, cancellable, error))
++    return FALSE;
++
++  if (!ostree_cmd__private__()->ostree_boot_complete (sysroot, cancellable, error))
++    return FALSE;
++
++  return TRUE;
++}
+diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
+index d32b617e..8d9451be 100644
+--- a/src/ostree/ot-admin-builtins.h
++++ b/src/ostree/ot-admin-builtins.h
+@@ -39,6 +39,7 @@ BUILTINPROTO(deploy);
+ BUILTINPROTO(cleanup);
+ BUILTINPROTO(pin);
+ BUILTINPROTO(finalize_staged);
++BUILTINPROTO(boot_complete);
+ BUILTINPROTO(unlock);
+ BUILTINPROTO(status);
+ BUILTINPROTO(set_origin);
+diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c
+index e0d2a60c..af09a614 100644
+--- a/src/ostree/ot-builtin-admin.c
++++ b/src/ostree/ot-builtin-admin.c
+@@ -43,6 +43,9 @@ static OstreeCommand admin_subcommands[] = {
+   { "finalize-staged", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
+     ot_admin_builtin_finalize_staged,
+     "Internal command to run at shutdown time" },
++  { "boot-complete", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
++    ot_admin_builtin_boot_complete,
++    "Internal command to run at boot after an update was applied" },
+   { "init-fs", OSTREE_BUILTIN_FLAG_NO_REPO,
+      ot_admin_builtin_init_fs,
+     "Initialize a root filesystem" },
diff --git a/SOURCES/0002-libarchive-Handle-archive_entry_symlink-returning-NU.patch b/SOURCES/0002-libarchive-Handle-archive_entry_symlink-returning-NU.patch
new file mode 100644
index 0000000..7c283e9
--- /dev/null
+++ b/SOURCES/0002-libarchive-Handle-archive_entry_symlink-returning-NU.patch
@@ -0,0 +1,40 @@
+From e5b45f861a4d5738679f37d46ebca6e171bb3212 Mon Sep 17 00:00:00 2001
+From: Colin Walters <walters@verbum.org>
+Date: Mon, 4 Apr 2022 10:25:35 -0400
+Subject: [PATCH 2/6] libarchive: Handle `archive_entry_symlink()` returning
+ NULL
+
+The `archive_entry_symlink()` API can definitely return `NULL`,
+reading through the libarchive sources.
+
+I hit this in the wild when using old ostree-ext to try to unpack
+a chunked archive.
+
+I didn't try to characterize this more, and sorry no unit test right
+now.
+---
+ src/libostree/ostree-repo-libarchive.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/src/libostree/ostree-repo-libarchive.c b/src/libostree/ostree-repo-libarchive.c
+index 679aa44d..631c6d4b 100644
+--- a/src/libostree/ostree-repo-libarchive.c
++++ b/src/libostree/ostree-repo-libarchive.c
+@@ -146,8 +146,12 @@ file_info_from_archive_entry (struct archive_entry *entry)
+ 
+   g_autoptr(GFileInfo) info = _ostree_stbuf_to_gfileinfo (&stbuf);
+   if (S_ISLNK (stbuf.st_mode))
+-    g_file_info_set_attribute_byte_string (info, "standard::symlink-target",
+-                                           archive_entry_symlink (entry));
++    {
++      const char *target = archive_entry_symlink (entry);
++      if (target != NULL)
++        g_file_info_set_attribute_byte_string (info, "standard::symlink-target",
++                                               target);
++    }
+ 
+   return g_steal_pointer (&info);
+ }
+-- 
+2.31.1
+
diff --git a/SOURCES/0003-repo-Factor-out-_ostree_repo_auto_transaction_new.patch b/SOURCES/0003-repo-Factor-out-_ostree_repo_auto_transaction_new.patch
new file mode 100644
index 0000000..69c5548
--- /dev/null
+++ b/SOURCES/0003-repo-Factor-out-_ostree_repo_auto_transaction_new.patch
@@ -0,0 +1,82 @@
+From 4a997ae08605ebe6ca02d9f422082f954e667a6c Mon Sep 17 00:00:00 2001
+From: Simon McVittie <smcv@collabora.com>
+Date: Sat, 30 Apr 2022 12:20:11 +0100
+Subject: [PATCH 3/6] repo: Factor out _ostree_repo_auto_transaction_new()
+
+This will allow the direct allocation in
+ostree_repo_prepare_transaction() to be replaced with a call to this
+function, avoiding breaking encapsulation.
+
+Signed-off-by: Simon McVittie <smcv@collabora.com>
+(cherry picked from commit 540e60c3e3ace66dd4e6cf825488fc918260a642)
+---
+ src/libostree/ostree-repo-private.h |  4 ++++
+ src/libostree/ostree-repo.c         | 32 ++++++++++++++++++++++++-----
+ 2 files changed, 31 insertions(+), 5 deletions(-)
+
+diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h
+index 988c2179..96253e77 100644
+--- a/src/libostree/ostree-repo-private.h
++++ b/src/libostree/ostree-repo-private.h
+@@ -554,4 +554,8 @@ GType _ostree_repo_auto_transaction_get_type (void);
+ 
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoTransaction, _ostree_repo_auto_transaction_unref);
+ 
++/* Internal function to break a circular dependency:
++ * should not be made into public API, even if the rest is */
++OstreeRepoAutoTransaction *_ostree_repo_auto_transaction_new (OstreeRepo *repo);
++
+ G_END_DECLS
+diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
+index a27591b3..f6bffd60 100644
+--- a/src/libostree/ostree-repo.c
++++ b/src/libostree/ostree-repo.c
+@@ -709,6 +709,32 @@ ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *auto_lock)
+     }
+ }
+ 
++/**
++ * _ostree_repo_auto_transaction_new:
++ * @repo: (not nullable): an #OsreeRepo object
++ * @cancellable: Cancellable
++ * @error: a #GError
++ *
++ * Return a guard for a transaction in @repo.
++ *
++ * Do not call this function outside the OstreeRepo transaction implementation.
++ * Use _ostree_repo_auto_transaction_start() instead.
++ *
++ * Returns: (transfer full): an #OstreeRepoAutoTransaction guard on success,
++ * %NULL otherwise.
++ */
++OstreeRepoAutoTransaction *
++_ostree_repo_auto_transaction_new (OstreeRepo *repo)
++{
++  g_assert (repo != NULL);
++
++  OstreeRepoAutoTransaction *txn = g_malloc(sizeof(OstreeRepoAutoTransaction));
++  txn->atomic_refcount = 1;
++  txn->repo = g_object_ref (repo);
++
++  return g_steal_pointer (&txn);
++}
++
+ /**
+  * _ostree_repo_auto_transaction_start:
+  * @repo: (not nullable): an #OsreeRepo object
+@@ -730,11 +756,7 @@ _ostree_repo_auto_transaction_start (OstreeRepo     *repo,
+   if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+     return NULL;
+ 
+-  OstreeRepoAutoTransaction *txn = g_malloc(sizeof(OstreeRepoAutoTransaction));
+-  txn->atomic_refcount = 1;
+-  txn->repo = g_object_ref (repo);
+-
+-  return g_steal_pointer (&txn);
++  return _ostree_repo_auto_transaction_new (repo);
+ }
+ 
+ /**
+-- 
+2.31.1
+
diff --git a/SOURCES/0004-repo-Correctly-initialize-refcount-of-temporary-tran.patch b/SOURCES/0004-repo-Correctly-initialize-refcount-of-temporary-tran.patch
new file mode 100644
index 0000000..3e8c33b
--- /dev/null
+++ b/SOURCES/0004-repo-Correctly-initialize-refcount-of-temporary-tran.patch
@@ -0,0 +1,39 @@
+From 51c7960bea081446ad217e9725408ce5cb531157 Mon Sep 17 00:00:00 2001
+From: Simon McVittie <smcv@collabora.com>
+Date: Sat, 30 Apr 2022 12:53:42 +0100
+Subject: [PATCH 4/6] repo: Correctly initialize refcount of temporary
+ transaction
+
+Previously, the reference count was left uninitialized as a result of
+bypassing the constructor, and the intended abort-on-error usually
+wouldn't have happened.
+
+Fixes: 8a9737a "repo/private: move OstreeRepoAutoTransaction to a boxed type"
+Resolves: https://github.com/ostreedev/ostree/issues/2592
+Signed-off-by: Simon McVittie <smcv@collabora.com>
+(cherry picked from commit 71304e854cdb344adb8b1ae7866929fbdde6c327)
+---
+ src/libostree/ostree-repo-commit.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c
+index 5b16be5b..dba98c32 100644
+--- a/src/libostree/ostree-repo-commit.c
++++ b/src/libostree/ostree-repo-commit.c
+@@ -1688,10 +1688,10 @@ ostree_repo_prepare_transaction (OstreeRepo     *self,
+   g_debug ("Preparing transaction in repository %p", self);
+ 
+   /* Set up to abort the transaction if we return early from this function.
+-   * This needs to be manually built here due to a circular dependency. */
+-  g_autoptr(OstreeRepoAutoTransaction) txn = g_malloc(sizeof(OstreeRepoAutoTransaction));
++   * We can't call _ostree_repo_auto_transaction_start() here, because that
++   * would be a circular dependency; use the lower-level version instead. */
++  g_autoptr(OstreeRepoAutoTransaction) txn = _ostree_repo_auto_transaction_new (self);
+   g_assert (txn != NULL);
+-  txn->repo = self;
+ 
+   memset (&self->txn.stats, 0, sizeof (OstreeRepoTransactionStats));
+ 
+-- 
+2.31.1
+
diff --git a/SOURCES/0005-deploy-Try-to-rebuild-policy-in-new-deployment-if-ne.patch b/SOURCES/0005-deploy-Try-to-rebuild-policy-in-new-deployment-if-ne.patch
new file mode 100644
index 0000000..59f86fb
--- /dev/null
+++ b/SOURCES/0005-deploy-Try-to-rebuild-policy-in-new-deployment-if-ne.patch
@@ -0,0 +1,172 @@
+From 62e62bcfd8a1770b906faed083d11e451a50f566 Mon Sep 17 00:00:00 2001
+From: Ondrej Mosnacek <omosnace@redhat.com>
+Date: Wed, 9 Mar 2022 15:27:11 +0100
+Subject: [PATCH 5/6] deploy: Try to rebuild policy in new deployment if needed
+
+Whenever the user has SELinux enabled and has any local
+modules/modifications installed, it is necessary to rebuild the policy
+in the final deployment, otherwise ostree will leave the binary policy
+files unchanged from last deployment as it detects difference against
+the base content (in rpm-ostree case this is the RPM content).
+
+To avoid the situation where the policy binaries go stale once any local
+customization of the policy is made, try to rebuild the policy as part
+of sysroot_finalize_deployment(). Use the special
+--rebuild-if-modules-changed switch, which detects if the input module
+files have changed relative to last time the policy was built and skips
+the most time-consuming part of the rebuild process if modules are
+unchanged (thus making this a relatively cheap operation if the user
+hasn't made any modifications to the shipped policy).
+
+As suggested by Jonathan Lebon, this uses bubblewrap (via
+g_spawn_sync()) to perform the rebuild inside the deployment's
+filesystem tree, which also means that ostree will have a runtime
+dependency on bubblewrap.
+
+Partially addresses: https://github.com/coreos/fedora-coreos-tracker/issues/701
+
+Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
+(cherry picked from commit edb4f3893474736156c654aa43bdbf3784991811)
+---
+ ci/gh-install.sh                      |   1 +
+ src/libostree/ostree-sysroot-deploy.c | 117 ++++++++++++++++++++++++++
+ 2 files changed, 118 insertions(+)
+
+diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
+index fc5916d8..a44721d8 100644
+--- a/src/libostree/ostree-sysroot-deploy.c
++++ b/src/libostree/ostree-sysroot-deploy.c
+@@ -2830,6 +2830,118 @@ get_var_dfd (OstreeSysroot      *self,
+   return glnx_opendirat (base_dfd, base_path, TRUE, ret_fd, error);
+ }
+ 
++#ifdef HAVE_SELINUX
++static void
++child_setup_fchdir (gpointer data)
++{
++  int fd = (int) (uintptr_t) data;
++  int rc __attribute__((unused));
++
++  rc = fchdir (fd);
++}
++
++/*
++ * Derived from rpm-ostree's rust/src/bwrap.rs
++ */
++static gboolean
++run_in_deployment (int deployment_dfd,
++                   const gchar * const *child_argv,
++                   gsize child_argc,
++                   gint *exit_status,
++                   gchar **stdout,
++                   GError **error)
++{
++  static const gchar * const COMMON_ARGV[] = {
++    "/usr/bin/bwrap",
++    "--dev", "/dev", "--proc", "/proc", "--dir", "/run", "--dir", "/tmp",
++    "--chdir", "/",
++    "--die-with-parent",
++    "--unshare-pid",
++    "--unshare-uts",
++    "--unshare-ipc",
++    "--unshare-cgroup-try",
++    "--ro-bind", "/sys/block",    "/sys/block",
++    "--ro-bind", "/sys/bus",      "/sys/bus",
++    "--ro-bind", "/sys/class",    "/sys/class",
++    "--ro-bind", "/sys/dev",      "/sys/dev",
++    "--ro-bind", "/sys/devices",  "/sys/devices",
++    "--bind", "usr", "/usr",
++    "--bind", "etc", "/etc",
++    "--bind", "var", "/var",
++    "--symlink", "/usr/lib",      "/lib",
++    "--symlink", "/usr/lib32",    "/lib32",
++    "--symlink", "/usr/lib64",    "/lib64",
++    "--symlink", "/usr/bin",      "/bin",
++    "--symlink", "/usr/sbin",     "/sbin",
++  };
++  static const gsize COMMON_ARGC = sizeof (COMMON_ARGV) / sizeof (*COMMON_ARGV);
++
++  gsize i;
++  GPtrArray *args = g_ptr_array_sized_new (COMMON_ARGC + child_argc + 1);
++  g_autofree gchar **args_raw = NULL;
++
++  for (i = 0; i < COMMON_ARGC; i++)
++    g_ptr_array_add (args, (gchar *) COMMON_ARGV[i]);
++
++  for (i = 0; i < child_argc; i++)
++    g_ptr_array_add (args, (gchar *) child_argv[i]);
++
++  g_ptr_array_add (args, NULL);
++
++  args_raw = (gchar **) g_ptr_array_free (args, FALSE);
++
++  return g_spawn_sync (NULL, args_raw, NULL, 0, &child_setup_fchdir,
++                       (gpointer) (uintptr_t) deployment_dfd,
++                       stdout, NULL, exit_status, error);
++}
++
++/*
++ * Run semodule to check if the module content changed after merging /etc
++ * and rebuild the policy if needed.
++ */
++static gboolean
++sysroot_finalize_selinux_policy (int deployment_dfd, GError **error)
++{
++  struct stat stbuf;
++  gint exit_status;
++  g_autofree gchar *stdout = NULL;
++
++  if (!glnx_fstatat_allow_noent (deployment_dfd, "etc/selinux/config", &stbuf,
++                                 AT_SYMLINK_NOFOLLOW, error))
++    return FALSE;
++
++  /* Skip the SELinux policy refresh if /etc/selinux/config doesn't exist. */
++  if (errno != 0)
++    return TRUE;
++
++  /*
++   * Skip the SELinux policy refresh if the --rebuild-if-modules-changed
++   * flag is not supported by semodule.
++   */
++  static const gchar * const SEMODULE_HELP_ARGV[] = {
++    "semodule", "--help"
++  };
++  static const gsize SEMODULE_HELP_ARGC = sizeof (SEMODULE_HELP_ARGV) / sizeof (*SEMODULE_HELP_ARGV);
++  if (!run_in_deployment (deployment_dfd, SEMODULE_HELP_ARGV,
++                          SEMODULE_HELP_ARGC, &exit_status, &stdout, error))
++    return FALSE;
++  if (!g_spawn_check_exit_status (exit_status, error))
++    return FALSE;
++  if (!strstr(stdout, "--rebuild-if-modules-changed"))
++    return TRUE;
++
++  static const gchar * const SEMODULE_REBUILD_ARGV[] = {
++    "semodule", "-N", "--rebuild-if-modules-changed"
++  };
++  static const gsize SEMODULE_REBUILD_ARGC = sizeof (SEMODULE_REBUILD_ARGV) / sizeof (*SEMODULE_REBUILD_ARGV);
++
++  if (!run_in_deployment (deployment_dfd, SEMODULE_REBUILD_ARGV,
++                          SEMODULE_REBUILD_ARGC, &exit_status, NULL, error))
++    return FALSE;
++  return g_spawn_check_exit_status (exit_status, error);
++}
++#endif /* HAVE_SELINUX */
++
+ static gboolean
+ sysroot_finalize_deployment (OstreeSysroot     *self,
+                              OstreeDeployment  *deployment,
+@@ -2866,6 +2978,11 @@ sysroot_finalize_deployment (OstreeSysroot     *self,
+         return FALSE;
+     }
+ 
++#ifdef HAVE_SELINUX
++  if (!sysroot_finalize_selinux_policy(deployment_dfd, error))
++    return FALSE;
++#endif /* HAVE_SELINUX */
++
+   const char *osdeploypath = glnx_strjoina ("ostree/deploy/", ostree_deployment_get_osname (deployment));
+   glnx_autofd int os_deploy_dfd = -1;
+   if (!glnx_opendirat (self->sysroot_fd, osdeploypath, TRUE, &os_deploy_dfd, error))
+-- 
+2.31.1
+
diff --git a/SOURCES/0006-deploy-Be-a-bit-more-verbose-about-SELinux-bits.patch b/SOURCES/0006-deploy-Be-a-bit-more-verbose-about-SELinux-bits.patch
new file mode 100644
index 0000000..f963cb5
--- /dev/null
+++ b/SOURCES/0006-deploy-Be-a-bit-more-verbose-about-SELinux-bits.patch
@@ -0,0 +1,35 @@
+From dd194eca7272afa457541abb2d8c25f90c4f478a Mon Sep 17 00:00:00 2001
+From: Colin Walters <walters@verbum.org>
+Date: Mon, 28 Mar 2022 17:46:59 -0400
+Subject: [PATCH 6/6] deploy: Be a bit more verbose about SELinux bits
+
+Let's log when we don't find the expected CLI argument which
+will help debug things.
+
+(cherry picked from commit c58a4fe661d9d3bf2c515aa5605b1e094c0a62ca)
+---
+ src/libostree/ostree-sysroot-deploy.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
+index a44721d8..404f336f 100644
+--- a/src/libostree/ostree-sysroot-deploy.c
++++ b/src/libostree/ostree-sysroot-deploy.c
+@@ -2926,9 +2926,12 @@ sysroot_finalize_selinux_policy (int deployment_dfd, GError **error)
+                           SEMODULE_HELP_ARGC, &exit_status, &stdout, error))
+     return FALSE;
+   if (!g_spawn_check_exit_status (exit_status, error))
+-    return FALSE;
++    return glnx_prefix_error (error, "failed to run semodule");
+   if (!strstr(stdout, "--rebuild-if-modules-changed"))
+-    return TRUE;
++    {
++      ot_journal_print (LOG_INFO, "semodule does not have --rebuild-if-modules-changed");
++      return TRUE;
++    }
+ 
+   static const gchar * const SEMODULE_REBUILD_ARGV[] = {
+     "semodule", "-N", "--rebuild-if-modules-changed"
+-- 
+2.31.1
+
diff --git a/SPECS/ostree.spec b/SPECS/ostree.spec
index 6bec58c..a86168e 100644
--- a/SPECS/ostree.spec
+++ b/SPECS/ostree.spec
@@ -7,12 +7,21 @@
 
 Summary: Tool for managing bootable, immutable filesystem trees
 Name: ostree
-Version: 2022.1
-Release: 2%{?dist}
+Version: 2022.2
+Release: 4%{?dist}
 Source0: https://github.com/ostreedev/%{name}/releases/download/v%{version}/libostree-%{version}.tar.xz
 License: LGPLv2+
 URL: https://ostree.readthedocs.io/en/latest/
 
+# We now track the rhel8 branch upstream, these are the patches
+# since the 2022.2 release.
+Patch0: 0001-Add-an-ostree-boot-complete.service-to-propagate-sta.patch
+Patch1: 0002-libarchive-Handle-archive_entry_symlink-returning-NU.patch
+Patch2: 0003-repo-Factor-out-_ostree_repo_auto_transaction_new.patch
+Patch3: 0004-repo-Correctly-initialize-refcount-of-temporary-tran.patch
+Patch4: 0005-deploy-Try-to-rebuild-policy-in-new-deployment-if-ne.patch
+Patch5: 0006-deploy-Be-a-bit-more-verbose-about-SELinux-bits.patch
+
 BuildRequires: make
 BuildRequires: git
 # We always run autogen.sh
@@ -163,6 +172,14 @@ find %{buildroot} -name '*.la' -delete
 %endif
 
 %changelog
+* Wed May 04 2022 Colin Walters <walters@verbum.org> - 2022.2-4
+- Backport patches from 2022.3, particularly SELinux
+  Resolves: rhbz#2057497
+
+* Tue Apr 19 2022 Colin Walters <walters@verbum.org> - 2022.2-3
+- https://github.com/ostreedev/ostree/releases/tag/v2022.2
+  Resolves: rhbz#2057497
+
 * Mon Jan 10 2022 Colin Walters <walters@verbum.org> - 2022.1-2
 - Rebase to 2022.1
   Resolves: rhbz#2032593