diff --git a/client/installation-helper/Makefile.am b/client/installation-helper/Makefile.am index bc50f1e0..022bc8cd 100644 --- a/client/installation-helper/Makefile.am +++ b/client/installation-helper/Makefile.am @@ -2,7 +2,7 @@ # Makefile.am for snapper/client/installation-helper # -AM_CPPFLAGS = -I$(top_srcdir) +AM_CPPFLAGS = -I$(top_srcdir) $(XML2_CFLAGS) libexecdir = /usr/lib/snapper diff --git a/client/installation-helper/installation-helper.cc b/client/installation-helper/installation-helper.cc index 5ff13f9d..ac30b184 100644 --- a/client/installation-helper/installation-helper.cc +++ b/client/installation-helper/installation-helper.cc @@ -1,6 +1,6 @@ /* * Copyright (c) 2015 Novell, Inc. - * Copyright (c) [2018-2022] SUSE LLC + * Copyright (c) [2018-2024] SUSE LLC * * All Rights Reserved. * @@ -25,10 +25,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -37,6 +37,7 @@ #include #include #include "snapper/Log.h" +#include "snapper/XmlFile.h" #include "../utils/GetOpts.h" @@ -265,6 +266,239 @@ step5(const string& root_prefix, const string& snapshot_type, unsigned int pre_n } +// Without the rollback support of libsnapper some btrfs utils are undefined. +#ifdef ENABLE_ROLLBACK + + +bool +step_filesystem(const string& root_prefix) +{ + // create subvolume //.snapshots + + cout << "creating btrfs subvolume //.snapshots" << endl; + + try + { + int fd = open(prepend_root_prefix(root_prefix, "/").c_str(), + O_RDONLY | O_NOATIME | O_CLOEXEC); + if (fd < 0) + SN_THROW(Exception("open failed")); + + create_subvolume(fd, ".snapshots"); + + close(fd); + } + catch (const exception& e) + { + cerr << "creating //.snapshots failed" << endl; + + return false; + } + + // create //.snapshots/1 + + cout << "creating directory //.snapshots/1" << endl; + + try + { + if (mkdir(prepend_root_prefix(root_prefix, "/.snapshots/1").c_str(), 0777) != 0) + SN_THROW(Exception("mkdir failed")); + } + catch (const exception& e) + { + cerr << "creating //.snapshots/1 failed" << endl; + + return false; + } + + // create btrfs subvolume //.snapshots/1/snapshot + + cout << "creating btrfs subvolume //.snapshots/1/snapshot" << endl; + + try + { + int fd = open(prepend_root_prefix(root_prefix, "/.snapshots/1").c_str(), + O_RDONLY | O_NOATIME | O_CLOEXEC); + if (fd < 0) + SN_THROW(Exception("open failed")); + + create_subvolume(fd, "snapshot"); + + close(fd); + } + catch (const exception& e) + { + cerr << "creating //.snapshots/1/snapshot failed" << endl; + + return false; + } + + // set default btrfs subvolume to //.snapshots/1/snapshot + + cout << "setting default btrfs subvolume to //.snapshots/1/snapshot" << endl; + + try + { + int fd = open(prepend_root_prefix(root_prefix, "/.snapshots/1/snapshot").c_str(), + O_RDONLY | O_NOATIME | O_CLOEXEC); + if (fd < 0) + SN_THROW(Exception("open failed")); + + subvolid_t id = get_id(fd); + + set_default_id(fd, id); + + close(fd); + } + catch (const exception& e) + { + cerr << "setting default subvolume failed" << endl; + + return false; + } + + // create //.snapshots/1/snapshot/.snapshots + + cout << "creating directory //.snapshots/1/snapshot/.snapshots" << endl; + + try + { + if (mkdir(prepend_root_prefix(root_prefix, "/.snapshots/1/snapshot/.snapshots").c_str(), 0777) != 0) + SN_THROW(Exception("mkdir failed")); + } + catch (const exception& e) + { + cerr << "creating //.snapshots/1/snapshot/.snapshots failed" << endl; + + return false; + } + + return true; +} + + +bool +step_config(const string& root_prefix, const string& description, const string& cleanup, + const map& userdata) +{ + // create directories + + cout << "creating directories in /" << endl; + + mkdir(prepend_root_prefix(root_prefix, "/etc").c_str(), 0777); + mkdir(prepend_root_prefix(root_prefix, "/etc/sysconfig").c_str(), 0777); + mkdir(prepend_root_prefix(root_prefix, "/etc/snapper").c_str(), 0777); + mkdir(prepend_root_prefix(root_prefix, "/etc/snapper/configs").c_str(), 0777); + + // create snapper sysconfig //etc/sysconfig/snapper + + cout << "creating snapper sysconfig //etc/sysconfig/snapper" << endl; + + try + { + /* + SysconfigFile sysconfig; // TODO, add in class SysconfigFile + + sysconfig.set_name(prepend_root_prefix(root_prefix, SYSCONFIG_FILE)); + + sysconfig.set_value("SNAPPER_CONFIGS", "root"); + + sysconfig.save(); + */ + + AsciiFileWriter sysconfig(prepend_root_prefix(root_prefix, SYSCONFIG_FILE), Compression::NONE); + + sysconfig.write_line("SNAPPER_CONFIGS=\"root\""); + + sysconfig.close(); + } + catch (const Exception& e) + { + cerr << "setup of //etc/sysconfig/snapper failed, " + << e.what() << endl; + + return false; + } + + // create snapper config //etc/snapper/configs/root + + cout << "creating snapper config //etc/snapper/configs/root" << endl; + + try + { + string template_file = locate_file("default", ETC_CONFIG_TEMPLATE_DIR, USR_CONFIG_TEMPLATE_DIR); + + SysconfigFile config(template_file); + + config.set_name(prepend_root_prefix(root_prefix, CONFIGS_DIR "/root")); + + config.set_value(KEY_SUBVOLUME, "/"); + config.set_value(KEY_FSTYPE, "btrfs"); + + config.save(); + } + catch (const Exception& e) + { + cerr << "setup of //etc/snapper/configs/root failed, " + << e.what() << endl; + + return false; + } + + // create info file //.snapshots/1/info.xml + + cout << "creating snapper info file //.snapshots/1/info.xml" << endl; + + try + { + XmlFile xml; + xmlNode* node = xmlNewNode("snapshot"); + xml.setRootElement(node); + + setChildValue(node, "type", toString(SINGLE)); + + setChildValue(node, "num", 1); + + setChildValue(node, "date", datetime(time(NULL), true, true)); + + if (!description.empty()) + setChildValue(node, "description", description); + + if (!cleanup.empty()) + setChildValue(node, "cleanup", cleanup); + + for (const map::value_type& tmp : userdata) + { + xmlNode* userdata_node = xmlNewChild(node, "userdata"); + setChildValue(userdata_node, "key", tmp.first); + setChildValue(userdata_node, "value", tmp.second); + } + + int fd = open(prepend_root_prefix(root_prefix, "/.snapshots/1/info.xml").c_str(), + O_RDWR | O_CREAT | O_CLOEXEC, 0666); + if (fd < 0) + SN_THROW(Exception("open failed")); + + fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + xml.save(fd); + } + catch (const Exception& e) + { + SN_CAUGHT(e); + + cerr << "setup of //.snapshots/1/info.xml failed, " + << e.what() << endl; + + return false; + } + + return true; +} + +#endif + + void log_do(LogLevel level, const string& component, const char* file, const int line, const char* func, const string& text) @@ -362,4 +596,14 @@ main(int argc, char** argv) step4(); else if (step == "5") return step5(root_prefix, snapshot_type, pre_num, description, cleanup, userdata) ? EXIT_SUCCESS : EXIT_FAILURE; + +#ifdef ENABLE_ROLLBACK + + else if (step == "filesystem") + return step_filesystem(root_prefix) ? EXIT_SUCCESS : EXIT_FAILURE; + else if (step == "config") + return step_config(root_prefix, description, cleanup, userdata) ? EXIT_SUCCESS : EXIT_FAILURE; + +#endif + } diff --git a/client/installation-helper/readme.txt b/client/installation-helper/readme.txt new file mode 100644 index 00000000..3feba9e3 --- /dev/null +++ b/client/installation-helper/readme.txt @@ -0,0 +1,36 @@ + +The "old" way using the steps 1 to 5 is deprecated. + + +The "new" way is to use the two steps "filesystem" and "config". + +The step "filesystem" does the following: + + create btrfs subvolume //.snapshots + create directory //.snapshots/1 + create btrfs subvolume //.snapshots/1/snapshot + set default btrfs subvolume to //.snapshots/1/snapshot + create directory //.snapshots/1/snapshot/.snapshots + +The step "config" does the following: + + create snapper sysconfig //etc/sysconfig/snapper + create snapper config //etc/snapper/configs/root + create snapper info file //.snapshots/1/info.xml + +The installer has to mount the filesystem before the step +"filesystem". Between the two steps the filesystem must be remounted +(since the default subvolume was changes). Additionally the .snapshots +subvolume must be mounted by the installer. + +The installer must also handle /etc/fstab or similar. + +Works with and without the optional "@" subvolume. + +No plugins are run. + +Example workflow can be seen in test1.sh and test2.sh. + +The "filesystem" step can of course be implemented somewhere +else. E.g. libstorage-ng is capable of doing this (see example there). + diff --git a/client/installation-helper/test1.sh b/client/installation-helper/test1.sh new file mode 100755 index 00000000..be501c55 --- /dev/null +++ b/client/installation-helper/test1.sh @@ -0,0 +1,20 @@ +#!/usr/bin/bash -x + +umount /test/.snapshots +umount /test + +mkfs.btrfs -f /dev/sdc1 + +mount /dev/sdc1 /test + +/usr/lib/snapper/installation-helper --root-prefix /test --step filesystem + +umount /test + +mount /dev/sdc1 /test +mount /dev/sdc1 -o subvol=.snapshots /test/.snapshots + +/usr/lib/snapper/installation-helper --root-prefix /test --step config --description initial --userdata a=1 + +snapper --no-dbus --root /test ls + diff --git a/client/installation-helper/test2.sh b/client/installation-helper/test2.sh new file mode 100755 index 00000000..309a01d2 --- /dev/null +++ b/client/installation-helper/test2.sh @@ -0,0 +1,27 @@ +#!/usr/bin/bash -x + +umount /test/.snapshots +umount /test + +mkfs.btrfs -f /dev/sdc1 + +mount /dev/sdc1 /test + +btrfs subvolume create /test/@ +btrfs subvolume set-default /test/@ + +umount /test + +mount /dev/sdc1 /test + +/usr/lib/snapper/installation-helper --root-prefix /test --step filesystem + +umount /test + +mount /dev/sdc1 /test +mount /dev/sdc1 -o subvol=@/.snapshots /test/.snapshots + +/usr/lib/snapper/installation-helper --root-prefix /test --step config --description initial --userdata a=1 + +snapper --no-dbus --root /test ls + diff --git a/package/snapper.changes b/package/snapper.changes index 2e0eddaa..31c950d4 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Dec 02 16:51:50 CET 2024 - aschnell@suse.com + +- added new workflow using only two steps to installation-helper + (gh#openSUSE/snapper#944) + ------------------------------------------------------------------- Tue Nov 05 10:50:50 CET 2024 - aschnell@suse.com