Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package combustion for openSUSE:Factory checked in at 2023-08-30 10:17:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/combustion (Old) and /work/SRC/openSUSE:Factory/.combustion.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "combustion" Wed Aug 30 10:17:40 2023 rev:20 rq:1105495 version:1.2+git2 Changes: -------- --- /work/SRC/openSUSE:Factory/combustion/combustion.changes 2023-08-04 15:02:59.876121428 +0200 +++ /work/SRC/openSUSE:Factory/.combustion.new.1766/combustion.changes 2023-08-30 10:18:32.743999044 +0200 @@ -1,0 +2,21 @@ +Tue Aug 22 15:57:49 UTC 2023 - Fabian Vogt <fv...@suse.com> + +- Update to version 1.2+git2: + * Don't consider /var/lib/YaST2/reconfig_system for firstboot detection + * Use improved x-initrd.mount code in firstboot-detect as well + +------------------------------------------------------------------- +Mon Aug 21 06:35:50 UTC 2023 - Fabian Vogt <fv...@suse.com> + +- Update to version 1.2: + * Don't remove YaST reconfig_system marker if called from ignition + * Remove now obsolete workaround for ignition-mount.service ExecStop + * Set dasd_mod.dasd=autodetect in modprobe.d + * Omit combustion module in initrds for already configured systems + * New module to handle firstboot detection in the initrd (poo#127196, + jsc#PED-5843) + * Fixes for use with ignition-kargs-helper + * Improve x-initrd.mount handling + * Work around systemd issue with emergency.target at the root + +------------------------------------------------------------------- Old: ---- combustion-1.1+git0.obscpio New: ---- combustion-1.2+git2.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ combustion.spec ++++++ --- /var/tmp/diff_new_pack.1Iiiy5/_old 2023-08-30 10:18:34.120048151 +0200 +++ /var/tmp/diff_new_pack.1Iiiy5/_new 2023-08-30 10:18:34.124048293 +0200 @@ -17,14 +17,15 @@ Name: combustion -Version: 1.1+git0 +Version: 1.2+git2 Release: 0 Summary: System for initial configuration of appliances License: GPL-2.0-or-later Group: System/Management URL: https://github.com/openSUSE/combustion Source0: %{name}-%{version}.tar.xz -Requires: ignition-dracut-grub2 +# This doesn't work with old ignition,conflict with the old firstboot mechanism. +Conflicts: ignition-dracut-grub2 BuildArch: noarch %description @@ -56,6 +57,9 @@ %doc README.md %dir %{_prefix}/lib/dracut/ %dir %{_prefix}/lib/dracut/modules.d/ +# Also used by ignition. Could be split into a separate package, +# but ignition depends on combustion anyway. +%{_prefix}/lib/dracut/modules.d/30firstboot/ %{_prefix}/lib/dracut/modules.d/35combustion/ %changelog ++++++ _service ++++++ --- /var/tmp/diff_new_pack.1Iiiy5/_old 2023-08-30 10:18:34.156049435 +0200 +++ /var/tmp/diff_new_pack.1Iiiy5/_new 2023-08-30 10:18:34.160049578 +0200 @@ -4,7 +4,7 @@ <param name="scm">git</param> <param name="changesgenerate">enable</param> <param name="versionformat">@PARENT_TAG@+git@TAG_OFFSET@</param> - <param name="versionrewrite-pattern">v(.*)</param> + <param name="versionrewrite-pattern">v(.*?)(\+git0)?$</param> </service> <service mode="disabled" name="set_version" /> <service mode="buildtime" name="tar" /> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.1Iiiy5/_old 2023-08-30 10:18:34.180050292 +0200 +++ /var/tmp/diff_new_pack.1Iiiy5/_new 2023-08-30 10:18:34.184050435 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/openSUSE/combustion.git</param> - <param name="changesrevision">a7be9f0f6e3d85fb44f36f809a734cae0e455fca</param></service></servicedata> + <param name="changesrevision">09b719e283fe6505de11a5745cafec6681bb0c5a</param></service></servicedata> (No newline at EOF) ++++++ combustion-1.1+git0.obscpio -> combustion-1.2+git2.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/30firstboot/firstboot-detect new/combustion-1.2+git2/30firstboot/firstboot-detect --- old/combustion-1.1+git0/30firstboot/firstboot-detect 1970-01-01 01:00:00.000000000 +0100 +++ new/combustion-1.2+git2/30firstboot/firstboot-detect 2023-08-22 17:56:39.000000000 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh +set -eu +what=$(systemctl show -P What sysroot.mount) +opts=$(systemctl show -P Options sysroot.mount) +mount -o "$opts" "$what" /sysroot + +# Handle x-initrd.mount without initrd-parse-etc.service +awk '$1 !~ /^#/ && $4 ~ /(\<|,)x-initrd\.mount(\>|,)/ { if(system("mount --target-prefix /sysroot --fstab /sysroot/etc/fstab " $2) != 0) exit 1; }' /sysroot/etc/fstab + +if ! [ -e /sysroot/etc/machine-id ] \ + || grep -qw 'ignition\.firstboot' /proc/cmdline || grep -qw 'combustion\.firstboot' /proc/cmdline; then + echo "Firstboot detected" + # Make initrd.target require firstboot.target + systemctl enable --quiet firstboot.target + # As initrd.target/start was already scheduled, ^ does not have any immediate effect. + # Triggering a start of initrd.target again schedules the missing jobs. + # With --job-mode=fail this fails if any of those missing jobs is destructive, likely + # caused by dep cycles in firstboot units. + systemctl start --now --no-block --job-mode=fail initrd.target +fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/30firstboot/firstboot-detect.service new/combustion-1.2+git2/30firstboot/firstboot-detect.service --- old/combustion-1.1+git0/30firstboot/firstboot-detect.service 1970-01-01 01:00:00.000000000 +0100 +++ new/combustion-1.2+git2/30firstboot/firstboot-detect.service 2023-08-22 17:56:39.000000000 +0200 @@ -0,0 +1,33 @@ +[Unit] +Description=Detect Firstboot +DefaultDependencies=no + +# This needs /sysroot to be mountable +# Should also include systemd-fsck-root, but that would create a cycle +# with dracut-initqueue below. +Requires=initrd-root-device.target +After=initrd-root-device.target + +# But before it's actually mounted +Before=sysroot.mount + +# Combustion/ignition may configure networking, which runs during +# the initqueue (wicked) or has its own service (NM) +Before=dracut-initqueue.service nm-initrd.service + +# Make sure this is stopped before switch root or emergency: +# https://github.com/systemd/systemd/issues/3436 +Conflicts=initrd-switch-root.target umount.target +Conflicts=dracut-emergency.service emergency.service emergency.target + +[Service] +Type=oneshot +# This has to mount /sysroot as /sysroot, but starting sysroot.mount would +# screw up ordering +PrivateMounts=true +# Work around https://github.com/systemd/systemd/issues/28723 +TemporaryFileSystem=/run/mount +ExecStart=/usr/bin/firstboot-detect + +[Install] +RequiredBy=initrd.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/30firstboot/firstboot.target new/combustion-1.2+git2/30firstboot/firstboot.target --- old/combustion-1.1+git0/30firstboot/firstboot.target 1970-01-01 01:00:00.000000000 +0100 +++ new/combustion-1.2+git2/30firstboot/firstboot.target 2023-08-22 17:56:39.000000000 +0200 @@ -0,0 +1,10 @@ +[Unit] +Description=Initrd Firstboot + +# Make sure this is stopped before switch root or emergency: +# https://github.com/systemd/systemd/issues/3436 +Conflicts=initrd-switch-root.target umount.target +Conflicts=dracut-emergency.service emergency.service emergency.target + +[Install] +RequiredBy=initrd.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/30firstboot/module-setup.sh new/combustion-1.2+git2/30firstboot/module-setup.sh --- old/combustion-1.1+git0/30firstboot/module-setup.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/combustion-1.2+git2/30firstboot/module-setup.sh 2023-08-22 17:56:39.000000000 +0200 @@ -0,0 +1,20 @@ +check() { + # Pulled in by other modules only + return 255 +} + +depends() { + echo bash systemd +} + +install() { + inst_simple "${moddir}/firstboot-detect.service" "${systemdsystemunitdir}/firstboot-detect.service" + inst_simple "${moddir}/firstboot.target" "${systemdsystemunitdir}/firstboot.target" + inst_simple "${moddir}/firstboot-detect" "/usr/bin/firstboot-detect" + $SYSTEMCTL -q --root "$initdir" enable firstboot-detect.service + inst_multiple awk grep mount + + # Work around https://github.com/systemd/systemd/pull/28718 + mkdir -p "${initdir}/${systemdsystemunitdir}/initrd-parse-etc.service.d/" + echo -e "[Unit]\nConflicts=emergency.target" > "${initdir}/${systemdsystemunitdir}/initrd-parse-etc.service.d/emergency.conf" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/Makefile new/combustion-1.2+git2/Makefile --- old/combustion-1.1+git0/Makefile 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/Makefile 2023-08-22 17:56:39.000000000 +0200 @@ -1,10 +1,19 @@ +FB_MODULEDIR ?= $(DESTDIR)/usr/lib/dracut/modules.d/30firstboot MODULEDIR ?= $(DESTDIR)/usr/lib/dracut/modules.d/35combustion all: -install: +install-30firstboot: + install -Dm0644 30firstboot/module-setup.sh $(FB_MODULEDIR)/module-setup.sh + install -Dm0644 30firstboot/firstboot.target $(FB_MODULEDIR)/firstboot.target + install -Dm0755 30firstboot/firstboot-detect $(FB_MODULEDIR)/firstboot-detect + install -Dm0644 30firstboot/firstboot-detect.service $(FB_MODULEDIR)/firstboot-detect.service + +install: install-30firstboot install -Dm0644 module-setup.sh $(MODULEDIR)/module-setup.sh install -Dm0644 combustion.service $(MODULEDIR)/combustion.service install -Dm0644 combustion-prepare.service $(MODULEDIR)/combustion-prepare.service install -Dm0755 combustion $(MODULEDIR)/combustion install -Dm0644 combustion.rules $(MODULEDIR)/combustion.rules + +.PHONY: all install install-30firstboot diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/README.md new/combustion-1.2+git2/README.md --- old/combustion-1.1+git0/README.md 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/README.md 2023-08-22 17:56:39.000000000 +0200 @@ -144,21 +144,50 @@ How it works ------------ -The ignition-dracut-grub2 package contains a GRUB2 config snippet which adds -`ignition.firstboot` to the kernel commandline unless the -`/boot/writable/firstboot_happened` flag file exists. - -If this option is found on the kernel cmdline, combustion.service's -ConditionKernelCommandLine is fulfilled and it'll be required by initrd.target. -This pulls in combustion-prepare.service, which runs after the config drive or -QEMU fw_cfg blob appears (see combustion.rules for details). The combustion -configuration is copied from the config source into /dev/shm/combustion/config +### Firstboot detection + +Combustion ships a `firstboot` dracut module which introduces `firstboot.target` +as well as a `firstboot-detect.service`. This service mounts `/sysroot/etc` +early in the initrd (in a private namespace, to not trigger `.mount` units and +dependencies out of order). It then checks for the following conditions: + +* `combustion.firstboot` or `ignition.firstboot` are present on the kernel +cmdline +* `/etc/machine-id` does not exist in `/sysroot`. Note: Unlike systemd's +`ConditionFirstBoot`, this is not triggered by "uninitialized" in the +machine-id file or influcenced by `systemd.firstboot=` on the kernel cmdline. + +If one of them applies, it enables and starts `firstboot.target`. It's +important that all units started by `firstboot.target` are effectively +ordered after `firstboot-detect.service` to prevent loops. Add some Before= +to `firstboot-detect.service` if necessary. + +If any of the firstboot configuration mechanisms (combustion, ignition) +find a user specified config, they delete `/var/lib/YaST2/reconfig_system`. +The result is that if (and only if) no configuration for those was provided, +the file still exists in the real system and triggers interactive setup +(jeos-firstboot, YaST Firstboot) if present. + +The final system eventually reaches `first-boot-complete.target` and after that +`systemd-machine-id-commit.service`, which commits `/etc/machine-id` to disk and +thus `firstboot-detect.service` no longer triggers `firstboot.target` on +subsequent boots. + +### Combustion + +The `combustion` dracut module is included by default, but omitted if dracut is +run on an already configured system in `hostonly` mode. + +`firstboot.target` pulls in `combustion.service` and +`combustion-prepare.service`. The latter runs after the config drive or +QEMU fw_cfg blob appears (see `combustion.rules` for details). The combustion +configuration is copied from the config source into `/dev/shm/combustion/config` (this is accessible in `transactional-update shell` later). If the script -contains the `prepare` flag, it's executed now with the `--prepare` option. -If the `network` flag is present, networking is enabled in the initrd. -After /sysroot is mounted and network is up (if enabled), combustion.service -runs, which tries to activate all mountpoints in the system's /etc/fstab and -then calls transactional-update in a chroot. +contains the `prepare` flag, it's executed now with the `--prepare` option. If +the `network` flag is present, networking is enabled in the initrd. After +`/sysroot` is mounted and network is up (if enabled), `combustion.service` runs, +which tries to activate all mountpoints in the system's /etc/fstab and then +calls transactional-update in a chroot. In this transactional-update session the script is started and the exit code recorded. If the script failed, transactional-update rollback is called and @@ -169,6 +198,6 @@ /sysroot is unmounted and mounted again, so that the default subvolume gets reevaluated and directly booted into. -The ignition-firstboot-complete service in the final system runs, which creates -the firstboot_happened flag file. Thus combustion does not run on subsequent -boots. +Now, `initrd-parse-etc.service` can evaluate the final `/sysroot/etc/fstab` and +create the matching `sysroot-FOO.mount` units which are started before switching +into the root filesystem. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/combustion new/combustion-1.2+git2/combustion --- old/combustion-1.1+git0/combustion 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/combustion 2023-08-22 17:56:39.000000000 +0200 @@ -98,6 +98,15 @@ exit 0 fi +# Set by combustion.service but not ignition-kargs-helper. +# Controls whether config is actually completed by this step, +# triggering deletion of /var/lib/YaST2/reconfig_system +complete=0 + +if [ "${1-}" = "--complete" ]; then + complete=1 +fi + delete_resolv_conf=0 cleanup() { @@ -129,6 +138,7 @@ # "/run/combustion/mount/combustion" and then calls combustion if [ -d "${config_mount}/combustion" ]; then rm -rf "${config_dir}" + mkdir -p "${exchangedir}" cp -R "${config_mount}/combustion" "${config_dir}" chmod a+x "${config_dir}/script" fi @@ -143,8 +153,9 @@ systemctl start sysroot-usr.mount fi -# Have to take care of x-initrd.mount first and from the outside -awk '$4 ~ /x-initrd.mount/ { system("findmnt /sysroot" $2 " >/dev/null || mount -t " $3 " -o " $4 " " $1 " /sysroot" $2) }' /sysroot/etc/fstab +# Have to take care of x-initrd.mount first and from the outside. +# Note: ignition-kargs-helper calls combustion but already mounted those itself. +awk '$1 !~ /^#/ && $4 ~ /(\<|,)x-initrd\.mount(\>|,)/ { if(system("findmnt /sysroot/" $2 " >/dev/null || mount --target-prefix /sysroot --fstab /sysroot/etc/fstab " $2) != 0) exit 1; }' /sysroot/etc/fstab # Make sure the old snapshot is relabeled too, otherwise syncing its /etc fails. if [ -e /lib/dracut/hooks/pre-pivot/50-selinux-microos-relabel.sh ]; then @@ -206,6 +217,8 @@ chroot /sysroot snapper --no-dbus create -d "After combustion configuration" || : fi -rm -f /sysroot/var/lib/YaST2/reconfig_system +if [ "${complete}" -eq 1 ]; then + rm -f /sysroot/var/lib/YaST2/reconfig_system +fi exit 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/combustion-prepare.service new/combustion-1.2+git2/combustion-prepare.service --- old/combustion-1.1+git0/combustion-prepare.service 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/combustion-prepare.service 2023-08-22 17:56:39.000000000 +0200 @@ -2,11 +2,6 @@ Description=Combustion (preparations) DefaultDependencies=false -# Systemd evaluates Requires/After before conditions, so this unit is pulled in -# even when combustion.service won't run. -ConditionKernelCommandLine=|ignition.firstboot -ConditionKernelCommandLine=|combustion.firstboot - # Config drive has to be available Wants=dev-combustion-config.device After=dev-combustion-config.device @@ -23,10 +18,6 @@ Conflicts=initrd-switch-root.target umount.target Conflicts=dracut-emergency.service emergency.service emergency.target -# Without this it goes into an endless loop on failure -OnFailure=emergency.target -OnFailureJobMode=isolate - [Service] Type=oneshot ExecStart=/usr/bin/combustion --prepare diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/combustion.rules new/combustion-1.2+git2/combustion.rules --- old/combustion-1.1+git0/combustion.rules 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/combustion.rules 2023-08-22 17:56:39.000000000 +0200 @@ -1,11 +1,8 @@ # SPDX-FileCopyrightText: 2020 SUSE LLC # SPDX-License-Identifier: GPL-2.0-or-later -# These rules are needed to work around two systemd limitations: -# - It's not possible to wait for one of multiple devices to appear -# - ConditionKernelCommandLine is evaluated after Wants/After, -# so it waits for the devices unnecessarily -# Introduce a dev-combustion-config.device unit as alias to the actual device(s). +# It's not possible to wait for one of multiple devices to appear, so +# introduce a dev-combustion-config.device unit as alias to the actual device(s). # This is only used for the .service dependencies. # Filesystems with either combustion or ignition as label @@ -17,15 +14,3 @@ # There are add events for keys inside fw_cfg, but they are unreliable: https://github.com/systemd/systemd/issues/28638 # Using the platform device with add|bind does not work with TAG+="systemd" for some reason, so use the module... ACTION=="add", SUBSYSTEM=="module", KERNEL=="qemu_fw_cfg", TEST=="/sys/firmware/qemu_fw_cfg/by_name/opt/org.opensuse.combustion", ENV{SYSTEMD_ALIAS}+="/dev/combustion/config", TAG+="systemd" - -# If combustion won't run, alias it to /dev/null to avoid waiting -ACTION=="add", SUBSYSTEM=="mem", ENV{DEVPATH}=="/devices/virtual/mem/null", GOTO="combustion_dev_null" -GOTO="combustion_end" - -LABEL="combustion_dev_null" -# IMPORT has to be on its own as it returns success or not, even with "="... -IMPORT{cmdline}="ignition.firstboot" -IMPORT{cmdline}="combustion.firstboot" -ENV{ignition.firstboot}!="1", ENV{combustion.firstboot}!="1", ENV{SYSTEMD_ALIAS}+="/dev/combustion/config", TAG+="systemd" - -LABEL="combustion_end" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/combustion.service new/combustion-1.2+git2/combustion.service --- old/combustion-1.1+git0/combustion.service 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/combustion.service 2023-08-22 17:56:39.000000000 +0200 @@ -2,9 +2,6 @@ Description=Combustion DefaultDependencies=false -ConditionKernelCommandLine=|ignition.firstboot -ConditionKernelCommandLine=|combustion.firstboot - # /sysroot needs to be available, but it's temporarily stopped # for remounting so a direct requirement is not possible Requires=initrd-root-device.target @@ -23,16 +20,15 @@ # So that /etc/fstab's x-initrd.mount entries are read (again) later Before=initrd-parse-etc.service +# Without DefaultDependencies the target would be reached without us +Before=firstboot.target + Conflicts=initrd-switch-root.target umount.target Conflicts=dracut-emergency.service emergency.service emergency.target -# Without this it goes into an endless loop on failure -OnFailure=emergency.target -OnFailureJobMode=isolate - [Service] Type=oneshot -ExecStart=/usr/bin/combustion +ExecStart=/usr/bin/combustion --complete [Install] -RequiredBy=initrd.target +RequiredBy=firstboot.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/combustion-1.1+git0/module-setup.sh new/combustion-1.2+git2/module-setup.sh --- old/combustion-1.1+git0/module-setup.sh 2023-08-03 09:17:48.000000000 +0200 +++ new/combustion-1.2+git2/module-setup.sh 2023-08-22 17:56:39.000000000 +0200 @@ -1,5 +1,13 @@ +check() { + # Omit if building for this already configured system + if [[ $hostonly ]] && [ -e /etc/machine-id ]; then + return 255 + fi + return 0 +} + depends() { - echo bash network systemd url-lib + echo bash firstboot network systemd url-lib } install() { @@ -10,11 +18,9 @@ inst_multiple awk chroot findmnt grep rmdir inst_simple "${moddir}/combustion" "/usr/bin/combustion" - # ignition-mount.service mounts stuff below /sysroot in ExecStart and umounts - # it on ExecStop, failing if umounting fails. This conflicts with the - # mounts/umounts done by combustion. Just let combustion do it instead. - mkdir -p "${initdir}/${systemdsystemunitdir}/ignition-mount.service.d/" - echo -e "[Service]\nExecStop=" > "${initdir}/${systemdsystemunitdir}/ignition-mount.service.d/noexecstop.conf" + # Autodetect dasd devices on s390x to discover the config drive + mkdir -p "${initdir}/etc/modprobe.d" + echo "options dasd_mod dasd=autodetect" > "${initdir}/etc/modprobe.d/dasd-autodetect.conf" # Wait up to 10s (30s on aarch64) for the config drive devtimeout=10 ++++++ combustion.obsinfo ++++++ --- /var/tmp/diff_new_pack.1Iiiy5/_old 2023-08-30 10:18:34.272053575 +0200 +++ /var/tmp/diff_new_pack.1Iiiy5/_new 2023-08-30 10:18:34.276053718 +0200 @@ -1,5 +1,5 @@ name: combustion -version: 1.1+git0 -mtime: 1691047068 -commit: a7be9f0f6e3d85fb44f36f809a734cae0e455fca +version: 1.2+git2 +mtime: 1692719799 +commit: 09b719e283fe6505de11a5745cafec6681bb0c5a