@@ -127,20 +127,7 @@ async fn do_connect(client_mgr: &ClientManager) -> Result<MeshResponse, HttpHand
127127 . wait_ready_info ( )
128128 . await
129129 . map_err ( internal_error) ?;
130- let instance_id = mesh_response_instance_id ( & zt_info)
131- . map_err ( |e| internal_error ( format ! ( "invalid zerotier id: {e}" ) ) ) ?;
132-
133- // Keep meshtier as a single instance: always remove old meshtier instances before create.
134- clear_meshtier_networks ( client_mgr, runtime. identity ) . await ?;
135-
136- let cfg = default_mesh_network_config ( instance_id, & zt_info. id , & zt_info. id ) ;
137- client_mgr
138- . handle_run_network_instance ( runtime. identity , cfg, true )
139- . await
140- . map_err ( convert_remote_error) ?;
141-
142- // Keep startup checks for EasyTier instance, but return ZT-compatible payload for API callers.
143- wait_easytier_online ( client_mgr, runtime. identity , instance_id) . await ?;
130+ register_meshtier_instance ( client_mgr, runtime. identity , & zt_info) . await ?;
144131
145132 match runtime. zerotier . get_info ( ) . await {
146133 Ok ( info) => Ok ( info) ,
@@ -217,6 +204,25 @@ async fn wait_easytier_online(
217204 . map_err ( |_| internal_error ( "wait easytier online timeout" ) ) ?
218205}
219206
207+ async fn register_meshtier_instance (
208+ client_mgr : & ClientManager ,
209+ identity : SessionIdentity ,
210+ zt_info : & MeshResponse ,
211+ ) -> Result < MeshResponse , HttpHandleError > {
212+ let instance_id = mesh_response_instance_id ( zt_info)
213+ . map_err ( |e| internal_error ( format ! ( "invalid zerotier id: {e}" ) ) ) ?;
214+
215+ clear_meshtier_networks ( client_mgr, identity) . await ?;
216+
217+ let cfg = default_mesh_network_config ( instance_id, & zt_info. id , & zt_info. id ) ;
218+ client_mgr
219+ . handle_run_network_instance ( identity, cfg, true )
220+ . await
221+ . map_err ( convert_remote_error) ?;
222+
223+ wait_easytier_online ( client_mgr, identity, instance_id) . await
224+ }
225+
220226async fn clear_meshtier_networks (
221227 client_mgr : & ClientManager ,
222228 identity : SessionIdentity ,
@@ -268,6 +274,33 @@ fn add_running_meshtier_network_ids(
268274 }
269275}
270276
277+ async fn has_meshtier_instance (
278+ client_mgr : & ClientManager ,
279+ identity : SessionIdentity ,
280+ ) -> Result < bool , HttpHandleError > {
281+ let mut network_ids = BTreeSet :: new ( ) ;
282+
283+ if let Ok ( info) = client_mgr. handle_collect_network_info ( identity, None ) . await {
284+ add_running_meshtier_network_ids ( & mut network_ids, info) ;
285+ }
286+
287+ let saved_networks: Vec < user_running_network_configs:: Model > = client_mgr
288+ . get_storage ( )
289+ . list_network_configs ( identity, ListNetworkProps :: All )
290+ . await
291+ . map_err ( convert_db_error) ?;
292+
293+ for network in saved_networks {
294+ if let Ok ( inst_id) = uuid:: Uuid :: parse_str ( network. get_network_inst_id ( ) ) {
295+ if is_meshtier_instance ( & inst_id) {
296+ network_ids. insert ( inst_id) ;
297+ }
298+ }
299+ }
300+
301+ Ok ( !network_ids. is_empty ( ) )
302+ }
303+
271304fn is_meshtier_instance ( inst_id : & uuid:: Uuid ) -> bool {
272305 inst_id. as_bytes ( ) [ ..8 ] == MESH_TIER_PREFIX
273306}
@@ -475,6 +508,74 @@ async fn pick_admin_identity(
475508 Ok ( ( admin. user_id , admin. machine_id ) )
476509}
477510
511+ pub ( crate ) async fn ensure_meshtier_instance_for_startup (
512+ client_mgr : & ClientManager ,
513+ ) -> anyhow:: Result < bool > {
514+ let runtime = match MeshRuntime :: from_request ( client_mgr) . await {
515+ Ok ( runtime) => runtime,
516+ Err ( ( StatusCode :: SERVICE_UNAVAILABLE , Json ( err) ) ) => {
517+ tracing:: debug!(
518+ "startup meshtier reconcile waiting for core session: {}" ,
519+ err. message
520+ ) ;
521+ return Ok ( false ) ;
522+ }
523+ Err ( ( status, Json ( err) ) ) => {
524+ return Err ( anyhow:: anyhow!(
525+ "startup meshtier reconcile failed to build runtime, status {}: {}" ,
526+ status,
527+ err. message
528+ ) ) ;
529+ }
530+ } ;
531+
532+ let zt_info = runtime
533+ . zerotier
534+ . get_info ( )
535+ . await
536+ . map_err ( |e| anyhow:: anyhow!( "failed to get zerotier info: {e}" ) ) ?;
537+
538+ if zt_info. status != MeshStatus :: Online || !mesh_response_is_ready ( & zt_info) {
539+ tracing:: debug!(
540+ id = %zt_info. id,
541+ ip = ?zt_info. ip,
542+ status = ?zt_info. status,
543+ "startup meshtier reconcile waiting for zerotier readiness"
544+ ) ;
545+ return Ok ( false ) ;
546+ }
547+
548+ match has_meshtier_instance ( client_mgr, runtime. identity ) . await {
549+ Ok ( true ) => {
550+ tracing:: info!( "startup meshtier reconcile found existing meshtier instance" ) ;
551+ return Ok ( true ) ;
552+ }
553+ Ok ( false ) => { }
554+ Err ( ( status, Json ( err) ) ) => {
555+ return Err ( anyhow:: anyhow!(
556+ "failed to inspect meshtier instance state, status {}: {}" ,
557+ status,
558+ err. message
559+ ) ) ;
560+ }
561+ }
562+
563+ register_meshtier_instance ( client_mgr, runtime. identity , & zt_info)
564+ . await
565+ . map_err ( |( status, Json ( err) ) | {
566+ anyhow:: anyhow!(
567+ "startup meshtier reconcile failed to register instance, status {}: {}" ,
568+ status,
569+ err. message
570+ )
571+ } ) ?;
572+ tracing:: info!(
573+ zerotier_id = %zt_info. id,
574+ "startup meshtier reconcile registered meshtier instance"
575+ ) ;
576+ Ok ( true )
577+ }
578+
478579impl ZeroTierClient {
479580 fn new ( base_url : Url ) -> Self {
480581 Self {
0 commit comments