feat(service): add NATS-based client synchronization#954
Conversation
- Implement BrokerService for publishing and subscribing to client events - Add ClientEvent struct for standardized event format - Update ClientService to handle client events and publish changes - Modify APP structure to include brokerService and clientService - Add NATS initialization and event subscription to APP lifecycle - Update API handlers to use new service structure - Add NatsUrl setting to SettingService
…nitialization - ClientService now properly handles JSON unmarshalling and database operations for various client events. - BrokerService initialization now always returns a non-nil instance, with error logging for visibility. - Removed unnecessary functions and simplified client event processing logic. - Added reconnect/disconnect handlers for NATS connection in BrokerService.
Kagashini
left a comment
There was a problem hiding this comment.
Fix with copilot:
fix(service): make BrokerService resilient, guard publishes, and fix client save
-
Ensure NewBrokerService ALWAYS returns a non-nil *BrokerService.
- If NATS URL is empty or connection fails, BrokerService is returned in "disabled" mode (nc == nil) and an error is returned for visibility.
- Added NATS connection options (client name, reconnect handler, disconnect handler, unlimited reconnects) to improve diagnostics and resilience.
-
PublishClientEvent is a no-op when broker is disabled (nc == nil) to avoid panics.
-
ClientService refactor:
- Add brokerService field and NewClientService constructor.
- Implement HandleClientEvent to apply remote client create/update/delete events.
- Guard all PublishClientEvent calls in ClientService.Save with nil checks and log publish errors instead of panicking.
-
Fix compile error in Save: declare inboundIds before using json.Unmarshal(client.Inbounds, &inboundIds).
-
Minor logging improvements for connect/reconnect/disconnect and publish failures.
Notes:
- This patch is intentionally minimal and focused on stability (prevent nil-pointer panics and make publish failures visible).
- Recommended follow-ups: publish-after-commit semantics, add event metadata (timestamp/event-id/version) and idempotency handling, and consider migrating client IDs to globally unique identifiers (UUID) to avoid cross-panel PK collisions.
|
Thanks for the contribution. Regarding this idea, the argue is multi-server handling is beyond SQLite’s capabilities. |
|
Thanks for your patience and clarification. To clarify my role: this PR was neither submitted nor implemented by me. I only opened issue #729 to discuss a multi-node use case, and the contributor later implemented the current solution based on that issue. The original intention of my issue was to enable user data synchronization between a main server and node servers, making multi-node deployments easier to manage. The key point is that the main control panel pushes configurations, and nodes do not hold authoritative data. This is a single-direction or weak bidirectional synchronization, focused on usability, not full multi-panel state replication. I fully agree with s-ui’s current single-instance design and also recognize that true multi-master state synchronization and distributed consistency are beyond SQLite and the current architecture. My goal was only to explore a controlled user-sync scenario, not to turn s-ui into a multi-server management system. If you feel this functionality does not fit the project’s scope, I completely respect that decision. |
Thanks for your answer! At the same time, I’d prefer not to make separate API requests to multiple servers or build an additional middleware layer just to handle this. Using a lightweight broker solution would allow managing all servers from a single panel without changing the database structure or switching away from SQLite. The goal isn’t to turn S-UI into a full multi-server platform, but rather to automate routine operations and improve usability for users who need to work with more than one server. Maybe it would make sense to consider adding this mode of operation to the Services menu in the future? I’ve been using your panel for quite a while, and as far as I remember, there haven’t been any options using the sing-box core — only x-ray variants are available, but not sing-box. At the moment, there are larger projects that include a wide range of features and support node-based setups. However, they are primarily designed for large-scale infrastructures with many servers. They also require significantly more resources and are generally more suitable for commercial use. Therefore, I apologize for repeating myself, but within this project, I’d like to keep the implementation lightweight and simplify the management of multiple panels across different locations. The current circumstances often require having at least one backup instance to handle unstable connections, outages, or other unexpected situations. |
2a266f3 to
a651432
Compare
|
Does this also account for bandwidth usage of users across the various nodes? |
I haven’t done a full set of tests, but with this tool, it’s possible to implement full synchronization between panels and clients. |
1ba9194 to
c70f0f9
Compare
857b17a to
1f393fc
Compare
feat: Centralized Multi-Panel Sync via NATS
https://docs.nats.io/
Description
This pull request introduces a major architectural enhancement to enable real-time, centralized synchronization of client data across multiple
s-uipanels. The current method of manually creating and managing clients on each server is inefficient and error-prone. This feature solves that problem by implementing a publish-subscribe (Pub/Sub) model using a NATS message broker.When a client is created, updated, or deleted on one panel, it publishes an event to a central NATS topic. All other panels subscribed to this topic will receive the event and automatically apply the corresponding changes to their local database.
This feature is designed to be non-breaking. If the NATS URL is not configured in the settings, the panel will continue to operate in a standalone mode, exactly as it did before.
Architectural Notes
This implementation was chosen as a lightweight and scalable solution for multi-panel synchronization. It avoids the complexity and overhead of more heavyweight approaches, such as a centralized database or API-based mesh networks. The Pub/Sub model provided by NATS ensures low coupling between panels and allows for easy scaling by simply connecting new panels to the same broker.
Disclaimer
Please note: This implementation is a proof-of-concept and has not been thoroughly tested in a production environment. It serves as a solid foundation for the feature, but further testing and potential refinement are recommended before deployment.
Key Changes
NATS Integration:
nats.goclient library to the project dependencies.BrokerService(service/broker.go) to manage the NATS connection, publish events, and handle subscriptions.Configuration:
NatsUrl, has been added to the panel settings (service/setting.go). If this field is empty, the broker functionality is gracefully disabled.Event Publishing:
ClientService(service/client.go) now uses theBrokerServiceto publish events whenever a client is created, updated, or deleted via the API.ClientEventstruct is defined to standardize the message format, including the action type (CLIENT_CREATED,CLIENT_UPDATED,CLIENT_DELETED), the client data, and the ID of the source panel.Event Subscribing & Handling:
APP(app/app.go) launches a goroutine that subscribes to client events.HandleClientEvent, has been added toClientServiceto process incoming events. It applies the necessary database operations (create, update, delete) based on the event's action.How to Test
Run a NATS Server:
The easiest way is to use Docker:
Configure Panels:
s-uipanels from this branch.NatsUrlfield to the address of your NATS server (e.g.,nats://127.0.0.1:4222).Verify Synchronization:
Verify Standalone Mode:
NatsUrlfield in its settings.