mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
feat: Doing the swap
This commit is contained in:
committed by
Aiden McClelland
parent
316e6e7425
commit
43cb20d390
@@ -1,7 +1,9 @@
|
|||||||
use anyhow::anyhow;
|
use anyhow::{anyhow, bail, Result};
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
@@ -13,22 +15,98 @@ use crate::context::RpcContext;
|
|||||||
use crate::update::latest_information::LatestInformation;
|
use crate::update::latest_information::LatestInformation;
|
||||||
use crate::{Error, ErrorKind, ResultExt};
|
use crate::{Error, ErrorKind, ResultExt};
|
||||||
|
|
||||||
|
/// An user/ daemon would call this to update the system to the latest version and do the updates available,
|
||||||
|
/// and this will return something if there is an update, and in that case there will need to be a restart.
|
||||||
|
#[command(display(display_properties))]
|
||||||
|
pub async fn update_system(#[context] ctx: RpcContext) -> Result<UpdateSystem, Error> {
|
||||||
|
if let None = maybe_do_update(ctx).await? {
|
||||||
|
return Ok(UpdateSystem::Updated);
|
||||||
|
}
|
||||||
|
Ok(UpdateSystem::NoUpdates)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What is the status of the updates?
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
pub enum UpdateSystem {
|
||||||
|
NoUpdates,
|
||||||
|
Updated,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_properties(status: UpdateSystem, _: &ArgMatches<'_>) {
|
||||||
|
match status {
|
||||||
|
UpdateSystem::NoUpdates => {
|
||||||
|
println!("Updates are ready, please reboot");
|
||||||
|
}
|
||||||
|
UpdateSystem::Updated => {
|
||||||
|
println!("No updates needed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const URL: &str = "https://beta-registry-0-3.start9labs.com/eos/latest";
|
const URL: &str = "https://beta-registry-0-3.start9labs.com/eos/latest";
|
||||||
const HEADER_KEY: &str = "CHECKSUM";
|
const HEADER_KEY: &str = "CHECKSUM";
|
||||||
mod latest_information;
|
mod latest_information;
|
||||||
|
|
||||||
pub fn display_properties(_: (), _: &ArgMatches<'_>) {
|
enum WritableDrives {
|
||||||
println!("Test");
|
Green,
|
||||||
}
|
Blue,
|
||||||
#[command(display(display_properties))]
|
|
||||||
pub async fn update_system(#[context] ctx: RpcContext) -> Result<(), Error> {
|
|
||||||
if let None = maybe_do_update(ctx).await? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn maybe_do_update(mut ctx: RpcContext) -> Result<Option<()>, Error> {
|
struct Boot;
|
||||||
|
|
||||||
|
/// We are going to be creating some folders and mounting so
|
||||||
|
/// we need to know the labels for those types. These labels
|
||||||
|
/// are the labels that are shipping with the embassy, blue/ green
|
||||||
|
/// are where the os sits and will do a swap during update.
|
||||||
|
trait FileType {
|
||||||
|
fn mount_folder(&self) -> String {
|
||||||
|
format!("/media/{}", self.label())
|
||||||
|
}
|
||||||
|
fn label(&self) -> String;
|
||||||
|
}
|
||||||
|
impl FileType for WritableDrives {
|
||||||
|
fn label(&self) -> String {
|
||||||
|
match self {
|
||||||
|
WritableDrives::Green => "green",
|
||||||
|
WritableDrives::Blue => "blue",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FileType for Boot {
|
||||||
|
fn label(&self) -> String {
|
||||||
|
"system-boot".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proven data that this is mounted, should be consumed in an unmount
|
||||||
|
struct MountedResource<X: FileType>(X);
|
||||||
|
impl<X: FileType> MountedResource<X> {
|
||||||
|
async fn unmount_label(&self) -> Result<()> {
|
||||||
|
let folder = self.0.mount_folder();
|
||||||
|
tokio::process::Command::new("umount")
|
||||||
|
.arg(&folder)
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
tokio::process::Command::new("rmdir")
|
||||||
|
.arg(folder)
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This will be where we are going to be putting the new update
|
||||||
|
struct NewLabel<'a>(&'a MountedResource<WritableDrives>);
|
||||||
|
|
||||||
|
/// This is our current label where the os is running
|
||||||
|
struct CurrentLabel<'a>(&'a MountedResource<WritableDrives>);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PARSE_COLOR: Regex = Regex::new("#LABEL=(\\w+) /media/root-ro/").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn maybe_do_update(mut ctx: RpcContext) -> Result<Option<()>, Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
let latest_version = reqwest::get(URL)
|
let latest_version = reqwest::get(URL)
|
||||||
.await
|
.await
|
||||||
@@ -42,17 +120,72 @@ pub async fn maybe_do_update(mut ctx: RpcContext) -> Result<Option<()>, Error> {
|
|||||||
.version()
|
.version()
|
||||||
.get_mut(&mut db)
|
.get_mut(&mut db)
|
||||||
.await?;
|
.await?;
|
||||||
if &latest_version > ¤t_version {
|
if &latest_version <= ¤t_version {
|
||||||
let file_name = "/tmp/test";
|
return Ok(None);
|
||||||
download_file(file_name).await?;
|
}
|
||||||
swap(&mut ctx).await?;
|
let file_name = "/tmp/test";
|
||||||
Ok(Some(()))
|
let mounted_blue = mount_label(WritableDrives::Blue)
|
||||||
} else {
|
.await
|
||||||
Ok(None)
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
let mounted_green = mount_label(WritableDrives::Green)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
let mounted_boot = mount_label(Boot).await.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
let potential_error_actions = async {
|
||||||
|
let (new_label, _current_label) = query_mounted_label(&mounted_blue, &mounted_green)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
download_file(file_name, &new_label).await?;
|
||||||
|
|
||||||
|
swap_boot_label(&mut ctx, &new_label, &mounted_boot).await?;
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mounted_blue
|
||||||
|
.unmount_label()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
mounted_green
|
||||||
|
.unmount_label()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
mounted_boot
|
||||||
|
.unmount_label()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
potential_error_actions?;
|
||||||
|
Ok(Some(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn query_mounted_label<'a>(
|
||||||
|
mounted_resource_left: &'a MountedResource<WritableDrives>,
|
||||||
|
mounted_resource_right: &'a MountedResource<WritableDrives>,
|
||||||
|
) -> Result<(NewLabel<'a>, CurrentLabel<'a>)> {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
tokio::process::Command::new("cat")
|
||||||
|
.arg("/etc/fstab")
|
||||||
|
.output()
|
||||||
|
.await?
|
||||||
|
.stdout,
|
||||||
|
)?;
|
||||||
|
match &PARSE_COLOR
|
||||||
|
.captures(&output)
|
||||||
|
.ok_or_else(|| anyhow!("Can't find pattern in {}", output))?[1]
|
||||||
|
{
|
||||||
|
x if x == &mounted_resource_left.0.label() => Ok((
|
||||||
|
NewLabel(mounted_resource_left),
|
||||||
|
CurrentLabel(mounted_resource_right),
|
||||||
|
)),
|
||||||
|
x if x == &mounted_resource_right.0.label() => Ok((
|
||||||
|
NewLabel(mounted_resource_right),
|
||||||
|
CurrentLabel(mounted_resource_left),
|
||||||
|
)),
|
||||||
|
e => bail!("Could not find a mounted resource for {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_file(file_name: &str) -> Result<(), Error> {
|
async fn download_file(file_name: &str, new_label: &NewLabel<'_>) -> Result<(), Error> {
|
||||||
let download_request = reqwest::get(URL).await.with_kind(ErrorKind::Network)?;
|
let download_request = reqwest::get(URL).await.with_kind(ErrorKind::Network)?;
|
||||||
let hash_from_header: String = download_request
|
let hash_from_header: String = download_request
|
||||||
.headers()
|
.headers()
|
||||||
@@ -83,7 +216,7 @@ async fn write_stream_to_file(
|
|||||||
Ok(hasher.finalize().to_vec())
|
Ok(hasher.finalize().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_download(hash_from_header: &str, file_digest: Vec<u8>) -> Result<(), Error> {
|
async fn check_download(hash_from_header: &str, file_digest: Vec<u8>) -> Result<(), Error> {
|
||||||
if hex::decode(hash_from_header).with_kind(ErrorKind::Network)? != file_digest {
|
if hex::decode(hash_from_header).with_kind(ErrorKind::Network)? != file_digest {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
anyhow!("Hash sum does not match source"),
|
anyhow!("Hash sum does not match source"),
|
||||||
@@ -92,9 +225,54 @@ pub async fn check_download(hash_from_header: &str, file_digest: Vec<u8>) -> Res
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
async fn swap_boot_label(
|
||||||
pub async fn swap(ctx: &mut RpcContext) -> Result<Value, Error> {
|
ctx: &mut RpcContext,
|
||||||
|
new_label: &NewLabel<'_>,
|
||||||
|
mounted_boot: &MountedResource<Boot>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
// disk/util add setLabel
|
// disk/util add setLabel
|
||||||
todo!("Do swap");
|
tokio::process::Command::new("sed")
|
||||||
todo!("Let system know that we need a reboot or something")
|
.arg(format!(r#""r/(blue|green)/{}/g""#, new_label.0 .0.label()))
|
||||||
|
.arg(format!("{}/etc/fstab", mounted_boot.0.mount_folder()))
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mount_label<F>(file_type: F) -> Result<MountedResource<F>>
|
||||||
|
where
|
||||||
|
F: FileType,
|
||||||
|
{
|
||||||
|
let label = file_type.label();
|
||||||
|
let folder = file_type.mount_folder();
|
||||||
|
tokio::process::Command::new("mdkir")
|
||||||
|
.arg(&folder)
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
tokio::process::Command::new("mount")
|
||||||
|
.arg("-L")
|
||||||
|
.arg(label)
|
||||||
|
.arg(folder)
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
Ok(MountedResource(file_type))
|
||||||
|
}
|
||||||
|
/// Captured from doing an fstab with an embassy box and the cat from the /etc/fstab
|
||||||
|
#[test]
|
||||||
|
fn test_capture() {
|
||||||
|
let output = r#"
|
||||||
|
#
|
||||||
|
# This fstab is for overlayroot. The real one can be found at
|
||||||
|
# /media/root-ro/etc/fstab
|
||||||
|
# The original entry for '/' and other mounts have been updated to be placed
|
||||||
|
# under /media/root-ro.
|
||||||
|
# To permanently modify this (or any other file), you should change-root into
|
||||||
|
# a writable view of the underlying filesystem using:
|
||||||
|
# sudo overlayroot-chroot
|
||||||
|
#
|
||||||
|
#LABEL=blue /media/root-ro/ ext4 ro,discard,errors=remount-ro,noauto 0 1
|
||||||
|
/media/root-ro/ / overlay lowerdir=/media/root-ro/,upperdir=/media/root-rw/overlay/,workdir=/media/root-rw/overlay-workdir/_ 0 1
|
||||||
|
LABEL=system-boot /boot/firmware vfat defaults 0 1 # overlayroot:fs-unsupported
|
||||||
|
"#;
|
||||||
|
assert_eq!(&PARSE_COLOR.captures(&output).unwrap()[1], "blue");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user