mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Feature/consolidate setup (#3092)
* start consolidating * add start-cli flash-os * combine install and setup and refactor all * use http * undo mock * fix translation * translations * use dialogservice wrapper * better ST messaging on setup * only warn on update if breakages (#3097) * finish setup wizard and ui language-keyboard feature * fix typo * wip: localization * remove start-tunnel readme * switch to posix strings for language internal * revert mock * translate backend strings * fix missing about text * help text for args * feat: add "Add new gateway" option (#3098) * feat: add "Add new gateway" option * Update web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add translation --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix dns selection * keyboard keymap also * ability to shutdown after install * revert mock * working setup flow + manifest localization * (mostly) redundant localization on frontend * version bump * omit live medium from disk list and better space management * ignore missing package archive on 035 migration * fix device migration * add i18n helper to sdk * fix install over 0.3.5.1 * fix grub config --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com> Co-authored-by: Alex Inkin <alexander@inkin.ru> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::FutureExt;
|
||||
use futures::future::BoxFuture;
|
||||
use rust_i18n::t;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -62,33 +63,31 @@ async fn e2fsck_runner(
|
||||
let e2fsck_stderr = String::from_utf8(e2fsck_out.stderr)?;
|
||||
let code = e2fsck_out.status.code().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("e2fsck: process terminated by signal"),
|
||||
eyre!("{}", t!("disk.fsck.process-terminated-by-signal")),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
)
|
||||
})?;
|
||||
if code & 4 != 0 {
|
||||
tracing::error!(
|
||||
"some filesystem errors NOT corrected on {}:\n{}",
|
||||
logicalname.as_ref().display(),
|
||||
e2fsck_stderr,
|
||||
"{}",
|
||||
t!("disk.fsck.errors-not-corrected", device = logicalname.as_ref().display(), stderr = e2fsck_stderr),
|
||||
);
|
||||
} else if code & 1 != 0 {
|
||||
tracing::warn!(
|
||||
"filesystem errors corrected on {}:\n{}",
|
||||
logicalname.as_ref().display(),
|
||||
e2fsck_stderr,
|
||||
"{}",
|
||||
t!("disk.fsck.errors-corrected", device = logicalname.as_ref().display(), stderr = e2fsck_stderr),
|
||||
);
|
||||
}
|
||||
if code < 8 {
|
||||
if code & 2 != 0 {
|
||||
tracing::warn!("reboot required");
|
||||
tracing::warn!("{}", t!("disk.fsck.reboot-required"));
|
||||
Ok(RequiresReboot(true))
|
||||
} else {
|
||||
Ok(RequiresReboot(false))
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("e2fsck: {}", e2fsck_stderr),
|
||||
eyre!("{}", t!("disk.fsck.e2fsck-error", stderr = e2fsck_stderr)),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use imbl_value::InternedString;
|
||||
use rust_i18n::t;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -20,10 +22,10 @@ pub const MAIN_FS_SIZE: FsSize = FsSize::Gigabytes(8);
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create<I, P>(
|
||||
disks: &I,
|
||||
pvscan: &BTreeMap<PathBuf, Option<String>>,
|
||||
pvscan: &BTreeMap<PathBuf, Option<InternedString>>,
|
||||
datadir: impl AsRef<Path>,
|
||||
password: Option<&str>,
|
||||
) -> Result<String, Error>
|
||||
) -> Result<InternedString, Error>
|
||||
where
|
||||
for<'a> &'a I: IntoIterator<Item = &'a P>,
|
||||
P: AsRef<Path>,
|
||||
@@ -37,9 +39,9 @@ where
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create_pool<I, P>(
|
||||
disks: &I,
|
||||
pvscan: &BTreeMap<PathBuf, Option<String>>,
|
||||
pvscan: &BTreeMap<PathBuf, Option<InternedString>>,
|
||||
encrypted: bool,
|
||||
) -> Result<String, Error>
|
||||
) -> Result<InternedString, Error>
|
||||
where
|
||||
for<'a> &'a I: IntoIterator<Item = &'a P>,
|
||||
P: AsRef<Path>,
|
||||
@@ -79,7 +81,7 @@ where
|
||||
cmd.arg(disk.as_ref());
|
||||
}
|
||||
cmd.invoke(crate::ErrorKind::DiskManagement).await?;
|
||||
Ok(guid)
|
||||
Ok(guid.into())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -224,7 +226,7 @@ pub async fn import<P: AsRef<Path>>(
|
||||
.is_none()
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("StartOS disk not found."),
|
||||
eyre!("{}", t!("disk.main.disk-not-found")),
|
||||
crate::ErrorKind::DiskNotAvailable,
|
||||
));
|
||||
}
|
||||
@@ -234,7 +236,7 @@ pub async fn import<P: AsRef<Path>>(
|
||||
.any(|id| id == guid)
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("A StartOS disk was found, but it is not the correct disk for this device."),
|
||||
eyre!("{}", t!("disk.main.incorrect-disk")),
|
||||
crate::ErrorKind::IncorrectDisk,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ pub struct OsPartitionInfo {
|
||||
pub bios: Option<PathBuf>,
|
||||
pub boot: PathBuf,
|
||||
pub root: PathBuf,
|
||||
#[serde(skip)] // internal use only
|
||||
pub data: Option<PathBuf>,
|
||||
}
|
||||
impl OsPartitionInfo {
|
||||
pub fn contains(&self, logicalname: impl AsRef<Path>) -> bool {
|
||||
@@ -49,7 +51,7 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(list)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_disk_info(handle.params, result))
|
||||
.with_about("List disk info")
|
||||
.with_about("about.list-disk-info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("repair", from_fn_async(|_: C| repair()).no_cli())
|
||||
@@ -58,7 +60,7 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
||||
CallRemoteHandler::<CliContext, _, _>::new(
|
||||
from_fn_async(|_: RpcContext| repair())
|
||||
.no_display()
|
||||
.with_about("Repair disk in the event of corruption"),
|
||||
.with_about("about.repair-disk-corruption"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,9 +23,8 @@ pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||
read_only: bool,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!(
|
||||
"Binding {} to {}",
|
||||
src.as_ref().display(),
|
||||
dst.as_ref().display()
|
||||
"{}",
|
||||
t!("disk.mount.binding", src = src.as_ref().display(), dst = dst.as_ref().display())
|
||||
);
|
||||
if is_mountpoint(&dst).await? {
|
||||
unmount(dst.as_ref(), true).await?;
|
||||
|
||||
@@ -20,9 +20,9 @@ use super::mount::guard::TmpMountGuard;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::disk::mount::guard::GenericMountGuard;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{Error, ResultExt as _};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -40,7 +40,7 @@ pub struct DiskInfo {
|
||||
pub model: Option<String>,
|
||||
pub partitions: Vec<PartitionInfo>,
|
||||
pub capacity: u64,
|
||||
pub guid: Option<String>,
|
||||
pub guid: Option<InternedString>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@@ -51,7 +51,7 @@ pub struct PartitionInfo {
|
||||
pub capacity: u64,
|
||||
pub used: Option<u64>,
|
||||
pub start_os: BTreeMap<String, StartOsRecoveryInfo>,
|
||||
pub guid: Option<String>,
|
||||
pub guid: Option<InternedString>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
@@ -95,7 +95,7 @@ pub async fn get_vendor<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error
|
||||
Path::new(SYS_BLOCK_PATH)
|
||||
.join(path.as_ref().strip_prefix("/dev").map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("not a canonical block device"),
|
||||
eyre!("{}", t!("disk.util.not-canonical-block-device")),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?)
|
||||
@@ -118,7 +118,7 @@ pub async fn get_model<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error>
|
||||
Path::new(SYS_BLOCK_PATH)
|
||||
.join(path.as_ref().strip_prefix("/dev").map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("not a canonical block device"),
|
||||
eyre!("{}", t!("disk.util.not-canonical-block-device")),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?)
|
||||
@@ -215,7 +215,7 @@ pub async fn get_percentage<P: AsRef<Path>>(path: P) -> Result<u64, Error> {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn pvscan() -> Result<BTreeMap<PathBuf, Option<String>>, Error> {
|
||||
pub async fn pvscan() -> Result<BTreeMap<PathBuf, Option<InternedString>>, Error> {
|
||||
let pvscan_out = Command::new("pvscan")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
@@ -259,6 +259,31 @@ pub async fn recovery_info(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns the canonical path of the source device for a given mount point,
|
||||
/// or None if the mount point doesn't exist or isn't mounted.
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_mount_source(mountpoint: impl AsRef<Path>) -> Result<Option<PathBuf>, Error> {
|
||||
let mounts_content = tokio::fs::read_to_string("/proc/mounts")
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, "/proc/mounts"))?;
|
||||
|
||||
let mountpoint = mountpoint.as_ref();
|
||||
for line in mounts_content.lines() {
|
||||
let mut parts = line.split_whitespace();
|
||||
let source = parts.next();
|
||||
let mount = parts.next();
|
||||
if let (Some(source), Some(mount)) = (source, mount) {
|
||||
if Path::new(mount) == mountpoint {
|
||||
// Try to canonicalize the source path
|
||||
if let Ok(canonical) = tokio::fs::canonicalize(source).await {
|
||||
return Ok(Some(canonical));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
|
||||
struct DiskIndex {
|
||||
@@ -374,23 +399,53 @@ async fn disk_info(disk: PathBuf) -> DiskInfo {
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"Could not get partition table of {}: {}",
|
||||
disk.display(),
|
||||
e.source
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-partition-table",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let vendor = get_vendor(&disk)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get vendor of {}: {}", disk.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-vendor",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let model = get_model(&disk)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get model of {}: {}", disk.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-model",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let capacity = get_capacity(&disk)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get capacity of {}: {}", disk.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-capacity",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
DiskInfo {
|
||||
logicalname: disk,
|
||||
@@ -407,21 +462,49 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
let mut start_os = BTreeMap::new();
|
||||
let label = get_label(&part)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get label of {}: {}", part.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-label",
|
||||
part = part.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let capacity = get_capacity(&part)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get capacity of {}: {}", part.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-capacity-part",
|
||||
part = part.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut used = None;
|
||||
|
||||
match TmpMountGuard::mount(&BlockDev::new(&part), ReadOnly).await {
|
||||
Err(e) => tracing::warn!("Could not collect usage information: {}", e.source),
|
||||
Err(e) => tracing::warn!(
|
||||
"{}",
|
||||
t!("disk.util.could-not-collect-usage-info", error = e.source)
|
||||
),
|
||||
Ok(mount_guard) => {
|
||||
used = get_used(mount_guard.path())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!("Could not get usage of {}: {}", part.display(), e.source)
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-usage",
|
||||
part = part.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
match recovery_info(mount_guard.path()).await {
|
||||
@@ -429,11 +512,21 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
start_os = a;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error fetching unencrypted backup metadata: {}", e);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("disk.util.error-fetching-backup-metadata", error = e)
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Err(e) = mount_guard.unmount().await {
|
||||
tracing::error!("Error unmounting partition {}: {}", part.display(), e);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.error-unmounting-partition",
|
||||
part = part.display(),
|
||||
error = e
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,7 +541,7 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<String>> {
|
||||
fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<InternedString>> {
|
||||
fn parse_line(line: &str) -> IResult<&str, (&str, Option<&str>)> {
|
||||
let pv_parse = preceded(
|
||||
tag(" PV "),
|
||||
@@ -471,10 +564,10 @@ fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<String>>
|
||||
for entry in entries {
|
||||
match parse_line(entry) {
|
||||
Ok((_, (pv, vg))) => {
|
||||
ret.insert(PathBuf::from(pv), vg.map(|s| s.to_owned()));
|
||||
ret.insert(PathBuf::from(pv), vg.map(InternedString::intern));
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("Failed to parse pvscan output line: {}", entry);
|
||||
tracing::warn!("{}", t!("disk.util.failed-to-parse-pvscan", line = entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user