Add SSH inbound, log level. Update MTPROXY. Fixes

This commit is contained in:
Shtorm
2026-06-07 07:59:43 +03:00
parent 6f6af8e902
commit 9f5ccf43d4
115 changed files with 2742 additions and 527 deletions

View File

@@ -49,46 +49,50 @@ type BaseNode struct {
}
type User struct {
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"required"`
Inbound string `json:"inbound" validate:"required"`
Type string `json:"type" validate:"required"`
UUID string `json:"uuid" validate:"required"`
Password string `json:"password" validate:"required"`
Secret string `json:"secret" validate:"required"`
Flow string `json:"flow" validate:"required"`
AlterID int `json:"alter_id" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"required"`
Inbound string `json:"inbound" validate:"required"`
Type string `json:"type" validate:"required"`
UUID string `json:"uuid" validate:"required"`
Password string `json:"password" validate:"required"`
Secret string `json:"secret" validate:"required"`
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
Flow string `json:"flow" validate:"required"`
AlterID int `json:"alter_id" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
type UserCreate struct {
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"required"`
Inbound string `json:"inbound" validate:"required"`
Type string `json:"type" validate:"required,oneof=anytls http hysteria hysteria2 mixed mtproxy naive socks trojan trusttunnel tuic vless vmess"`
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
Flow string `json:"flow" validate:"omitempty"`
AlterID int `json:"alter_id" validate:"omitempty"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"required"`
Inbound string `json:"inbound" validate:"required"`
Type string `json:"type" validate:"required,oneof=anytls http hysteria hysteria2 mixed mtproxy naive socks ssh trojan trusttunnel tuic vless vmess"`
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
Flow string `json:"flow" validate:"omitempty"`
AlterID int `json:"alter_id" validate:"omitempty"`
}
type UserUpdate struct {
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
Flow string `json:"flow" validate:"omitempty"`
AlterID int `json:"alter_id" validate:"omitempty"`
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
Flow string `json:"flow" validate:"omitempty"`
AlterID int `json:"alter_id" validate:"omitempty"`
}
type BaseUser struct {
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
Flow string `json:"flow" validate:"omitempty"`
AlterID int `json:"alter_id" validate:"omitempty"`
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
Flow string `json:"flow" validate:"omitempty"`
AlterID int `json:"alter_id" validate:"omitempty"`
}
type ConnectionLimiter struct {
@@ -261,5 +265,3 @@ type BaseRateLimiter struct {
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Interval string `json:"interval" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}

View File

@@ -51,5 +51,3 @@ type Repository interface {
UpdateRateLimiter(id int, limiter RateLimiterUpdate) (RateLimiter, error)
DeleteRateLimiter(id int) (RateLimiter, error)
}

View File

@@ -331,6 +331,12 @@ var migrations = map[string]string{
DROP TABLE IF EXISTS traffic_limiter_to_squad;
DROP TABLE IF EXISTS traffic_limiters;
`,
"3_add_authorized_keys.up.sql": `
ALTER TABLE users ADD COLUMN authorized_keys JSONB NOT NULL DEFAULT '[]'::jsonb;
`,
"3_add_authorized_keys.down.sql": `
ALTER TABLE users DROP COLUMN authorized_keys;
`,
}
func Migrate(db *sql.DB) error {

View File

@@ -515,6 +515,11 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
}
defer tx.Rollback(r.ctx)
now := time.Now()
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
if err != nil {
return u, err
}
var authorizedKeys stringSliceJSON
err = tx.QueryRow(
r.ctx, `
INSERT INTO users (
@@ -524,12 +529,13 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING
id,
username,
@@ -538,6 +544,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -549,6 +556,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
user.UUID,
user.Password,
user.Secret,
authorizedKeysJSON,
user.Flow,
user.AlterID,
now,
@@ -561,6 +569,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
&u.UUID,
&u.Password,
&u.Secret,
&authorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -569,6 +578,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
if err != nil {
return u, err
}
u.AuthorizedKeys = []string(authorizedKeys)
rows := make([][]any, len(user.SquadIDs))
for i, squadID := range user.SquadIDs {
rows[i] = []any{u.ID, squadID}
@@ -605,6 +615,7 @@ func (r *PostgreSQLRepository) GetUsers(filters map[string][]string) ([]constant
"uuid",
"password",
"secret",
"authorized_keys",
"flow",
"alter_id",
"created_at",
@@ -636,6 +647,7 @@ func (r *PostgreSQLRepository) GetUsers(filters map[string][]string) ([]constant
&u.UUID,
&u.Password,
&u.Secret,
&u.AuthorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -681,6 +693,7 @@ func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -696,6 +709,7 @@ func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
&u.UUID,
&u.Password,
&u.Secret,
&u.AuthorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -706,17 +720,22 @@ func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (constant.User, error) {
var u constant.User
err := r.db.QueryRow(
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
if err != nil {
return u, err
}
err = r.db.QueryRow(
r.ctx, `
UPDATE users
SET
uuid = $1,
password = $2,
secret = $3,
flow = $4,
alter_id = $5,
updated_at = $6
WHERE id = $7
authorized_keys = $4,
flow = $5,
alter_id = $6,
updated_at = $7
WHERE id = $8
RETURNING
id,
ARRAY(
@@ -730,6 +749,7 @@ func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (con
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -738,6 +758,7 @@ func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (con
user.UUID,
user.Password,
user.Secret,
authorizedKeysJSON,
user.Flow,
user.AlterID,
time.Now(),
@@ -751,6 +772,7 @@ func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (con
&u.UUID,
&u.Password,
&u.Secret,
&u.AuthorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -777,6 +799,7 @@ func (r *PostgreSQLRepository) DeleteUser(id int) (constant.User, error) {
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -790,6 +813,7 @@ func (r *PostgreSQLRepository) DeleteUser(id int) (constant.User, error) {
&u.UUID,
&u.Password,
&u.Secret,
&u.AuthorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -2143,11 +2167,11 @@ func init() {
"updated_at_end": LessThanFilter("updated_at"),
"sort_asc": ReplacedSortAscFilter(
map[string]string{"speed": "raw_speed"},
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
),
"sort_desc": ReplacedSortDescFilter(
map[string]string{"speed": "raw_speed"},
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
),
"offset": OffsetFilter(),
"limit": LimitFilter(),

View File

@@ -213,6 +213,12 @@ var migrations = map[string]string{
DROP TABLE IF EXISTS nodes;
DROP TABLE IF EXISTS squads;
`,
"2_add_authorized_keys.up.sql": `
ALTER TABLE users ADD COLUMN authorized_keys TEXT NOT NULL DEFAULT '[]';
`,
"2_add_authorized_keys.down.sql": `
ALTER TABLE users DROP COLUMN authorized_keys;
`,
}
func Migrate(db *sql.DB) error {

View File

@@ -510,6 +510,11 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
}
defer tx.Rollback()
now := time.Now()
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
if err != nil {
return u, err
}
var authorizedKeys stringSliceJSON
err = tx.QueryRowContext(
r.ctx, `
INSERT INTO users (
@@ -519,12 +524,13 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING
id,
username,
@@ -533,6 +539,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -544,6 +551,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
user.UUID,
user.Password,
user.Secret,
authorizedKeysJSON,
user.Flow,
user.AlterID,
now,
@@ -556,6 +564,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
&u.UUID,
&u.Password,
&u.Secret,
&authorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -564,6 +573,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
if err != nil {
return u, err
}
u.AuthorizedKeys = []string(authorizedKeys)
stmt, err := tx.PrepareContext(r.ctx, `INSERT INTO user_to_squad (user_id, squad_id) VALUES (?, ?)`)
if err != nil {
return u, err
@@ -596,6 +606,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
"uuid",
"password",
"secret",
"authorized_keys",
"flow",
"alter_id",
"created_at",
@@ -619,6 +630,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
for rows.Next() {
var u constant.User
var squadIDs intSliceJSON
var authorizedKeys stringSliceJSON
if err := rows.Scan(
&u.ID,
&squadIDs,
@@ -628,6 +640,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
&u.UUID,
&u.Password,
&u.Secret,
&authorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
@@ -636,6 +649,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
return nil, err
}
u.SquadIDs = []int(squadIDs)
u.AuthorizedKeys = []string(authorizedKeys)
result = append(result, u)
}
return result, rows.Err()
@@ -661,6 +675,7 @@ func (r *SQLiteRepository) GetUsersCount(filters map[string][]string) (int, erro
func (r *SQLiteRepository) GetUser(id int) (constant.User, error) {
var u constant.User
var squadIDs intSliceJSON
var authorizedKeys stringSliceJSON
err := r.db.QueryRowContext(r.ctx, `
SELECT
id,
@@ -675,6 +690,7 @@ func (r *SQLiteRepository) GetUser(id int) (constant.User, error) {
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -690,25 +706,33 @@ func (r *SQLiteRepository) GetUser(id int) (constant.User, error) {
&u.UUID,
&u.Password,
&u.Secret,
&authorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
&u.UpdatedAt,
)
u.SquadIDs = []int(squadIDs)
u.AuthorizedKeys = []string(authorizedKeys)
return u, notFoundErr(err)
}
func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constant.User, error) {
var u constant.User
var squadIDs intSliceJSON
err := r.db.QueryRowContext(
var authorizedKeys stringSliceJSON
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
if err != nil {
return u, err
}
err = r.db.QueryRowContext(
r.ctx, `
UPDATE users
SET
uuid = ?,
password = ?,
secret = ?,
authorized_keys = ?,
flow = ?,
alter_id = ?,
updated_at = ?
@@ -726,6 +750,7 @@ func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constan
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -734,6 +759,7 @@ func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constan
user.UUID,
user.Password,
user.Secret,
authorizedKeysJSON,
user.Flow,
user.AlterID,
time.Now(),
@@ -747,18 +773,21 @@ func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constan
&u.UUID,
&u.Password,
&u.Secret,
&authorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
&u.UpdatedAt,
)
u.SquadIDs = []int(squadIDs)
u.AuthorizedKeys = []string(authorizedKeys)
return u, err
}
func (r *SQLiteRepository) DeleteUser(id int) (constant.User, error) {
var u constant.User
var squadIDs intSliceJSON
var authorizedKeys stringSliceJSON
err := r.db.QueryRowContext(r.ctx, `
DELETE FROM users
WHERE id = ?
@@ -775,6 +804,7 @@ func (r *SQLiteRepository) DeleteUser(id int) (constant.User, error) {
uuid,
password,
secret,
authorized_keys,
flow,
alter_id,
created_at,
@@ -788,12 +818,14 @@ func (r *SQLiteRepository) DeleteUser(id int) (constant.User, error) {
&u.UUID,
&u.Password,
&u.Secret,
&authorizedKeys,
&u.Flow,
&u.AlterID,
&u.CreatedAt,
&u.UpdatedAt,
)
u.SquadIDs = []int(squadIDs)
u.AuthorizedKeys = []string(authorizedKeys)
return u, err
}
@@ -2160,11 +2192,11 @@ func init() {
"updated_at_end": LessThanFilter("updated_at"),
"sort_asc": ReplacedSortAscFilter(
map[string]string{"speed": "raw_speed"},
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
),
"sort_desc": ReplacedSortDescFilter(
map[string]string{"speed": "raw_speed"},
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
),
"offset": OffsetFilter(),
"limit": LimitFilter(),

View File

@@ -32,10 +32,10 @@ func RegisterService(registry *boxService.Registry) {
type Service struct {
boxService.Adapter
ctx context.Context
logger log.ContextLogger
repository constant.Repository
nodes map[string]constant.ConnectedNode
ctx context.Context
logger log.ContextLogger
repository constant.Repository
nodes map[string]constant.ConnectedNode
limiterLocks map[int]map[string]*cache.Cache[string, struct{}]
trafficUsage map[int]*TrafficUsage
@@ -93,6 +93,10 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
if user.Password == "" {
sl.ReportError(user.Password, "password", "Password", "required", "")
}
case "ssh":
if user.Password == "" && len(user.AuthorizedKeys) == 0 {
sl.ReportError(user.Password, "password", "Password", "required_without", "")
}
case "mtproxy":
if user.Secret == "" {
sl.ReportError(user.Secret, "secret", "Secret", "required", "")