How S-UI Handles Multi-Client and Multi-Inbound Configurations

S-UI manages multi-client and multi-inbound configurations by storing inbound IDs as a JSON array in the clients.inbounds column, using the ClientService.Save method in service/client.go to detect changes, regenerate links via util.LinkGenerator, and synchronize relationships atomically within GORM transactions.

Handling complex proxy topologies requires flexible many-to-many relationships between users and entry points. The alireza0/s-ui repository implements a sophisticated mapping system that allows any client to connect to any number of inbounds while maintaining strict data consistency and minimizing service disruption.

The JSON-Based Many-to-Many Relationship

Unlike traditional relational designs that rely on join tables, S-UI stores the client-to-inbound mapping directly within the client record. The clients.inbounds column uses the json.RawMessage type to hold a JSON-encoded array of inbound IDs.

When processing client data, the system unmarshals this field using json.Unmarshal(client.Inbounds, &inboundIds) to extract the slice of uint identifiers. This approach eliminates the need for separate junction tables while supporting unlimited inbound assignments per client.

Processing Client Changes with ClientService.Save

The orchestration logic for all multi-client and multi-inbound operations resides in service/client.go. The ClientService.Save method handles creation, editing, bulk addition, and bulk editing through a unified pipeline.

Detecting Inbound Differences

For edit operations, the service invokes findInboundsChanges to perform a diff between the stored inbound ID array and the new submission. This comparison identifies precisely which inbounds were added or removed, enabling targeted updates rather than full rewrites.

After determining the inbound changes, updateLinksWithFixedInbounds rebuilds the local link list for each affected client. For every inbound assigned to the client, the function calls util.LinkGenerator to create protocol-specific URIs (such as V2Ray or Trojan links). The system preserves non-local links, including custom URLs, ensuring that manual configurations remain intact during automated updates.

Inbound-Side Synchronization Hooks

When administrators modify inbounds directly, S-UI propagates these changes to affected clients through three specialized helpers in the client service:

  • UpdateClientsOnInboundAdd – Automatically appends the new inbound ID to each specified client's inbounds array and generates the corresponding local links for immediate connectivity.

  • UpdateClientsOnInboundDelete – Removes the obsolete inbound ID from all client records and strips the associated local links to prevent stale configuration entries.

  • UpdateLinksByInboundChange – Recomputes local links when inbound properties such as port, protocol type, or tag change, while maintaining any non-local custom links.

These methods are triggered from service/inbounds.go during the inbound lifecycle, ensuring the many-to-many mapping remains consistent regardless of which side initiates the change.

Transaction Safety and Bulk Operations

All client-inbound mapping operations execute within a single GORM transaction (tx *gorm.DB). This guarantees atomicity during bulk operations, preventing partial updates when processing hundreds of clients simultaneously.

The Save method returns a slice of affected inbound IDs ([]uint), which the broader system uses to restart only the modified inbounds. This targeted approach avoids the performance penalty of reloading the entire proxy configuration.

Implementation Examples

// Attach a new inbound to specific clients by ID
svc := service.NewClientService()
err := svc.UpdateClientsOnInboundAdd(db, "1,3,7", newInboundID, "example.com")
// Clean up references when deleting an inbound
svc := service.NewClientService()
err := svc.UpdateClientsOnInboundDelete(db, obsoleteInboundID, "old-tag")
// Bulk edit clients with automatic link regeneration
svc := service.NewClientService()
changedInboundIDs, err := svc.Save(db, "editbulk", bulkDataJSON, "example.com")

Summary

  • S-UI implements multi-client and multi-inbound configurations using a JSON array in the clients.inbounds column rather than traditional join tables.
  • The ClientService.Save method in service/client.go handles all client modifications with full transaction safety.
  • findInboundsChanges calculates precise differences between old and new inbound assignments to minimize processing overhead.
  • util.LinkGenerator creates protocol-specific URIs while preserving existing non-local custom links.
  • Inbound lifecycle hooks maintain synchronization when entry points are added, deleted, or modified.
  • The architecture returns only affected inbound IDs to optimize service restarts and reduce downtime.

Frequently Asked Questions

How does S-UI store relationships between multiple clients and inbounds?

S-UI stores these relationships as a JSON array of inbound IDs within the clients.inbounds column, typed as json.RawMessage. This design allows each client to reference any number of inbounds without requiring a separate join table, simplifying the schema while maintaining flexibility.

When an inbound's configuration changes, the UpdateLinksByInboundChange method recomputes the local links for all affected clients by invoking util.LinkGenerator. Non-local links such as custom URLs are preserved, ensuring that manual overrides remain intact while protocol-specific URIs update automatically.

Are bulk client operations atomic in S-UI?

Yes. Whether performing bulk addition or bulk editing, the ClientService.Save method wraps all database operations in a GORM transaction. This ensures that all client-inbound mappings update simultaneously or roll back entirely, preventing configuration inconsistencies during large-scale changes.

How does S-UI minimize service restarts during configuration updates?

The system tracks exactly which inbound IDs are modified during client or inbound operations. The Save method returns these IDs as []uint, allowing S-UI to restart only the specific inbounds that changed rather than reloading the entire proxy service, significantly reducing downtime.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →