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

@@ -42,6 +42,7 @@ export type UserType =
| "mtproxy"
| "naive"
| "socks"
| "ssh"
| "trojan"
| "trusttunnel"
| "tuic"
@@ -57,6 +58,7 @@ export interface User {
uuid: string;
password: string;
secret: string;
authorized_keys: string[];
flow: string;
alter_id: number;
created_at: string;
@@ -70,6 +72,7 @@ export interface UserCreate {
uuid?: string;
password?: string;
secret?: string;
authorized_keys?: string[];
flow?: string;
alter_id?: number;
}
@@ -77,6 +80,7 @@ export interface UserUpdate {
uuid?: string;
password?: string;
secret?: string;
authorized_keys?: string[];
flow?: string;
alter_id?: number;
}

View File

@@ -64,7 +64,7 @@ import type { Listable } from "../api/types";
import { notifyApiError, useNotify } from "../notifications/NotificationsProvider";
import { PageHeader } from "./PageHeader";
export type FieldType = "text" | "number" | "select" | "multiselect" | "ids" | "uuid";
export type FieldType = "text" | "number" | "select" | "multiselect" | "ids" | "uuid" | "string-list";
// FILTER_WIDTH is the fixed CSS width (px) of a single filter cell in the
// filter panel. FILTER_GAP is the flex gap between cells (matches the
@@ -126,7 +126,7 @@ export interface FieldSpec<TValue = unknown> {
// spec, matching what `emptyForm` would produce.
function emptyValueForField(f: FieldSpec): unknown {
if (f.defaultValue !== undefined) return f.defaultValue;
if (f.type === "multiselect") return [];
if (f.type === "multiselect" || f.type === "string-list") return [];
return "";
}
@@ -575,7 +575,7 @@ function fieldVisible(
// strings, missing selections, and empty arrays for multi-select / ids.
function isFieldEmpty(f: FieldSpec, value: unknown): boolean {
if (value === undefined || value === null) return true;
if (f.type === "multiselect" || f.type === "ids") {
if (f.type === "multiselect" || f.type === "ids" || f.type === "string-list") {
if (Array.isArray(value)) return value.length === 0;
if (typeof value === "string") return value.trim() === "";
return true;
@@ -4764,6 +4764,40 @@ function CrudDialog({
/>
);
}
if (f.type === "string-list") {
const arr = Array.isArray(value) ? (value as string[]) : [];
return (
<Box key={f.name} sx={{ mt: -1 }}>
<Typography variant="caption" color={errored ? "error" : "textSecondary"} sx={{ mb: 0.5, ml: "14px", display: "block" }}>
{f.label}{f.required ? " *" : ""}
</Typography>
<Stack spacing={1}>
{arr.map((item, idx) => (
<Stack key={idx} direction="row" spacing={1} alignItems="center">
<TextField
fullWidth
size="small"
value={item}
placeholder={`Key ${idx + 1}`}
onChange={(e) => {
const next = [...arr];
next[idx] = e.target.value;
set(f.name, next);
}}
/>
<IconButton size="small" color="error" onClick={() => set(f.name, arr.filter((_, i) => i !== idx))}>
<DeleteIcon fontSize="small" />
</IconButton>
</Stack>
))}
</Stack>
<Button size="small" startIcon={<AddIcon />} sx={{ mt: 1 }} onClick={() => set(f.name, [...arr, ""])}>
Add
</Button>
{fieldErr && <Typography variant="caption" color="error" sx={{ mt: 0.5, display: "block" }}>{fieldErr}</Typography>}
</Box>
);
}
const isNumber = f.type === "number";
// Numeric value of the current cell. Falls back to 0 for the
// empty state so the up-arrow always has a sensible base to

View File

@@ -25,6 +25,7 @@ const USER_TYPES: { value: UserType; label: string }[] = [
{ value: "mtproxy", label: "MTProxy" },
{ value: "naive", label: "Naive" },
{ value: "socks", label: "SOCKS" },
{ value: "ssh", label: "SSH" },
{ value: "trojan", label: "Trojan" },
{ value: "trusttunnel", label: "TrustTunnel" },
{ value: "tuic", label: "TUIC" },
@@ -44,8 +45,9 @@ const FLOW_OPTIONS: { value: string; label: string }[] = [
// same rule up-front (required fields invisible for the current type
// are filtered out before validateRequired runs).
const SHOW_UUID = new Set<UserType>(["vless", "vmess", "tuic"]);
const SHOW_PASSWORD = new Set<UserType>(["anytls", "http", "hysteria", "hysteria2", "mixed", "naive", "socks", "trojan", "trusttunnel", "tuic"]);
const SHOW_PASSWORD = new Set<UserType>(["anytls", "http", "hysteria", "hysteria2", "mixed", "naive", "socks", "ssh", "trojan", "trusttunnel", "tuic"]);
const SHOW_SECRET = new Set<UserType>(["mtproxy"]);
const SHOW_AUTHORIZED_KEYS = new Set<UserType>(["ssh"]);
const SHOW_FLOW = new Set<UserType>(["vless"]);
const SHOW_ALTER_ID = new Set<UserType>(["vmess"]);
@@ -103,7 +105,7 @@ export function UsersPage() {
options: USER_TYPES,
// Switching the user type wipes every credential field so the form
// matches the legacy admin's behaviour of starting fresh.
clears: ["uuid", "password", "secret", "flow", "alter_id"],
clears: ["uuid", "password", "secret", "authorized_keys", "flow", "alter_id"],
},
// Credential fields: the Go struct validator reports "required" for
// whichever of these is missing once the type is chosen, so each one
@@ -111,8 +113,9 @@ export function UsersPage() {
// are filtered out before validateRequired runs, so e.g. Password is
// only enforced for hysteria/hysteria2/trojan/tuic and not for vless.
{ name: "uuid", label: "UUID", type: "uuid", required: true, visibleWhen: showFor(SHOW_UUID) },
{ name: "password", label: "Password", type: "text", required: true, visibleWhen: showFor(SHOW_PASSWORD) },
{ name: "password", label: "Password", type: "text", visibleWhen: showFor(SHOW_PASSWORD) },
{ name: "secret", label: "Secret", type: "text", required: true, visibleWhen: showFor(SHOW_SECRET) },
{ name: "authorized_keys", label: "Authorized Keys", type: "string-list", visibleWhen: showFor(SHOW_AUTHORIZED_KEYS) },
{
name: "flow",
label: "Flow",
@@ -135,6 +138,7 @@ export function UsersPage() {
uuid: u.uuid,
password: u.password,
secret: u.secret,
authorized_keys: u.authorized_keys ?? [],
flow: u.flow,
alter_id: u.alter_id,
}),
@@ -146,6 +150,7 @@ export function UsersPage() {
uuid: f.uuid ? String(f.uuid).trim() : undefined,
password: f.password ? String(f.password) : undefined,
secret: f.secret ? String(f.secret) : undefined,
authorized_keys: Array.isArray(f.authorized_keys) ? (f.authorized_keys as string[]).filter(Boolean) : undefined,
flow: f.flow ? String(f.flow) : undefined,
alter_id: f.alter_id !== undefined && f.alter_id !== "" ? Number(f.alter_id) : undefined,
}),
@@ -154,6 +159,7 @@ export function UsersPage() {
if (f.uuid && String(f.uuid).trim() !== "") out.uuid = String(f.uuid).trim();
if (f.password !== undefined && f.password !== "") out.password = String(f.password);
if (f.secret !== undefined && f.secret !== "") out.secret = String(f.secret);
if (Array.isArray(f.authorized_keys) && (f.authorized_keys as string[]).filter(Boolean).length > 0) out.authorized_keys = (f.authorized_keys as string[]).filter(Boolean);
if (f.flow !== undefined && f.flow !== "") out.flow = String(f.flow);
if (f.alter_id !== undefined && f.alter_id !== "") out.alter_id = Number(f.alter_id);
return out;

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", "")

View File

@@ -59,18 +59,19 @@ func convertNode(v *pb.Node) CM.Node {
func convertUser(v *pb.User) CM.User {
return CM.User{
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Inbound: v.GetInbound(),
Type: v.GetType(),
UUID: v.GetUuid(),
Password: v.GetPassword(),
Secret: v.GetSecret(),
Flow: v.GetFlow(),
AlterID: int(v.GetAlterId()),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
ID: int(v.GetId()),
SquadIDs: toIntSlice(v.GetSquadIds()),
Username: v.GetUsername(),
Inbound: v.GetInbound(),
Type: v.GetType(),
UUID: v.GetUuid(),
Password: v.GetPassword(),
Secret: v.GetSecret(),
AuthorizedKeys: v.GetAuthorizedKeys(),
Flow: v.GetFlow(),
AlterID: int(v.GetAlterId()),
CreatedAt: timeFromNano(v.GetCreatedAt()),
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
}
}
@@ -83,7 +84,7 @@ func convertBandwidthLimiter(v *pb.BandwidthLimiter) CM.BandwidthLimiter {
Strategy: v.GetStrategy(),
ConnectionType: v.GetConnectionType(),
Mode: v.GetMode(),
FlowKeys: v.GetFlowKeys(),
FlowKeys: v.GetFlowKeys(),
Speed: v.GetSpeed(),
RawSpeed: v.GetRawSpeed(),
CreatedAt: timeFromNano(v.GetCreatedAt()),

View File

@@ -188,15 +188,16 @@ func (s *Client) CreateUser(in CM.UserCreate) (CM.User, error) {
return CM.User{}, err
}
reply, err := c.CreateUser(s.callContext(), &pb.UserCreate{
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Inbound: in.Inbound,
Type: in.Type,
Uuid: in.UUID,
Password: in.Password,
Secret: in.Secret,
Flow: in.Flow,
AlterId: int32(in.AlterID),
SquadIds: toInt32Slice(in.SquadIDs),
Username: in.Username,
Inbound: in.Inbound,
Type: in.Type,
Uuid: in.UUID,
Password: in.Password,
Secret: in.Secret,
AuthorizedKeys: in.AuthorizedKeys,
Flow: in.Flow,
AlterId: int32(in.AlterID),
})
if err != nil {
return CM.User{}, mapError(err)
@@ -252,11 +253,12 @@ func (s *Client) UpdateUser(id int, in CM.UserUpdate) (CM.User, error) {
reply, err := c.UpdateUser(s.callContext(), &pb.UserUpdateRequest{
Id: int32(id),
Update: &pb.UserUpdate{
Uuid: in.UUID,
Password: in.Password,
Secret: in.Secret,
Flow: in.Flow,
AlterId: int32(in.AlterID),
Uuid: in.UUID,
Password: in.Password,
Secret: in.Secret,
AuthorizedKeys: in.AuthorizedKeys,
Flow: in.Flow,
AlterId: int32(in.AlterID),
},
})
if err != nil {
@@ -289,7 +291,7 @@ func (s *Client) CreateBandwidthLimiter(in CM.BandwidthLimiterCreate) (CM.Bandwi
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
Mode: in.Mode,
FlowKeys: in.FlowKeys,
FlowKeys: in.FlowKeys,
Speed: in.Speed,
})
if err != nil {
@@ -351,7 +353,7 @@ func (s *Client) UpdateBandwidthLimiter(id int, in CM.BandwidthLimiterUpdate) (C
Strategy: in.Strategy,
ConnectionType: in.ConnectionType,
Mode: in.Mode,
FlowKeys: in.FlowKeys,
FlowKeys: in.FlowKeys,
Speed: in.Speed,
},
})

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.31.1
// protoc v7.34.1
// source: service/manager_api/grpc/manager/manager.proto
package manager
@@ -550,21 +550,22 @@ func (x *NodeUpdateRequest) GetUpdate() *NodeUpdate {
}
type User struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
SquadIds []int32 `protobuf:"varint,2,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Inbound string `protobuf:"bytes,4,opt,name=inbound,proto3" json:"inbound,omitempty"`
Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
Uuid string `protobuf:"bytes,6,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,8,opt,name=secret,proto3" json:"secret,omitempty"`
Flow string `protobuf:"bytes,9,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,10,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
UpdatedAt int64 `protobuf:"varint,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
SquadIds []int32 `protobuf:"varint,2,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Inbound string `protobuf:"bytes,4,opt,name=inbound,proto3" json:"inbound,omitempty"`
Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
Uuid string `protobuf:"bytes,6,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,8,opt,name=secret,proto3" json:"secret,omitempty"`
AuthorizedKeys []string `protobuf:"bytes,13,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
Flow string `protobuf:"bytes,9,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,10,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
UpdatedAt int64 `protobuf:"varint,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *User) Reset() {
@@ -653,6 +654,13 @@ func (x *User) GetSecret() string {
return ""
}
func (x *User) GetAuthorizedKeys() []string {
if x != nil {
return x.AuthorizedKeys
}
return nil
}
func (x *User) GetFlow() string {
if x != nil {
return x.Flow
@@ -682,18 +690,19 @@ func (x *User) GetUpdatedAt() int64 {
}
type UserCreate struct {
state protoimpl.MessageState `protogen:"open.v1"`
SquadIds []int32 `protobuf:"varint,1,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
SquadIds []int32 `protobuf:"varint,1,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
AuthorizedKeys []string `protobuf:"bytes,10,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UserCreate) Reset() {
@@ -775,6 +784,13 @@ func (x *UserCreate) GetSecret() string {
return ""
}
func (x *UserCreate) GetAuthorizedKeys() []string {
if x != nil {
return x.AuthorizedKeys
}
return nil
}
func (x *UserCreate) GetFlow() string {
if x != nil {
return x.Flow
@@ -790,14 +806,15 @@ func (x *UserCreate) GetAlterId() int32 {
}
type UserUpdate struct {
state protoimpl.MessageState `protogen:"open.v1"`
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,3,opt,name=secret,proto3" json:"secret,omitempty"`
Flow string `protobuf:"bytes,4,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,5,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,3,opt,name=secret,proto3" json:"secret,omitempty"`
AuthorizedKeys []string `protobuf:"bytes,6,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
Flow string `protobuf:"bytes,4,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,5,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UserUpdate) Reset() {
@@ -851,6 +868,13 @@ func (x *UserUpdate) GetSecret() string {
return ""
}
func (x *UserUpdate) GetAuthorizedKeys() []string {
if x != nil {
return x.AuthorizedKeys
}
return nil
}
func (x *UserUpdate) GetFlow() string {
if x != nil {
return x.Flow
@@ -2878,7 +2902,7 @@ const file_service_manager_api_grpc_manager_manager_proto_rawDesc = "" +
"\x06values\x18\x01 \x03(\v2\x14.manager_api.v1.NodeR\x06values\"[\n" +
"\x11NodeUpdateRequest\x12\x12\n" +
"\x04uuid\x18\x01 \x01(\tR\x04uuid\x122\n" +
"\x06update\x18\x02 \x01(\v2\x1a.manager_api.v1.NodeUpdateR\x06update\"\xb2\x02\n" +
"\x06update\x18\x02 \x01(\v2\x1a.manager_api.v1.NodeUpdateR\x06update\"\xdb\x02\n" +
"\x04User\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x1b\n" +
"\tsquad_ids\x18\x02 \x03(\x05R\bsquadIds\x12\x1a\n" +
@@ -2887,14 +2911,15 @@ const file_service_manager_api_grpc_manager_manager_proto_rawDesc = "" +
"\x04type\x18\x05 \x01(\tR\x04type\x12\x12\n" +
"\x04uuid\x18\x06 \x01(\tR\x04uuid\x12\x1a\n" +
"\bpassword\x18\a \x01(\tR\bpassword\x12\x16\n" +
"\x06secret\x18\b \x01(\tR\x06secret\x12\x12\n" +
"\x06secret\x18\b \x01(\tR\x06secret\x12'\n" +
"\x0fauthorized_keys\x18\r \x03(\tR\x0eauthorizedKeys\x12\x12\n" +
"\x04flow\x18\t \x01(\tR\x04flow\x12\x19\n" +
"\balter_id\x18\n" +
" \x01(\x05R\aalterId\x12\x1d\n" +
"\n" +
"created_at\x18\v \x01(\x03R\tcreatedAt\x12\x1d\n" +
"\n" +
"updated_at\x18\f \x01(\x03R\tupdatedAt\"\xea\x01\n" +
"updated_at\x18\f \x01(\x03R\tupdatedAt\"\x93\x02\n" +
"\n" +
"UserCreate\x12\x1b\n" +
"\tsquad_ids\x18\x01 \x03(\x05R\bsquadIds\x12\x1a\n" +
@@ -2903,14 +2928,17 @@ const file_service_manager_api_grpc_manager_manager_proto_rawDesc = "" +
"\x04type\x18\x04 \x01(\tR\x04type\x12\x12\n" +
"\x04uuid\x18\x05 \x01(\tR\x04uuid\x12\x1a\n" +
"\bpassword\x18\x06 \x01(\tR\bpassword\x12\x16\n" +
"\x06secret\x18\a \x01(\tR\x06secret\x12\x12\n" +
"\x06secret\x18\a \x01(\tR\x06secret\x12'\n" +
"\x0fauthorized_keys\x18\n" +
" \x03(\tR\x0eauthorizedKeys\x12\x12\n" +
"\x04flow\x18\b \x01(\tR\x04flow\x12\x19\n" +
"\balter_id\x18\t \x01(\x05R\aalterId\"\x83\x01\n" +
"\balter_id\x18\t \x01(\x05R\aalterId\"\xac\x01\n" +
"\n" +
"UserUpdate\x12\x12\n" +
"\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x16\n" +
"\x06secret\x18\x03 \x01(\tR\x06secret\x12\x12\n" +
"\x06secret\x18\x03 \x01(\tR\x06secret\x12'\n" +
"\x0fauthorized_keys\x18\x06 \x03(\tR\x0eauthorizedKeys\x12\x12\n" +
"\x04flow\x18\x04 \x01(\tR\x04flow\x12\x19\n" +
"\balter_id\x18\x05 \x01(\x05R\aalterId\"8\n" +
"\bUserList\x12,\n" +

View File

@@ -116,6 +116,7 @@ message User {
string uuid = 6;
string password = 7;
string secret = 8;
repeated string authorized_keys = 13;
string flow = 9;
int32 alter_id = 10;
int64 created_at = 11;
@@ -130,6 +131,7 @@ message UserCreate {
string uuid = 5;
string password = 6;
string secret = 7;
repeated string authorized_keys = 10;
string flow = 8;
int32 alter_id = 9;
}
@@ -138,6 +140,7 @@ message UserUpdate {
string uuid = 1;
string password = 2;
string secret = 3;
repeated string authorized_keys = 6;
string flow = 4;
int32 alter_id = 5;
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v6.31.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.34.1
// source: service/manager_api/grpc/manager/manager.proto
package manager

View File

@@ -42,18 +42,19 @@ func convertNode(v CM.Node) *pb.Node {
func convertUser(v CM.User) *pb.User {
return &pb.User{
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Inbound: v.Inbound,
Type: v.Type,
Uuid: v.UUID,
Password: v.Password,
Secret: v.Secret,
Flow: v.Flow,
AlterId: int32(v.AlterID),
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
Id: int32(v.ID),
SquadIds: toInt32Slice(v.SquadIDs),
Username: v.Username,
Inbound: v.Inbound,
Type: v.Type,
Uuid: v.UUID,
Password: v.Password,
Secret: v.Secret,
AuthorizedKeys: v.AuthorizedKeys,
Flow: v.Flow,
AlterId: int32(v.AlterID),
CreatedAt: v.CreatedAt.UnixNano(),
UpdatedAt: v.UpdatedAt.UnixNano(),
}
}
@@ -66,7 +67,7 @@ func convertBandwidthLimiter(v CM.BandwidthLimiter) *pb.BandwidthLimiter {
Strategy: v.Strategy,
ConnectionType: v.ConnectionType,
Mode: v.Mode,
FlowKeys: v.FlowKeys,
FlowKeys: v.FlowKeys,
Speed: v.Speed,
RawSpeed: v.RawSpeed,
CreatedAt: v.CreatedAt.UnixNano(),

View File

@@ -125,15 +125,16 @@ func (s *Server) DeleteNode(_ context.Context, req *pb.UuidRequest) (*pb.Node, e
func (s *Server) CreateUser(_ context.Context, req *pb.UserCreate) (*pb.User, error) {
v, err := s.manager.CreateUser(CM.UserCreate{
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Inbound: req.GetInbound(),
Type: req.GetType(),
UUID: req.GetUuid(),
Password: req.GetPassword(),
Secret: req.GetSecret(),
Flow: req.GetFlow(),
AlterID: int(req.GetAlterId()),
SquadIDs: toIntSlice(req.GetSquadIds()),
Username: req.GetUsername(),
Inbound: req.GetInbound(),
Type: req.GetType(),
UUID: req.GetUuid(),
Password: req.GetPassword(),
Secret: req.GetSecret(),
AuthorizedKeys: req.GetAuthorizedKeys(),
Flow: req.GetFlow(),
AlterID: int(req.GetAlterId()),
})
if err != nil {
return nil, err
@@ -172,11 +173,12 @@ func (s *Server) GetUser(_ context.Context, req *pb.IdRequest) (*pb.User, error)
func (s *Server) UpdateUser(_ context.Context, req *pb.UserUpdateRequest) (*pb.User, error) {
u := req.GetUpdate()
v, err := s.manager.UpdateUser(int(req.GetId()), CM.UserUpdate{
UUID: u.GetUuid(),
Password: u.GetPassword(),
Secret: u.GetSecret(),
Flow: u.GetFlow(),
AlterID: int(u.GetAlterId()),
UUID: u.GetUuid(),
Password: u.GetPassword(),
Secret: u.GetSecret(),
AuthorizedKeys: u.GetAuthorizedKeys(),
Flow: u.GetFlow(),
AlterID: int(u.GetAlterId()),
})
if err != nil {
return nil, err
@@ -200,7 +202,7 @@ func (s *Server) CreateBandwidthLimiter(_ context.Context, req *pb.BandwidthLimi
Strategy: req.GetStrategy(),
ConnectionType: req.GetConnectionType(),
Mode: req.GetMode(),
FlowKeys: req.GetFlowKeys(),
FlowKeys: req.GetFlowKeys(),
Speed: req.GetSpeed(),
})
if err != nil {
@@ -245,7 +247,7 @@ func (s *Server) UpdateBandwidthLimiter(_ context.Context, req *pb.BandwidthLimi
Strategy: u.GetStrategy(),
ConnectionType: u.GetConnectionType(),
Mode: u.GetMode(),
FlowKeys: u.GetFlowKeys(),
FlowKeys: u.GetFlowKeys(),
Speed: u.GetSpeed(),
})
if err != nil {

View File

@@ -194,7 +194,7 @@ paths:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: username, schema: {type: string}}
- {in: query, name: inbound, schema: {type: string}}
- {in: query, name: type, schema: {type: string, enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]}}
- {in: query, name: type, schema: {type: string, enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]}}
- {$ref: "#/components/parameters/FilterSquadIdIn"}
- {$ref: "#/components/parameters/FilterCreatedAtStart"}
- {$ref: "#/components/parameters/FilterCreatedAtEnd"}
@@ -210,6 +210,15 @@ paths:
post:
tags: [Users]
summary: Create user
description: |
Required fields depend on `type`:
- **vless**: uuid (flow optional)
- **vmess**: uuid, alter_id
- **trojan, hysteria, hysteria2**: password
- **tuic**: uuid, password
- **mtproxy**: secret
- **ssh**: password OR authorized_keys (at least one)
- **anytls, http, mixed, naive, socks, trusttunnel**: password
requestBody: {required: true, content: {application/json: {schema: {$ref: "#/components/schemas/UserCreate"}}}}
responses:
"201": {content: {application/json: {schema: {$ref: "#/components/schemas/User"}}}}
@@ -222,7 +231,7 @@ paths:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: username, schema: {type: string}}
- {in: query, name: inbound, schema: {type: string}}
- {in: query, name: type, schema: {type: string, enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]}}
- {in: query, name: type, schema: {type: string, enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]}}
- {$ref: "#/components/parameters/FilterSquadIdIn"}
- {$ref: "#/components/parameters/FilterCreatedAtStart"}
- {$ref: "#/components/parameters/FilterCreatedAtEnd"}
@@ -247,6 +256,15 @@ paths:
put:
tags: [Users]
summary: Update user
description: |
Required fields depend on user `type`:
- **vless**: uuid (flow optional)
- **vmess**: uuid, alter_id
- **trojan, hysteria, hysteria2**: password
- **tuic**: uuid, password
- **mtproxy**: secret
- **ssh**: password OR authorized_keys (at least one)
- **anytls, http, mixed, naive, socks, trusttunnel**: password
requestBody: {required: true, content: {application/json: {schema: {$ref: "#/components/schemas/UserUpdate"}}}}
responses:
"200": {content: {application/json: {schema: {$ref: "#/components/schemas/User"}}}}
@@ -266,7 +284,7 @@ paths:
summary: List bandwidth limiters
parameters:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: strategy, schema: {type: string, enum: [global, connection]}}
- {in: query, name: strategy, schema: {type: string, enum: [global, connection, bypass]}}
- {in: query, name: mode, schema: {type: string, enum: [upload, download, bidirectional]}}
- {in: query, name: type, schema: {type: string}}
- {in: query, name: username, schema: {type: string}}
@@ -299,7 +317,7 @@ paths:
summary: Count bandwidth limiters
parameters:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: strategy, schema: {type: string, enum: [global, connection]}}
- {in: query, name: strategy, schema: {type: string, enum: [global, connection, bypass]}}
- {in: query, name: mode, schema: {type: string, enum: [upload, download, bidirectional]}}
- {in: query, name: type, schema: {type: string}}
- {in: query, name: username, schema: {type: string}}
@@ -430,7 +448,7 @@ paths:
summary: List connection limiters
parameters:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: strategy, schema: {type: string, enum: [connection]}}
- {in: query, name: strategy, schema: {type: string, enum: [connection, bypass]}}
- {in: query, name: username, schema: {type: string}}
- {in: query, name: outbound, schema: {type: string}}
- {in: query, name: connection_type, schema: {type: string, enum: [default, hwid, mux, ip]}}
@@ -460,7 +478,7 @@ paths:
summary: Count connection limiters
parameters:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: strategy, schema: {type: string, enum: [connection]}}
- {in: query, name: strategy, schema: {type: string, enum: [connection, bypass]}}
- {in: query, name: username, schema: {type: string}}
- {in: query, name: outbound, schema: {type: string}}
- {in: query, name: connection_type, schema: {type: string, enum: [default, hwid, mux, ip]}}
@@ -507,7 +525,7 @@ paths:
summary: List rate limiters
parameters:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}}
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}}
- {in: query, name: username, schema: {type: string}}
- {in: query, name: outbound, schema: {type: string}}
- {in: query, name: connection_type, schema: {type: string, enum: [hwid, mux, ip, default]}}
@@ -539,7 +557,7 @@ paths:
summary: Count rate limiters
parameters:
- {in: query, name: id, schema: {type: integer, format: int32}}
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}}
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}}
- {in: query, name: username, schema: {type: string}}
- {in: query, name: outbound, schema: {type: string}}
- {in: query, name: connection_type, schema: {type: string, enum: [hwid, mux, ip, default]}}
@@ -686,16 +704,17 @@ components:
User:
type: object
required: [id, squad_ids, username, inbound, type, uuid, password, secret, flow, alter_id, created_at, updated_at]
required: [id, squad_ids, username, inbound, type, uuid, password, secret, authorized_keys, flow, alter_id, created_at, updated_at]
properties:
id: {type: integer, format: int32}
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string, example: "alice"}
inbound: {type: string, example: "vless-in"}
type: {type: string, enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]}
type: {type: string, enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]}
uuid: {type: string}
password: {type: string}
secret: {type: string}
authorized_keys: {type: array, items: {type: string}}
flow: {type: string}
alter_id: {type: integer, format: int32}
created_at: {type: string, format: date-time}
@@ -703,24 +722,44 @@ components:
UserCreate:
type: object
required: [squad_ids, username, inbound, type]
description: |
Required fields depend on `type`:
- vless: uuid (flow optional)
- vmess: uuid, alter_id
- trojan, shadowsocks, hysteria, hysteria2: password
- tuic: uuid, password
- mtproxy: secret
- ssh: password OR authorized_keys (at least one)
- anytls, http, mixed, naive, socks, trusttunnel: password
properties:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string, example: "alice"}
inbound: {type: string, example: "vless-in"}
type:
type: string
enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]
enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]
uuid: {type: string, format: uuid}
password: {type: string}
secret: {type: string}
authorized_keys: {type: array, items: {type: string}}
flow: {type: string}
alter_id: {type: integer, format: int32}
UserUpdate:
type: object
description: |
All fields are optional. Validation rules match UserCreate by type:
- vless: uuid (flow optional)
- vmess: uuid, alter_id
- trojan, shadowsocks, hysteria, hysteria2: password
- tuic: uuid, password
- mtproxy: secret
- ssh: password OR authorized_keys (at least one)
- anytls, http, mixed, naive, socks, trusttunnel: password
properties:
uuid: {type: string, format: uuid}
password: {type: string}
secret: {type: string}
authorized_keys: {type: array, items: {type: string}}
flow: {type: string}
alter_id: {type: integer, format: int32}
@@ -732,41 +771,43 @@ components:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
outbound: {type: string, example: "direct"}
strategy: {type: string, enum: [global, connection]}
connection_type: {type: string, enum: [default, hwid, mux, ip]}
strategy: {type: string, enum: [global, connection, bypass]}
connection_type: {type: string}
mode: {type: string, enum: [upload, download, bidirectional]}
flow_keys: {type: array, items: {type: string, enum: [user, destination, ip, hwid, mux]}}
flow_keys: {type: array, items: {type: string, enum: [user, source_ip, hwid, mux, protocol, destination]}}
speed: {type: string, example: "10mbit"}
raw_speed: {type: integer, format: int64}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
BandwidthLimiterCreate:
type: object
required: [squad_ids, outbound, strategy, mode, speed]
required: [squad_ids, outbound, strategy]
description: "mode, speed, flow_keys, connection_type are required/relevant unless strategy=bypass"
properties:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
outbound: {type: string, example: "direct"}
strategy: {type: string, enum: [global, connection]}
connection_type: {type: string, enum: [default, hwid, mux, ip]}
strategy: {type: string, enum: [global, connection, bypass]}
connection_type: {type: string}
mode: {type: string, enum: [upload, download, bidirectional]}
flow_keys: {type: array, items: {type: string, enum: [user, destination, ip, hwid, mux]}}
flow_keys: {type: array, items: {type: string, enum: [user, source_ip, hwid, mux, protocol, destination]}}
speed: {type: string, example: "10mbit"}
BandwidthLimiterUpdate:
type: object
required: [outbound, strategy, mode, speed]
required: [outbound, strategy]
description: "mode, speed, flow_keys, connection_type are required/relevant unless strategy=bypass"
properties:
username: {type: string}
outbound: {type: string}
strategy: {type: string, enum: [global, connection]}
connection_type: {type: string, enum: [default, hwid, mux, ip]}
strategy: {type: string, enum: [global, connection, bypass]}
connection_type: {type: string}
mode: {type: string, enum: [upload, download, bidirectional]}
flow_keys: {type: array, items: {type: string, enum: [user, destination, ip, hwid, mux]}}
flow_keys: {type: array, items: {type: string, enum: [user, source_ip, hwid, mux, protocol, destination]}}
speed: {type: string}
TrafficLimiter:
type: object
required: [id, squad_ids, outbound, strategy, mode, raw_used, quota, raw_quota, created_at, updated_at]
required: [id, squad_ids, outbound, strategy, mode, raw_used, quota, raw_quota, usage, created_at, updated_at]
properties:
id: {type: integer, format: int32}
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
@@ -777,11 +818,13 @@ components:
raw_used: {type: integer, format: int64}
quota: {type: string, example: "10gb"}
raw_quota: {type: integer, format: int64}
usage: {type: integer, format: int32, description: "Usage percentage 0-100"}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
TrafficLimiterCreate:
type: object
required: [squad_ids, outbound, strategy, mode, quota]
required: [squad_ids, outbound, strategy]
description: "mode, quota are required unless strategy=bypass"
properties:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
@@ -791,7 +834,8 @@ components:
quota: {type: string, example: "10gb"}
TrafficLimiterUpdate:
type: object
required: [outbound, strategy, mode, quota]
required: [outbound, strategy]
description: "mode, quota are required unless strategy=bypass"
properties:
username: {type: string}
outbound: {type: string}
@@ -807,7 +851,7 @@ components:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
outbound: {type: string, example: "direct"}
strategy: {type: string, enum: [connection]}
strategy: {type: string, enum: [connection, bypass]}
connection_type: {type: string, enum: [default, hwid, mux, ip]}
lock_type: {type: string, enum: [manager, default]}
count: {type: integer, format: int64}
@@ -815,22 +859,24 @@ components:
updated_at: {type: string, format: date-time}
ConnectionLimiterCreate:
type: object
required: [squad_ids, outbound, strategy, lock_type, count]
required: [squad_ids, outbound, strategy]
description: "lock_type, connection_type, count are required unless strategy=bypass"
properties:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
outbound: {type: string, example: "direct"}
strategy: {type: string, enum: [connection]}
strategy: {type: string, enum: [connection, bypass]}
connection_type: {type: string, enum: [default, hwid, mux, ip]}
lock_type: {type: string, enum: [manager, default]}
count: {type: integer, format: int64}
ConnectionLimiterUpdate:
type: object
required: [outbound, strategy, lock_type, count]
required: [outbound, strategy]
description: "lock_type, connection_type, count are required unless strategy=bypass"
properties:
username: {type: string}
outbound: {type: string}
strategy: {type: string, enum: [connection]}
strategy: {type: string, enum: [connection, bypass]}
connection_type: {type: string, enum: [default, hwid, mux, ip]}
lock_type: {type: string, enum: [manager, default]}
count: {type: integer, format: int64}
@@ -843,7 +889,7 @@ components:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
outbound: {type: string, example: "direct"}
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}
connection_type: {type: string, enum: [hwid, mux, ip, default]}
count: {type: integer, format: int64}
interval: {type: string, example: "1s"}
@@ -851,22 +897,24 @@ components:
updated_at: {type: string, format: date-time}
RateLimiterCreate:
type: object
required: [squad_ids, outbound, strategy, connection_type, count, interval]
required: [squad_ids, outbound, strategy]
description: "connection_type, count, interval are required unless strategy=bypass"
properties:
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
username: {type: string}
outbound: {type: string, example: "direct"}
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}
connection_type: {type: string, enum: [hwid, mux, ip, default]}
count: {type: integer, format: int64}
interval: {type: string, example: "1s"}
RateLimiterUpdate:
type: object
required: [outbound, strategy, connection_type, count, interval]
required: [outbound, strategy]
description: "connection_type, count, interval are required unless strategy=bypass"
properties:
username: {type: string}
outbound: {type: string}
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}
connection_type: {type: string, enum: [hwid, mux, ip, default]}
count: {type: integer, format: int64}
interval: {type: string}

View File

@@ -0,0 +1,97 @@
package inbound
import (
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/ssh"
CM "github.com/sagernet/sing-box/service/manager/constant"
"github.com/sagernet/sing-box/service/node/constant"
)
type SSHManager struct {
inbounds map[string]*SSHUserManager
mtx sync.Mutex
}
func NewSSHManager() *SSHManager {
return &SSHManager{
inbounds: make(map[string]*SSHUserManager),
}
}
func (m *SSHManager) AddUserManager(inbound adapter.Inbound) error {
m.mtx.Lock()
defer m.mtx.Unlock()
m.inbounds[inbound.Tag()] = &SSHUserManager{
inbound: inbound.(*ssh.Inbound),
usersMap: make(map[string]option.SSHUser),
}
return nil
}
func (m *SSHManager) GetUserManager(tag string) (constant.UserManager, bool) {
m.mtx.Lock()
defer m.mtx.Unlock()
inbound, ok := m.inbounds[tag]
return inbound, ok
}
func (m *SSHManager) GetUserManagerTags() []string {
m.mtx.Lock()
defer m.mtx.Unlock()
tags := make([]string, 0, len(m.inbounds))
for tag := range m.inbounds {
tags = append(tags, tag)
}
return tags
}
type SSHUserManager struct {
inbound *ssh.Inbound
usersMap map[string]option.SSHUser
mtx sync.Mutex
}
func (i *SSHUserManager) postUpdate() {
users := make([]option.SSHUser, 0, len(i.usersMap))
for _, user := range i.usersMap {
users = append(users, user)
}
i.inbound.UpdateUsers(users)
}
func convertSSHUser(user CM.User) option.SSHUser {
return option.SSHUser{
Name: user.Username,
Password: user.Password,
AuthorizedKeys: user.AuthorizedKeys,
}
}
func (i *SSHUserManager) UpdateUser(user CM.User) {
i.mtx.Lock()
defer i.mtx.Unlock()
i.usersMap[user.Username] = convertSSHUser(user)
i.postUpdate()
}
func (i *SSHUserManager) UpdateUsers(users []CM.User) {
i.mtx.Lock()
defer i.mtx.Unlock()
clear(i.usersMap)
for _, user := range users {
i.usersMap[user.Username] = convertSSHUser(user)
}
i.postUpdate()
}
func (i *SSHUserManager) DeleteUser(username string) {
i.mtx.Lock()
defer i.mtx.Unlock()
delete(i.usersMap, username)
i.postUpdate()
}

View File

@@ -33,7 +33,7 @@ type Service struct {
rateManager constant.RateLimiterManager
options option.NodeServiceOptions
nodeManager CM.NodeManager
nodeManager CM.NodeManager
mtx sync.Mutex
}
@@ -79,6 +79,7 @@ func (s *Service) Start(stage adapter.StartStage) error {
"tuic": inbound.NewTUICManager(),
"vless": inbound.NewVLESSManager(),
"vmess": inbound.NewVMessManager(),
"ssh": inbound.NewSSHManager(),
}
s.connectionManager = limiter.NewConnectionLimiterManager(s.ctx, nodeManager, s.logger)
s.bandwidthManager = limiter.NewBandwidthLimiterManager(s.ctx, nodeManager, s.logger)

View File

@@ -281,15 +281,16 @@ func (s *APIClient) handler(node CM.ConnectedNode, stream grpc.ServerStreamingCl
func (s *APIClient) convertUser(user *pb.User) CM.User {
return CM.User{
ID: int(user.Id),
Username: user.Username,
Inbound: user.Inbound,
Type: user.Type,
UUID: user.Uuid,
Password: user.Password,
Secret: user.Secret,
Flow: user.Flow,
AlterID: int(user.AlterId),
ID: int(user.Id),
Username: user.Username,
Inbound: user.Inbound,
Type: user.Type,
UUID: user.Uuid,
Password: user.Password,
Secret: user.Secret,
AuthorizedKeys: user.AuthorizedKeys,
Flow: user.Flow,
AlterID: int(user.AlterId),
}
}
@@ -301,7 +302,7 @@ func (s *APIClient) convertBandwidthLimiter(limiter *pb.BandwidthLimiter) CM.Ban
Strategy: limiter.Strategy,
ConnectionType: limiter.ConnectionType,
Mode: limiter.Mode,
FlowKeys: limiter.FlowKeys,
FlowKeys: limiter.FlowKeys,
Speed: limiter.Speed,
RawSpeed: limiter.RawSpeed,
}

View File

@@ -151,18 +151,19 @@ func (x *Node) GetUuid() string {
}
type User struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
AuthorizedKeys []string `protobuf:"bytes,10,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *User) Reset() {
@@ -258,6 +259,13 @@ func (x *User) GetAlterId() int32 {
return 0
}
func (x *User) GetAuthorizedKeys() []string {
if x != nil {
return x.AuthorizedKeys
}
return nil
}
type UserList struct {
state protoimpl.MessageState `protogen:"open.v1"`
Values []*User `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
@@ -1338,7 +1346,7 @@ const file_service_node_manager_api_manager_manager_proto_rawDesc = "" +
"\n" +
".service/node_manager_api/manager/manager.proto\x12\x13node_manager_api.v1\"\x1a\n" +
"\x04Node\x12\x12\n" +
"\x04uuid\x18\x01 \x01(\tR\x04uuid\"\xd7\x01\n" +
"\x04uuid\x18\x01 \x01(\tR\x04uuid\"\x80\x02\n" +
"\x04User\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x1a\n" +
"\busername\x18\x02 \x01(\tR\busername\x12\x18\n" +
@@ -1348,7 +1356,9 @@ const file_service_node_manager_api_manager_manager_proto_rawDesc = "" +
"\bpassword\x18\x06 \x01(\tR\bpassword\x12\x16\n" +
"\x06secret\x18\a \x01(\tR\x06secret\x12\x12\n" +
"\x04flow\x18\b \x01(\tR\x04flow\x12\x19\n" +
"\balter_id\x18\t \x01(\x05R\aalterId\"=\n" +
"\balter_id\x18\t \x01(\x05R\aalterId\x12'\n" +
"\x0fauthorized_keys\x18\n" +
" \x03(\tR\x0eauthorizedKeys\"=\n" +
"\bUserList\x121\n" +
"\x06values\x18\x01 \x03(\v2\x19.node_manager_api.v1.UserR\x06values\"\x83\x02\n" +
"\x10BandwidthLimiter\x12\x0e\n" +

View File

@@ -48,6 +48,7 @@ message User {
string secret = 7;
string flow = 8;
int32 alter_id = 9;
repeated string authorized_keys = 10;
}
message UserList {

View File

@@ -239,15 +239,16 @@ func (s *RemoteNode) close(err error) {
func (s *RemoteNode) convertUser(user CS.User) *pb.User {
return &pb.User{
Id: int32(user.ID),
Username: user.Username,
Inbound: user.Inbound,
Type: user.Type,
Uuid: user.UUID,
Password: user.Password,
Secret: user.Secret,
Flow: user.Flow,
AlterId: int32(user.AlterID),
Id: int32(user.ID),
Username: user.Username,
Inbound: user.Inbound,
Type: user.Type,
Uuid: user.UUID,
Password: user.Password,
Secret: user.Secret,
AuthorizedKeys: user.AuthorizedKeys,
Flow: user.Flow,
AlterId: int32(user.AlterID),
}
}

View File

@@ -98,12 +98,12 @@ func (s *Service) Start(stage adapter.StartStage) error {
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
s.adaptiveTimer.start(false)
if s.memoryLimit > 0 {
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
s.logger.Notice("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
} else {
s.logger.Info("started memory monitor with available memory detection")
s.logger.Notice("started memory monitor with available memory detection")
}
} else {
s.logger.Info("started memory pressure monitor")
s.logger.Notice("started memory pressure monitor")
}
globalAccess.Lock()

View File

@@ -66,9 +66,9 @@ func (s *Service) Start(stage adapter.StartStage) error {
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
s.adaptiveTimer.start(false)
if s.useAvailable {
s.logger.Info("started memory monitor with available memory detection")
s.logger.Notice("started memory monitor with available memory detection")
} else {
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
s.logger.Notice("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
}
return nil
}