commit fdee427e98d9e384e0fbd88b098a250c4607d907
parent e3c9a2a891df00357784ac495786394ef508df9f
Author: triesap <tyson@radroots.org>
Date: Sat, 23 May 2026 23:15:47 +0000
app: add repo-local path profile
- add explicit desktop app path profile env support
- require a repo-local root before resolving localhost app state
- keep interactive-user paths as the production default
- cover repo-local and invalid profile behavior with core tests
Diffstat:
3 files changed, 154 insertions(+), 26 deletions(-)
diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs
@@ -3317,7 +3317,7 @@ impl DesktopAppRuntimeState {
let timestamp = current_runtime_time_ms()?;
let farm_d_tag = d_tag_from_uuid(saved_farm.farm_id.as_uuid());
let owner_pubkey = self.local_events_owner_pubkey(account);
- let exportability = app_local_work_exportability(owner_pubkey.as_deref());
+ let exportability = local_work_exportability(owner_pubkey.as_deref());
let delivery_method = projection
.draft
.order_methods
@@ -3409,7 +3409,7 @@ impl DesktopAppRuntimeState {
let listing_addr = owner_pubkey
.as_ref()
.map(|pubkey| format!("30402:{pubkey}:{listing_d_tag}"));
- let exportability = app_local_work_exportability(owner_pubkey.as_deref());
+ let exportability = local_work_exportability(owner_pubkey.as_deref());
let farm_setup = self.state_store.farm_setup_projection();
let delivery_method = farm_setup
.draft
@@ -3969,7 +3969,7 @@ fn is_hex_64(value: &str) -> bool {
value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit())
}
-fn app_local_work_exportability(owner_pubkey: Option<&str>) -> serde_json::Value {
+fn local_work_exportability(owner_pubkey: Option<&str>) -> serde_json::Value {
match owner_pubkey {
Some(_) => json!({
"state": "exportable"
diff --git a/crates/shared/core/src/lib.rs b/crates/shared/core/src/lib.rs
@@ -17,9 +17,10 @@ pub use pack_day_export::{
prepare_pack_day_export_bundle_at_data_root, write_prepared_pack_day_export_bundle,
};
pub use paths::{
- APP_RUNTIME_NAMESPACE, APP_RUNTIME_NAMESPACE_KIND, APP_RUNTIME_NAMESPACE_VALUE,
- AppDesktopRuntimePaths, AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform,
- AppRuntimeRoots, AppSharedAccountsPaths, AppSharedIdentityPaths, SHARED_ACCOUNTS_NAMESPACE,
+ APP_PATHS_PROFILE_ENV, APP_PATHS_REPO_LOCAL_ROOT_ENV, APP_RUNTIME_NAMESPACE,
+ APP_RUNTIME_NAMESPACE_KIND, APP_RUNTIME_NAMESPACE_VALUE, AppDesktopRuntimePaths,
+ AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform, AppRuntimeRoots,
+ AppSharedAccountsPaths, AppSharedIdentityPaths, SHARED_ACCOUNTS_NAMESPACE,
SHARED_ACCOUNTS_NAMESPACE_KIND, SHARED_ACCOUNTS_NAMESPACE_VALUE,
SHARED_ACCOUNTS_STORE_FILE_NAME, SHARED_IDENTITIES_NAMESPACE, SHARED_IDENTITIES_NAMESPACE_KIND,
SHARED_IDENTITIES_NAMESPACE_VALUE, SHARED_IDENTITY_FILE_NAME,
diff --git a/crates/shared/core/src/paths.rs b/crates/shared/core/src/paths.rs
@@ -16,6 +16,11 @@ pub const SHARED_IDENTITIES_NAMESPACE_KIND: &str = "shared";
pub const SHARED_IDENTITIES_NAMESPACE_VALUE: &str = "identities";
pub const SHARED_IDENTITIES_NAMESPACE: &str = "shared/identities";
pub const SHARED_IDENTITY_FILE_NAME: &str = "default.json";
+pub const APP_PATHS_PROFILE_ENV: &str = "RADROOTS_APP_PATHS_PROFILE";
+pub const APP_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RADROOTS_APP_PATHS_REPO_LOCAL_ROOT";
+
+const APP_INTERACTIVE_USER_PROFILE: &str = "interactive_user";
+const APP_REPO_LOCAL_PROFILE: &str = "repo_local";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AppRuntimePlatform {
@@ -50,6 +55,8 @@ pub struct AppRuntimeHostEnvironment {
pub home_dir: Option<PathBuf>,
pub appdata_dir: Option<PathBuf>,
pub localappdata_dir: Option<PathBuf>,
+ pub paths_profile: Option<String>,
+ pub repo_local_root: Option<PathBuf>,
}
impl AppRuntimeHostEnvironment {
@@ -58,6 +65,8 @@ impl AppRuntimeHostEnvironment {
home_dir: env::var_os("HOME").map(PathBuf::from),
appdata_dir: env::var_os("APPDATA").map(PathBuf::from),
localappdata_dir: env::var_os("LOCALAPPDATA").map(PathBuf::from),
+ paths_profile: env::var(APP_PATHS_PROFILE_ENV).ok(),
+ repo_local_root: env::var_os(APP_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from),
}
}
}
@@ -170,43 +179,85 @@ fn resolve_desktop_base_roots(
platform: AppRuntimePlatform,
host_environment: AppRuntimeHostEnvironment,
) -> Result<AppRuntimeRoots, AppRuntimePathsError> {
- let roots = match platform {
+ let roots = match resolve_desktop_profile(host_environment.paths_profile.as_deref())? {
+ AppDesktopPathProfile::InteractiveUser => resolve_interactive_user_roots(
+ platform,
+ host_environment.home_dir,
+ host_environment.appdata_dir,
+ host_environment.localappdata_dir,
+ )?,
+ AppDesktopPathProfile::RepoLocal => {
+ let repo_local_root = host_environment
+ .repo_local_root
+ .ok_or(AppRuntimePathsError::MissingRepoLocalRoot)?;
+ if repo_local_root.as_os_str().is_empty() {
+ return Err(AppRuntimePathsError::EmptyRepoLocalRoot);
+ }
+ AppRuntimeRoots::from_base_root(repo_local_root)
+ }
+ };
+
+ Ok(roots)
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum AppDesktopPathProfile {
+ InteractiveUser,
+ RepoLocal,
+}
+
+fn resolve_desktop_profile(
+ profile: Option<&str>,
+) -> Result<AppDesktopPathProfile, AppRuntimePathsError> {
+ match profile {
+ None => Ok(AppDesktopPathProfile::InteractiveUser),
+ Some(value) => match value.trim().to_ascii_lowercase().as_str() {
+ APP_INTERACTIVE_USER_PROFILE => Ok(AppDesktopPathProfile::InteractiveUser),
+ APP_REPO_LOCAL_PROFILE => Ok(AppDesktopPathProfile::RepoLocal),
+ _ => Err(AppRuntimePathsError::UnsupportedPathProfile {
+ value: value.to_owned(),
+ }),
+ },
+ }
+}
+
+fn resolve_interactive_user_roots(
+ platform: AppRuntimePlatform,
+ home_dir: Option<PathBuf>,
+ appdata_dir: Option<PathBuf>,
+ localappdata_dir: Option<PathBuf>,
+) -> Result<AppRuntimeRoots, AppRuntimePathsError> {
+ match platform {
AppRuntimePlatform::Linux | AppRuntimePlatform::Macos => {
- let home_dir = host_environment
- .home_dir
- .ok_or(AppRuntimePathsError::MissingHomeDir { platform })?;
- AppRuntimeRoots::from_base_root(home_dir.join(".radroots"))
+ let home_dir = home_dir.ok_or(AppRuntimePathsError::MissingHomeDir { platform })?;
+ Ok(AppRuntimeRoots::from_base_root(home_dir.join(".radroots")))
}
AppRuntimePlatform::Windows => {
- let appdata_dir = host_environment
- .appdata_dir
- .ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?;
- let localappdata_dir = host_environment
- .localappdata_dir
- .ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?;
+ let appdata_dir = appdata_dir.ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?;
+ let localappdata_dir =
+ localappdata_dir.ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?;
let config_root = appdata_dir.join("Radroots");
let local_root = localappdata_dir.join("Radroots");
- AppRuntimeRoots {
+ Ok(AppRuntimeRoots {
config: config_root.join("config"),
data: local_root.join("data"),
cache: local_root.join("cache"),
logs: local_root.join("logs"),
run: local_root.join("run"),
secrets: config_root.join("secrets"),
- }
- }
- AppRuntimePlatform::Other(_) => {
- return Err(AppRuntimePathsError::UnsupportedPlatform { platform });
+ })
}
- };
-
- Ok(roots)
+ AppRuntimePlatform::Other(_) => Err(AppRuntimePathsError::UnsupportedPlatform { platform }),
+ }
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AppRuntimePathsError {
MissingHomeDir { platform: AppRuntimePlatform },
MissingWindowsUserDirs,
+ MissingRepoLocalRoot,
+ EmptyRepoLocalRoot,
+ UnsupportedPathProfile { value: String },
UnsupportedPlatform { platform: AppRuntimePlatform },
}
@@ -222,6 +273,18 @@ impl fmt::Display for AppRuntimePathsError {
}
Self::MissingWindowsUserDirs => formatter
.write_str("desktop runtime roots require APPDATA and LOCALAPPDATA on windows"),
+ Self::MissingRepoLocalRoot => write!(
+ formatter,
+ "desktop runtime roots require {APP_PATHS_REPO_LOCAL_ROOT_ENV} when {APP_PATHS_PROFILE_ENV}=repo_local"
+ ),
+ Self::EmptyRepoLocalRoot => write!(
+ formatter,
+ "{APP_PATHS_REPO_LOCAL_ROOT_ENV} must not be empty when {APP_PATHS_PROFILE_ENV}=repo_local"
+ ),
+ Self::UnsupportedPathProfile { value } => write!(
+ formatter,
+ "{APP_PATHS_PROFILE_ENV} must be `interactive_user` or `repo_local`, got `{value}`"
+ ),
Self::UnsupportedPlatform { platform } => write!(
formatter,
"desktop runtime roots are unsupported on {}",
@@ -334,6 +397,70 @@ mod tests {
}
#[test]
+ fn desktop_runtime_roots_use_explicit_repo_local_root() {
+ let paths = AppDesktopRuntimePaths::for_desktop(
+ AppRuntimePlatform::Macos,
+ AppRuntimeHostEnvironment {
+ paths_profile: Some("repo_local".to_owned()),
+ repo_local_root: Some(PathBuf::from("/repo/infra/local/runtime/radroots")),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect("repo-local roots should resolve");
+
+ assert_eq!(
+ paths.app.data,
+ PathBuf::from("/repo/infra/local/runtime/radroots/data/apps/app")
+ );
+ assert_eq!(
+ paths.app.logs,
+ PathBuf::from("/repo/infra/local/runtime/radroots/logs/apps/app")
+ );
+ assert_eq!(
+ paths.shared_accounts.data_root,
+ PathBuf::from("/repo/infra/local/runtime/radroots/data/shared/accounts")
+ );
+ assert_eq!(
+ paths.shared_identity.default_identity_path,
+ PathBuf::from("/repo/infra/local/runtime/radroots/secrets/shared/identities")
+ .join(SHARED_IDENTITY_FILE_NAME)
+ );
+ }
+
+ #[test]
+ fn repo_local_profile_requires_explicit_root() {
+ let err = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Macos,
+ AppRuntimeHostEnvironment {
+ paths_profile: Some("repo_local".to_owned()),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect_err("repo-local root should be required");
+
+ assert_eq!(err, AppRuntimePathsError::MissingRepoLocalRoot);
+ }
+
+ #[test]
+ fn unsupported_path_profile_is_rejected() {
+ let err = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Macos,
+ AppRuntimeHostEnvironment {
+ paths_profile: Some("dev".to_owned()),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect_err("unsupported profile should fail");
+
+ assert_eq!(
+ err,
+ AppRuntimePathsError::UnsupportedPathProfile {
+ value: "dev".to_owned(),
+ }
+ );
+ }
+
+ #[test]
fn desktop_runtime_roots_require_home_dir_on_unix() {
let err = AppRuntimeRoots::for_desktop(
AppRuntimePlatform::Macos,