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

@@ -102,7 +102,7 @@ pub fn update_tasks(
}
}
None => {
tracing::error!("action request exists in an invalid state {:?}", v.task);
tracing::error!("{}", t!("service.action.action-request-invalid-state", task = format!("{:?}", v.task)));
}
},
}
@@ -151,7 +151,7 @@ impl Handler<RunAction> for ServiceActor {
.de()?;
if matches!(&action.visibility, ActionVisibility::Disabled(_)) {
return Err(Error::new(
eyre!("action {action_id} is disabled"),
eyre!("{}", t!("service.action.action-is-disabled", action_id = action_id)),
ErrorKind::Action,
));
}
@@ -162,7 +162,7 @@ impl Handler<RunAction> for ServiceActor {
_ => false,
} {
return Err(Error::new(
eyre!("service is not in allowed status for {action_id}"),
eyre!("{}", t!("service.action.service-not-in-allowed-status", action_id = action_id)),
ErrorKind::Action,
));
}

View File

@@ -14,7 +14,7 @@ use crate::service::effects::context::EffectContext;
#[derive(Debug, Default, Parser)]
pub struct ContainerClientConfig {
#[arg(long = "socket")]
#[arg(long = "socket", help = "help.arg.socket-path")]
pub socket: Option<PathBuf>,
}

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
use rust_i18n::t;
use crate::action::{ActionInput, ActionResult, display_action_result};
use crate::db::model::package::{
@@ -80,7 +81,7 @@ pub async fn export_action(
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ClearActionsParams {
#[arg(long)]
#[arg(long, help = "help.arg.except-actions")]
pub except: Vec<ActionId>,
}
@@ -117,7 +118,9 @@ pub struct GetActionInputParams {
#[arg(skip)]
procedure_id: Guid,
#[ts(optional)]
#[arg(help = "help.arg.package-id")]
package_id: Option<PackageId>,
#[arg(help = "help.arg.action-id")]
action_id: ActionId,
}
async fn get_action_input(
@@ -155,9 +158,12 @@ pub struct RunActionParams {
#[arg(skip)]
procedure_id: Guid,
#[ts(optional)]
#[arg(help = "help.arg.package-id")]
package_id: Option<PackageId>,
#[arg(help = "help.arg.action-id")]
action_id: ActionId,
#[ts(type = "any")]
#[arg(help = "help.arg.action-input")]
input: Value,
}
async fn run_action(
@@ -175,7 +181,7 @@ async fn run_action(
if package_id != &context.seed.id {
return Err(Error::new(
eyre!("calling actions on other packages is unsupported at this time"),
eyre!("{}", t!("service.effects.action.calling-actions-on-other-packages-unsupported")),
ErrorKind::InvalidRequest,
));
context
@@ -220,7 +226,7 @@ async fn create_task(
TaskCondition::InputNotMatches => {
let Some(input) = task.input.as_ref() else {
return Err(Error::new(
eyre!("input-not-matches trigger requires input to be specified"),
eyre!("{}", t!("service.effects.action.input-not-matches-requires-input")),
ErrorKind::InvalidRequest,
));
};
@@ -238,9 +244,7 @@ async fn create_task(
else {
return Err(Error::new(
eyre!(
"action {} of {} has no input",
task.action_id,
task.package_id
"{}", t!("service.effects.action.action-has-no-input", action_id = task.action_id, package_id = task.package_id)
),
ErrorKind::InvalidRequest,
));
@@ -286,9 +290,9 @@ async fn create_task(
#[ts(type = "{ only: string[] } | { except: string[] }")]
#[ts(export)]
pub struct ClearTasksParams {
#[arg(long, conflicts_with = "except")]
#[arg(long, conflicts_with = "except", help = "help.arg.only-tasks")]
pub only: Option<Vec<ReplayId>>,
#[arg(long, conflicts_with = "only")]
#[arg(long, conflicts_with = "only", help = "help.arg.except-tasks")]
pub except: Option<Vec<ReplayId>>,
}

View File

@@ -319,9 +319,9 @@ impl CallbackHandlers {
#[ts(type = "{ only: number[] } | { except: number[] }")]
#[ts(export)]
pub struct ClearCallbacksParams {
#[arg(long, conflicts_with = "except")]
#[arg(long, conflicts_with = "except", help = "help.arg.only-callbacks")]
pub only: Option<Vec<CallbackId>>,
#[arg(long, conflicts_with = "only")]
#[arg(long, conflicts_with = "only", help = "help.arg.except-callbacks")]
pub except: Option<Vec<CallbackId>>,
}

View File

@@ -4,7 +4,7 @@ use std::str::FromStr;
use clap::builder::ValueParserFactory;
use exver::VersionRange;
use imbl_value::InternedString;
use rust_i18n::t;
use crate::db::model::package::{
CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference,
@@ -148,13 +148,25 @@ impl FromStr for DependencyRequirement {
.map(|id| id.parse().map_err(Error::from))
.collect(),
Some((kind, _)) => Err(Error::new(
eyre!("unknown dependency kind {kind}"),
eyre!(
"{}",
t!(
"service.effects.dependency.unknown-dependency-kind",
kind = kind
)
),
ErrorKind::InvalidRequest,
)),
None => match rest {
"r" | "running" => Ok(BTreeSet::new()),
kind => Err(Error::new(
eyre!("unknown dependency kind {kind}"),
eyre!(
"{}",
t!(
"service.effects.dependency.unknown-dependency-kind",
kind = kind
)
),
ErrorKind::InvalidRequest,
)),
},
@@ -293,8 +305,7 @@ pub struct CheckDependenciesParam {
#[ts(export)]
pub struct CheckDependenciesResult {
package_id: PackageId,
#[ts(type = "string | null")]
title: Option<InternedString>,
title: Option<String>,
installed_version: Option<VersionString>,
satisfies: BTreeSet<VersionString>,
is_running: bool,
@@ -334,7 +345,7 @@ pub async fn check_dependencies(
.collect();
results.push(CheckDependenciesResult {
package_id,
title,
title: title.map(|t| t.localized()),
installed_version: None,
satisfies: BTreeSet::new(),
is_running: false,
@@ -360,7 +371,7 @@ pub async fn check_dependencies(
.collect();
results.push(CheckDependenciesResult {
package_id,
title,
title: title.map(|t| t.localized()),
installed_version,
satisfies,
is_running,

View File

@@ -11,6 +11,6 @@ pub(super) use crate::service::effects::context::EffectContext;
#[ts(export)]
pub struct EventId {
#[serde(default)]
#[arg(default_value_t, long)]
#[arg(default_value_t, long, help = "help.arg.event-id")]
pub event_id: Guid,
}

View File

@@ -107,22 +107,23 @@ fn open_file_read(path: impl AsRef<Path>) -> Result<File, Error> {
#[derive(Debug, Clone, Serialize, Deserialize, Parser)]
pub struct ExecParams {
#[arg(long)]
#[arg(long, help = "help.arg.force-tty")]
force_tty: bool,
#[arg(long)]
#[arg(long, help = "help.arg.force-stderr-tty")]
force_stderr_tty: bool,
#[arg(long)]
#[arg(long, help = "help.arg.pty-size")]
pty_size: Option<TermSize>,
#[arg(short, long)]
#[arg(short, long, help = "help.arg.env-variable")]
env: Vec<String>,
#[arg(long)]
#[arg(long, help = "help.arg.env-file-path")]
env_file: Option<PathBuf>,
#[arg(short, long)]
#[arg(short, long, help = "help.arg.workdir-path")]
workdir: Option<PathBuf>,
#[arg(short, long)]
#[arg(short, long, help = "help.arg.user-name")]
user: Option<String>,
#[arg(help = "help.arg.chroot-path")]
chroot: PathBuf,
#[arg(trailing_var_arg = true)]
#[arg(trailing_var_arg = true, help = "help.arg.command-to-execute")]
command: Vec<OsString>,
}
impl ExecParams {

View File

@@ -28,6 +28,7 @@ use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
use ts_rs::TS;
use url::Url;
use crate::context::{CliContext, RpcContext};
use crate::db::model::package::{
InstalledState, ManifestPreference, PackageState, PackageStateMatchModelRef, TaskSeverity,
@@ -172,7 +173,7 @@ impl ServiceRef {
}
let service = Arc::try_unwrap(self.0).map_err(|_| {
Error::new(
eyre!("ServiceActor held somewhere after actor shutdown"),
eyre!("{}", t!("service.mod.service-actor-held-after-shutdown")),
ErrorKind::Unknown,
)
})?;
@@ -183,7 +184,7 @@ impl ServiceRef {
Arc::try_unwrap(service.seed)
.map_err(|_| {
Error::new(
eyre!("ServiceActorSeed held somewhere after actor shutdown"),
eyre!("{}", t!("service.mod.service-actor-seed-held-after-shutdown")),
ErrorKind::Unknown,
)
})?
@@ -375,7 +376,7 @@ impl Service {
{
Ok(PackageState::Installed(InstalledState { manifest }))
} else {
Err(Error::new(eyre!("Race condition detected - package state changed during load"), ErrorKind::Database))
Err(Error::new(eyre!("{}", t!("service.mod.race-condition-detected")), ErrorKind::Database))
}
})
}
@@ -446,7 +447,7 @@ impl Service {
handle_installed(S9pk::open(s9pk_path, Some(id)).await?).await
}
PackageStateMatchModelRef::Error(e) => Err(Error::new(
eyre!("Failed to parse PackageDataEntry, found {e:?}"),
eyre!("{}", t!("service.mod.failed-to-parse-package-data-entry", error = format!("{e:?}"))),
ErrorKind::Deserialization,
)),
}
@@ -552,7 +553,7 @@ impl Service {
true
} else {
tracing::warn!(
"Deleting task {id} because action no longer exists"
"{}", t!("service.mod.deleting-task-action-no-longer-exists", id = id)
);
false
}
@@ -684,6 +685,7 @@ struct ServiceActorSeed {
#[derive(Deserialize, Serialize, Parser, TS)]
pub struct RebuildParams {
#[arg(help = "help.arg.package-id")]
pub id: PackageId,
}
pub async fn rebuild(ctx: RpcContext, RebuildParams { id }: RebuildParams) -> Result<(), Error> {
@@ -789,7 +791,7 @@ pub async fn attach(
.join("\n");
return Err(Error::new(
eyre!(
"no matching subcontainers are running for {id}; some possible choices are:\n{subcontainers}"
"{}", t!("service.mod.no-matching-subcontainers", id = id, subcontainers = subcontainers)
),
ErrorKind::NotFound,
));
@@ -828,7 +830,7 @@ pub async fn attach(
.map(format_subcontainer_pair)
.join("\n");
return Err(Error::new(
eyre!("multiple subcontainers found for {id}: \n{subcontainer_ids}"),
eyre!("{}", t!("service.mod.multiple-subcontainers-found", id = id, subcontainer_ids = subcontainer_ids)),
ErrorKind::InvalidRequest,
));
}
@@ -985,7 +987,7 @@ pub async fn attach(
"signal" => {
if data.len() != 4 {
return Err(Error::new(
eyre!("invalid byte length for signal: {}", data.len()),
eyre!("{}", t!("service.mod.invalid-byte-length-for-signal", length = data.len())),
ErrorKind::InvalidRequest
));
}
@@ -1118,14 +1120,14 @@ async fn get_passwd_command(etc_passwd_path: PathBuf, user: &str) -> RootCommand
}
}
Err(Error::new(
eyre!("Could not parse /etc/passwd for shell: {}", contents),
eyre!("{}", t!("service.mod.could-not-parse-etc-passwd", contents = contents)),
ErrorKind::Filesystem,
))
}
.await
.map(RootCommand)
.unwrap_or_else(|e| {
tracing::error!("Could not get the /etc/passwd: {e}");
tracing::error!("{}", t!("service.mod.could-not-get-etc-passwd", error = e));
tracing::debug!("{e:?}");
RootCommand("/bin/sh".to_string())
})
@@ -1133,18 +1135,19 @@ async fn get_passwd_command(etc_passwd_path: PathBuf, user: &str) -> RootCommand
#[derive(Deserialize, Serialize, Parser)]
pub struct CliAttachParams {
#[arg(help = "help.arg.package-id")]
pub id: PackageId,
#[arg(long)]
#[arg(long, help = "help.arg.force-tty")]
pub force_tty: bool,
#[arg(trailing_var_arg = true)]
#[arg(trailing_var_arg = true, help = "help.arg.command-to-execute")]
pub command: Vec<OsString>,
#[arg(long, short)]
#[arg(long, short, help = "help.arg.subcontainer-name")]
subcontainer: Option<InternedString>,
#[arg(long, short)]
#[arg(long, short, help = "help.arg.container-name")]
name: Option<InternedString>,
#[arg(long, short)]
#[arg(long, short, help = "help.arg.user-name")]
user: Option<InternedString>,
#[arg(long, short)]
#[arg(long, short, help = "help.arg.image-id")]
image_id: Option<ImageId>,
}
#[instrument[skip_all]]
@@ -1287,7 +1290,7 @@ pub async fn cli_attach(
"exit" => {
if data.len() != 4 {
return Err(Error::new(
eyre!("invalid byte length for exit code: {}", data.len()),
eyre!("{}", t!("service.mod.invalid-byte-length-for-exit-code", length = data.len())),
ErrorKind::InvalidRequest
));
}

View File

@@ -318,7 +318,7 @@ impl PersistentContainer {
.get()
.ok_or_else(|| {
Error::new(
eyre!("PersistentContainer has been destroyed"),
eyre!("{}", t!("service.persistent-container.container-destroyed")),
ErrorKind::Incoherent,
)
})?
@@ -354,7 +354,7 @@ impl PersistentContainer {
.get()
.ok_or_else(|| {
Error::new(
eyre!("PersistentContainer has been destroyed"),
eyre!("{}", t!("service.persistent-container.container-destroyed")),
ErrorKind::Incoherent,
)
})?
@@ -364,7 +364,7 @@ impl PersistentContainer {
let handle = NonDetachingJoinHandle::from(tokio::spawn(async move {
let chown_status = async {
let res = server.run_unix(&path, |err| {
tracing::error!("error on unix socket {}: {err}", path.display())
tracing::error!("{}", t!("service.persistent-container.error-on-unix-socket", path = path.display(), error = err))
})?;
Command::new("chown")
.arg("100000:100000")
@@ -386,7 +386,7 @@ impl PersistentContainer {
}));
let shutdown = recv.await.map_err(|_| {
Error::new(
eyre!("unix socket server thread panicked"),
eyre!("{}", t!("service.persistent-container.unix-socket-server-panicked")),
ErrorKind::Unknown,
)
})??;
@@ -396,7 +396,7 @@ impl PersistentContainer {
.is_some()
{
return Err(Error::new(
eyre!("PersistentContainer already initialized"),
eyre!("{}", t!("service.persistent-container.already-initialized")),
ErrorKind::InvalidRequest,
));
}
@@ -473,7 +473,7 @@ impl PersistentContainer {
if let Some(destroy) = self.destroy(uninit) {
destroy.await?;
}
tracing::info!("Service for {} exited", self.s9pk.as_manifest().id);
tracing::info!("{}", t!("service.persistent-container.service-exited", id = self.s9pk.as_manifest().id));
Ok(())
}

View File

@@ -47,9 +47,9 @@ impl Actor for ServiceActor {
}
.await
{
tracing::error!("error synchronizing state of service: {e}");
tracing::error!("{}", t!("service.service-actor.error-synchronizing-state", error = e));
tracing::debug!("{e:?}");
tracing::error!("Retrying in {}s...", SYNC_RETRY_COOLDOWN_SECONDS);
tracing::error!("{}", t!("service.service-actor.retrying-in-seconds", seconds = SYNC_RETRY_COOLDOWN_SECONDS));
tokio::time::timeout(
Duration::from_secs(SYNC_RETRY_COOLDOWN_SECONDS),
async {

View File

@@ -25,7 +25,7 @@ impl ServiceActorSeed {
fut.await.map_err(Error::from)
} else {
Err(Error::new(
eyre!("No backup to resume"),
eyre!("{}", t!("service.transition.backup.no-backup-to-resume")),
ErrorKind::Cancelled,
))
};

View File

@@ -62,7 +62,7 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, soft: bool) -> Result<(),
| PackageState::Removing(InstalledState { manifest }) => manifest,
s => {
return Err(Error::new(
eyre!("Invalid package state for cleanup: {s:?}"),
eyre!("{}", t!("service.uninstall.invalid-package-state-for-cleanup", state = format!("{s:?}"))),
ErrorKind::InvalidRequest,
));
}