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:
Aiden McClelland
2026-01-27 14:44:41 -08:00
committed by GitHub
parent 99871805bd
commit c65db31fd9
251 changed files with 12163 additions and 3966 deletions

View File

@@ -67,7 +67,7 @@ pub async fn get_available_governors() -> Result<BTreeSet<Governor>, Error> {
for_cpu
.entry(current_cpu.ok_or_else(|| {
Error::new(
eyre!("governors listed before cpu"),
eyre!("{}", t!("util.cpupower.governors-listed-before-cpu")),
ErrorKind::ParseSysInfo,
)
})?)
@@ -95,6 +95,7 @@ pub async fn current_governor() -> Result<Option<Governor>, Error> {
let Some(raw) = Command::new("cpupower")
.arg("frequency-info")
.arg("-p")
.env("LANG", "C.UTF-8")
.invoke(ErrorKind::CpuSettings)
.await
.and_then(|s| Ok(Some(String::from_utf8(s)?)))
@@ -122,7 +123,10 @@ pub async fn current_governor() -> Result<Option<Governor>, Error> {
}
}
Err(Error::new(
eyre!("Failed to parse cpupower output:\n{raw}"),
eyre!(
"{}",
t!("util.cpupower.failed-to-parse-output", output = raw)
),
ErrorKind::ParseSysInfo,
))
}

View File

@@ -775,6 +775,23 @@ pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + S
.boxed()
}
pub async fn copy_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Error> {
let src = src.as_ref();
tokio::fs::metadata(src)
.await
.with_ctx(|_| (ErrorKind::Filesystem, src.display()))?;
let dst = dst.as_ref();
if let Some(parent) = dst.parent() {
tokio::fs::create_dir_all(parent)
.await
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("mkdir -p {parent:?}")))?;
}
tokio::fs::copy(src, dst)
.await
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("cp {src:?} -> {dst:?}")))?;
Ok(())
}
#[pin_project::pin_project]
pub struct TimeoutStream<S: AsyncRead + AsyncWrite = TcpStream> {
timeout: Duration,

View File

@@ -25,6 +25,7 @@ impl LshwDevice {
Self::Display(_) => "display",
}
}
#[instrument(skip_all)]
pub fn from_value(value: &Value) -> Option<Self> {
match value["class"].as_str() {
Some("processor") => Some(LshwDevice::Processor(LshwProcessor::from_value(value))),
@@ -41,6 +42,7 @@ pub struct LshwProcessor {
pub capabilities: BTreeSet<InternedString>,
}
impl LshwProcessor {
#[instrument(skip_all)]
fn from_value(value: &Value) -> Self {
Self {
product: value["product"].as_str().map(From::from),
@@ -63,6 +65,7 @@ pub struct LshwDisplay {
pub driver: Option<InternedString>,
}
impl LshwDisplay {
#[instrument(skip_all)]
fn from_value(value: &Value) -> Self {
Self {
product: value["product"].as_str().map(From::from),

View File

@@ -666,6 +666,27 @@ pub fn new_guid() -> InternedString {
))
}
/// A utility for lazily computing a Display value. This is useful for i18n
/// where you want to defer the translation until the value is actually displayed,
/// avoiding allocations in the common case where the message is not rendered.
pub struct LazyDisplay<F>(F);
impl<F: Fn() -> D, D: fmt::Display> LazyDisplay<F> {
pub fn new(f: F) -> Self {
LazyDisplay(f)
}
}
impl<F: Fn() -> D, D: fmt::Display> fmt::Display for LazyDisplay<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", (self.0)())
}
}
/// Creates a lazily-evaluated Display implementation.
/// The closure is only called when the value is actually displayed.
pub fn lazy_display<F: Fn() -> D, D: fmt::Display>(f: F) -> LazyDisplay<F> {
LazyDisplay::new(f)
}
#[derive(Debug, Clone, TS)]
#[ts(type = "string")]
pub enum PathOrUrl {

View File

@@ -97,7 +97,7 @@ impl WebSocket {
if self.ping_state.is_some() {
self.fused = true;
break Poll::Ready(Some(Err(axum::Error::new(eyre!(
"Timeout: WebSocket did not respond to ping within {PING_TIMEOUT:?}"
"{}", t!("util.net.websocket-ping-timeout", timeout = format!("{PING_TIMEOUT:?}"))
)))));
}
self.ping_state = Some((false, rand::random()));

View File

@@ -17,13 +17,13 @@ use crate::util::{Apply, PathOrUrl};
pub fn util<C: Context>() -> ParentHandler<C> {
ParentHandler::new().subcommand(
"b3sum",
from_fn_async(b3sum).with_about("Calculate blake3 hash for a file"),
from_fn_async(b3sum).with_about("about.calculate-blake3-hash-for-file"),
)
}
#[derive(Debug, Deserialize, Serialize, Parser)]
pub struct B3sumParams {
#[arg(long = "no-mmap", action = clap::ArgAction::SetFalse)]
#[arg(long = "no-mmap", action = clap::ArgAction::SetFalse, help = "help.arg.no-mmap")]
allow_mmap: bool,
file: String,
}
@@ -57,7 +57,7 @@ pub async fn b3sum(
.await
} else {
Err(Error::new(
eyre!("unknown scheme: {}", url.scheme()),
eyre!("{}", t!("util.rpc.unknown-scheme", scheme = url.scheme())),
ErrorKind::InvalidRequest,
))
}

View File

@@ -652,7 +652,7 @@ impl std::str::FromStr for Duration {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let units_idx = s.find(|c: char| c.is_alphabetic()).ok_or_else(|| {
Error::new(
eyre!("Must specify units for duration"),
eyre!("{}", t!("util.serde.must-specify-units-for-duration")),
crate::ErrorKind::Deserialization,
)
})?;
@@ -691,7 +691,7 @@ impl std::str::FromStr for Duration {
}
_ => {
return Err(Error::new(
eyre!("Invalid units for duration"),
eyre!("{}", t!("util.serde.invalid-units-for-duration")),
crate::ErrorKind::Deserialization,
));
}
@@ -1050,7 +1050,7 @@ impl<T: TryFrom<Vec<u8>>> FromStr for Base64<T> {
.map(Self)
.map_err(|_| {
Error::new(
eyre!("failed to create from buffer"),
eyre!("{}", t!("util.serde.failed-to-create-from-buffer")),
ErrorKind::Deserialization,
)
})
@@ -1151,7 +1151,7 @@ pub fn apply_expr(input: jaq_core::Val, expr: &str) -> Result<jaq_core::Val, Err
let Some(expr) = expr else {
return Err(Error::new(
eyre!("Failed to parse expression: {:?}", errs),
eyre!("{}", t!("util.serde.failed-to-parse-expression", errors = format!("{:?}", errs))),
crate::ErrorKind::InvalidRequest,
));
};
@@ -1167,7 +1167,7 @@ pub fn apply_expr(input: jaq_core::Val, expr: &str) -> Result<jaq_core::Val, Err
if !errs.is_empty() {
return Err(Error::new(
eyre!("Failed to compile expression: {:?}", errs),
eyre!("{}", t!("util.serde.failed-to-compile-expression", errors = format!("{:?}", errs))),
crate::ErrorKind::InvalidRequest,
));
};
@@ -1182,14 +1182,14 @@ pub fn apply_expr(input: jaq_core::Val, expr: &str) -> Result<jaq_core::Val, Err
.with_kind(crate::ErrorKind::Deserialization)?
else {
return Err(Error::new(
eyre!("expr returned no results"),
eyre!("{}", t!("util.serde.expr-returned-no-results")),
crate::ErrorKind::InvalidRequest,
));
};
if res_iter.next().is_some() {
return Err(Error::new(
eyre!("expr returned too many results"),
eyre!("{}", t!("util.serde.expr-returned-too-many-results")),
crate::ErrorKind::InvalidRequest,
));
}

View File

@@ -10,7 +10,7 @@ fn map_miette(m: miette::Error) -> Error {
}
fn noninteractive_err() -> Error {
Error::new(
eyre!("Terminal must be in interactive mode for this wizard"),
eyre!("{}", t!("util.tui.terminal-must-be-interactive")),
ErrorKind::Filesystem,
)
}
@@ -21,7 +21,7 @@ where
{
move |s| {
s.parse::<T>()
.map_err(|_| format!("Please enter a valid {what}."))
.map_err(|_| t!("util.tui.enter-valid-value", what = what).to_string())
}
}
@@ -50,7 +50,7 @@ pub async fn prompt<T, E: std::fmt::Display, Parse: FnMut(&str) -> Result<T, E>>
}
}
ReadlineEvent::Eof | ReadlineEvent::Interrupted => {
return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled));
return Err(Error::new(eyre!("{}", t!("util.tui.aborted")), ErrorKind::Cancelled));
}
_ => (),
}
@@ -83,7 +83,7 @@ pub async fn prompt_multiline<
Err(e) => writeln!(&mut rl_ctx.shared_writer, "{e}")?,
},
ReadlineEvent::Eof | ReadlineEvent::Interrupted => {
return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled));
return Err(Error::new(eyre!("{}", t!("util.tui.aborted")), ErrorKind::Cancelled));
}
_ => (),
}
@@ -119,7 +119,7 @@ pub async fn choose_custom_display<'t, T>(
.await
.map_err(map_miette)?;
if choice.len() < 1 {
return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled));
return Err(Error::new(eyre!("{}", t!("util.tui.aborted")), ErrorKind::Cancelled));
}
let (idx, choice_str) = string_choices
.iter()
@@ -127,7 +127,7 @@ pub async fn choose_custom_display<'t, T>(
.find(|(_, s)| s.as_str() == choice[0].as_str())
.ok_or_else(|| {
Error::new(
eyre!("selected choice does not appear in input"),
eyre!("{}", t!("util.tui.selected-choice-not-in-input")),
ErrorKind::Incoherent,
)
})?;