Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP

This commit is contained in:
Sergei Maklagin
2026-05-11 00:59:35 +03:00
parent 652e0baf57
commit 3bd162ed6f
241 changed files with 36409 additions and 4086 deletions

View File

@@ -5,7 +5,7 @@ import (
"strconv"
"github.com/huandu/go-sqlbuilder"
"github.com/sagernet/sing/common/byteformats"
"github.com/sagernet/sing-box/common/byteformats"
)
type Filter func(sb *sqlbuilder.SelectBuilder, value []string) error
@@ -96,6 +96,20 @@ func ExistsAndWhereInFilter(subquery *sqlbuilder.SelectBuilder, field string) Fi
}
}
func InFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
if len(value) == 0 {
return nil
}
values := make([]interface{}, len(value))
for i, v := range value {
values[i] = v
}
sb.Where(sb.In(field, values...))
return nil
}
}
func SortAscFilter() Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.OrderByAsc(value[0])

View File

@@ -36,8 +36,8 @@ var migrations = map[string]string{
id SERIAL PRIMARY KEY,
node_uuid VARCHAR(36),
username TEXT NOT NULL,
type TEXT NOT NULL,
inbound TEXT NOT NULL,
type TEXT NOT NULL,
uuid TEXT NOT NULL,
password TEXT NOT NULL,
secret TEXT NOT NULL,
@@ -100,11 +100,236 @@ var migrations = map[string]string{
);
`,
"1_initialize_schema.down.sql": `
DROP TABLE IF EXISTS squas;
DROP TABLE IF EXISTS nodes;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS bandwidth_limiter_to_squad;
DROP TABLE IF EXISTS bandwidth_limiters;
DROP TABLE IF EXISTS connection_limiter_to_squad;
DROP TABLE IF EXISTS connection_limiters;
DROP TABLE IF EXISTS user_to_squad;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS node_to_squad;
DROP TABLE IF EXISTS nodes;
DROP TABLE IF EXISTS squads;
`,
"2_init_limiters_and_indexes.up.sql": `
CREATE TABLE traffic_limiters (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
mode TEXT NOT NULL,
raw_used BIGINT NOT NULL DEFAULT 0,
quota TEXT NOT NULL,
raw_quota BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE traffic_limiter_to_squad (
traffic_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (traffic_limiter_id, squad_id),
FOREIGN KEY (traffic_limiter_id) REFERENCES traffic_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE rate_limiters (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
connection_type TEXT NOT NULL,
count INTEGER NOT NULL,
interval TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE rate_limiter_to_squad (
rate_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (rate_limiter_id, squad_id),
FOREIGN KEY (rate_limiter_id) REFERENCES rate_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
ALTER TABLE bandwidth_limiters ADD COLUMN flow_keys JSONB NOT NULL DEFAULT '[]'::jsonb;
UPDATE connection_limiters SET lock_type = 'default' WHERE lock_type = '';
ALTER TABLE node_to_squad
DROP CONSTRAINT node_to_squad_squad_id_fkey,
ADD CONSTRAINT node_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
ALTER TABLE user_to_squad
DROP CONSTRAINT user_to_squad_squad_id_fkey,
ADD CONSTRAINT user_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
ALTER TABLE connection_limiter_to_squad
DROP CONSTRAINT connection_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT connection_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
ALTER TABLE bandwidth_limiter_to_squad
DROP CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
CREATE INDEX idx_squads_created_at ON squads(created_at);
CREATE INDEX idx_squads_updated_at ON squads(updated_at);
CREATE INDEX idx_nodes_created_at ON nodes(created_at);
CREATE INDEX idx_nodes_updated_at ON nodes(updated_at);
CREATE INDEX idx_node_to_squad_squad_id ON node_to_squad(squad_id);
CREATE INDEX idx_users_node_uuid ON users(node_uuid);
CREATE INDEX idx_users_inbound ON users(inbound);
CREATE INDEX idx_users_type ON users(type);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_users_updated_at ON users(updated_at);
CREATE INDEX idx_user_to_squad_squad_id ON user_to_squad(squad_id);
CREATE INDEX idx_connection_limiters_outbound ON connection_limiters(outbound);
CREATE INDEX idx_connection_limiters_strategy ON connection_limiters(strategy);
CREATE INDEX idx_connection_limiters_connection_type ON connection_limiters(connection_type);
CREATE INDEX idx_connection_limiters_lock_type ON connection_limiters(lock_type);
CREATE INDEX idx_connection_limiters_created_at ON connection_limiters(created_at);
CREATE INDEX idx_connection_limiters_updated_at ON connection_limiters(updated_at);
CREATE INDEX idx_connection_limiter_to_squad_squad_id ON connection_limiter_to_squad(squad_id);
CREATE INDEX idx_bandwidth_limiters_outbound ON bandwidth_limiters(outbound);
CREATE INDEX idx_bandwidth_limiters_strategy ON bandwidth_limiters(strategy);
CREATE INDEX idx_bandwidth_limiters_mode ON bandwidth_limiters(mode);
CREATE INDEX idx_bandwidth_limiters_connection_type ON bandwidth_limiters(connection_type);
CREATE INDEX idx_bandwidth_limiters_raw_speed ON bandwidth_limiters(raw_speed);
CREATE INDEX idx_bandwidth_limiters_created_at ON bandwidth_limiters(created_at);
CREATE INDEX idx_bandwidth_limiters_updated_at ON bandwidth_limiters(updated_at);
CREATE INDEX idx_bandwidth_limiter_to_squad_squad_id ON bandwidth_limiter_to_squad(squad_id);
CREATE INDEX idx_traffic_limiters_outbound ON traffic_limiters(outbound);
CREATE INDEX idx_traffic_limiters_strategy ON traffic_limiters(strategy);
CREATE INDEX idx_traffic_limiters_mode ON traffic_limiters(mode);
CREATE INDEX idx_traffic_limiters_raw_used ON traffic_limiters(raw_used);
CREATE INDEX idx_traffic_limiters_raw_quota ON traffic_limiters(raw_quota);
CREATE INDEX idx_traffic_limiters_created_at ON traffic_limiters(created_at);
CREATE INDEX idx_traffic_limiters_updated_at ON traffic_limiters(updated_at);
CREATE INDEX idx_traffic_limiter_to_squad_squad_id ON traffic_limiter_to_squad(squad_id);
CREATE INDEX idx_rate_limiters_outbound ON rate_limiters(outbound);
CREATE INDEX idx_rate_limiters_strategy ON rate_limiters(strategy);
CREATE INDEX idx_rate_limiters_connection_type ON rate_limiters(connection_type);
CREATE INDEX idx_rate_limiters_interval ON rate_limiters("interval");
CREATE INDEX idx_rate_limiters_count ON rate_limiters(count);
CREATE INDEX idx_rate_limiters_created_at ON rate_limiters(created_at);
CREATE INDEX idx_rate_limiters_updated_at ON rate_limiters(updated_at);
CREATE INDEX idx_rate_limiter_to_squad_squad_id ON rate_limiter_to_squad(squad_id);
UPDATE connection_limiters SET connection_type = 'source_ip' WHERE connection_type = 'ip';
UPDATE bandwidth_limiters SET connection_type = 'source_ip' WHERE connection_type = 'ip';
UPDATE rate_limiters SET connection_type = 'source_ip' WHERE connection_type = 'ip';
UPDATE bandwidth_limiters
SET flow_keys = REPLACE(flow_keys::text, '"ip"', '"source_ip"')::jsonb
WHERE flow_keys @> '["ip"]'::jsonb;
UPDATE bandwidth_limiters SET mode = 'bidirectional' WHERE mode = 'duplex';
UPDATE traffic_limiters SET mode = 'bidirectional' WHERE mode = 'duplex';
`,
"2_init_limiters_and_indexes.down.sql": `
UPDATE traffic_limiters SET mode = 'duplex' WHERE mode = 'bidirectional';
UPDATE bandwidth_limiters SET mode = 'duplex' WHERE mode = 'bidirectional';
UPDATE bandwidth_limiters
SET flow_keys = REPLACE(flow_keys::text, '"source_ip"', '"ip"')::jsonb
WHERE flow_keys @> '["source_ip"]'::jsonb;
UPDATE rate_limiters SET connection_type = 'ip' WHERE connection_type = 'source_ip';
UPDATE bandwidth_limiters SET connection_type = 'ip' WHERE connection_type = 'source_ip';
UPDATE connection_limiters SET connection_type = 'ip' WHERE connection_type = 'source_ip';
DROP INDEX IF EXISTS idx_rate_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_rate_limiters_updated_at;
DROP INDEX IF EXISTS idx_rate_limiters_created_at;
DROP INDEX IF EXISTS idx_rate_limiters_count;
DROP INDEX IF EXISTS idx_rate_limiters_interval;
DROP INDEX IF EXISTS idx_rate_limiters_connection_type;
DROP INDEX IF EXISTS idx_rate_limiters_strategy;
DROP INDEX IF EXISTS idx_rate_limiters_outbound;
DROP INDEX IF EXISTS idx_traffic_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_traffic_limiters_updated_at;
DROP INDEX IF EXISTS idx_traffic_limiters_created_at;
DROP INDEX IF EXISTS idx_traffic_limiters_raw_quota;
DROP INDEX IF EXISTS idx_traffic_limiters_raw_used;
DROP INDEX IF EXISTS idx_traffic_limiters_mode;
DROP INDEX IF EXISTS idx_traffic_limiters_strategy;
DROP INDEX IF EXISTS idx_traffic_limiters_outbound;
DROP INDEX IF EXISTS idx_bandwidth_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_bandwidth_limiters_updated_at;
DROP INDEX IF EXISTS idx_bandwidth_limiters_created_at;
DROP INDEX IF EXISTS idx_bandwidth_limiters_raw_speed;
DROP INDEX IF EXISTS idx_bandwidth_limiters_connection_type;
DROP INDEX IF EXISTS idx_bandwidth_limiters_mode;
DROP INDEX IF EXISTS idx_bandwidth_limiters_strategy;
DROP INDEX IF EXISTS idx_bandwidth_limiters_outbound;
DROP INDEX IF EXISTS idx_connection_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_connection_limiters_updated_at;
DROP INDEX IF EXISTS idx_connection_limiters_created_at;
DROP INDEX IF EXISTS idx_connection_limiters_lock_type;
DROP INDEX IF EXISTS idx_connection_limiters_connection_type;
DROP INDEX IF EXISTS idx_connection_limiters_strategy;
DROP INDEX IF EXISTS idx_connection_limiters_outbound;
DROP INDEX IF EXISTS idx_user_to_squad_squad_id;
DROP INDEX IF EXISTS idx_users_updated_at;
DROP INDEX IF EXISTS idx_users_created_at;
DROP INDEX IF EXISTS idx_users_type;
DROP INDEX IF EXISTS idx_users_inbound;
DROP INDEX IF EXISTS idx_users_node_uuid;
DROP INDEX IF EXISTS idx_node_to_squad_squad_id;
DROP INDEX IF EXISTS idx_nodes_updated_at;
DROP INDEX IF EXISTS idx_nodes_created_at;
DROP INDEX IF EXISTS idx_squads_updated_at;
DROP INDEX IF EXISTS idx_squads_created_at;
ALTER TABLE bandwidth_limiter_to_squad
DROP CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
ALTER TABLE connection_limiter_to_squad
DROP CONSTRAINT connection_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT connection_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
ALTER TABLE user_to_squad
DROP CONSTRAINT user_to_squad_squad_id_fkey,
ADD CONSTRAINT user_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
ALTER TABLE node_to_squad
DROP CONSTRAINT node_to_squad_squad_id_fkey,
ADD CONSTRAINT node_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
UPDATE connection_limiters SET lock_type = '' WHERE lock_type = 'default';
ALTER TABLE bandwidth_limiters DROP COLUMN flow_keys;
DROP TABLE IF EXISTS rate_limiter_to_squad;
DROP TABLE IF EXISTS rate_limiters;
DROP TABLE IF EXISTS traffic_limiter_to_squad;
DROP TABLE IF EXISTS traffic_limiters;
`,
}
@@ -113,12 +338,10 @@ func Migrate(db *sql.DB) error {
if err != nil {
return err
}
sourceDriver := source.NewRawDriver(migrations)
if err := sourceDriver.Init(); err != nil {
return err
}
m, err := migrate.NewWithInstance(
"raw",
sourceDriver,
@@ -128,6 +351,5 @@ func Migrate(db *sql.DB) error {
if err != nil {
return err
}
return m.Up()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
package postgresql
import (
"encoding/json"
"fmt"
)
type stringSliceJSON []string
func (s *stringSliceJSON) Scan(src interface{}) error {
if src == nil {
*s = nil
return nil
}
var data []byte
switch v := src.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("stringSliceJSON.Scan: unsupported type %T", src)
}
if len(data) == 0 {
*s = nil
return nil
}
return json.Unmarshal(data, (*[]string)(s))
}
func marshalStringSlice(values []string) ([]byte, error) {
if values == nil {
values = []string{}
}
return json.Marshal(values)
}