Skip to content

feat(service): add NATS-based client synchronization#954

Open
Kagashini wants to merge 4 commits into
alireza0:mainfrom
Kagashini:feat/broker-sync
Open

feat(service): add NATS-based client synchronization#954
Kagashini wants to merge 4 commits into
alireza0:mainfrom
Kagashini:feat/broker-sync

Conversation

@Kagashini

@Kagashini Kagashini commented Jan 1, 2026

Copy link
Copy Markdown
Contributor

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-ui panels. 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:

    • Added the official nats.go client library to the project dependencies.
    • Introduced a new BrokerService (service/broker.go) to manage the NATS connection, publish events, and handle subscriptions.
    • Each panel instance is assigned a unique ID to prevent processing its own "echoed" events.
  • Configuration:

    • A new field, NatsUrl, has been added to the panel settings (service/setting.go). If this field is empty, the broker functionality is gracefully disabled.
  • Event Publishing:

    • The ClientService (service/client.go) now uses the BrokerService to publish events whenever a client is created, updated, or deleted via the API.
    • A ClientEvent struct 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:

    • On application startup, if NATS is configured, the APP (app/app.go) launches a goroutine that subscribes to client events.
    • A new method, HandleClientEvent, has been added to ClientService to process incoming events. It applies the necessary database operations (create, update, delete) based on the event's action.

How to Test

  1. Run a NATS Server:
    The easiest way is to use Docker:

    docker run -p 4222:4222 -ti nats:latest
  2. Configure Panels:

    • Run two or more s-ui panels from this branch.
    • In the settings UI of each panel, set the NatsUrl field to the address of your NATS server (e.g., nats://127.0.0.1:4222).
    • Save the settings and restart the panels.
  3. Verify Synchronization:

    • On Panel A, create a new client.
    • Check Panel B. The new client should appear automatically within a few moments.
    • On Panel B, edit the client's name or description.
    • Check Panel A. The changes should be reflected.
    • On one of the panels, delete the client.
    • The client should be removed from all other panels.
    • Check the application logs for messages about connecting to NATS and processing events.
  4. Verify Standalone Mode:

    • Stop one of the panels.
    • Clear the NatsUrl field in its settings.
    • Restart the panel. It should start up and function normally without any connection attempts to NATS.

- 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 Kagashini left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@alireza0

alireza0 commented Jan 3, 2026

Copy link
Copy Markdown
Owner

Thanks for the contribution.
First please avoid updating packages on PR!

Regarding this idea, the argue is multi-server handling is beyond SQLite’s capabilities.
I don't want to reject it because it is added as an option.
But I want to know if there is other reason for expanding s-ui (which is designed for single server) to multiple servers

@Simon-xuan

Simon-xuan commented Jan 4, 2026

Copy link
Copy Markdown

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.

@Kagashini

Kagashini commented Jan 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the contribution. First please avoid updating packages on PR!

Regarding this idea, the argue is multi-server handling هس beyond SQLite’s capabilities. I don't want to reject it because it is added as an option. But I want to know if there is other reason for expanding s-ui (which is designed for single server) to multiple servers

Thanks for your answer!
The main reason for expanding S-UI to handle multiple servers is to simplify management when using several instances simultaneously. In my case, I have to manually create clients and synchronize subscriptions across two servers. This process is quite repetitive and prone to human error.

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.

@arshvimal

Copy link
Copy Markdown

Does this also account for bandwidth usage of users across the various nodes?

@Kagashini

Kagashini commented Jan 22, 2026

Copy link
Copy Markdown
Contributor Author

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.

@alireza0 alireza0 force-pushed the main branch 5 times, most recently from 1ba9194 to c70f0f9 Compare March 4, 2026 23:48
@alireza0 alireza0 force-pushed the main branch 4 times, most recently from 857b17a to 1f393fc Compare March 29, 2026 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants