Database Schema for Inbounds and Outbounds in S-UI: Complete Technical Reference

S-UI stores routing configurations in a SQLite database using GORM models, with inbounds and outbounds tables that combine fixed relational columns and JSON blobs for protocol-specific settings.

S-UI is a web-based management interface for V2Ray/Xray proxies built by Alireza. Understanding the database schema for inbounds and outbounds in S-UI is essential for developers extending the platform, migrating configurations, or troubleshooting routing logic. The schema follows a hybrid relational/JSON design that balances query performance with the flexibility needed to support diverse proxy protocols.

Inbounds Table Structure

The inbounds table, defined in database/model/inbounds.go, stores incoming connection configurations:

Column Go Type SQLite Type Description
id uint INTEGER PRIMARY KEY AUTOINCREMENT Primary key generated by GORM
type string TEXT Protocol identifier (e.g., vmess, trojan, vless)
tag string TEXT UNIQUE Human-readable unique identifier
tls_id uint INTEGER Foreign key referencing tls.id
addrs json.RawMessage BLOB JSON-encoded listening addresses
out_json json.RawMessage BLOB Embedded outbound reference as JSON
options json.RawMessage BLOB Protocol-specific configuration blob

TLS Relationship

The tls_id column establishes a one-to-one relationship with the tls table. This allows each inbound to reference dedicated TLS settings stored separately in the database. When tls_id equals 0, the inbound operates without TLS encryption.

Outbounds Table Structure

Located in database/model/outbounds.go, the outbounds table uses a simplified schema optimized for egress routing:

Column Go Type SQLite Type Description
id uint INTEGER PRIMARY KEY AUTOINCREMENT Primary key
type string TEXT Outbound protocol (e.g., direct, freedom, socks)
tag string TEXT UNIQUE Unique identifier for routing rules
options json.RawMessage BLOB JSON-encoded server configuration

Unlike inbounds, outbounds do not require foreign key relationships. All protocol-specific data—including server addresses, authentication credentials, and transport settings—resides in the options JSON column.

GORM Model Definitions

Both tables are implemented as Go structs with GORM annotations. In database/model/inbounds.go, the Inbound struct implements custom marshaling:

type Inbound struct {
    ID      uint            `gorm:"primaryKey"`
    Type    string          `gorm:"type:text"`
    Tag     string          `gorm:"type:text;uniqueIndex"`
    TlsId   uint            `gorm:"column:tls_id"`
    Addrs   json.RawMessage `gorm:"type:blob"`
    OutJson json.RawMessage `gorm:"type:blob"`
    Options json.RawMessage `gorm:"type:blob"`
}

The Outbound struct in database/model/outbounds.go follows a similar pattern but omits the TLS and address fields, reflecting the asymmetric configuration requirements between inbound listeners and outbound proxies.

Database Initialization and Migration

S-UI automatically creates these tables during startup. In database/db.go, the AutoMigrate function ensures schema consistency:

db.AutoMigrate(&model.Inbound{}, &model.Outbound{}, …)

This migration also seeds a default entry. If the outbounds table is empty, S-UI inserts a direct outbound automatically, ensuring basic connectivity exists immediately after installation.

JSON Serialization Strategy

The schema relies heavily on custom MarshalJSON and UnmarshalJSON methods. These methods separate fixed relational fields from variable protocol configurations:

  • Fixed fields (id, type, tag, tls_id) map to database columns for querying and indexing
  • Dynamic fields pack into json.RawMessage blobs, allowing S-UI to support new V2Ray-compatible protocols without schema migrations

When the API serves configuration data, the MarshalFull() method (implemented on the Inbound struct) merges the fixed fields with the JSON blobs to produce complete configuration objects.

Practical Query Examples

Creating an Inbound

To insert a new VMess inbound with specific options:

in := model.Inbound{
    Type:    "vmess",
    Tag:     "vmess-inbound",
    TlsId:   0,
    Addrs:   json.RawMessage(`["0.0.0.0:1080"]`),
    OutJson: json.RawMessage(`{"tag":"direct"}`),
    Options: json.RawMessage(`{
        "clients": [{"id":"uuid-here","alterId":0}]
    }`),
}
if err := database.GetDB().Create(&in).Error; err != nil {
    // handle error
}

Reading Outbound Configuration

To retrieve and decode an outbound's settings:

var out model.Outbound
if err := database.GetDB().Where("tag = ?", "direct").First(&out).Error; err != nil {
    // handle error
}

var opts map[string]interface{}
json.Unmarshal(out.Options, &opts)
// opts now contains the full protocol configuration

Full JSON Marshaling

To generate complete configuration output including all fields:

// Standard marshaling (fixed fields only)
b, _ := json.Marshal(in)

// Full marshaling (includes id, tls_id, addrs, out_json, and options)
full, _ := in.MarshalFull()

Summary

  • S-UI uses SQLite with GORM models for configuration persistence
  • The inbounds table includes seven columns: metadata fields plus addrs, out_json, and options JSON blobs
  • The outbounds table uses four columns: id, type, tag, and an options blob
  • Foreign key: inbounds.tls_id references tls.id for encryption settings
  • Auto-migration occurs in database/db.go with default direct outbound seeding
  • Custom JSON marshaling preserves fixed columns while supporting flexible protocol extensions

Frequently Asked Questions

How does S-UI handle protocol-specific settings without database schema changes?

S-UI stores variable configuration data in json.RawMessage blobs within the options column (and addrs/out_json for inbounds). Custom MarshalJSON methods pack protocol-specific fields into these BLOB columns, while fixed fields remain as queryable database columns. This hybrid approach accommodates new V2Ray protocols without requiring ALTER TABLE operations.

What is the relationship between inbounds and TLS configurations?

The inbounds table includes a tls_id column that functions as a foreign key to the tls table. This creates a one-to-one relationship where each inbound optionally references TLS settings (certificates, encryption modes) stored in a separate table. A tls_id value of 0 indicates the inbound uses no TLS encryption.

Where does S-UI initialize the database schema?

Database initialization occurs in database/db.go through GORM's AutoMigrate function, which creates the inbounds and outbounds tables if they don't exist. The same file handles default data seeding, inserting a direct outbound entry automatically when the database is first created.

Can I query inbound listening addresses using SQL?

While the addrs column stores data as a BLOB containing JSON (e.g., ["0.0.0.0:1080"]), SQLite can query JSON content using the json_extract function. However, S-UI typically reads these values into Go structs and unmarshals them via json.RawMessage rather than performing complex SQL queries on the JSON blobs.

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 →