|
|
@@ -1841,7 +1841,14 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
|
|
// from the node arriving after a central disable would otherwise
|
|
|
// overwrite enable=false back to true, letting the client accumulate
|
|
|
// far more traffic than their limit before being disabled again.
|
|
|
- enableExpr := "enable AND ?"
|
|
|
+ //
|
|
|
+ // We use a dialect-aware expression (see database.ClientTrafficEnableMergeExpr)
|
|
|
+ // because the old "enable AND ?" form (and naive CASE with :: casts)
|
|
|
+ // caused type mismatches on PostgreSQL after public API inbound updates
|
|
|
+ // (which go through updateClientTraffics + SyncInbound and can touch
|
|
|
+ // client_traffics rows) and would also break on SQLite due to PG-only
|
|
|
+ // ::boolean syntax.
|
|
|
+ enableExpr := database.ClientTrafficEnableMergeExpr()
|
|
|
if err := tx.Exec(
|
|
|
fmt.Sprintf(
|
|
|
`UPDATE client_traffics
|
|
|
@@ -3556,6 +3563,40 @@ func (s *InboundService) MigrationRequirements() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Normalize "enable" columns to boolean on Postgres. Legacy SQLite data
|
|
|
+ // (0/1 integers), partial migrations, or mixed write paths (public API
|
|
|
+ // inbound updates that flow through UpdateClientStat + client syncs, plus
|
|
|
+ // node traffic merge deltas) can leave the column as integer or with mixed
|
|
|
+ // interpretation. This (combined with the dialect-aware
|
|
|
+ // ClientTrafficEnableMergeExpr) prevents type problems in the node traffic
|
|
|
+ // sync merge (SetRemoteTraffic) and makes the sync robust even when
|
|
|
+ // inbounds are updated via the public API (incl. ones carrying
|
|
|
+ // externalProxy in streamSettings). The same expression is also safe on
|
|
|
+ // SQLite (no PG :: casts).
|
|
|
+ if database.IsPostgres() {
|
|
|
+ // Use DO block so it is idempotent and doesn't fail if already boolean.
|
|
|
+ normalizeBool := func(table, col string) {
|
|
|
+ tx.Exec(fmt.Sprintf(`
|
|
|
+ DO $$
|
|
|
+ BEGIN
|
|
|
+ IF EXISTS (
|
|
|
+ SELECT 1 FROM information_schema.columns
|
|
|
+ WHERE table_name = '%s' AND column_name = '%s'
|
|
|
+ AND data_type <> 'boolean'
|
|
|
+ ) THEN
|
|
|
+ ALTER TABLE %s ALTER COLUMN %s
|
|
|
+ TYPE boolean USING (CASE WHEN %s::text IN ('1','true','t','yes') THEN true ELSE false END);
|
|
|
+ END IF;
|
|
|
+ END $$;`, table, col, table, col, col))
|
|
|
+ }
|
|
|
+ normalizeBool("inbounds", "enable")
|
|
|
+ normalizeBool("client_traffics", "enable")
|
|
|
+ normalizeBool("nodes", "enable")
|
|
|
+ normalizeBool("clients", "enable")
|
|
|
+ normalizeBool("api_tokens", "enabled")
|
|
|
+ normalizeBool("outbound_subscriptions", "enabled")
|
|
|
+ }
|
|
|
+
|
|
|
// Fix inbounds based problems
|
|
|
var inbounds []*model.Inbound
|
|
|
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria"}).Find(&inbounds).Error
|