mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
185 lines
5.1 KiB
Go
185 lines
5.1 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
//go:build !tinygo
|
|
|
|
package sharedinstance
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"quad4/reticulum-go/pkg/backbone"
|
|
"quad4/reticulum-go/pkg/common"
|
|
"quad4/reticulum-go/pkg/debug"
|
|
"quad4/reticulum-go/pkg/interfaces"
|
|
"quad4/reticulum-go/pkg/transport"
|
|
)
|
|
|
|
// Mode describes how this process participates in the shared instance.
|
|
type Mode int
|
|
|
|
const (
|
|
ModeDisabled Mode = iota
|
|
ModeServer
|
|
ModeClient
|
|
)
|
|
|
|
// Instance holds shared-instance state for a running node.
|
|
type Instance struct {
|
|
Mode Mode
|
|
Server *interfaces.LocalServerInterface
|
|
Client *interfaces.LocalClientInterface
|
|
RPC *RPCServer
|
|
}
|
|
|
|
// Hooks wires shared-instance clients into the transport stack.
|
|
type Hooks struct {
|
|
RegisterInterface func(name string, iface common.NetworkInterface) error
|
|
UnregisterInterface func(name string)
|
|
HandleInterface func(iface common.NetworkInterface)
|
|
OnClientAttach func()
|
|
}
|
|
|
|
// Attach starts or joins a shared local instance when share_instance is
|
|
// enabled.
|
|
//
|
|
// With an explicit shared_instance_type it binds as server first, then falls
|
|
// back to client (Python behavior).
|
|
//
|
|
// When the type is unset it matches Python platform defaults (Unix on Linux,
|
|
// TCP elsewhere). Before binding, it tries client dials on the primary then
|
|
// alternate transport so utilities can join stock Python Unix or a TCP Go
|
|
// daemon without rewriting config.
|
|
func Attach(cfg *common.ReticulumConfig, tr *transport.Transport, hooks Hooks) (*Instance, error) {
|
|
if cfg == nil || !cfg.ShareInstance {
|
|
return &Instance{Mode: ModeDisabled}, nil
|
|
}
|
|
auto := strings.TrimSpace(cfg.SharedInstanceType) == ""
|
|
typ := common.ResolveSharedInstanceType(cfg.SharedInstanceType)
|
|
useUnix := typ == common.SharedInstanceUnix
|
|
socketPath := cfg.InstanceName
|
|
if socketPath == "" && useUnix {
|
|
socketPath = "default"
|
|
}
|
|
|
|
if auto {
|
|
order := []bool{useUnix}
|
|
if useUnix {
|
|
order = append(order, false)
|
|
} else {
|
|
order = append(order, true)
|
|
}
|
|
for _, unix := range order {
|
|
path := socketPath
|
|
if unix && path == "" {
|
|
path = "default"
|
|
}
|
|
if inst, err := attachClient(cfg, tr, hooks, cfg.SharedInstancePort, path, unix); err == nil {
|
|
return inst, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return attachServerOrClient(cfg, tr, hooks, cfg.SharedInstancePort, socketPath, useUnix)
|
|
}
|
|
|
|
func attachServerOrClient(cfg *common.ReticulumConfig, tr *transport.Transport, hooks Hooks, port int, socketPath string, useUnix bool) (*Instance, error) {
|
|
inst := &Instance{}
|
|
spawn := func(client *interfaces.LocalClientInterface) {
|
|
if hooks.RegisterInterface == nil || hooks.HandleInterface == nil {
|
|
return
|
|
}
|
|
if err := hooks.RegisterInterface(client.GetName(), client); err != nil {
|
|
debug.Log(debug.DebugCritical, "Failed to register spawned local client", "error", err)
|
|
return
|
|
}
|
|
hooks.HandleInterface(client)
|
|
}
|
|
|
|
server, err := interfaces.NewLocalServerInterface(port, socketPath, useUnix, spawn, backbone.Get())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := server.Start(); err == nil {
|
|
inst.Mode = ModeServer
|
|
inst.Server = server
|
|
if hooks.RegisterInterface != nil {
|
|
if err := hooks.RegisterInterface(server.GetName(), server); err != nil {
|
|
_ = server.Stop()
|
|
return nil, fmt.Errorf("register shared server: %w", err)
|
|
}
|
|
}
|
|
// Persist resolved transport so RPC listen matches the data plane.
|
|
if strings.TrimSpace(cfg.SharedInstanceType) == "" {
|
|
if useUnix {
|
|
cfg.SharedInstanceType = common.SharedInstanceUnix
|
|
} else {
|
|
cfg.SharedInstanceType = common.SharedInstanceTCP
|
|
}
|
|
}
|
|
rpc, err := StartRPCServer(cfg, tr)
|
|
if err != nil {
|
|
_ = server.Stop()
|
|
return nil, err
|
|
}
|
|
inst.RPC = rpc
|
|
debug.Log(debug.DebugInfo, "Started shared instance server", "port", port, "unix", useUnix)
|
|
return inst, nil
|
|
}
|
|
|
|
return attachClient(cfg, tr, hooks, port, socketPath, useUnix)
|
|
}
|
|
|
|
func attachClient(cfg *common.ReticulumConfig, tr *transport.Transport, hooks Hooks, port int, socketPath string, useUnix bool) (*Instance, error) {
|
|
client, err := interfaces.NewLocalClientInterface(port, socketPath, useUnix, backbone.Get())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
client.SetDisconnectHooks(
|
|
func() { tr.SetConnectedToSharedInstance(false) },
|
|
func() { tr.SetConnectedToSharedInstance(true) },
|
|
)
|
|
if err := client.Start(); err != nil {
|
|
return nil, fmt.Errorf("connect to local shared instance: %w", err)
|
|
}
|
|
tr.SetConnectedToSharedInstance(true)
|
|
cfg.ConnectedToSharedInstance = true
|
|
inst := &Instance{Mode: ModeClient, Client: client}
|
|
if hooks.RegisterInterface != nil {
|
|
if err := hooks.RegisterInterface(client.GetName(), client); err != nil {
|
|
_ = client.Stop()
|
|
return nil, err
|
|
}
|
|
}
|
|
if hooks.HandleInterface != nil {
|
|
hooks.HandleInterface(client)
|
|
}
|
|
if hooks.OnClientAttach != nil {
|
|
hooks.OnClientAttach()
|
|
}
|
|
debug.Log(debug.DebugInfo, "Connected to existing local shared instance", "unix", useUnix)
|
|
return inst, nil
|
|
}
|
|
|
|
func (i *Instance) Close() {
|
|
if i == nil {
|
|
return
|
|
}
|
|
if i.RPC != nil {
|
|
_ = i.RPC.Close()
|
|
}
|
|
if i.Server != nil {
|
|
_ = i.Server.Stop()
|
|
}
|
|
if i.Client != nil {
|
|
_ = i.Client.Stop()
|
|
}
|
|
}
|
|
|
|
func (i *Instance) OwnsNetworkInterfaces() bool {
|
|
if i == nil {
|
|
return true
|
|
}
|
|
return i.Mode != ModeClient
|
|
}
|