refactor(backend): rename OpenFlare directory to lowercase openflare

This commit is contained in:
ryan
2026-08-30 17:43:23 +08:00
parent 06d5fedbfc
commit c93ff6674f
543 changed files with 819 additions and 819 deletions
@@ -0,0 +1,116 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package analytics defines ClickHouse analytics domain models and query DTOs
// (pure data, no IO).
package analytics
import "time"
// AccessLogFilter scopes user access log queries.
// 单一权威字段集(CH 原字段,Task 1 迁入):禁止追加仅某实现使用的字段(避免双字段集分叉)。
type AccessLogFilter struct {
// UserIDs filters by user IDs. nil means no user filter; an empty slice means no matches.
UserIDs []uint64
Path string
// StartTime filters created_at >= StartTime when non-nil.
StartTime *time.Time
// EndTime filters created_at <= EndTime when non-nil(闭区间,与 CH/GORM 实现一致)。
EndTime *time.Time
}
// NodeAccessLogFilter scopes ClickHouse node access log queries.
type NodeAccessLogFilter struct {
NodeID string
RemoteAddr string
Host string
// Hosts exact-matches any host (case-insensitive). Prefer over Host for multi-domain scopes.
Hosts []string
Path string
// StatusCode filters by exact HTTP status code when > 0.
StatusCode int
Since time.Time
Until time.Time
Page int
PageSize int
SortBy string
SortOrder string
}
// NodeObservabilityFilter scopes ClickHouse node observability queries.
type NodeObservabilityFilter struct {
NodeID string
Since time.Time
Limit int
}
// DailyTrend is a single day's access count.
type DailyTrend struct {
Date string
Count uint64
}
// BrowserShare is a browser group's share of access logs.
type BrowserShare struct {
Browser string
Count uint64
}
// TopUser is an active user ranked by access count.
type TopUser struct {
UserID uint64
Count uint64
}
// NodeAccessLogRegionCount aggregates access log regions.
type NodeAccessLogRegionCount struct {
Region string
Count int64
}
// NodeAccessLogTrafficSummary is a window-level access log traffic summary.
type NodeAccessLogTrafficSummary struct {
RequestCount int64
ErrorCount int64
UniqueIPCount int64
BytesSent int64
RequestLength int64
NodeCount int64
}
// NodeAccessLogValueCount is a grouped value count (status_code, host, ...).
type NodeAccessLogValueCount struct {
Value string
Count int64
}
// NodeAccessLogNodeAggregate is per-node traffic over a window.
type NodeAccessLogNodeAggregate struct {
NodeID string
RequestCount int64
ErrorCount int64
UniqueIPCount int64
}
// BatchWriterStats is a point-in-time snapshot of a batch writer queue and failure counters.
type BatchWriterStats struct {
Name string `json:"name"`
Depth int `json:"depth"`
Cap int `json:"cap"`
Drops int64 `json:"drops"`
FlushErrors int64 `json:"flush_errors"`
Running bool `json:"running"`
}
// ClickHouseOperationalStats summarizes ClickHouse merge/mutation pressure
// and in-process batch writer queue health.
type ClickHouseOperationalStats struct {
Database string `json:"database"`
ActiveParts int64 `json:"active_parts"`
TotalRows int64 `json:"total_rows"`
PendingMutations int64 `json:"pending_mutations"`
AsyncInsertQueue int64 `json:"async_insert_queue"`
AsyncInsertBytes int64 `json:"async_insert_bytes"`
// BatchWriters reports in-process queue depth/drops/flush errors for CH writers.
BatchWriters []BatchWriterStats `json:"batch_writers,omitempty"`
}
@@ -0,0 +1,47 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package analytics
import (
"fmt"
"time"
)
const (
nodeAccessLogTableName = "of_node_access_logs"
nodeAccessLogInsertColumns = "id, node_id, logged_at, remote_addr, region, host, path, user_agent, cache_status, status_code, bytes_sent, request_length, request_time_ms, created_at"
)
// NodeAccessLog stores OpenFlare edge node access records in ClickHouse.
type NodeAccessLog struct {
ID uint64 `gorm:"column:id"`
NodeID string `gorm:"column:node_id"`
LoggedAt time.Time `gorm:"column:logged_at"`
RemoteAddr string `gorm:"column:remote_addr"`
Region string `gorm:"column:region"`
Host string `gorm:"column:host"`
Path string `gorm:"column:path"`
UserAgent string `gorm:"column:user_agent"`
CacheStatus string `gorm:"column:cache_status"`
StatusCode int32 `gorm:"column:status_code"`
BytesSent uint64 `gorm:"column:bytes_sent"`
RequestLength uint64 `gorm:"column:request_length"`
RequestTimeMs uint32 `gorm:"column:request_time_ms"`
CreatedAt time.Time `gorm:"column:created_at"`
}
// TableName returns the ClickHouse table name.
func (NodeAccessLog) TableName() string {
return nodeAccessLogTableName
}
// InsertColumns returns comma-separated column names for batch insert.
func (NodeAccessLog) InsertColumns() string {
return nodeAccessLogInsertColumns
}
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
func (NodeAccessLog) BatchInsertSQL() string {
return fmt.Sprintf("INSERT INTO %s (%s)", nodeAccessLogTableName, nodeAccessLogInsertColumns)
}
@@ -0,0 +1,68 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package analytics
// NodeAccessLogBucketAggregate is a folded bucket aggregate row.
type NodeAccessLogBucketAggregate struct {
BucketEpoch int64 `gorm:"column:bucket_epoch"`
RequestCount int64 `gorm:"column:request_count"`
SuccessCount int64 `gorm:"column:success_count"`
ClientErrorCount int64 `gorm:"column:client_error_count"`
ServerErrorCount int64 `gorm:"column:server_error_count"`
Status2xxCount int64 `gorm:"column:status_2xx_count"`
Status4xxCount int64 `gorm:"column:status_4xx_count"`
Status5xxCount int64 `gorm:"column:status_5xx_count"`
UniqueIPCount int64 `gorm:"column:unique_ip_count"`
UniqueHostCount int64 `gorm:"column:unique_host_count"`
BytesSent int64 `gorm:"column:bytes_sent"`
RequestLength int64 `gorm:"column:request_length"`
}
// NodeAccessLogWAFIPAggregate is a per-IP aggregate row for WAF automatic rules.
type NodeAccessLogWAFIPAggregate struct {
RemoteAddr string
RequestCount int64
Status404Count int64
ClientErrorCount int64
ServerErrorCount int64
IPHostCount int64
LastSeenEpoch int64
StatusCounts map[int]int64
}
// NodeAccessLogBucketDimension is a bucket dimension value.
type NodeAccessLogBucketDimension struct {
BucketEpoch int64 `gorm:"column:bucket_epoch"`
Value string `gorm:"column:value"`
}
// NodeAccessLogIPAggregate is an IP aggregate row.
type NodeAccessLogIPAggregate struct {
RemoteAddr string `gorm:"column:remote_addr"`
RequestCount int64 `gorm:"column:request_count"`
SuccessCount int64 `gorm:"column:success_count"`
ClientErrorCount int64 `gorm:"column:client_error_count"`
ServerErrorCount int64 `gorm:"column:server_error_count"`
LastSeenEpoch int64 `gorm:"column:last_seen_epoch"`
}
// NodeAccessLogIPSummary is an IP summary row.
type NodeAccessLogIPSummary struct {
RemoteAddr string `gorm:"column:remote_addr"`
Region string `gorm:"column:region"`
TotalRequests int64 `gorm:"column:total_requests"`
Success2xxCount int64 `gorm:"column:success_2xx_count"`
SuccessRatio float64 `gorm:"column:success_ratio"`
BytesReceived int64 `gorm:"column:request_length"`
BytesSent int64 `gorm:"column:bytes_sent"`
// RecentRequests is deprecated (always 0); kept for wire compatibility.
RecentRequests int64 `gorm:"column:recent_requests"`
LastSeenEpoch int64 `gorm:"column:last_seen_epoch"`
}
// NodeAccessLogIPTrend is an IP trend bucket row.
type NodeAccessLogIPTrend struct {
BucketEpoch int64 `gorm:"column:bucket_epoch"`
RequestCount int64 `gorm:"column:request_count"`
}
@@ -0,0 +1,171 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package analytics
import (
"fmt"
"time"
)
const (
nodeMetricSnapshotTableName = "of_node_metric_snapshots"
nodeMetricSnapshotInsertColumns = "id, node_id, captured_at, cpu_usage_percent, memory_used_bytes, memory_total_bytes, storage_used_bytes, storage_total_bytes, disk_read_bytes, disk_write_bytes, network_rx_bytes, network_tx_bytes, created_at"
nodeEdgeHealthTableName = "of_node_edge_health"
nodeEdgeHealthInsertColumns = "id, node_id, captured_at, status, connections, created_at"
nodeObsFrpsTableName = "of_node_obs_frps"
nodeObsFrpsInsertColumns = "id, node_id, captured_at, frps_connections, frps_proxy_count, frps_client_count, frps_proxies, created_at"
nodeObsFrpcTableName = "of_node_obs_frpc"
nodeObsFrpcInsertColumns = "id, node_id, captured_at, tunnel_status, connected_relays_count, created_at"
)
// NodeMetricSnapshot stores periodic node resource utilization metrics in ClickHouse.
type NodeMetricSnapshot struct {
ID uint64 `gorm:"column:id"`
NodeID string `gorm:"column:node_id"`
CapturedAt time.Time `gorm:"column:captured_at"`
CPUUsagePercent float64 `gorm:"column:cpu_usage_percent"`
MemoryUsedBytes int64 `gorm:"column:memory_used_bytes"`
MemoryTotalBytes int64 `gorm:"column:memory_total_bytes"`
StorageUsedBytes int64 `gorm:"column:storage_used_bytes"`
StorageTotalBytes int64 `gorm:"column:storage_total_bytes"`
DiskReadBytes int64 `gorm:"column:disk_read_bytes"`
DiskWriteBytes int64 `gorm:"column:disk_write_bytes"`
NetworkRxBytes int64 `gorm:"column:network_rx_bytes"`
NetworkTxBytes int64 `gorm:"column:network_tx_bytes"`
CreatedAt time.Time `gorm:"column:created_at"`
}
// TableName returns the ClickHouse table name.
func (NodeMetricSnapshot) TableName() string {
return nodeMetricSnapshotTableName
}
// InsertColumns returns comma-separated column names for batch insert.
func (NodeMetricSnapshot) InsertColumns() string {
return nodeMetricSnapshotInsertColumns
}
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
func (NodeMetricSnapshot) BatchInsertSQL() string {
return fmt.Sprintf("INSERT INTO %s (%s)", nodeMetricSnapshotTableName, nodeMetricSnapshotInsertColumns)
}
// NodeEdgeHealth stores L2 OpenResty health snapshots (connections + status).
type NodeEdgeHealth struct {
ID uint64 `gorm:"column:id"`
NodeID string `gorm:"column:node_id"`
CapturedAt time.Time `gorm:"column:captured_at"`
Status string `gorm:"column:status"`
Connections int64 `gorm:"column:connections"`
CreatedAt time.Time `gorm:"column:created_at"`
}
// TableName returns the ClickHouse table name.
func (NodeEdgeHealth) TableName() string {
return nodeEdgeHealthTableName
}
// InsertColumns returns comma-separated column names for batch insert.
func (NodeEdgeHealth) InsertColumns() string {
return nodeEdgeHealthInsertColumns
}
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
func (NodeEdgeHealth) BatchInsertSQL() string {
return fmt.Sprintf("INSERT INTO %s (%s)", nodeEdgeHealthTableName, nodeEdgeHealthInsertColumns)
}
// AccessLogHourly is a Server-side hourly rollup of access logs.
type AccessLogHourly struct {
NodeID string `gorm:"column:node_id"`
Hour time.Time `gorm:"column:hour"`
Host string `gorm:"column:host"`
RequestCount int64 `gorm:"column:request_count"`
ErrorCount int64 `gorm:"column:error_count"`
BytesSent int64 `gorm:"column:bytes_sent"`
RequestLength int64 `gorm:"column:request_length"`
}
// NodeTrafficHourly is an hourly traffic rollup row.
//
// UniqueVisitorCount is always 0 when sourced from of_access_log_hourly
// (true UV requires raw uniqExact on access logs).
type NodeTrafficHourly struct {
NodeID string
Hour time.Time
RequestCount int64
ErrorCount int64
UniqueVisitorCount int64
}
// NodeMetricHourly is an hourly metric snapshot aggregation row.
//
// Disk and host network counters are cumulative. Prefer pre-aggregated min/max
// deltas from of_node_metric_capacity_hourly; raw fallback uses consecutive
// lagInFrame samples per node (negative deltas after counter reset are dropped).
type NodeMetricHourly struct {
Hour time.Time
AverageCPUUsagePercent float64
AverageMemoryUsagePercent float64
NetworkRxBytes int64
NetworkTxBytes int64
DiskReadBytes int64
DiskWriteBytes int64
ReportedNodes int
}
// NodeObsFrps stores FRPS observability snapshots in ClickHouse.
type NodeObsFrps struct {
ID uint64 `gorm:"column:id"`
NodeID string `gorm:"column:node_id"`
CapturedAt time.Time `gorm:"column:captured_at"`
FrpsConnections int32 `gorm:"column:frps_connections"`
FrpsProxyCount int32 `gorm:"column:frps_proxy_count"`
FrpsClientCount int32 `gorm:"column:frps_client_count"`
FrpsProxies string `gorm:"column:frps_proxies"`
CreatedAt time.Time `gorm:"column:created_at"`
}
// TableName returns the ClickHouse table name.
func (NodeObsFrps) TableName() string {
return nodeObsFrpsTableName
}
// InsertColumns returns comma-separated column names for batch insert.
func (NodeObsFrps) InsertColumns() string {
return nodeObsFrpsInsertColumns
}
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
func (NodeObsFrps) BatchInsertSQL() string {
return fmt.Sprintf("INSERT INTO %s (%s)", nodeObsFrpsTableName, nodeObsFrpsInsertColumns)
}
// NodeObsFrpc stores FRPC observability snapshots in ClickHouse.
type NodeObsFrpc struct {
ID uint64 `gorm:"column:id"`
NodeID string `gorm:"column:node_id"`
CapturedAt time.Time `gorm:"column:captured_at"`
TunnelStatus string `gorm:"column:tunnel_status"`
ConnectedRelaysCount int32 `gorm:"column:connected_relays_count"`
CreatedAt time.Time `gorm:"column:created_at"`
}
// TableName returns the ClickHouse table name.
func (NodeObsFrpc) TableName() string {
return nodeObsFrpcTableName
}
// InsertColumns returns comma-separated column names for batch insert.
func (NodeObsFrpc) InsertColumns() string {
return nodeObsFrpcInsertColumns
}
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
func (NodeObsFrpc) BatchInsertSQL() string {
return fmt.Sprintf("INSERT INTO %s (%s)", nodeObsFrpcTableName, nodeObsFrpcInsertColumns)
}
@@ -0,0 +1,11 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package analytics
import (
risklogstore "Wavelet/plugins/domain/risk_control/logstore"
)
// UserAccessLog is Wavelet risk_control's w_user_access_logs entity.
type UserAccessLog = risklogstore.UserAccessLog
@@ -0,0 +1,124 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package analytics
import "strings"
// User-Agent 浏览器/OS/设备分类(纯函数,无 IO)。
// 与 internal/repository/analytics/browser.go 的判定逻辑保持一致(Task 4 复制,
// 因为 model 不得 import analyticsrepo);后续若移除旧 CH 实现,可让 analyticsrepo 改以别名复用本包。
const (
uaLabelUnknown = "Unknown"
uaLabelBot = "Bot"
uaLabelOther = "Other"
uaTokenBot = "bot"
uaTokenAndroid = "android"
uaTokenSpider = "spider"
uaTokenCrawler = "crawler"
)
type uaMatchRule struct {
label string
contains []string
allOf []string
noneOf []string
}
func matchUARules(uaLower string, rules []uaMatchRule, fallback string) string {
if uaLower == "" {
return uaLabelUnknown
}
for _, rule := range rules {
matched := false
for _, token := range rule.contains {
if strings.Contains(uaLower, token) {
matched = true
break
}
}
if !matched && len(rule.allOf) > 0 {
matched = true
for _, token := range rule.allOf {
if !strings.Contains(uaLower, token) {
matched = false
break
}
}
}
if !matched {
continue
}
excluded := false
for _, token := range rule.noneOf {
if strings.Contains(uaLower, token) {
excluded = true
break
}
}
if excluded {
continue
}
return rule.label
}
return fallback
}
var browserRules = []uaMatchRule{
{label: "WeChat", contains: []string{"micromessenger"}},
{label: "Postman", contains: []string{"postman"}},
{label: "CLI", contains: []string{"curl/", "wget/"}},
{label: "Edge", contains: []string{"edg/", "edgios/", "edga/"}},
{label: "Opera", contains: []string{"opr/", "opera"}},
{label: "Firefox", contains: []string{"firefox", "fxios"}},
{label: "Chrome", contains: []string{"crios", "chrome"}, noneOf: []string{"chromium"}},
{label: "Chromium", contains: []string{"chromium"}},
{label: "Safari", contains: []string{"safari"}},
{label: uaLabelBot, contains: []string{uaTokenBot, uaTokenSpider, uaTokenCrawler, "slurp"}},
}
var osRules = []uaMatchRule{
{label: "Android", contains: []string{uaTokenAndroid}},
{label: "iOS", contains: []string{"iphone", "ipad", "ipod", "ios"}},
{label: "Windows", contains: []string{"windows"}},
{label: "macOS", contains: []string{"mac os x", "macintosh", "macos"}},
{label: "Chrome OS", contains: []string{"cros"}},
{label: "Linux", contains: []string{"linux"}},
{label: uaLabelBot, contains: []string{uaTokenBot, uaTokenSpider, uaTokenCrawler}},
}
var deviceRules = []uaMatchRule{
{
label: uaLabelBot,
contains: []string{uaTokenBot, uaTokenSpider, uaTokenCrawler, "slurp", "curl/", "wget/", "python-requests", "go-http-client", "postman"},
},
{
label: "Tablet",
contains: []string{"ipad", "tablet"},
},
{
label: "Tablet",
allOf: []string{uaTokenAndroid},
noneOf: []string{"mobile"},
},
{
label: "Mobile",
contains: []string{"mobi", "iphone", "ipod", uaTokenAndroid},
},
}
// ParseBrowserName performs lightweight User-Agent browser identification.
func ParseBrowserName(ua string) string {
return matchUARules(strings.ToLower(ua), browserRules, uaLabelOther)
}
// ParseOSName performs lightweight User-Agent OS identification.
func ParseOSName(ua string) string {
return matchUARules(strings.ToLower(ua), osRules, uaLabelOther)
}
// ParseDeviceType performs lightweight User-Agent device type identification.
func ParseDeviceType(ua string) string {
return matchUARules(strings.ToLower(ua), deviceRules, "Desktop")
}
@@ -0,0 +1,17 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
// Domain validation messages used by model.Validate and other no-IO rules.
// Persistence / data-access messages belong in internal/repository (do not import repository).
const (
errTemplateKeyRequired = "模板标识符不能为空"
errTemplateNameRequired = "模板名称不能为空"
errTemplateContentRequired = "模板内容不能为空"
errAuthSourceNameRequired = "认证源名称不能为空"
errAuthSourceNameInvalid = "认证源名称只能包含字母、数字、短横线或下划线,且必须以字母或数字开头"
errAuthSourceTypeUnsupported = "认证源类型仅支持 oidc"
errAuthSourceDiscoveryURLRequired = "OIDC 认证源必须配置 Discovery URL"
errAuthSourceClientCredentialsRequired = "启用认证源前必须配置 Client ID 和 Client Secret" //nolint:gosec // false positive: this is an error message, not hardcoded credentials
)
@@ -0,0 +1,28 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
// OpenFlareAccessLogTrafficSummary is a window-level traffic summary from access logs.
type OpenFlareAccessLogTrafficSummary struct {
RequestCount int64
ErrorCount int64
UniqueIPCount int64
BytesSent int64
RequestLength int64
NodeCount int64
}
// OpenFlareAccessLogValueCount is a dimension value count.
type OpenFlareAccessLogValueCount struct {
Value string
Count int64
}
// OpenFlareAccessLogNodeAggregate is per-node traffic over a window.
type OpenFlareAccessLogNodeAggregate struct {
NodeID string
RequestCount int64
ErrorCount int64
UniqueIPCount int64
}
@@ -0,0 +1,23 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// AcmeAccount OpenFlare ACME 账号实体。
type AcmeAccount struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Email string `json:"email" gorm:"size:255"`
URL string `json:"url" gorm:"size:255"`
PrivateKey string `json:"-" gorm:"type:text;not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (AcmeAccount) TableName() string {
return "of_acme_accounts"
}
@@ -0,0 +1,45 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"strings"
"time"
)
// OpenFlareApplyLogQuery filters apply logs for list queries.
type OpenFlareApplyLogQuery struct {
NodeID string
PageNo int
PageSize int
}
// OpenFlareApplyLog stores node configuration apply results.
type OpenFlareApplyLog struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
Version string `json:"version" gorm:"size:32;not null"`
Result string `json:"result" gorm:"size:32;not null"`
Message string `json:"message" gorm:"type:text"`
Checksum string `json:"checksum" gorm:"size:64;not null;default:''"`
MainConfigChecksum string `json:"main_config_checksum" gorm:"size:64;not null;default:''"`
RouteConfigChecksum string `json:"route_config_checksum" gorm:"size:64;not null;default:''"`
SupportFileCount int `json:"support_file_count" gorm:"not null;default:0"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index"`
}
// TableName returns the GORM table name.
func (OpenFlareApplyLog) TableName() string {
return "of_apply_logs"
}
// IsRepeatSuccessApplyLog reports whether the payload repeats an already-recorded success entry.
func IsRepeatSuccessApplyLog(latest *OpenFlareApplyLog, version, checksum, result string) bool {
if latest == nil || result != "success" {
return false
}
return latest.Result == "success" &&
strings.TrimSpace(latest.Version) == strings.TrimSpace(version) &&
strings.TrimSpace(latest.Checksum) == strings.TrimSpace(checksum)
}
@@ -0,0 +1,77 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import "time"
const (
// CFConnectionSourceDNSAccount imports credentials from an existing DNS account.
CFConnectionSourceDNSAccount = "dns_account"
// CFConnectionSourceStandalone stores an independent API token.
CFConnectionSourceStandalone = "standalone"
// CFConnectionStatusReady indicates the credential passed verification.
CFConnectionStatusReady = "ready"
// CFConnectionStatusError indicates the latest verification failed.
CFConnectionStatusError = "error"
// CFMemberSyncPending indicates synchronization is queued or required.
CFMemberSyncPending = "pending"
// CFMemberSyncing indicates a worker is reconciling the record.
CFMemberSyncing = "syncing"
// CFMemberSyncOK indicates the remote record matches the desired state.
CFMemberSyncOK = "ok"
// CFMemberSyncError indicates the latest reconciliation failed.
CFMemberSyncError = "error"
)
// CFConnection stores the single Cloudflare API credential source.
type CFConnection struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Source string `json:"source" gorm:"size:32;not null;default:''"`
DNSAccountID *uint `json:"dns_account_id" gorm:"index:idx_of_cf_connections_dns_account_id"`
Authorization string `json:"-" gorm:"type:text;not null;default:''"`
Status string `json:"status" gorm:"size:16;not null;default:''"`
VerifiedAt *time.Time `json:"verified_at"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the Cloudflare connection table name.
func (CFConnection) TableName() string { return "of_cf_connections" }
// CFPointingGroup stores a reusable node target for DNS records.
type CFPointingGroup struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:128;not null"`
PrimaryNodeID uint `json:"primary_node_id" gorm:"not null;index:idx_of_cf_pointing_groups_primary_node_id"`
BackupNodeID *uint `json:"backup_node_id" gorm:"index:idx_of_cf_pointing_groups_backup_node_id"`
ActiveNodeID uint `json:"active_node_id" gorm:"not null;index:idx_of_cf_pointing_groups_active_node_id"`
DefaultProxied bool `json:"default_proxied" gorm:"not null;default:false"`
Enabled bool `json:"enabled" gorm:"not null;default:false"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the Cloudflare pointing group table name.
func (CFPointingGroup) TableName() string { return "of_cf_pointing_groups" }
// CFPointingMember stores one managed ZoneDomain A record.
type CFPointingMember struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
GroupID uint `json:"group_id" gorm:"not null;index:idx_of_cf_pointing_members_group_id"`
ZoneDomainID uint `json:"zone_domain_id" gorm:"not null;uniqueIndex:idx_of_cf_pointing_members_zone_domain_id"`
Proxied bool `json:"proxied" gorm:"not null;default:false"`
CFZoneID string `json:"cf_zone_id" gorm:"size:64;not null;default:''"`
CFRecordID string `json:"cf_record_id" gorm:"size:64;not null;default:''"`
DesiredIP string `json:"desired_ip" gorm:"size:64;not null;default:''"`
SyncStatus string `json:"sync_status" gorm:"size:16;not null;default:'pending'"`
LastError string `json:"last_error" gorm:"type:text;not null;default:''"`
SyncedAt *time.Time `json:"synced_at"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the Cloudflare pointing member table name.
func (CFPointingMember) TableName() string { return "of_cf_pointing_members" }
@@ -0,0 +1,57 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
"gorm.io/gorm"
)
// ConfigVersionSummary is the list view for config versions.
type ConfigVersionSummary struct {
ID string `json:"id" gorm:"-"`
Version string `json:"version" gorm:"primaryKey;column:version"`
Checksum string `json:"checksum"`
IsActive bool `json:"is_active"`
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
}
// AfterFind hook for ConfigVersionSummary.
func (cvs *ConfigVersionSummary) AfterFind(_ *gorm.DB) (err error) {
cvs.ID = cvs.Version
return
}
// ConfigVersion stores a published OpenResty configuration snapshot.
type ConfigVersion struct {
ID string `json:"id" gorm:"-"`
Version string `json:"version" gorm:"primaryKey;size:32;not null"`
SnapshotJSON string `json:"snapshot_json" gorm:"type:text;not null"`
MainConfig string `json:"main_config" gorm:"type:text;not null;default:''"`
RenderedConfig string `json:"rendered_config" gorm:"type:text;not null"`
SupportFilesJSON string `json:"support_files_json" gorm:"type:text;not null;default:'[]'"`
Checksum string `json:"checksum" gorm:"size:64;not null"`
IsActive bool `json:"is_active" gorm:"not null;default:false;index"`
CreatedBy string `json:"created_by" gorm:"size:64;not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// AfterFind hook for ConfigVersion.
func (cv *ConfigVersion) AfterFind(_ *gorm.DB) (err error) {
cv.ID = cv.Version
return
}
// AfterCreate hook for ConfigVersion.
func (cv *ConfigVersion) AfterCreate(_ *gorm.DB) (err error) {
cv.ID = cv.Version
return
}
// TableName returns the GORM table name.
func (*ConfigVersion) TableName() string {
return "of_config_versions"
}
@@ -0,0 +1,23 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// DNSAccount OpenFlare DNS 账号实体。
type DNSAccount struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:255;not null"`
Type string `json:"type" gorm:"size:64;not null"`
Authorization string `json:"-" gorm:"type:text;not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (DNSAccount) TableName() string {
return "of_dns_accounts"
}
@@ -0,0 +1,52 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// OpenFlareNode stores an edge, relay, or tunnel client node.
type OpenFlareNode struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"uniqueIndex;size:64;not null"`
Name string `json:"name" gorm:"size:128;not null"`
IP string `json:"ip" gorm:"size:64;not null;default:''"`
IPManualOverride bool `json:"ip_manual_override" gorm:"not null;default:false"`
GeoName string `json:"geo_name" gorm:"size:128;not null;default:''"`
GeoLatitude *float64 `json:"geo_latitude"`
GeoLongitude *float64 `json:"geo_longitude"`
GeoManualOverride bool `json:"geo_manual_override" gorm:"not null;default:false"`
AccessToken string `json:"-" gorm:"column:access_token;size:128;index"`
AutoUpdateEnabled bool `json:"auto_update_enabled" gorm:"not null;default:false"`
UpdateRequested bool `json:"update_requested" gorm:"not null;default:false"`
UpdateChannel string `json:"update_channel" gorm:"size:16;not null;default:'stable'"`
UpdateTag string `json:"update_tag" gorm:"size:64;not null;default:''"`
RestartOpenrestyRequested bool `json:"restart_openresty_requested" gorm:"not null;default:false"`
Version string `json:"version" gorm:"size:64;not null;default:''"`
ExtVersion string `json:"ext_version" gorm:"size:64;not null;default:''"`
OpenrestyStatus string `json:"openresty_status" gorm:"size:16;not null;default:'unknown'"`
OpenrestyMessage string `json:"openresty_message" gorm:"type:text"`
Status string `json:"status" gorm:"size:16;not null;default:'offline'"`
CurrentVersion string `json:"current_version" gorm:"size:32;not null;default:''"`
LastSeenAt *time.Time `json:"last_seen_at"`
LastError string `json:"last_error" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
NodeType string `json:"node_type" gorm:"size:32;not null;default:'edge_node'"`
RelayBindPort int `json:"relay_bind_port" gorm:"not null;default:0"`
RelayVhostHTTPPort int `json:"relay_vhost_http_port" gorm:"not null;default:0"`
RelayAuthToken string `json:"-" gorm:"size:128;not null;default:''"`
RelayAgentAccessAddr string `json:"relay_agent_access_addr" gorm:"size:255;not null;default:''"`
RelayClientAccessAddr string `json:"relay_client_access_addr" gorm:"size:255;not null;default:''"`
RelayClientProxyURL string `json:"relay_client_proxy_url" gorm:"size:512;not null;default:''"`
CapabilitiesJSON string `json:"capabilities_json" gorm:"type:text;not null;default:'[]'"`
RelayStatus string `json:"relay_status" gorm:"size:16;not null;default:'unknown'"`
RelayWebServerEnabled bool `json:"relay_web_server_enabled" gorm:"not null;default:false"`
}
// TableName returns the GORM table name.
func (OpenFlareNode) TableName() string {
return "of_nodes"
}
@@ -0,0 +1,316 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// OpenFlareMetricSnapshot stores a node capacity snapshot in ClickHouse (database: openflare, table: of_node_metric_snapshots).
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
type OpenFlareMetricSnapshot struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
CapturedAt time.Time `json:"captured_at" gorm:"index"`
CPUUsagePercent float64 `json:"cpu_usage_percent"`
MemoryUsedBytes int64 `json:"memory_used_bytes"`
MemoryTotalBytes int64 `json:"memory_total_bytes"`
StorageUsedBytes int64 `json:"storage_used_bytes"`
StorageTotalBytes int64 `json:"storage_total_bytes"`
DiskReadBytes int64 `json:"disk_read_bytes"`
DiskWriteBytes int64 `json:"disk_write_bytes"`
NetworkRxBytes int64 `json:"network_rx_bytes"`
NetworkTxBytes int64 `json:"network_tx_bytes"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareMetricSnapshot) TableName() string {
return "of_node_metric_snapshots"
}
// OpenFlareAccessLog stores a single access log row in ClickHouse (database: openflare, table: of_node_access_logs).
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
type OpenFlareAccessLog struct {
ID uint64 `json:"id,string" gorm:"column:id"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
LoggedAt time.Time `json:"logged_at" gorm:"index"`
RemoteAddr string `json:"remote_addr" gorm:"index;size:128"`
Region string `json:"region" gorm:"size:128"`
Host string `json:"host" gorm:"index;size:255"`
Path string `json:"path" gorm:"size:2048"`
UserAgent string `json:"user_agent" gorm:"column:user_agent;size:512"`
CacheStatus string `json:"cache_status" gorm:"column:cache_status;size:32"`
StatusCode int `json:"status_code" gorm:"index"`
BytesSent int64 `json:"bytes_sent" gorm:"column:bytes_sent;not null;default:0"`
RequestLength int64 `json:"request_length" gorm:"column:request_length;not null;default:0"`
RequestTimeMs int64 `json:"request_time_ms" gorm:"column:request_time_ms;not null;default:0"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareAccessLog) TableName() string {
return "of_node_access_logs"
}
// OpenFlareAccessLogRegionCount aggregates access log regions.
type OpenFlareAccessLogRegionCount struct {
Region string `json:"region"`
Count int64 `json:"count"`
}
// OpenFlareHealthEvent stores node health alert events.
type OpenFlareHealthEvent struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
EventType string `json:"event_type" gorm:"index;size:64;not null"`
Severity string `json:"severity" gorm:"size:16;not null"`
Status string `json:"status" gorm:"index;size:16;not null"`
Message string `json:"message" gorm:"type:text"`
FirstTriggeredAt time.Time `json:"first_triggered_at" gorm:"index"`
LastTriggeredAt time.Time `json:"last_triggered_at" gorm:"index"`
ReportedAt time.Time `json:"reported_at" gorm:"index"`
ResolvedAt *time.Time `json:"resolved_at" gorm:"index"`
MetadataJSON string `json:"metadata_json" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareHealthEvent) TableName() string {
return "of_node_health_events"
}
// OpenFlareNodeSystemProfile stores the latest node system profile.
type OpenFlareNodeSystemProfile struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"uniqueIndex;size:64;not null"`
Hostname string `json:"hostname" gorm:"size:255"`
OSName string `json:"os_name" gorm:"size:128"`
OSVersion string `json:"os_version" gorm:"size:128"`
KernelVersion string `json:"kernel_version" gorm:"size:128"`
Architecture string `json:"architecture" gorm:"size:64"`
CPUModel string `json:"cpu_model" gorm:"size:255"`
CPUCores int `json:"cpu_cores"`
TotalMemoryBytes int64 `json:"total_memory_bytes"`
TotalDiskBytes int64 `json:"total_disk_bytes"`
UptimeSeconds int64 `json:"uptime_seconds"`
ReportedAt time.Time `json:"reported_at" gorm:"index"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareNodeSystemProfile) TableName() string {
return "of_node_system_profiles"
}
// OpenFlareEdgeHealth is L2 OpenResty health (of_node_edge_health).
type OpenFlareEdgeHealth struct {
ID uint `json:"id"`
NodeID string `json:"node_id"`
CapturedAt time.Time `json:"captured_at"`
Status string `json:"status"`
Connections int64 `json:"connections"`
CreatedAt time.Time `json:"created_at"`
}
// TableName returns the ClickHouse table name.
func (OpenFlareEdgeHealth) TableName() string {
return "of_node_edge_health"
}
// OpenFlareNodeObservationFrpc stores tunnel client frpc observations in ClickHouse (database: openflare, table: of_node_obs_frpc).
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
type OpenFlareNodeObservationFrpc struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
CapturedAt time.Time `json:"captured_at" gorm:"index"`
TunnelStatus string `json:"tunnel_status" gorm:"size:16"`
ConnectedRelaysCount int `json:"connected_relays_count"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareNodeObservationFrpc) TableName() string {
return "of_node_obs_frpc"
}
// OpenFlareNodeObservationFrps stores tunnel relay frps observations in ClickHouse (database: openflare, table: of_node_obs_frps).
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
type OpenFlareNodeObservationFrps struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
CapturedAt time.Time `json:"captured_at" gorm:"index"`
FrpsConnections int `json:"frps_connections"`
FrpsProxyCount int `json:"frps_proxy_count"`
FrpsClientCount int `json:"frps_client_count"`
FrpsProxies string `json:"frps_proxies" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareNodeObservationFrps) TableName() string {
return "of_node_obs_frps"
}
// OpenFlareAccessLogQuery filters access log list queries.
type OpenFlareAccessLogQuery struct {
NodeID string
RemoteAddr string
Host string
// Hosts exact-matches any host (case-insensitive). Prefer over Host for multi-domain scopes.
Hosts []string
Path string
// StatusCode filters by exact HTTP status code when > 0.
StatusCode int
Since time.Time
Until time.Time
Page int
PageSize int
SortBy string
SortOrder string
}
// OpenFlareAccessLogBucketQuery filters folded access log queries (v1 stub).
type OpenFlareAccessLogBucketQuery struct {
NodeID string
RemoteAddr string
Host string
Hosts []string
Path string
Since time.Time
Until time.Time
Page int
PageSize int
SortBy string
SortOrder string
FoldMinutes int
}
// OpenFlareAccessLogBucketRow is a folded access log bucket row (v1 stub).
type OpenFlareAccessLogBucketRow struct {
BucketEpoch int64 `json:"bucket_epoch"`
RequestCount int64 `json:"request_count"`
UniqueIPCount int64 `json:"unique_ip_count"`
UniqueHostCount int64 `json:"unique_host_count"`
SuccessCount int64 `json:"success_count"`
ClientErrorCount int64 `json:"client_error_count"`
ServerErrorCount int64 `json:"server_error_count"`
Status2xxCount int64 `json:"status_2xx_count"`
Status4xxCount int64 `json:"status_4xx_count"`
Status5xxCount int64 `json:"status_5xx_count"`
BytesSent int64 `json:"bytes_sent"`
RequestLength int64 `json:"request_length"`
}
// OpenFlareAccessLogBucketIPQuery filters folded IP summary queries (v1 stub).
type OpenFlareAccessLogBucketIPQuery struct {
NodeID string
RemoteAddr string
Host string
Path string
BucketStartedAt time.Time
FoldMinutes int
Page int
PageSize int
SortBy string
SortOrder string
}
// OpenFlareAccessLogBucketIPRow is a folded IP row (v1 stub).
type OpenFlareAccessLogBucketIPRow struct {
RemoteAddr string `json:"remote_addr"`
RequestCount int64 `json:"request_count"`
SuccessCount int64 `json:"success_count"`
ClientErrorCount int64 `json:"client_error_count"`
ServerErrorCount int64 `json:"server_error_count"`
LastSeenEpoch int64 `json:"last_seen_epoch"`
}
// OpenFlareAccessLogIPSummaryQuery filters IP summary list queries (v1 stub).
type OpenFlareAccessLogIPSummaryQuery struct {
NodeID string
RemoteAddr string
Host string
Since time.Time
Until time.Time
Page int
PageSize int
SortBy string
SortOrder string
}
// OpenFlareAccessLogIPSummaryRow is an IP summary row (v1 stub).
type OpenFlareAccessLogIPSummaryRow struct {
RemoteAddr string `json:"remote_addr"`
Region string `json:"region"`
TotalRequests int64 `json:"total_requests"`
Success2xxCount int64 `json:"success_2xx_count"`
SuccessRatio float64 `json:"success_ratio"`
BytesReceived int64 `json:"bytes_received"`
BytesSent int64 `json:"bytes_sent"`
// RecentRequests is deprecated and always 0.
RecentRequests int64 `json:"recent_requests"`
LastSeenEpoch int64 `json:"last_seen_epoch"`
}
// OpenFlareAccessLogIPTrendQuery filters IP trend queries (v1 stub).
type OpenFlareAccessLogIPTrendQuery struct {
NodeID string
RemoteAddr string
Host string
Since time.Time
BucketMinutes int
}
// OpenFlareAccessLogIPTrendRow is an IP trend bucket row (v1 stub).
type OpenFlareAccessLogIPTrendRow struct {
BucketEpoch int64 `json:"bucket_epoch"`
RequestCount int64 `json:"request_count"`
}
// OpenFlareAccessLogWAFIPAggregate is a per-IP aggregate row for WAF automatic rules.
type OpenFlareAccessLogWAFIPAggregate struct {
RemoteAddr string
RequestCount int
Status404Count int
ClientErrorCount int
ServerErrorCount int
IPHostCount int
LastSeenEpoch int64
StatusCounts map[int]int
}
// OpenFlareTrafficHourly is an hourly traffic rollup row.
type OpenFlareTrafficHourly struct {
NodeID string `json:"node_id"`
Hour time.Time `json:"hour"`
RequestCount int64 `json:"request_count"`
ErrorCount int64 `json:"error_count"`
UniqueVisitorCount int64 `json:"unique_visitor_count"`
}
// OpenFlareAccessLogHourly is a per-node/host hourly access log rollup.
type OpenFlareAccessLogHourly struct {
NodeID string `json:"node_id"`
Hour time.Time `json:"hour"`
Host string `json:"host"`
RequestCount int64 `json:"request_count"`
ErrorCount int64 `json:"error_count"`
BytesSent int64 `json:"bytes_sent"`
RequestLength int64 `json:"request_length"`
}
// OpenFlareMetricHourly is an hourly metric snapshot aggregation row.
type OpenFlareMetricHourly struct {
Hour time.Time `json:"hour"`
AverageCPUUsagePercent float64 `json:"average_cpu_usage_percent"`
AverageMemoryUsagePercent float64 `json:"average_memory_usage_percent"`
NetworkRxBytes int64 `json:"network_rx_bytes"`
NetworkTxBytes int64 `json:"network_tx_bytes"`
DiskReadBytes int64 `json:"disk_read_bytes"`
DiskWriteBytes int64 `json:"disk_write_bytes"`
ReportedNodes int `json:"reported_nodes"`
}
@@ -0,0 +1,66 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import "time"
// OpenFlareOption is the key/value DTO used by the OpenFlare option API surface.
// Key 与 w_system_configs.key 一致(snake_case)。此类型仅作为
// /api/v1/d/option 接口的请求/响应载体,不再映射到独立的 of_options 表。
type OpenFlareOption struct {
Key string `json:"key"`
Value string `json:"value"`
}
// StartTime records process start time (seconds) for the public /status endpoint.
var StartTime = time.Now().Unix()
// DefaultOpenRestyMainConfigTemplate 是 OpenResty 主配置模板的内置默认值。
// 当 SystemConfig 中缺失 openresty_main_config_template 时作为兜底使用。
const DefaultOpenRestyMainConfigTemplate = `# This file is generated by OpenFlare. Do not edit manually.
user openflare;
worker_processes {{OpenRestyWorkerProcesses}};
worker_rlimit_nofile {{OpenRestyWorkerRlimitNofile}};
pid __OPENFLARE_PID_PATH__;
error_log {{OpenRestyErrorLogPath}} warn;
events {
worker_connections {{OpenRestyWorkerConnections}};
{{OpenRestyEventsUseDirective}}{{OpenRestyEventsMultiAcceptDirective}}}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
client_body_temp_path __OPENFLARE_NGINX_CACHE_DIR__/client_temp;
proxy_temp_path __OPENFLARE_NGINX_CACHE_DIR__/proxy_temp;
fastcgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/fastcgi_temp;
uwsgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/uwsgi_temp;
scgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/scgi_temp;
{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length,"user_agent":"$http_user_agent","cache_status":"$upstream_cache_status"}';
access_log {{OpenRestyAccessLogPath}} openflare_json;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout {{OpenRestyKeepaliveTimeout}};
keepalive_requests {{OpenRestyKeepaliveRequests}};
client_header_timeout {{OpenRestyClientHeaderTimeout}};
client_body_timeout {{OpenRestyClientBodyTimeout}};
client_max_body_size {{OpenRestyClientMaxBodySize}};
large_client_header_buffers {{OpenRestyLargeClientHeaderBuffers}};
send_timeout {{OpenRestySendTimeout}};
proxy_connect_timeout {{OpenRestyProxyConnectTimeout}};
proxy_send_timeout {{OpenRestyProxySendTimeout}};
proxy_read_timeout {{OpenRestyProxyReadTimeout}};
proxy_request_buffering {{OpenRestyProxyRequestBuffering}};
proxy_buffering {{OpenRestyProxyBuffering}};
proxy_buffers {{OpenRestyProxyBuffers}};
proxy_buffer_size {{OpenRestyProxyBufferSize}};
proxy_busy_buffers_size {{OpenRestyProxyBusyBuffersSize}};
gzip {{OpenRestyGzip}};
gzip_min_length {{OpenRestyGzipMinLength}};
gzip_comp_level {{OpenRestyGzipCompLevel}};
{{OpenRestyResolverDirective}}{{OpenRestyCacheBlock}} include {{OpenRestyRouteConfigInclude}};
}
`
@@ -0,0 +1,45 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// Origin OpenFlare 源站实体。
type Origin struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:255;not null"`
Address string `json:"address" gorm:"uniqueIndex;size:255;not null"`
Remark string `json:"remark" gorm:"size:255"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (Origin) TableName() string {
return "of_origins"
}
// OriginRouteCount 源站关联的代理规则数量。
type OriginRouteCount struct {
OriginID uint `json:"origin_id"`
RouteCount int64 `json:"route_count"`
}
// OriginProxyRoute 源站模块查询代理规则时使用的最小字段集。
type OriginProxyRoute struct {
ID uint `gorm:"column:id;primaryKey"`
OriginID *uint `gorm:"column:origin_id"`
Domain string `gorm:"column:domain"`
OriginURL string `gorm:"column:origin_url"`
Upstreams string `gorm:"column:upstreams"`
Enabled bool `gorm:"column:enabled"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName 表名。
func (OriginProxyRoute) TableName() string {
return tableOfProxyRoutes
}
@@ -0,0 +1,82 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// Pages deployment status constants.
const (
PagesDeploymentStatusUploaded = "uploaded"
PagesDeploymentStatusActive = "active"
)
// PagesProject OpenFlare Pages 静态托管项目。
type PagesProject struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:255;not null"`
Slug string `json:"slug" gorm:"uniqueIndex;size:128;not null"`
Description string `json:"description" gorm:"type:text;not null;default:''"`
Enabled bool `json:"enabled" gorm:"not null;default:true"`
SPAFallbackEnabled bool `json:"spa_fallback_enabled" gorm:"not null;default:false"`
SPAFallbackPath string `json:"spa_fallback_path" gorm:"size:512;not null;default:'/index.html'"`
APIProxyEnabled bool `json:"api_proxy_enabled" gorm:"not null;default:false"`
APIProxyPath string `json:"api_proxy_path" gorm:"size:255;not null;default:''"`
APIProxyPass string `json:"api_proxy_pass" gorm:"size:2048;not null;default:''"`
APIProxyRewrite string `json:"api_proxy_rewrite" gorm:"size:255;not null;default:''"`
ActiveDeploymentID *uint `json:"active_deployment_id" gorm:"index"`
RootDir string `json:"root_dir" gorm:"size:512;not null;default:''"`
EntryFile string `json:"entry_file" gorm:"size:512;not null;default:'index.html'"`
ContentConfigVersion int `json:"-" gorm:"not null;default:0"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (PagesProject) TableName() string {
return "of_pages_projects"
}
// PagesDeployment OpenFlare Pages 不可变部署记录。
type PagesDeployment struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
ProjectID uint `json:"project_id" gorm:"not null;index;uniqueIndex:idx_of_pages_deployments_project_number,priority:1;uniqueIndex:idx_of_pages_deployments_source_revision,priority:1,where:source_identity IS NOT NULL AND source_revision IS NOT NULL"`
DeploymentNumber int `json:"deployment_number" gorm:"not null;uniqueIndex:idx_of_pages_deployments_project_number,priority:2"`
Checksum string `json:"checksum" gorm:"size:64;not null;index"`
Status string `json:"status" gorm:"size:32;not null;default:'uploaded';index"`
UploadID uint64 `json:"upload_id,string" gorm:"not null;default:0;index"`
ArtifactPath string `json:"artifact_path,omitempty" gorm:"size:2048;not null;default:''"` // legacy only
FileCount int `json:"file_count" gorm:"not null;default:0"`
TotalSize int64 `json:"total_size" gorm:"not null;default:0"`
CreatedBy string `json:"created_by" gorm:"size:64;not null;default:''"`
SourceType string `json:"source_type" gorm:"size:32;not null;default:''"`
SourceIdentity *string `json:"-" gorm:"type:char(64);uniqueIndex:idx_of_pages_deployments_source_revision,priority:2,where:source_identity IS NOT NULL AND source_revision IS NOT NULL"`
SourceRevision *string `json:"-" gorm:"type:char(64);uniqueIndex:idx_of_pages_deployments_source_revision,priority:3,where:source_identity IS NOT NULL AND source_revision IS NOT NULL"`
SourceLabel string `json:"source_label" gorm:"size:255;not null;default:''"`
SourceMeta string `json:"-" gorm:"type:text;not null;default:''"`
TriggerType string `json:"trigger_type" gorm:"size:32;not null;default:''"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
ActivatedAt *time.Time `json:"activated_at"`
}
// TableName 表名。
func (PagesDeployment) TableName() string {
return "of_pages_deployments"
}
// PagesDeploymentFile OpenFlare Pages 部署文件清单。
type PagesDeploymentFile struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
DeploymentID uint `json:"deployment_id" gorm:"not null;index"`
Path string `json:"path" gorm:"size:2048;not null"`
Size int64 `json:"size" gorm:"not null;default:0"`
Checksum string `json:"checksum" gorm:"size:64;not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// TableName 表名。
func (PagesDeploymentFile) TableName() string {
return "of_pages_deployment_files"
}
@@ -0,0 +1,26 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
const (
// PagesOrphanUploadCandidateLimit bounds one delayed Pages upload cleanup pass.
PagesOrphanUploadCandidateLimit = 100
// PagesOrphanMarkerPredicatePostgres is the Postgres JSON marker match SQL fragment.
PagesOrphanMarkerPredicatePostgres = "w_uploads.metadata #>> '{extra,pages_ingest_marker}' = ?"
// PagesOrphanMarkerPredicateSQLite is the SQLite JSON marker match SQL fragment.
PagesOrphanMarkerPredicateSQLite = "CASE WHEN json_valid(w_uploads.metadata) THEN json_extract(w_uploads.metadata, '$.extra.pages_ingest_marker') ELSE NULL END = ?"
)
// PagesOrphanUploadCandidateQuery describes the fail-closed SQL candidate set
// for delayed Pages upload compensation.
type PagesOrphanUploadCandidateQuery struct {
SystemUserID uint64
UploadType string
Marker string
CreatedBefore time.Time
}
@@ -0,0 +1,74 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import "time"
// PagesProjectSource 保存 Pages 项目的持久部署源配置。
//
// 对外接口必须映射到 pages 包内的 source view,避免直接序列化 model。
type PagesProjectSource struct {
ID uint `json:"-" gorm:"primaryKey;autoIncrement"`
ProjectID uint `json:"-" gorm:"not null;uniqueIndex:idx_of_pages_project_sources_project_id"`
SourceType string `json:"-" gorm:"size:32;not null;default:''"`
RemoteURL string `json:"-" gorm:"type:text;not null;default:''"`
AllowInsecure bool `json:"-" gorm:"not null;default:false"`
GitHubRepository string `json:"-" gorm:"column:github_repository;size:255;not null;default:''"`
ReleaseSelector string `json:"-" gorm:"size:16;not null;default:''"`
ReleaseTag string `json:"-" gorm:"size:255;not null;default:''"`
AssetName string `json:"-" gorm:"size:255;not null;default:''"`
AutoUpdateEnabled bool `json:"-" gorm:"not null;default:false"`
CheckIntervalMinutes int `json:"-" gorm:"not null;default:0"`
ConfigVersion int `json:"-" gorm:"not null;default:0"`
SourceIdentity string `json:"-" gorm:"type:char(64);not null;default:''"`
CreatedAt time.Time `json:"-" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
}
// TableName 返回 Pages 项目部署源配置表名。
func (PagesProjectSource) TableName() string {
return "of_pages_project_sources"
}
// PagesProjectSourceRuntime 保存 Pages 项目部署源的可变运行态。
//
// Runtime 不冗余 project_id;调用方通过 SourceID 关联配置,并在最终提交时
// 同时校验 source config version 与 project content config version。
type PagesProjectSourceRuntime struct {
SourceID uint `json:"-" gorm:"primaryKey;autoIncrement:false"`
ETag string `json:"-" gorm:"column:etag;size:512;not null;default:''"`
LastSeenRevision string `json:"-" gorm:"type:char(64);not null;default:''"`
LastSeenDetail string `json:"-" gorm:"type:text;not null;default:''"`
LastAppliedRevision string `json:"-" gorm:"type:char(64);not null;default:''"`
LastAppliedDetail string `json:"-" gorm:"type:text;not null;default:''"`
SyncStatus string `json:"-" gorm:"size:32;not null;default:''"`
LastError string `json:"-" gorm:"type:text;not null;default:''"`
LastCheckedAt *time.Time `json:"-"`
LastSyncedAt *time.Time `json:"-"`
NextCheckAt *time.Time `json:"-" gorm:"index:idx_of_pages_project_source_runtime_next_check_at"`
LeaseExpiresAt *time.Time `json:"-"`
LeaseToken string `json:"-" gorm:"size:64;not null;default:''"`
UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
}
// TableName 返回 Pages 项目部署源运行态表名。
func (PagesProjectSourceRuntime) TableName() string {
return "of_pages_project_source_runtime"
}
// PagesExpiredSourceLeaseCandidate is a scanner query DTO for expired runtime leases.
type PagesExpiredSourceLeaseCandidate struct {
SourceID uint
LeaseToken string
LeaseExpiresAt time.Time
SyncStatus string
SourceType string
ReleaseSelector string
}
// PagesDueGitHubSourceCandidate is a scanner query DTO for due GitHub latest checks.
type PagesDueGitHubSourceCandidate struct {
SourceID uint
ConfigVersion int
}
@@ -0,0 +1,80 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"encoding/json"
"strings"
"testing"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
func TestPagesSourceModelsMatchMigrationSchema(t *testing.T) {
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
require.NoError(t, gormDB.AutoMigrate(
&PagesProject{},
&PagesDeployment{},
&PagesProjectSource{},
&PagesProjectSourceRuntime{},
))
assert.Equal(t, "of_pages_project_sources", (PagesProjectSource{}).TableName())
assert.Equal(t, "of_pages_project_source_runtime", (PagesProjectSourceRuntime{}).TableName())
assert.True(t, gormDB.Migrator().HasColumn(&PagesProjectSource{}, "github_repository"))
assert.False(t, gormDB.Migrator().HasColumn(&PagesProjectSource{}, "git_hub_repository"))
assert.True(t, gormDB.Migrator().HasColumn(&PagesProjectSourceRuntime{}, "etag"))
assert.False(t, gormDB.Migrator().HasColumn(&PagesProjectSourceRuntime{}, "e_tag"))
var indexSQL string
require.NoError(t, gormDB.Raw(
"SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?",
"idx_of_pages_deployments_source_revision",
).Scan(&indexSQL).Error)
assert.Contains(t, strings.ToUpper(indexSQL), "WHERE SOURCE_IDENTITY IS NOT NULL AND SOURCE_REVISION IS NOT NULL")
}
func TestPagesSourceModelsDoNotSerializeSecretsOrFencingState(t *testing.T) {
sourceJSON, err := json.Marshal(PagesProjectSource{
ID: 1,
ProjectID: 2,
RemoteURL: "https://example.com/site.zip?token=secret",
ConfigVersion: 3,
SourceIdentity: strings.Repeat("a", 64),
})
require.NoError(t, err)
assert.JSONEq(t, `{}`, string(sourceJSON))
runtimeJSON, err := json.Marshal(PagesProjectSourceRuntime{
SourceID: 1,
ETag: `"secret-etag"`,
LeaseToken: "secret-lease",
})
require.NoError(t, err)
assert.JSONEq(t, `{}`, string(runtimeJSON))
identity := strings.Repeat("b", 64)
revision := strings.Repeat("c", 64)
deploymentJSON, err := json.Marshal(PagesDeployment{
SourceType: "remote_url",
SourceIdentity: &identity,
SourceRevision: &revision,
SourceLabel: "site.zip",
SourceMeta: `{"provider":"remote_url","private":"secret"}`,
TriggerType: "manual_sync",
})
require.NoError(t, err)
assert.NotContains(t, string(deploymentJSON), identity)
assert.NotContains(t, string(deploymentJSON), revision)
assert.NotContains(t, string(deploymentJSON), "private")
assert.Contains(t, string(deploymentJSON), `"source_type":"remote_url"`)
assert.Contains(t, string(deploymentJSON), `"source_label":"site.zip"`)
assert.Contains(t, string(deploymentJSON), `"trigger_type":"manual_sync"`)
}
@@ -0,0 +1,48 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
const tableOfProxyRoutes = "of_proxy_routes"
// ProxyRoute OpenFlare 代理规则实体。
// 域名与证书仅通过 of_zone_domains 关联,不再持久化在本表。
type ProxyRoute struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
OriginID *uint `json:"origin_id" gorm:"index"`
OriginURL string `json:"origin_url" gorm:"size:2048;not null"`
OriginHost string `json:"origin_host" gorm:"size:255"`
Upstreams string `json:"upstreams" gorm:"type:text;not null;default:'[]'"`
Enabled bool `json:"enabled" gorm:"not null;default:true"`
EnableHTTPS bool `json:"enable_https" gorm:"column:enable_https;not null;default:false"`
RedirectHTTP bool `json:"redirect_http" gorm:"not null;default:false"`
LimitConnPerServer int `json:"limit_conn_per_server" gorm:"not null;default:0"`
LimitConnPerIP int `json:"limit_conn_per_ip" gorm:"not null;default:0"`
LimitRate string `json:"limit_rate" gorm:"size:32;not null;default:''"`
LimitReqPerIP string `json:"limit_req_per_ip" gorm:"size:32;not null;default:''"`
CacheEnabled bool `json:"cache_enabled" gorm:"not null;default:false"`
CachePolicy string `json:"cache_policy" gorm:"size:32;not null;default:''"`
CacheRules string `json:"cache_rules" gorm:"type:text;not null;default:'[]'"`
CustomHeaders string `json:"custom_headers" gorm:"type:text;not null;default:'[]'"`
BasicAuthEnabled bool `json:"basic_auth_enabled" gorm:"not null;default:false"`
BasicAuthUsername string `json:"basic_auth_username" gorm:"size:255;not null;default:''"`
BasicAuthPassword string `json:"basic_auth_password" gorm:"size:255;not null;default:''"`
UpstreamType string `json:"upstream_type" gorm:"size:32;not null;default:'direct'"`
TunnelNodeID *uint `json:"tunnel_node_id" gorm:"index"`
TunnelTargetAddr string `json:"tunnel_target_addr" gorm:"size:512"`
TunnelTargetProtocol string `json:"tunnel_target_protocol" gorm:"size:16"`
PagesProjectID *uint `json:"pages_project_id" gorm:"index"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
ZoneDomains []ZoneDomain `json:"zone_domains,omitempty" gorm:"foreignKey:ProxyRouteID"`
}
// TableName 表名。
func (ProxyRoute) TableName() string {
return tableOfProxyRoutes
}
@@ -0,0 +1,52 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
// TLSCertificate OpenFlare TLS 证书实体。
type TLSCertificate struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"uniqueIndex;size:255;not null"`
CertPEM string `json:"-" gorm:"type:text;not null"`
KeyPEM string `json:"-" gorm:"type:text;not null"`
NotBefore time.Time `json:"not_before"`
NotAfter time.Time `json:"not_after"`
Remark string `json:"remark" gorm:"size:255"`
Provider string `json:"provider" gorm:"size:64;default:upload"`
AcmeAccountID uint `json:"acme_account_id"`
DNSAccountID uint `json:"dns_account_id"`
KeyAlgorithm string `json:"key_algorithm" gorm:"size:32"`
AutoRenew bool `json:"auto_renew"`
PrimaryDomain string `json:"primary_domain" gorm:"size:255"`
OtherDomains string `json:"other_domains" gorm:"type:text"`
DisableCNAME bool `json:"disable_cname"`
SkipDNS bool `json:"skip_dns"`
DNS1 string `json:"dns1" gorm:"size:128"`
DNS2 string `json:"dns2" gorm:"size:128"`
ApplyStatus string `json:"apply_status" gorm:"size:64;default:ready"`
ApplyMessage string `json:"apply_message" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (TLSCertificate) TableName() string {
return "of_tls_certificates"
}
// TLSProxyRouteRef 删除证书时检查代理规则引用的最小字段集。
type TLSProxyRouteRef struct {
ID uint `gorm:"column:id;primaryKey"`
CertID *uint `gorm:"column:cert_id"`
CertIDs string `gorm:"column:cert_ids"`
DomainCertIDs string `gorm:"column:domain_cert_ids"`
}
// TableName 表名。
func (TLSProxyRouteRef) TableName() string {
return tableOfProxyRoutes
}
@@ -0,0 +1,69 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"errors"
"time"
)
// OpenFlareWAFRuleGroup stores a WAF rule group.
type OpenFlareWAFRuleGroup struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:255;not null"`
Enabled bool `json:"enabled" gorm:"not null;default:true"`
IsGlobal bool `json:"is_global" gorm:"not null;default:false;index"`
Graph string `json:"graph" gorm:"type:text;not null;default:''"`
Revision uint64 `json:"revision" gorm:"not null;default:1"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareWAFRuleGroup) TableName() string {
return "of_waf_rule_groups"
}
// OpenFlareWAFIPGroup stores a WAF IP group.
type OpenFlareWAFIPGroup struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:255;not null"`
Type string `json:"type" gorm:"size:32;not null;index"`
Enabled bool `json:"enabled" gorm:"not null;default:true"`
IPList string `json:"ip_list" gorm:"type:text;not null;default:'[]'"`
AutoConfig string `json:"auto_config" gorm:"type:text;not null;default:'{}'"`
ExtIPs string `json:"ext_ips" gorm:"type:text;not null;default:'[]'"`
SubscriptionURL string `json:"subscription_url" gorm:"size:2048;not null;default:''"`
SubscriptionFormat string `json:"subscription_format" gorm:"size:32;not null;default:'text'"`
SubscriptionMappingRule string `json:"subscription_mapping_rule" gorm:"size:255;not null;default:''"`
SyncIntervalMinutes int `json:"sync_interval_minutes" gorm:"not null;default:1440"`
LastSyncedAt *time.Time `json:"last_synced_at"`
NextSyncAt *time.Time `json:"next_sync_at" gorm:"index"`
LastSyncStatus string `json:"last_sync_status" gorm:"size:32;not null;default:''"`
LastSyncMessage string `json:"last_sync_message" gorm:"type:text;not null;default:''"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName returns the GORM table name.
func (OpenFlareWAFIPGroup) TableName() string {
return "of_waf_ip_groups"
}
// OpenFlareWAFRuleGroupBinding binds a rule group to a proxy route.
type OpenFlareWAFRuleGroupBinding struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
RuleGroupID uint `json:"rule_group_id" gorm:"not null;uniqueIndex:idx_of_waf_group_route"`
ProxyRouteID uint `json:"proxy_route_id" gorm:"not null;uniqueIndex:idx_of_waf_group_route;index"`
Sequence int `json:"sequence" gorm:"not null;default:0"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
// ErrWAFRuleRevisionConflict indicates that a rule graph was updated from a stale revision.
var ErrWAFRuleRevisionConflict = errors.New("waf rule revision conflict")
// TableName returns the GORM table name.
func (OpenFlareWAFRuleGroupBinding) TableName() string {
return "of_waf_rule_group_bindings"
}
@@ -0,0 +1,48 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"time"
)
const (
tableOfZones = "of_zones"
tableOfZoneDomains = "of_zone_domains"
)
// Zone OpenFlare 注册根域实体。
type Zone struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Domain string `json:"domain" gorm:"uniqueIndex:idx_of_zones_domain;size:255;not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (Zone) TableName() string {
return tableOfZones
}
// ZoneDomain OpenFlare Zone 下的明确域名实体。
type ZoneDomain struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
ZoneID uint `json:"zone_id" gorm:"not null;index:idx_of_zone_domains_zone_id"`
ProxyRouteID *uint `json:"proxy_route_id" gorm:"index:idx_of_zone_domains_proxy_route_id"`
Domain string `json:"domain" gorm:"uniqueIndex:idx_of_zone_domains_domain;size:255;not null"`
CertID *uint `json:"cert_id" gorm:"index:idx_of_zone_domains_cert_id"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名。
func (ZoneDomain) TableName() string {
return tableOfZoneDomains
}
// ZoneDomainCount is the per-zone explicit domain count for list queries.
type ZoneDomainCount struct {
ZoneID uint `json:"zone_id" gorm:"column:zone_id"`
Count int64 `json:"count" gorm:"column:count"`
}
@@ -0,0 +1,96 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
adminmodel "Wavelet/plugins/domain/admin/model"
authmodel "Wavelet/plugins/domain/auth"
uploadmodels "Wavelet/plugins/domain/upload/models"
usermodel "Wavelet/plugins/domain/user"
)
const (
tokenByteLength = 24
maskThreshold = 8
)
// User is the Wavelet w_users entity.
type User = usermodel.User
// AccessToken is the Wavelet w_access_tokens entity.
type AccessToken = usermodel.AccessToken
// AuthSource is the Wavelet w_auth_sources entity.
type AuthSource = authmodel.AuthSource
// ExternalAccount is the Wavelet w_external_accounts entity.
type ExternalAccount = authmodel.ExternalAccount
// TaskExecution is the Wavelet w_task_executions entity.
type TaskExecution = adminmodel.TaskExecution
// Template is the Wavelet w_templates entity.
type Template = adminmodel.Template
// Schedule is the Wavelet w_schedules entity.
type Schedule = adminmodel.Schedule
// Upload is the Wavelet w_uploads entity.
type Upload = uploadmodels.Upload
// UploadMetadata is the Wavelet upload metadata JSON.
type UploadMetadata = uploadmodels.UploadMetadata
// UploadStatus is the Wavelet upload status.
type UploadStatus = uploadmodels.UploadStatus
// UploadStat is the Wavelet w_upload_stats entity.
type UploadStat = uploadmodels.UploadStat
const (
// UploadStatusPending is a newly stored unused upload.
UploadStatusPending = uploadmodels.UploadStatusPending
// UploadStatusUsed is an in-use upload.
UploadStatusUsed = uploadmodels.UploadStatusUsed
// UploadStatusDeleted is a soft-deleted upload.
UploadStatusDeleted = uploadmodels.UploadStatusDeleted
// UploadStatDimensionTotal is the total stats dimension.
UploadStatDimensionTotal = uploadmodels.UploadStatDimensionTotal
// UploadStatDimensionType is the type stats dimension.
UploadStatDimensionType = uploadmodels.UploadStatDimensionType
// UploadStatDimensionCategory is the category stats dimension.
UploadStatDimensionCategory = uploadmodels.UploadStatDimensionCategory
// UploadStatDimensionTrend is the trend stats dimension.
UploadStatDimensionTrend = uploadmodels.UploadStatDimensionTrend
)
// GenerateTokenString 生成加密安全的随机 Token 值
func GenerateTokenString() (string, error) {
bytes := make([]byte, tokenByteLength)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return "at_" + hex.EncodeToString(bytes), nil
}
// HashToken 计算 Token 的 SHA-256 哈希值用于数据库存储与查询
func HashToken(token string) string {
h := sha256.New()
h.Write([]byte(token))
return hex.EncodeToString(h.Sum(nil))
}
// MaskTokenString 生成脱敏显示的 Token,仅保留前缀和最后四位
func MaskTokenString(token string) string {
if len(token) <= maskThreshold {
return "at_****"
}
return fmt.Sprintf("%s...%s", token[:7], token[len(token)-4:])
}
@@ -0,0 +1,145 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package model
import (
adminmodel "Wavelet/plugins/domain/admin/model"
)
// 配置键常量 - 所有系统配置的 key 定义
const (
ConfigKeyUploadAllowedExtensions = "upload_allowed_extensions" // 允许上传的文件扩展名,逗号分隔
ConfigKeySiteName = "site_name" // 站点名称
ConfigKeyPasswordLoginEnabled = "password_login_enabled" // 是否允许密码登录
ConfigKeyRegistrationEnabled = "registration_enabled" // 是否允许注册
ConfigKeyPasswordRegisterEnabled = "password_register_enabled" // 是否允许密码注册
ConfigKeyOIDCLoginEnabled = "oidc_login_enabled" // 是否允许 OIDC 登录
ConfigKeyMaxAPIKeysPerUser = "max_api_keys_per_user" //nolint:gosec // false positive: config key name. 每个用户最大 API Key 数量
ConfigKeyCapLoginEnabled = "cap_login_enabled" // 是否启用登录人机验证
ConfigKeyCapAutoSolve = "cap_auto_solve" // 打开页面后是否自动开始计算(false 则需用户手动点击)
ConfigKeyCapChallengeCount = "cap_challenge_count" // 客户端需求解的 PoW 难题总数,默认 1,推荐 1~5
ConfigKeyCapChallengeSize = "cap_challenge_size" // 人机验证盐值长度
ConfigKeyCapChallengeDifficulty = "cap_challenge_difficulty" // 人机验证 PoW 难度(目标前缀长度)
ConfigKeyCapChallengeTTL = "cap_challenge_ttl_seconds" // 人机验证难题有效时间(秒)
ConfigKeyCapTokenTTL = "cap_token_ttl_seconds" //nolint:gosec // false positive: config key name. 人机验证兑换凭证有效时间(秒)
ConfigKeyServerAddress = "server_address" // 服务器地址
ConfigKeySMTPHost = "smtp_host" // SMTP 服务器地址
ConfigKeySMTPPort = "smtp_port" // SMTP 端口
ConfigKeySMTPUsername = "smtp_username" // SMTP 账户
ConfigKeySMTPPassword = "smtp_password" // SMTP 访问凭证
ConfigKeyEmailLoginVerificationEnabled = "email_login_verification_enabled" // 是否启用邮箱登录验证
ConfigKeyEmailRegisterVerificationEnabled = "email_register_verification_enabled" // 是否启用邮箱注册验证
ConfigKeyMenuDisplayConfig = "menu_display_config" // 目录显示配置 (JSON 字符串)
ConfigKeySearchEngineIndexingEnabled = "search_engine_indexing_enabled" // 是否允许搜索引擎检索
ConfigKeyFileAccessWhitelist = "file_access_whitelist" // 免登录访问的文件业务类型白名单 (JSON 数组格式)
ConfigKeyDiskCacheMaxSizeMB = "disk_cache_max_size_mb" // 磁盘缓存最大空间大小 (MB)
ConfigKeyDiskCacheTTLMinutes = "disk_cache_ttl_minutes" // 磁盘缓存默认有效期 (分钟)
ConfigKeyDiskCacheLRUEnabled = "disk_cache_lru_enabled" // 是否启用 LRU 淘汰机制
ConfigKeyLoginSessionTTLHours = "login_session_ttl_hours" // 登录会话过期时间 (小时,0表示浏览器关闭后自动退出登录,-1表示永不过期)
ConfigKeyUpdateUpstreamRepository = "update_upstream_repository" // GitHub Actions Release 上游仓库
ConfigKeyStorageConfig = "storage_config" // 文件存储配置 (JSON)
ConfigKeyRelayFRPSWebUIEnabled = "relay_frps_web_ui_enabled" // 是否启用 FRPS 内置 Web 界面
ConfigKeyRelayFRPSWebUIPort = "relay_frps_web_ui_port" // FRPS 内置 Web 界面端口
// OpenFlare 业务配置(从 of_options 迁移)
ConfigKeyAgentDiscoveryToken = "agent_discovery_token" //nolint:gosec // false positive: config key name. Agent 发现令牌
ConfigKeyAgentHeartbeatInterval = "agent_heartbeat_interval" // Agent 心跳间隔(毫秒)
ConfigKeyAgentWebsocketUpgradeEnabled = "agent_websocket_upgrade_enabled" // Agent WebSocket 升级开关
ConfigKeyNodeOfflineThreshold = "node_offline_threshold" // 节点离线阈值(毫秒)
ConfigKeyAgentUpdateRepo = "agent_update_repo" // Agent 更新仓库
ConfigKeyGeoIPProvider = "geoip_provider" // GeoIP 服务商
// Pages 静态托管配置
ConfigKeyPagesMaxPackageSizeMB = "pages_max_package_size_mb" // Pages 部署包上传大小上限(MiB)
ConfigKeyPagesMaxHistoryCount = "pages_max_history_count" // Pages 每个项目最大历史部署保留数(0 表示不限制)
// UptimeKuma 集成配置
ConfigKeyUptimeKumaEnabled = "uptime_kuma_enabled" // UptimeKuma 集成开关
ConfigKeyUptimeKumaURL = "uptime_kuma_url" // UptimeKuma URL
ConfigKeyUptimeKumaUsername = "uptime_kuma_username" // UptimeKuma 用户名
ConfigKeyUptimeKumaPassword = "uptime_kuma_password" //nolint:gosec // false positive: config key name. UptimeKuma 密码
ConfigKeyUptimeKumaMonitorScope = "uptime_kuma_monitor_scope" // UptimeKuma 监控范围
ConfigKeyUptimeKumaSelectedSites = "uptime_kuma_selected_sites" // UptimeKuma 选定站点
ConfigKeyUptimeKumaSyncInterval = "uptime_kuma_sync_interval" // UptimeKuma 同步间隔(分钟)
ConfigKeyUptimeKumaInterval = "uptime_kuma_interval" // UptimeKuma 监控间隔(秒)
ConfigKeyUptimeKumaRetry = "uptime_kuma_retry" // UptimeKuma 重试次数
ConfigKeyUptimeKumaRetryInterval = "uptime_kuma_retry_interval" // UptimeKuma 重试间隔(秒)
ConfigKeyUptimeKumaTimeout = "uptime_kuma_timeout" // UptimeKuma 超时(秒)
// OpenResty 配置
ConfigKeyOpenRestyDefaultServerReturnStatus = "openresty_default_server_return_status" // 默认服务器返回状态码
ConfigKeyOpenRestyWorkerProcesses = "openresty_worker_processes" // Worker 进程数
ConfigKeyOpenRestyWorkerConnections = "openresty_worker_connections" // Worker 连接数
ConfigKeyOpenRestyWorkerRlimitNofile = "openresty_worker_rlimit_nofile" // Worker 文件描述符限制
ConfigKeyOpenRestyEventsUse = "openresty_events_use" // 事件模型
ConfigKeyOpenRestyEventsMultiAcceptEnabled = "openresty_events_multi_accept_enabled" // 多路接受开关
ConfigKeyOpenRestyKeepaliveTimeout = "openresty_keepalive_timeout" // Keepalive 超时(秒)
ConfigKeyOpenRestyKeepaliveRequests = "openresty_keepalive_requests" // Keepalive 请求数
ConfigKeyOpenRestyClientHeaderTimeout = "openresty_client_header_timeout" // 客户端头超时(秒)
ConfigKeyOpenRestyClientBodyTimeout = "openresty_client_body_timeout" // 客户端体超时(秒)
ConfigKeyOpenRestyClientMaxBodySize = "openresty_client_max_body_size" // 客户端最大体大小
ConfigKeyOpenRestyLargeClientHeaderBuffers = "openresty_large_client_header_buffers" // 大客户端头缓冲区
ConfigKeyOpenRestySendTimeout = "openresty_send_timeout" // 发送超时(秒)
ConfigKeyOpenRestyResolvers = "openresty_resolvers" // DNS 解析器
ConfigKeyOpenRestyProxyConnectTimeout = "openresty_proxy_connect_timeout" // 代理连接超时(秒)
ConfigKeyOpenRestyProxySendTimeout = "openresty_proxy_send_timeout" // 代理发送超时(秒)
ConfigKeyOpenRestyProxyReadTimeout = "openresty_proxy_read_timeout" // 代理读取超时(秒)
ConfigKeyOpenRestyWebsocketEnabled = "openresty_websocket_enabled" // WebSocket 支持开关
ConfigKeyOpenRestyHTTP3Enabled = "openresty_http3_enabled" // HTTP/3 支持开关
ConfigKeyOpenRestyProxyRequestBufferingEnabled = "openresty_proxy_request_buffering_enabled" // 代理请求缓冲开关
ConfigKeyOpenRestyProxyBufferingEnabled = "openresty_proxy_buffering_enabled" // 代理响应缓冲开关
ConfigKeyOpenRestyProxyBuffers = "openresty_proxy_buffers" // 代理缓冲区
ConfigKeyOpenRestyProxyBufferSize = "openresty_proxy_buffer_size" // 代理缓冲区大小
ConfigKeyOpenRestyProxyBusyBuffersSize = "openresty_proxy_busy_buffers_size" // 代理繁忙缓冲区大小
ConfigKeyOpenRestyGzipEnabled = "openresty_gzip_enabled" // Gzip 压缩开关
ConfigKeyOpenRestyGzipMinLength = "openresty_gzip_min_length" // Gzip 最小长度
ConfigKeyOpenRestyGzipCompLevel = "openresty_gzip_comp_level" // Gzip 压缩级别
ConfigKeyOpenRestyCacheEnabled = "openresty_cache_enabled" // 缓存开关
ConfigKeyOpenRestyCachePath = "openresty_cache_path" // 缓存路径
ConfigKeyOpenRestyCacheLevels = "openresty_cache_levels" // 缓存层级
ConfigKeyOpenRestyCacheInactive = "openresty_cache_inactive" // 缓存不活跃时间
ConfigKeyOpenRestyCacheMaxSize = "openresty_cache_max_size" // 缓存最大大小
ConfigKeyOpenRestyCacheKeyTemplate = "openresty_cache_key_template" // 缓存键模板
ConfigKeyOpenRestyCacheLockEnabled = "openresty_cache_lock_enabled" // 缓存锁开关
ConfigKeyOpenRestyCacheLockTimeout = "openresty_cache_lock_timeout" // 缓存锁超时
ConfigKeyOpenRestyCacheUseStale = "openresty_cache_use_stale" // 缓存失效策略
ConfigKeyOpenRestyMainConfigTemplate = "openresty_main_config_template" // 主配置模板
ConfigKeyOpenRestyDefaultLimitConnPerServer = "openresty_default_limit_conn_per_server" // 默认站点并发连接
ConfigKeyOpenRestyDefaultLimitConnPerIP = "openresty_default_limit_conn_per_ip" // 默认单 IP 并发连接
ConfigKeyOpenRestyDefaultLimitRate = "openresty_default_limit_rate" // 默认单请求带宽
ConfigKeyOpenRestyDefaultLimitReqPerIP = "openresty_default_limit_req_per_ip" // 默认单 IP 请求频率限制
// 源站错误页
ConfigKeyOriginErrorPageEnabled = "origin_error_page_enabled" // 是否启用源站错误页
ConfigKeyOriginErrorPageStatusCodes = "origin_error_page_status_codes" // 源站错误页触发状态码标签 JSON 数组
ConfigKeyOriginErrorPageHTML = "origin_error_page_html" // 源站错误页自定义 HTML(空则内置默认)
ConfigKeyOriginErrorPageGetOnly = "origin_error_page_get_only" // 是否仅对 GET 请求返回自定义错误页
// Service Worker 离线兜底
ConfigKeySWOfflineEnabled = "sw_offline_enabled" // 是否启用 Service Worker 离线兜底
ConfigKeySWOfflineHTML = "sw_offline_html" // 离线联系页自定义 HTML(空则内置默认)
ConfigKeySWOfflineDomains = "sw_offline_domains" // 离线兜底生效域名列表(JSON 数组,空则仅总开关无效)
)
// 日志数据库解耦
const (
ConfigKeyLogDatabase = "log_database" // 当前日志主库:postgres|sqlite|clickhouse(仅迁移任务写入)
ConfigKeyLogDBMigration = "log_db_migration" // 迁移冻结标记:"migrating" 或空
ConfigKeyLogRetentionDaysPostgres = "log_retention_days_postgres" // PostgreSQL 日志保留天数
ConfigKeyLogRetentionDaysSQLite = "log_retention_days_sqlite" // SQLite 日志保留天数
ConfigKeyLogRetentionDaysClickHouse = "log_retention_days_clickhouse" // ClickHouse 日志保留天数
// ConfigKeyMetricRetentionDays 性能指标(CPU/内存/磁盘/网络)保留天数,三库共用;
// 性能数据价值衰减快,默认短留存(3 天),不随访问日志保留配置。
ConfigKeyMetricRetentionDays = "metric_retention_days"
)
const (
// ConfigVisibilityHidden 表示配置不通过公共配置接口暴露
ConfigVisibilityHidden = adminmodel.ConfigVisibilityHidden
// ConfigVisibilityVisible 表示配置通过公共配置接口暴露
ConfigVisibilityVisible = adminmodel.ConfigVisibilityVisible
)
// SystemConfig is the Wavelet w_system_configs entity.
type SystemConfig = adminmodel.SystemConfig