mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
redesign firmware updater (#2521)
* bump version * Update image-recipe/build.sh * fix podman repo * improve firmware updater Co-authored-by: J H <Blu-J@users.noreply.github.com> * checksum firmware * include sha in json * fix build * fix semver parser, add rpc for manual trigger --------- Co-authored-by: J H <Blu-J@users.noreply.github.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,4 +27,5 @@ secrets.db
|
|||||||
/results
|
/results
|
||||||
/dpkg-workdir
|
/dpkg-workdir
|
||||||
/compiled.tar
|
/compiled.tar
|
||||||
/compiled-*.tar
|
/compiled-*.tar
|
||||||
|
/firmware
|
||||||
9
Makefile
9
Makefile
@@ -8,7 +8,8 @@ ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else ec
|
|||||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||||
BINS := core/target/$(ARCH)-unknown-linux-gnu/release/startbox core/target/aarch64-unknown-linux-musl/release/container-init core/target/x86_64-unknown-linux-musl/release/container-init
|
BINS := core/target/$(ARCH)-unknown-linux-gnu/release/startbox core/target/aarch64-unknown-linux-musl/release/container-init core/target/x86_64-unknown-linux-musl/release/container-init
|
||||||
WEB_UIS := web/dist/raw/ui web/dist/raw/setup-wizard web/dist/raw/diagnostic-ui web/dist/raw/install-wizard
|
WEB_UIS := web/dist/raw/ui web/dist/raw/setup-wizard web/dist/raw/diagnostic-ui web/dist/raw/install-wizard
|
||||||
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts
|
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||||
|
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||||
DEBIAN_SRC := $(shell git ls-files debian/)
|
DEBIAN_SRC := $(shell git ls-files debian/)
|
||||||
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
|
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
|
||||||
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
||||||
@@ -72,6 +73,7 @@ clean:
|
|||||||
rm -rf dpkg-workdir
|
rm -rf dpkg-workdir
|
||||||
rm -rf image-recipe/deb
|
rm -rf image-recipe/deb
|
||||||
rm -rf results
|
rm -rf results
|
||||||
|
rm -rf build/lib/firmware
|
||||||
rm -f ENVIRONMENT.txt
|
rm -f ENVIRONMENT.txt
|
||||||
rm -f PLATFORM.txt
|
rm -f PLATFORM.txt
|
||||||
rm -f GIT_HASH.txt
|
rm -f GIT_HASH.txt
|
||||||
@@ -134,6 +136,8 @@ install: $(ALL_TARGETS)
|
|||||||
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/compat.tar)
|
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/compat.tar)
|
||||||
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
|
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
|
||||||
$(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/binfmt.tar)
|
$(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/binfmt.tar)
|
||||||
|
|
||||||
|
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
||||||
|
|
||||||
update-overlay: $(ALL_TARGETS)
|
update-overlay: $(ALL_TARGETS)
|
||||||
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
||||||
@@ -165,6 +169,9 @@ upload-ota: results/$(BASENAME).squashfs
|
|||||||
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
||||||
build/dpkg-deps/generate.sh
|
build/dpkg-deps/generate.sh
|
||||||
|
|
||||||
|
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
||||||
|
./download-firmware.sh $(PLATFORM)
|
||||||
|
|
||||||
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) core/Cargo.lock
|
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) core/Cargo.lock
|
||||||
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||||
|
|
||||||
|
|||||||
13
build/lib/firmware.json
Normal file
13
build/lib/firmware.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "pureboot-librem_mini_v2-basic_usb_autoboot_blob_jail-Release-28.3",
|
||||||
|
"platform": ["x86_64"],
|
||||||
|
"system-product-name": "librem_mini_v2",
|
||||||
|
"bios-version": {
|
||||||
|
"semver-prefix": "PureBoot-Release-",
|
||||||
|
"semver-range": "<28.3"
|
||||||
|
},
|
||||||
|
"url": "https://source.puri.sm/firmware/releases/-/raw/master/librem_mini_v2/custom/pureboot-librem_mini_v2-basic_usb_autoboot_blob_jail-Release-28.3.rom.gz",
|
||||||
|
"shasum": "5019bcf53f7493c7aa74f8ef680d18b5fc26ec156c705a841433aaa2fdef8f35"
|
||||||
|
}
|
||||||
|
]
|
||||||
Binary file not shown.
4
core/Cargo.lock
generated
4
core/Cargo.lock
generated
@@ -4238,6 +4238,9 @@ name = "semver"
|
|||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver-parser"
|
name = "semver-parser"
|
||||||
@@ -5000,6 +5003,7 @@ dependencies = [
|
|||||||
"rpc-toolkit",
|
"rpc-toolkit",
|
||||||
"rust-argon2",
|
"rust-argon2",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"semver 1.0.20",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ rpassword = "7.2.0"
|
|||||||
rpc-toolkit = "0.2.2"
|
rpc-toolkit = "0.2.2"
|
||||||
rust-argon2 = "2.0.0"
|
rust-argon2 = "2.0.0"
|
||||||
scopeguard = "1.1" # because avahi-sys fucks your shit up
|
scopeguard = "1.1" # because avahi-sys fucks your shit up
|
||||||
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::context::rpc::RpcContextConfig;
|
use crate::context::rpc::RpcContextConfig;
|
||||||
use crate::context::{DiagnosticContext, InstallContext, SetupContext};
|
use crate::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||||
use crate::disk::fsck::RepairStrategy;
|
use crate::disk::fsck::{RepairStrategy, RequiresReboot};
|
||||||
use crate::disk::main::DEFAULT_PASSWORD;
|
use crate::disk::main::DEFAULT_PASSWORD;
|
||||||
use crate::disk::REPAIR_DISK_PATH;
|
use crate::disk::REPAIR_DISK_PATH;
|
||||||
use crate::firmware::update_firmware;
|
use crate::firmware::update_firmware;
|
||||||
@@ -30,11 +30,18 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Er
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if update_firmware().await?.0 {
|
match update_firmware().await {
|
||||||
return Ok(Some(Shutdown {
|
Ok(RequiresReboot(true)) => {
|
||||||
export_args: None,
|
return Ok(Some(Shutdown {
|
||||||
restart: true,
|
export_args: None,
|
||||||
}));
|
restart: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Error performing firmware update: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::new("ln")
|
Command::new("ln")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::Error;
|
|||||||
pub mod btrfs;
|
pub mod btrfs;
|
||||||
pub mod ext4;
|
pub mod ext4;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct RequiresReboot(pub bool);
|
pub struct RequiresReboot(pub bool);
|
||||||
impl std::ops::BitOrAssign for RequiresReboot {
|
impl std::ops::BitOrAssign for RequiresReboot {
|
||||||
|
|||||||
@@ -1,16 +1,63 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use async_compression::tokio::bufread::GzipDecoder;
|
use async_compression::tokio::bufread::GzipDecoder;
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use rpc_toolkit::command;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncRead, BufReader};
|
use tokio::io::BufReader;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::disk::fsck::RequiresReboot;
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::PLATFORM;
|
||||||
|
|
||||||
|
/// Part of the Firmware, look there for more about
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct VersionMatcher {
|
||||||
|
/// Strip this prefix on the version matcher
|
||||||
|
semver_prefix: Option<String>,
|
||||||
|
/// Match the semver to this range
|
||||||
|
semver_range: Option<semver::VersionReq>,
|
||||||
|
/// Strip this suffix on the version matcher
|
||||||
|
semver_suffix: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inside a file that is firmware.json, we
|
||||||
|
/// wanted a structure that could help decide what to do
|
||||||
|
/// for each of the firmware versions
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Firmware {
|
||||||
|
id: String,
|
||||||
|
/// This is the platform(s) the firmware was built for
|
||||||
|
platform: BTreeSet<String>,
|
||||||
|
/// This usally comes from the dmidecode
|
||||||
|
system_product_name: Option<String>,
|
||||||
|
/// The version comes from dmidecode, then we decide if it matches
|
||||||
|
bios_version: Option<VersionMatcher>,
|
||||||
|
/// the hash of the firmware rom.gz
|
||||||
|
shasum: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_firmware_update_result(arg: RequiresReboot, _: &ArgMatches) {
|
||||||
|
if arg.0 {
|
||||||
|
println!("Firmware successfully updated! Reboot to apply changes.");
|
||||||
|
} else {
|
||||||
|
println!("No firmware update available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We wanted to make sure during every init
|
||||||
|
/// that the firmware was the correct and updated for
|
||||||
|
/// systems like the Pure System that a new firmware
|
||||||
|
/// was released and the updates where pushed through the pure os.
|
||||||
|
#[command(rename = "update-firmware", display(display_firmware_update_result))]
|
||||||
pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
||||||
let product_name = String::from_utf8(
|
let system_product_name = String::from_utf8(
|
||||||
Command::new("dmidecode")
|
Command::new("dmidecode")
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.arg("system-product-name")
|
.arg("system-product-name")
|
||||||
@@ -19,52 +66,84 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
|||||||
)?
|
)?
|
||||||
.trim()
|
.trim()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
if product_name.is_empty() {
|
let bios_version = String::from_utf8(
|
||||||
|
Command::new("dmidecode")
|
||||||
|
.arg("-s")
|
||||||
|
.arg("bios-version")
|
||||||
|
.invoke(ErrorKind::Firmware)
|
||||||
|
.await?,
|
||||||
|
)?
|
||||||
|
.trim()
|
||||||
|
.to_owned();
|
||||||
|
if system_product_name.is_empty() || bios_version.is_empty() {
|
||||||
return Ok(RequiresReboot(false));
|
return Ok(RequiresReboot(false));
|
||||||
}
|
}
|
||||||
let firmware_dir = Path::new("/usr/lib/startos/firmware").join(&product_name);
|
|
||||||
if tokio::fs::metadata(&firmware_dir).await.is_ok() {
|
let firmware_dir = Path::new("/usr/lib/startos/firmware");
|
||||||
let current_firmware = String::from_utf8(
|
|
||||||
Command::new("dmidecode")
|
for firmware in serde_json::from_str::<Vec<Firmware>>(
|
||||||
.arg("-s")
|
&tokio::fs::read_to_string("/usr/lib/startos/firmware.json").await?,
|
||||||
.arg("bios-version")
|
)
|
||||||
.invoke(ErrorKind::Firmware)
|
.with_kind(ErrorKind::Deserialization)?
|
||||||
.await?,
|
{
|
||||||
)?
|
let id = firmware.id;
|
||||||
.trim()
|
let matches_product_name = firmware
|
||||||
.to_owned();
|
.system_product_name
|
||||||
if tokio::fs::metadata(firmware_dir.join(format!("{current_firmware}.rom.gz")))
|
.map_or(true, |spn| spn == system_product_name);
|
||||||
.await
|
let matches_bios_version = firmware
|
||||||
.is_err()
|
.bios_version
|
||||||
&& tokio::fs::metadata(firmware_dir.join(format!("{current_firmware}.rom")))
|
.map_or(Some(true), |bv| {
|
||||||
.await
|
let mut semver_str = bios_version.as_str();
|
||||||
.is_err()
|
if let Some(prefix) = &bv.semver_prefix {
|
||||||
{
|
semver_str = semver_str.strip_prefix(prefix)?;
|
||||||
let mut firmware_read_dir = tokio::fs::read_dir(&firmware_dir).await?;
|
|
||||||
while let Some(entry) = firmware_read_dir.next_entry().await? {
|
|
||||||
let filename = entry.file_name().to_string_lossy().into_owned();
|
|
||||||
let rdr: Option<Box<dyn AsyncRead + Unpin + Send>> =
|
|
||||||
if filename.ends_with(".rom.gz") {
|
|
||||||
Some(Box::new(GzipDecoder::new(BufReader::new(
|
|
||||||
File::open(entry.path()).await?,
|
|
||||||
))))
|
|
||||||
} else if filename.ends_with(".rom") {
|
|
||||||
Some(Box::new(File::open(entry.path()).await?))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(mut rdr) = rdr {
|
|
||||||
Command::new("flashrom")
|
|
||||||
.arg("-p")
|
|
||||||
.arg("internal")
|
|
||||||
.arg("-w-")
|
|
||||||
.input(Some(&mut rdr))
|
|
||||||
.invoke(ErrorKind::Firmware)
|
|
||||||
.await?;
|
|
||||||
return Ok(RequiresReboot(true));
|
|
||||||
}
|
}
|
||||||
}
|
if let Some(suffix) = &bv.semver_suffix {
|
||||||
|
semver_str = semver_str.strip_suffix(suffix)?;
|
||||||
|
}
|
||||||
|
let semver = semver_str
|
||||||
|
.split(".")
|
||||||
|
.filter_map(|v| v.parse().ok())
|
||||||
|
.chain(std::iter::repeat(0))
|
||||||
|
.take(3)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let semver = semver::Version::new(semver[0], semver[1], semver[2]);
|
||||||
|
Some(
|
||||||
|
bv.semver_range
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |r| r.matches(&semver)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if firmware.platform.contains(&*PLATFORM) && matches_product_name && matches_bios_version {
|
||||||
|
let filename = format!("{id}.rom.gz");
|
||||||
|
let firmware_path = firmware_dir.join(&filename);
|
||||||
|
Command::new("sha256sum")
|
||||||
|
.arg("-c")
|
||||||
|
.input(Some(&mut std::io::Cursor::new(format!(
|
||||||
|
"{} {}",
|
||||||
|
firmware.shasum,
|
||||||
|
firmware_path.display()
|
||||||
|
))))
|
||||||
|
.invoke(ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
|
let mut rdr = if tokio::fs::metadata(&firmware_path).await.is_ok() {
|
||||||
|
GzipDecoder::new(BufReader::new(File::open(&firmware_path).await?))
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Firmware {id}.rom.gz not found in {firmware_dir:?}"),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Command::new("flashrom")
|
||||||
|
.arg("-p")
|
||||||
|
.arg("internal")
|
||||||
|
.arg("-w-")
|
||||||
|
.input(Some(&mut rdr))
|
||||||
|
.invoke(ErrorKind::Firmware)
|
||||||
|
.await?;
|
||||||
|
return Ok(RequiresReboot(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RequiresReboot(false))
|
Ok(RequiresReboot(false))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ pub fn main_api() -> Result<(), RpcError> {
|
|||||||
shutdown::restart,
|
shutdown::restart,
|
||||||
shutdown::rebuild,
|
shutdown::rebuild,
|
||||||
update::update_system,
|
update::update_system,
|
||||||
|
firmware::update_firmware,
|
||||||
))]
|
))]
|
||||||
pub fn server() -> Result<(), RpcError> {
|
pub fn server() -> Result<(), RpcError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
28
download-firmware.sh
Executable file
28
download-firmware.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PLATFORM=$1
|
||||||
|
|
||||||
|
if [ -z "$PLATFORM" ]; then
|
||||||
|
>&2 echo "usage: $0 <PLATFORM>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf ./firmware/$PLATFORM
|
||||||
|
mkdir -p ./firmware/$PLATFORM
|
||||||
|
|
||||||
|
cd ./firmware/$PLATFORM
|
||||||
|
|
||||||
|
mapfile -t firmwares <<< "$(jq -c ".[] | select(.platform[] | contains(\"$PLATFORM\"))" ../../build/lib/firmware.json)"
|
||||||
|
for firmware in "${firmwares[@]}"; do
|
||||||
|
if [ -n "$firmware" ]; then
|
||||||
|
id=$(echo "$firmware" | jq --raw-output '.id')
|
||||||
|
url=$(echo "$firmware" | jq --raw-output '.url')
|
||||||
|
shasum=$(echo "$firmware" | jq --raw-output '.shasum')
|
||||||
|
curl --fail -L -o "${id}.rom.gz" "$url"
|
||||||
|
echo "$shasum ${id}.rom.gz" | sha256sum -c
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -344,4 +344,4 @@ elif [ "${IMAGE_TYPE}" = img ]; then
|
|||||||
|
|
||||||
mv $TARGET_NAME $RESULTS_DIR/$IMAGE_BASENAME.img
|
mv $TARGET_NAME $RESULTS_DIR/$IMAGE_BASENAME.img
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
8
system-images/compat/Cargo.lock
generated
8
system-images/compat/Cargo.lock
generated
@@ -3800,9 +3800,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.16"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@@ -4475,6 +4478,7 @@ dependencies = [
|
|||||||
"rpc-toolkit",
|
"rpc-toolkit",
|
||||||
"rust-argon2 2.0.0",
|
"rust-argon2 2.0.0",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
|||||||
Reference in New Issue
Block a user