myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit 997076a2020e74ad1861949a29b407325337f3b6
parent 3e47b6c508f338570748139f810b4d71bd26b8bf
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Apr 2026 17:18:30 +0000

config: report legacy myc paths

Diffstat:
Msrc/config.rs | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 83 insertions(+), 2 deletions(-)

diff --git a/src/config.rs b/src/config.rs @@ -7,7 +7,10 @@ use nostr::PublicKey; use radroots_nostr::prelude::RadrootsNostrRelayUrl; use radroots_nostr_connect::prelude::RadrootsNostrConnectPermissions; use radroots_nostr_signer::prelude::RadrootsNostrSignerApprovalRequirement; -use radroots_runtime_paths::RadrootsPathResolver; +use radroots_runtime_paths::{ + RadrootsLegacyPathCandidate, RadrootsMigrationReport, RadrootsPathResolver, + inspect_legacy_paths, +}; use serde::{Deserialize, Serialize}; use tracing_subscriber::EnvFilter; @@ -169,6 +172,7 @@ pub struct MycRuntimeContractOutput { #[serde(default, skip_serializing_if = "Option::is_none")] pub host_vault_policy: Option<String>, pub path_overrides: MycRuntimePathOverrideContractOutput, + pub migration: MycRuntimeMigrationContractOutput, } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] @@ -179,6 +183,25 @@ pub struct MycRuntimePathOverrideContractOutput { pub compatibility_leaf_path_keys: Vec<String>, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct MycRuntimeMigrationContractOutput { + pub posture: String, + pub state: String, + pub silent_startup_relocation: bool, + pub compatibility_window: String, + pub detected_legacy_paths: Vec<MycRuntimeLegacyPathOutput>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct MycRuntimeLegacyPathOutput { + pub id: String, + pub description: String, + pub path: PathBuf, + #[serde(skip_serializing_if = "Option::is_none")] + pub destination: Option<PathBuf>, + pub import_hint: String, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MycTransportDeliveryPolicy { @@ -393,6 +416,7 @@ const MYC_HOST_VAULT_POLICY: &str = "desktop"; const MYC_CANONICAL_ROOT_SELECTION: &str = "profile_root_env_or_repo_wrapper"; const MYC_CANONICAL_SUBORDINATE_PATH_OVERRIDE: &str = "config_artifact"; const MYC_LEAF_PATH_ENV_POSTURE: &str = "compatibility_break_glass"; +const MYC_MIGRATION_IMPORT_HINT: &str = "stop myc, inspect this legacy path, then run an explicit backup/restore, custody import, or manual copy into the canonical destination; myc will not move it on startup"; const MYC_COMPATIBILITY_LEAF_PATH_KEYS: [&str; 6] = [ "MYC_LOGGING_OUTPUT_DIR", "MYC_PATHS_STATE_DIR", @@ -415,6 +439,9 @@ impl MycRuntimeContractOutput { .collect(), host_vault_policy: Some(MYC_HOST_VAULT_POLICY.to_owned()), path_overrides: MycRuntimePathOverrideContractOutput::current(), + migration: MycRuntimeMigrationContractOutput::from_report( + RadrootsMigrationReport::empty(), + ), } } } @@ -433,6 +460,28 @@ impl MycRuntimePathOverrideContractOutput { } } +impl MycRuntimeMigrationContractOutput { + fn from_report(report: RadrootsMigrationReport) -> Self { + Self { + posture: report.posture.to_owned(), + state: report.state.to_owned(), + silent_startup_relocation: report.silent_startup_relocation, + compatibility_window: report.compatibility_window.to_owned(), + detected_legacy_paths: report + .detected_legacy_paths + .into_iter() + .map(|path| MycRuntimeLegacyPathOutput { + id: path.id, + description: path.description, + path: path.path, + destination: path.destination, + import_hint: path.import_hint, + }) + .collect(), + } + } +} + impl MycSignerStateBackend { pub fn as_str(self) -> &'static str { match self { @@ -476,7 +525,30 @@ impl MycConfig { } pub fn runtime_contract_output(&self) -> MycRuntimeContractOutput { - MycRuntimeContractOutput::for_active_profile(self.paths.profile) + let mut output = MycRuntimeContractOutput::for_active_profile(self.paths.profile); + output.migration = MycRuntimeMigrationContractOutput::from_report(inspect_legacy_paths( + self.legacy_path_candidates(), + )); + output + } + + fn legacy_path_candidates(&self) -> Vec<RadrootsLegacyPathCandidate> { + vec![ + RadrootsLegacyPathCandidate::new( + "myc_repo_var_v0", + "legacy myc repo-relative var directory", + PathBuf::from("var"), + Some(self.paths.state_dir.clone()), + MYC_MIGRATION_IMPORT_HINT, + ), + RadrootsLegacyPathCandidate::new( + "myc_service_var_lib_v0", + "legacy myc service state root", + PathBuf::from("/var/lib/myc"), + Some(self.paths.state_dir.clone()), + MYC_MIGRATION_IMPORT_HINT, + ), + ] } fn default_with_path_selection( @@ -3024,5 +3096,14 @@ MYC_PERSISTENCE_SIGNER_STATE_BACKEND=sqlite "MYC_DISCOVERY_NIP05_OUTPUT_PATH" ] ); + assert_eq!( + contract.migration.posture, + "explicit_operator_import_required" + ); + assert_eq!(contract.migration.silent_startup_relocation, false); + assert_eq!( + contract.migration.compatibility_window, + "detect_and_report_only" + ); } }