@@ -60,6 +60,7 @@ const FETCH_SPINNER_DELAY: Duration = Duration::from_millis(120);
6060const FETCH_SPINNER_TICK : Duration = Duration :: from_millis ( 80 ) ;
6161const FETCH_SPINNER_FRAMES : [ & str ; 4 ] = [ "⠋" , "⠙" , "⠹" , "⠸" ] ;
6262const RESTART_DAEMON_ACK_TIMEOUT : Duration = Duration :: from_millis ( 250 ) ;
63+ const DEFAULT_CONFIG_PATH : & str = "systemg.yaml" ;
6364
6465#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
6566enum InspectStreamAction {
@@ -321,6 +322,11 @@ fn main() -> Result<(), Box<dyn Error>> {
321322
322323 let start_target =
323324 resolve_start_target ( & config, service, name. as_deref ( ) , command) ?;
325+ let command_project = resolve_command_project (
326+ & config,
327+ project. clone ( ) ,
328+ start_target. service . as_deref ( ) ,
329+ ) ?;
324330
325331 if daemonize {
326332 if supervisor_running ( ) {
@@ -341,11 +347,11 @@ fn main() -> Result<(), Box<dyn Error>> {
341347 start_target. config_path. display( )
342348 ) ;
343349 } else if let Some ( service_name) = start_target. service {
344- let target_project =
345- project. clone ( ) . or ( start_target. project_id . clone ( ) ) ;
346350 let command = ControlCommand :: Start {
347351 service : Some ( service_name. clone ( ) ) ,
348- project : target_project,
352+ project : command_project
353+ . clone ( )
354+ . or ( start_target. project_id . clone ( ) ) ,
349355 } ;
350356 send_control_command ( command) ?;
351357 info ! (
@@ -384,11 +390,9 @@ fn main() -> Result<(), Box<dyn Error>> {
384390 if supervisor_running ( ) {
385391 match start_target. service {
386392 Some ( service_name) => {
387- let target_project =
388- project. clone ( ) . or ( start_target. project_id ) ;
389393 let command = ControlCommand :: Start {
390394 service : Some ( service_name. clone ( ) ) ,
391- project : target_project ,
395+ project : command_project . or ( start_target . project_id ) ,
392396 } ;
393397 send_control_command ( command) ?;
394398 info ! (
@@ -415,15 +419,17 @@ fn main() -> Result<(), Box<dyn Error>> {
415419 } => {
416420 let service_name = service. clone ( ) ;
417421 if supervisor_running ( ) {
422+ let target_project =
423+ resolve_command_project ( & config, project, service_name. as_deref ( ) ) ?;
418424 let command = if let Some ( name) = service_name. clone ( ) {
419425 ControlCommand :: Stop {
420426 service : Some ( name) ,
421- project,
427+ project : target_project ,
422428 }
423- } else if project . is_some ( ) {
429+ } else if target_project . is_some ( ) {
424430 ControlCommand :: Stop {
425431 service : None ,
426- project,
432+ project : target_project ,
427433 }
428434 } else {
429435 ControlCommand :: Shutdown
@@ -481,7 +487,14 @@ fn main() -> Result<(), Box<dyn Error>> {
481487 "--drop-privileges is managed by the running supervisor and has no effect for this restart request"
482488 ) ;
483489 }
484- let config_override = if config. is_empty ( ) {
490+ let target_project = resolve_command_project (
491+ & config,
492+ project. clone ( ) ,
493+ service. as_deref ( ) ,
494+ ) ?;
495+ let config_override = if config. is_empty ( )
496+ || ( config == DEFAULT_CONFIG_PATH && project. is_some ( ) )
497+ {
485498 None
486499 } else {
487500 Some ( resolve_config_path ( & config) ?. display ( ) . to_string ( ) )
@@ -490,7 +503,7 @@ fn main() -> Result<(), Box<dyn Error>> {
490503 let command = ControlCommand :: Restart {
491504 config : config_override. clone ( ) ,
492505 service : service. clone ( ) ,
493- project : project . clone ( ) ,
506+ project : target_project ,
494507 } ;
495508 if daemonize {
496509 let recycle_config_path = config_override
@@ -537,14 +550,19 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
537550 {
538551 effective_config = hint. to_string_lossy ( ) . to_string ( ) ;
539552 }
553+ let target_project = resolve_command_project (
554+ & effective_config,
555+ project. clone ( ) ,
556+ service. as_deref ( ) ,
557+ ) ?;
540558
541559 let render_opts = StatusRenderOptions {
542560 json,
543561 no_color,
544562 full_cmd,
545563 include_orphans : all,
546564 service_filter : service. as_deref ( ) ,
547- project_filter : project . as_deref ( ) ,
565+ project_filter : target_project . as_deref ( ) ,
548566 } ;
549567
550568 if let Some ( stream_interval) = stream {
@@ -616,6 +634,11 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
616634 {
617635 effective_config = hint. to_string_lossy ( ) . to_string ( ) ;
618636 }
637+ let target_project = resolve_command_project (
638+ & effective_config,
639+ project. clone ( ) ,
640+ Some ( & service) ,
641+ ) ?;
619642
620643 let stream_seconds = match stream. as_deref ( ) {
621644 Some ( value) => match parse_stream_duration ( value) {
@@ -659,7 +682,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
659682 let payload = fetch_inspect (
660683 & effective_config,
661684 & service,
662- project . as_deref ( ) ,
685+ target_project . as_deref ( ) ,
663686 samples_limit,
664687 live,
665688 ) ?;
@@ -693,7 +716,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
693716 let payload = fetch_inspect (
694717 & effective_config,
695718 & service,
696- project . as_deref ( ) ,
719+ target_project . as_deref ( ) ,
697720 samples_limit,
698721 live,
699722 ) ?;
@@ -711,7 +734,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
711734 fetch_inspect (
712735 & effective_config,
713736 & service,
714- project . as_deref ( ) ,
737+ target_project . as_deref ( ) ,
715738 samples_limit,
716739 live,
717740 )
@@ -749,12 +772,17 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
749772 }
750773 }
751774 } ;
775+ let target_project = resolve_command_project (
776+ & effective_config,
777+ project. clone ( ) ,
778+ service. as_deref ( ) ,
779+ ) ?;
752780
753781 let pid = Arc :: new ( Mutex :: new ( PidFile :: load ( ) . unwrap_or_default ( ) ) ) ;
754782 let manager = LogManager :: new ( pid. clone ( ) ) ;
755783
756784 if purge {
757- if project . is_some ( ) {
785+ if target_project . is_some ( ) {
758786 let snapshot = with_progress_spinner ( "Purging logs" , || {
759787 fetch_status_snapshot ( & effective_config, false )
760788 } ) ?;
@@ -765,7 +793,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
765793 status_unit_matches_selector (
766794 unit,
767795 service. as_deref ( ) ,
768- project . as_deref ( ) ,
796+ target_project . as_deref ( ) ,
769797 )
770798 } )
771799 . collect ( ) ;
@@ -796,7 +824,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
796824 |follow : bool | -> Result < ( ) , Box < dyn Error > > {
797825 let command = ControlCommand :: Logs {
798826 service : service. clone ( ) ,
799- project : project . clone ( ) ,
827+ project : target_project . clone ( ) ,
800828 lines,
801829 kind : kind. as_ref ( ) . map ( |kind| kind. as_str ( ) . to_string ( ) ) ,
802830 follow,
@@ -817,7 +845,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
817845 & manager,
818846 & snapshot,
819847 service_name,
820- project . as_deref ( ) ,
848+ target_project . as_deref ( ) ,
821849 lines,
822850 kind. as_ref ( ) . map ( |kind| kind. as_str ( ) ) ,
823851 snapshot_mode,
@@ -828,7 +856,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
828856 render_all_logs_from_snapshot (
829857 & manager,
830858 & snapshot,
831- project . as_deref ( ) ,
859+ target_project . as_deref ( ) ,
832860 lines,
833861 kind. as_ref ( ) . map ( |kind| kind. as_str ( ) ) ,
834862 snapshot_mode,
@@ -865,7 +893,7 @@ Use --daemonize in deployment scripts to ensure daemonized supervision is restor
865893 loop {
866894 let command = ControlCommand :: Logs {
867895 service : service. clone ( ) ,
868- project : project . clone ( ) ,
896+ project : target_project . clone ( ) ,
869897 lines,
870898 kind : kind. as_ref ( ) . map ( |kind| kind. as_str ( ) . to_string ( ) ) ,
871899 follow : false ,
@@ -3200,6 +3228,57 @@ fn yaml_single_quoted(value: &str) -> String {
32003228 format ! ( "'{}'" , value. replace( '\'' , "''" ) )
32013229}
32023230
3231+ /// Strips an optional `project/service` selector prefix.
3232+ fn service_selector_name ( selector : & str ) -> & str {
3233+ selector
3234+ . split_once ( '/' )
3235+ . map ( |( _, service) | service)
3236+ . unwrap_or ( selector)
3237+ }
3238+
3239+ /// Resolves the project a command should target from an explicit project flag and config.
3240+ fn resolve_command_project (
3241+ config_arg : & str ,
3242+ explicit_project : Option < String > ,
3243+ service : Option < & str > ,
3244+ ) -> Result < Option < String > , Box < dyn Error > > {
3245+ let config_path = resolve_config_path ( config_arg) ?;
3246+ let config_value = load_config ( Some ( config_path. to_string_lossy ( ) . as_ref ( ) ) ) . ok ( ) ;
3247+
3248+ if let Some ( project) = explicit_project {
3249+ if config_arg != DEFAULT_CONFIG_PATH
3250+ && let Some ( config) = config_value. as_ref ( )
3251+ && config. project . id != project
3252+ {
3253+ return Err ( io:: Error :: new (
3254+ io:: ErrorKind :: InvalidInput ,
3255+ format ! (
3256+ "project '{}' does not match config project '{}'" ,
3257+ project, config. project. id
3258+ ) ,
3259+ )
3260+ . into ( ) ) ;
3261+ }
3262+ return Ok ( Some ( project) ) ;
3263+ }
3264+
3265+ let Some ( config) = config_value else {
3266+ return Ok ( None ) ;
3267+ } ;
3268+
3269+ if config_arg != DEFAULT_CONFIG_PATH {
3270+ return Ok ( Some ( config. project . id ) ) ;
3271+ }
3272+
3273+ if let Some ( service) = service
3274+ && config. services . contains_key ( service_selector_name ( service) )
3275+ {
3276+ return Ok ( Some ( config. project . id ) ) ;
3277+ }
3278+
3279+ Ok ( None )
3280+ }
3281+
32033282/// Resolves config path.
32043283fn resolve_config_path ( path : & str ) -> Result < PathBuf , Box < dyn Error > > {
32053284 let candidate = PathBuf :: from ( path) ;
0 commit comments