From 4210412d189f312857c567a187d5ead0c94f905f Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Tue, 25 Jun 2024 00:44:43 +0000 Subject: [PATCH 1/2] updater: handle missing inactive partition set If a variant is built without support for in-place updates, it will not have the second, "inactive" set of partitions. Since `signpost` is required for various operations, like marking a boot successful, or indicating that the initial boot has finished, it cannot be omitted entirely. Teach it to deal with only one partition set, by making certain operations either optional or fallible. Signed-off-by: Ben Cressey --- sources/updater/signpost/src/error.rs | 6 + sources/updater/signpost/src/main.rs | 6 +- sources/updater/signpost/src/state.rs | 207 ++++++++++++++++++-------- sources/updater/updog/src/error.rs | 3 + sources/updater/updog/src/main.rs | 19 ++- 5 files changed, 169 insertions(+), 72 deletions(-) diff --git a/sources/updater/signpost/src/error.rs b/sources/updater/signpost/src/error.rs index 1f018fdf2..961f8a691 100644 --- a/sources/updater/signpost/src/error.rs +++ b/sources/updater/signpost/src/error.rs @@ -22,6 +22,9 @@ pub enum Error { source: block_party::Error, }, + #[snafu(display("Failed to convert vec to array"))] + ConvertVec {}, + #[snafu(display("Failed to get disk from partition {}: {}", device.display(), source))] DiskFromPartition { device: PathBuf, @@ -43,6 +46,9 @@ pub enum Error { #[snafu(display("Inactive partition {} is already marked for upgrade", inactive.display()))] InactiveAlreadyMarked { inactive: PathBuf }, + #[snafu(display("Inactive partition {} is not available", inactive.display()))] + InactiveNotAvailable { inactive: PathBuf }, + #[snafu(display("Inactive partition {} has not been marked valid for upgrade", inactive.display()))] InactiveNotValid { inactive: PathBuf }, diff --git a/sources/updater/signpost/src/main.rs b/sources/updater/signpost/src/main.rs index d9e13b078..abb081084 100644 --- a/sources/updater/signpost/src/main.rs +++ b/sources/updater/signpost/src/main.rs @@ -43,7 +43,7 @@ fn main() { match command { Command::Status => println!("{state}"), Command::ClearInactive => { - state.clear_inactive(); + state.clear_inactive()?; state.write()?; } Command::MarkSuccessfulBoot => { @@ -51,7 +51,7 @@ fn main() { state.write()?; } Command::MarkInactiveValid => { - state.mark_inactive_valid(); + state.mark_inactive_valid()?; state.write()?; } Command::UpgradeToInactive => { @@ -59,7 +59,7 @@ fn main() { state.write()?; } Command::CancelUpgrade => { - state.cancel_upgrade(); + state.cancel_upgrade()?; state.write()?; } Command::RollbackToInactive => { diff --git a/sources/updater/signpost/src/state.rs b/sources/updater/signpost/src/state.rs index 9cc85bc85..d209f6d6f 100644 --- a/sources/updater/signpost/src/state.rs +++ b/sources/updater/signpost/src/state.rs @@ -20,13 +20,29 @@ pub struct State { os_disk: PathBuf, // BOTTLEROCKET_PRIVATE partition number private_partition_num: u32, - sets: [PartitionSet; 2], + sets: PartitionSets, /// The partition numbers that correspond to the boot partitions in each partition set, /// respectively. /// /// This is used to load the correct partition flags from `table`. - boot_partition_nums: [u32; 2], + boot_partition_nums: Vec, table: GPT, +} + +#[derive(Debug, Clone)] +enum PartitionSets { + Single(SinglePartitionSet), + Dual(DualPartitionSet), +} + +#[derive(Debug, Clone)] +struct SinglePartitionSet { + set: PartitionSet, +} + +#[derive(Debug, Clone)] +struct DualPartitionSet { + sets: [PartitionSet; 2], active: SetSelect, } @@ -78,17 +94,21 @@ impl State { })?; // Finds the nth partition on `table` matching the partition type GUID `guid`. - let nth_guid = |guid, n| -> Result { - Ok(table + let nth_guid = |guid, n| -> Option { + table .iter() .filter(|(_, p)| p.partition_type_guid == guid) .nth(n) - .context(error::PartitionMissingFromSetSnafu { - part_type: stringify!(guid), - set: if n == 0 { "A" } else { "B" }, - })? - .0) + .map(|(n, _)| n) }; + + let required_guid = |guid, n| -> Result { + nth_guid(guid, n).context(error::PartitionMissingFromSetSnafu { + part_type: stringify!(guid), + set: if n == 0 { "A" } else { "B" }, + }) + }; + // Loads the path to partition number `num` on the OS disk. let device_from_part_num = |num| -> Result { Ok(os_disk @@ -103,44 +123,54 @@ impl State { .path()) }; - let boot_partition_nums = [ - nth_guid(BOTTLEROCKET_BOOT, 0)?, - nth_guid(BOTTLEROCKET_BOOT, 1)?, - ]; - let sets = [ - PartitionSet { - boot: device_from_part_num(boot_partition_nums[0])?, - root: device_from_part_num(nth_guid(BOTTLEROCKET_ROOT, 0)?)?, - hash: device_from_part_num(nth_guid(BOTTLEROCKET_HASH, 0)?)?, - }, - PartitionSet { + let mut boot_partition_nums = vec![required_guid(BOTTLEROCKET_BOOT, 0)?]; + if let Some(b) = nth_guid(BOTTLEROCKET_BOOT, 1) { + boot_partition_nums.push(b); + } + + let mut sets = Vec::new(); + sets.push(PartitionSet { + boot: device_from_part_num(boot_partition_nums[0])?, + root: device_from_part_num(required_guid(BOTTLEROCKET_ROOT, 0)?)?, + hash: device_from_part_num(required_guid(BOTTLEROCKET_HASH, 0)?)?, + }); + + if boot_partition_nums.len() == 2 { + sets.push(PartitionSet { boot: device_from_part_num(boot_partition_nums[1])?, - root: device_from_part_num(nth_guid(BOTTLEROCKET_ROOT, 1)?)?, - hash: device_from_part_num(nth_guid(BOTTLEROCKET_HASH, 1)?)?, - }, - ]; - - // Determine which set is active by seeing which set contains the current running root or - // hash partition. - let active = if sets[0].contains(&active_partition) { - SetSelect::A - } else if sets[1].contains(&active_partition) { - SetSelect::B + root: device_from_part_num(required_guid(BOTTLEROCKET_ROOT, 1)?)?, + hash: device_from_part_num(required_guid(BOTTLEROCKET_HASH, 1)?)?, + }); + } + + let sets = if sets.len() == 2 { + // Determine which set is active by seeing which set contains the current running root or + // hash partition. + let active = if sets[0].contains(&active_partition) { + SetSelect::A + } else if sets[1].contains(&active_partition) { + SetSelect::B + } else { + return error::ActiveNotInSetSnafu { + active_partition, + sets, + } + .fail(); + }; + let sets = sets.try_into().ok().context(error::ConvertVecSnafu)?; + PartitionSets::Dual(DualPartitionSet { sets, active }) } else { - return error::ActiveNotInSetSnafu { - active_partition, - sets, - } - .fail(); + PartitionSets::Single(SinglePartitionSet { + set: sets.remove(0), + }) }; Ok(Self { os_disk: os_disk.path(), - private_partition_num: nth_guid(BOTTLEROCKET_PRIVATE, 0)?, + private_partition_num: required_guid(BOTTLEROCKET_PRIVATE, 0)?, sets, boot_partition_nums, table, - active, }) } @@ -165,23 +195,42 @@ impl State { } pub fn active(&self) -> SetSelect { - self.active + match &self.sets { + PartitionSets::Dual(s) => s.active, + PartitionSets::Single(_) => SetSelect::A, + } } - pub fn inactive(&self) -> SetSelect { - // resolve opposing set member - !self.active + pub fn inactive(&self) -> Option { + match &self.sets { + PartitionSets::Dual(s) => Some(!s.active), + PartitionSets::Single(_) => None, + } } pub fn active_set(&self) -> &PartitionSet { - &self.sets[self.active().idx()] + match &self.sets { + PartitionSets::Dual(s) => &s.sets[s.active.idx()], + PartitionSets::Single(s) => &s.set, + } } - pub fn inactive_set(&self) -> &PartitionSet { - &self.sets[self.inactive().idx()] + pub fn inactive_set(&self) -> Option<&PartitionSet> { + match &self.sets { + PartitionSets::Dual(s) => Some(&s.sets[(!s.active).idx()]), + PartitionSets::Single(_) => None, + } } pub fn next(&self) -> Option { + if let PartitionSets::Single(_) = self.sets { + if self.gptprio(SetSelect::A).will_boot() { + return Some(SetSelect::A); + } else { + return None; + } + } + let gptprio_a = self.gptprio(SetSelect::A); let gptprio_b = self.gptprio(SetSelect::B); match (gptprio_a.will_boot(), gptprio_b.will_boot()) { @@ -212,21 +261,33 @@ impl State { /// Clears priority bits of the inactive partition in preparation to write new images, but /// **does not write to the disk**. - pub fn clear_inactive(&mut self) { - let mut inactive_flags = self.gptprio(self.inactive()); + pub fn clear_inactive(&mut self) -> Result<(), Error> { + let inactive = self.inactive().context(error::InactiveNotAvailableSnafu { + inactive: &self.os_disk, + })?; + + let mut inactive_flags = self.gptprio(inactive); inactive_flags.set_priority(0); inactive_flags.set_tries_left(0); inactive_flags.set_successful(false); - self.set_gptprio(self.inactive(), inactive_flags); + self.set_gptprio(inactive, inactive_flags); + + Ok(()) } /// Sets 'tries left' to 1 on the inactive partition to represent a /// potentially valid image, but does not change the priority. /// **does not write to the disk**. - pub fn mark_inactive_valid(&mut self) { - let mut inactive_flags = self.gptprio(self.inactive()); + pub fn mark_inactive_valid(&mut self) -> Result<(), Error> { + let inactive = self.inactive().context(error::InactiveNotAvailableSnafu { + inactive: &self.os_disk, + })?; + + let mut inactive_flags = self.gptprio(inactive); inactive_flags.set_tries_left(1); - self.set_gptprio(self.inactive(), inactive_flags); + self.set_gptprio(inactive, inactive_flags); + + Ok(()) } /// Sets the inactive partition as a new upgrade partition, but **does not write to the disk**. @@ -237,7 +298,11 @@ impl State { /// * Returns an error if the partition has not been marked as potentially /// valid or if it has already been marked for upgrade. pub fn upgrade_to_inactive(&mut self) -> Result<(), Error> { - let mut inactive_flags = self.gptprio(self.inactive()); + let inactive = self.inactive().context(error::InactiveNotAvailableSnafu { + inactive: &self.os_disk, + })?; + + let mut inactive_flags = self.gptprio(inactive); ensure!( inactive_flags.priority() == 0 && !inactive_flags.successful(), error::InactiveAlreadyMarkedSnafu { @@ -253,7 +318,7 @@ impl State { inactive_flags.set_priority(2); inactive_flags.set_successful(false); - self.set_gptprio(self.inactive(), inactive_flags); + self.set_gptprio(inactive, inactive_flags); let mut active_flags = self.gptprio(self.active()); active_flags.set_priority(1); @@ -265,14 +330,20 @@ impl State { /// /// * Sets the inactive partition's priority to 0 /// * Restores the active partition's priority to 2 - pub fn cancel_upgrade(&mut self) { - let mut inactive_flags = self.gptprio(self.inactive()); + pub fn cancel_upgrade(&mut self) -> Result<(), Error> { + let inactive = self.inactive().context(error::InactiveNotAvailableSnafu { + inactive: &self.os_disk, + })?; + + let mut inactive_flags = self.gptprio(inactive); inactive_flags.set_priority(0); - self.set_gptprio(self.inactive(), inactive_flags); + self.set_gptprio(inactive, inactive_flags); let mut active_flags = self.gptprio(self.active()); active_flags.set_priority(2); self.set_gptprio(self.active(), active_flags); + + Ok(()) } /// Prioritizes the inactive partition, but **does not write to the disk**. @@ -280,7 +351,11 @@ impl State { /// Returns an error if the inactive partition is not bootable (it doesn't have a prior /// successful boot and doesn't have the priority/tries_left that would make it safe to try). pub fn rollback_to_inactive(&mut self) -> Result<(), Error> { - let mut inactive_flags = self.gptprio(self.inactive()); + let inactive = self.inactive().context(error::InactiveNotAvailableSnafu { + inactive: &self.os_disk, + })?; + + let mut inactive_flags = self.gptprio(inactive); if !inactive_flags.will_boot() { return error::InactiveInvalidRollbackSnafu { priority: inactive_flags.priority(), @@ -290,7 +365,7 @@ impl State { .fail(); } inactive_flags.set_priority(2); - self.set_gptprio(self.inactive(), inactive_flags); + self.set_gptprio(inactive, inactive_flags); let mut active_flags = self.gptprio(self.active()); active_flags.set_priority(1); @@ -331,15 +406,17 @@ impl fmt::Display for State { writeln!( f, "Set A: {} {}", - self.sets[SetSelect::A.idx()], + self.active_set(), self.gptprio(SetSelect::A) )?; - writeln!( - f, - "Set B: {} {}", - self.sets[SetSelect::B.idx()], - self.gptprio(SetSelect::B) - )?; + if let Some(inactive_set) = self.inactive_set() { + writeln!( + f, + "Set B: {} {}", + inactive_set, + self.gptprio(SetSelect::B) + )?; + } writeln!(f, "Active: Set {}", self.active())?; match self.next() { Some(next) => write!(f, "Next: Set {}", next), diff --git a/sources/updater/updog/src/error.rs b/sources/updater/updog/src/error.rs index f602ff039..b22aa61fb 100644 --- a/sources/updater/updog/src/error.rs +++ b/sources/updater/updog/src/error.rs @@ -60,6 +60,9 @@ pub(crate) enum Error { #[snafu(display("Could not mark inactive partition for boot: {}", source))] InactivePartitionUpgrade { source: signpost::Error }, + #[snafu(display("Could not find inactive partition set"))] + InactivePartitionMissing {}, + #[snafu(display("Failed to decode LZ4-compressed target {}: {}", target, source))] Lz4Decode { target: String, diff --git a/sources/updater/updog/src/main.rs b/sources/updater/updog/src/main.rs index ea45e7fa5..5abd664d0 100644 --- a/sources/updater/updog/src/main.rs +++ b/sources/updater/updog/src/main.rs @@ -296,20 +296,28 @@ async fn retrieve_migrations( async fn update_image(update: &Update, repository: &Repository) -> Result<()> { let mut gpt_state = State::load().context(error::PartitionTableReadSnafu)?; - gpt_state.clear_inactive(); + gpt_state + .clear_inactive() + .ok() + .context(error::InactivePartitionMissingSnafu)?; // Write out the clearing of the inactive partition immediately, because we're about to // overwrite the partition set with update data and don't want it to be used until we // know we're done with all components. gpt_state.write().context(error::PartitionTableWriteSnafu)?; - let inactive = gpt_state.inactive_set(); + let inactive = gpt_state + .inactive_set() + .context(error::InactivePartitionMissingSnafu)?; // TODO Do we want to recover the inactive side on an error? write_target_to_disk(repository, &update.images.root, &inactive.root).await?; write_target_to_disk(repository, &update.images.boot, &inactive.boot).await?; write_target_to_disk(repository, &update.images.hash, &inactive.hash).await?; - gpt_state.mark_inactive_valid(); + gpt_state + .mark_inactive_valid() + .ok() + .context(error::InactivePartitionMissingSnafu)?; gpt_state.write().context(error::PartitionTableWriteSnafu)?; Ok(()) } @@ -325,7 +333,10 @@ fn update_flags() -> Result<()> { fn revert_update_flags() -> Result<()> { let mut gpt_state = State::load().context(error::PartitionTableReadSnafu)?; - gpt_state.cancel_upgrade(); + gpt_state + .cancel_upgrade() + .ok() + .context(error::InactivePartitionMissingSnafu)?; gpt_state.write().context(error::PartitionTableWriteSnafu)?; Ok(()) } From 095acef73f9d6484bdfa07f7af90e36d71c6b4c2 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Tue, 25 Jun 2024 19:31:13 +0000 Subject: [PATCH 2/2] os: adjust dependencies for new image features Host containers and in-place updates are now optional. Only require the supporting software when those image features are enabled. Signed-off-by: Ben Cressey --- Cargo.lock | 1 - packages/os/Cargo.toml | 4 ++-- packages/os/apiserver.service | 4 ++-- packages/os/mark-successful-boot.service | 1 - packages/os/migrator.service | 4 +++- packages/os/os.spec | 23 ++++++++++++++++------- packages/os/storewolf.service | 2 -- packages/release/Cargo.toml | 1 - packages/release/release.spec | 1 - 9 files changed, 23 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 302403567..108bba062 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -953,7 +953,6 @@ dependencies = [ "glibc", "grep", "grub", - "host-ctr", "iproute", "iptables", "kexec-tools", diff --git a/packages/os/Cargo.toml b/packages/os/Cargo.toml index 536347ac3..b8a3f7c55 100644 --- a/packages/os/Cargo.toml +++ b/packages/os/Cargo.toml @@ -33,8 +33,8 @@ glibc = { path = "../glibc" } # RPM Requires [dependencies] -# We depend on these packages at runtime, and are expected to be pulled in -# by way of the `release` package. +# We depend on these packages at runtime, and they are expected to be built +# because they are included in the core kit. # `host-ctr` for host containers functionality # host-ctr = { path = "../host-ctr" } # kexec-tools and makedumpfile required for prairiedog functionality diff --git a/packages/os/apiserver.service b/packages/os/apiserver.service index 29af00b04..91fc2773a 100644 --- a/packages/os/apiserver.service +++ b/packages/os/apiserver.service @@ -1,7 +1,7 @@ [Unit] Description=Bottlerocket API server -After=storewolf.service migrator.service -Requires=storewolf.service migrator.service +After=storewolf.service +Requires=storewolf.service [Service] Type=notify diff --git a/packages/os/mark-successful-boot.service b/packages/os/mark-successful-boot.service index 362d0c282..e44de93e3 100644 --- a/packages/os/mark-successful-boot.service +++ b/packages/os/mark-successful-boot.service @@ -3,7 +3,6 @@ Description=Call signpost to mark the boot as successful after all required targ # This unit is in charge of updating the partitions on successful boots. Use other service # units instead of adding more `ExecStart*` lines to prevent indirect dependencies on # other units not listed in the `RequiredBy` section. -Requires=migrator.service # Block manual interactions with this service, manually running it could leave the system in an # unexpected state RefuseManualStart=true diff --git a/packages/os/migrator.service b/packages/os/migrator.service index df181c2d3..9011da0ee 100644 --- a/packages/os/migrator.service +++ b/packages/os/migrator.service @@ -1,5 +1,7 @@ [Unit] Description=Bottlerocket data store migrator +Before=apiserver.service mark-successful-boot.service storewolf.service + RefuseManualStart=true RefuseManualStop=true @@ -16,4 +18,4 @@ StandardOutput=journal+console StandardError=journal+console [Install] -RequiredBy=preconfigured.target +RequiredBy=preconfigured.target apiserver.service mark-successful-boot.service storewolf.service diff --git a/packages/os/os.spec b/packages/os/os.spec index d091701bc..3951761d4 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -74,15 +74,11 @@ BuildRequires: %{_cross_os}glibc-devel Requires: %{_cross_os}apiclient Requires: %{_cross_os}apiserver Requires: %{_cross_os}bloodhound -Requires: %{_cross_os}bootstrap-containers -Requires: %{_cross_os}bork Requires: %{_cross_os}corndog Requires: %{_cross_os}certdog Requires: %{_cross_os}ghostdog -Requires: %{_cross_os}host-containers Requires: %{_cross_os}logdog Requires: %{_cross_os}metricdog -Requires: %{_cross_os}migration Requires: %{_cross_os}prairiedog Requires: %{_cross_os}schnauzer Requires: %{_cross_os}settings-committer @@ -92,8 +88,14 @@ Requires: %{_cross_os}storewolf Requires: %{_cross_os}sundog Requires: %{_cross_os}xfscli Requires: %{_cross_os}thar-be-settings -Requires: %{_cross_os}thar-be-updates -Requires: %{_cross_os}updog + +Requires: (%{_cross_os}bootstrap-containers or %{_cross_os}image-feature(no-host-containers)) +Requires: (%{_cross_os}host-containers or %{_cross_os}image-feature(no-host-containers)) + +Requires: (%{_cross_os}bork or %{_cross_os}image-feature(no-in-place-updates)) +Requires: (%{_cross_os}migration or %{_cross_os}image-feature(no-in-place-updates)) +Requires: (%{_cross_os}thar-be-updates or %{_cross_os}image-feature(no-in-place-updates)) +Requires: (%{_cross_os}updog or %{_cross_os}image-feature(no-in-place-updates)) Requires: (%{_cross_os}pluto if %{_cross_os}variant-family(aws-k8s)) Requires: (%{_cross_os}shibaken if %{_cross_os}variant-platform(aws)) @@ -124,6 +126,7 @@ Summary: Updates settings dynamically based on user-specified generators %package -n %{_cross_os}bork Summary: Dynamic setting generator for updog +Conflicts: %{_cross_os}image-feature(no-in-place-updates) %description -n %{_cross_os}bork %{summary}. @@ -144,12 +147,14 @@ Summary: Applies changed settings to a Bottlerocket system %package -n %{_cross_os}thar-be-updates Summary: Dispatches Bottlerocket update commands +Conflicts: %{_cross_os}image-feature(no-in-place-updates) %description -n %{_cross_os}thar-be-updates %{summary}. %package -n %{_cross_os}host-containers Summary: Manages system- and user-defined host containers Requires: %{_cross_os}host-ctr +Conflicts: %{_cross_os}image-feature(no-host-containers) %description -n %{_cross_os}host-containers %{summary}. @@ -161,6 +166,7 @@ Requires: %{_cross_os}settings-defaults %package -n %{_cross_os}migration Summary: Tools to migrate version formats +Conflicts: %{_cross_os}image-feature(no-in-place-updates) %description -n %{_cross_os}migration %package -n %{_cross_os}settings-committer @@ -181,6 +187,7 @@ Summary: Bottlerocket GPT priority querier/switcher %package -n %{_cross_os}updog Summary: Bottlerocket updater CLI +Conflicts: %{_cross_os}image-feature(no-in-place-updates) %description -n %{_cross_os}updog not much what's up with you @@ -241,6 +248,8 @@ Requires: %{_cross_os}binutils %package -n %{_cross_os}bootstrap-containers Summary: Manages bootstrap-containers +Requires: %{_cross_os}host-ctr +Conflicts: %{_cross_os}image-feature(no-host-containers) %description -n %{_cross_os}bootstrap-containers %{summary}. @@ -515,7 +524,6 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %files -n %{_cross_os}apiserver %{_cross_bindir}/apiserver %{_cross_unitdir}/apiserver.service -%{_cross_unitdir}/migrator.service %{_cross_sysusersdir}/api.conf %files -n %{_cross_os}apiclient @@ -560,6 +568,7 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %files -n %{_cross_os}migration %{_cross_bindir}/migrator +%{_cross_unitdir}/migrator.service %{_cross_tmpfilesdir}/migration.conf %files -n %{_cross_os}settings-committer diff --git a/packages/os/storewolf.service b/packages/os/storewolf.service index 776db1248..246cec22d 100644 --- a/packages/os/storewolf.service +++ b/packages/os/storewolf.service @@ -1,7 +1,5 @@ [Unit] Description=Datastore creator -After=migrator.service -Requires=migrator.service # Block manual interactions with this service, since it could leave the system in an # unexpected state RefuseManualStart=true diff --git a/packages/release/Cargo.toml b/packages/release/Cargo.toml index e0bd4def6..a99c9e27d 100644 --- a/packages/release/Cargo.toml +++ b/packages/release/Cargo.toml @@ -31,7 +31,6 @@ findutils = { path = "../findutils" } glibc = { path = "../glibc" } grep = { path = "../grep" } grub = { path = "../grub" } -host-ctr = { path = "../host-ctr" } iproute = { path = "../iproute" } iptables = { path = "../iptables" } kexec-tools = { path = "../../packages/kexec-tools" } diff --git a/packages/release/release.spec b/packages/release/release.spec index 0d2679256..eafa0a84c 100644 --- a/packages/release/release.spec +++ b/packages/release/release.spec @@ -115,7 +115,6 @@ Requires: %{_cross_os}findutils Requires: %{_cross_os}glibc Requires: %{_cross_os}grep Requires: %{_cross_os}grub -Requires: %{_cross_os}host-ctr Requires: %{_cross_os}iproute Requires: %{_cross_os}iptables Requires: %{_cross_os}kexec-tools