mirror of
https://codeberg.org/VnPower/PixivFE
synced 2024-12-06 19:16:23 +01:00
282 lines
10 KiB
Go
282 lines
10 KiB
Go
// Package config provides global server-wide settings.
|
|
package config
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
// TODO: figure out how to properly implement urlx
|
|
// the implementation in 5f8b659b49 causes config.go to segfault due to a nil pointer dereference when the PIXIVFE_IMAGEPROXY env var is not set
|
|
// "github.com/goware/urlx"
|
|
"github.com/sethvargo/go-envconfig"
|
|
|
|
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
|
"codeberg.org/vnpower/pixivfe/v2/server/token_manager"
|
|
"codeberg.org/vnpower/pixivfe/v2/server/utils"
|
|
)
|
|
|
|
var GlobalConfig ServerConfig
|
|
|
|
// revision stores the current version's revision information
|
|
var revision string = ""
|
|
|
|
const (
|
|
unknownRevision = "unknown"
|
|
revisionFormat = "date-hash[+dirty]"
|
|
|
|
version = "v2.10"
|
|
|
|
defaultPort = "8282"
|
|
|
|
defaultTimeZone = "UTC"
|
|
defaultRepoUrl = "https://codeberg.org/PixivFE/PixivFE"
|
|
defaultAcceptLanguage = "en-US,en;q=0.5"
|
|
defaultProxyServerStaging = BuiltinProxyUrl
|
|
defaultProxyCheckEnabled = true
|
|
defaultProxyCheckInterval = 8 * time.Hour
|
|
defaultProxyCheckTimeout = 4 * time.Second
|
|
defaultTokenLoadBalancing = "round-robin"
|
|
defaultTokenMaxRetries = 5
|
|
defaultTokenBaseTimeout = 1000 * time.Millisecond
|
|
defaultTokenMaxBackoffTime = 32000 * time.Millisecond
|
|
defaultCacheEnabled = false
|
|
defaultCacheSize = 100
|
|
defaultCacheTTL = 60 * time.Minute
|
|
defaultCacheShortTTL = 10 * time.Second
|
|
defaultCacheControlMaxAge = 30 * time.Second
|
|
defaultCacheControlStaleWhileRevalidate = 60 * time.Second
|
|
defaultPopularSearchEnabled = false
|
|
defaultResponseSaveLocation = "/tmp/pixivfe/responses"
|
|
defaultLogLevel = "info"
|
|
defaultLogFormat = "console"
|
|
)
|
|
|
|
var defaultLogOutputs = []string{"stdout"}
|
|
|
|
type ServerConfig struct {
|
|
Version string
|
|
Revision string
|
|
RevisionDate string
|
|
RevisionHash string
|
|
IsDirty bool
|
|
StartingTime string // used in /about page
|
|
|
|
Host string `env:"PIXIVFE_HOST"`
|
|
|
|
// One of the two is required
|
|
Port string `env:"PIXIVFE_PORT"`
|
|
UnixSocket string `env:"PIXIVFE_UNIXSOCKET"`
|
|
|
|
RepoURL string `env:"PIXIVFE_REPO_URL,overwrite"` // used in /about page
|
|
|
|
Token []string `env:"PIXIVFE_TOKEN,required"` // may be multiple tokens. delimiter is ','
|
|
TokenManager *token_manager.TokenManager
|
|
TokenLoadBalancing string `env:"PIXIVFE_TOKEN_LOAD_BALANCING,overwrite"`
|
|
TokenMaxRetries int `env:"PIXIVFE_TOKEN_MAX_RETRIES,overwrite"`
|
|
TokenBaseTimeout time.Duration `env:"PIXIVFE_TOKEN_BASE_TIMEOUT,overwrite"`
|
|
TokenMaxBackoffTime time.Duration `env:"PIXIVFE_TOKEN_MAX_BACKOFF_TIME,overwrite"`
|
|
|
|
AcceptLanguage string `env:"PIXIVFE_ACCEPTLANGUAGE,overwrite"`
|
|
RequestLimit uint64 `env:"PIXIVFE_REQUESTLIMIT"` // if 0, request limit is disabled
|
|
|
|
ProxyServer_staging string `env:"PIXIVFE_IMAGEPROXY,overwrite"`
|
|
ProxyServer url.URL // proxy server URL, may or may not contain authority part of the URL
|
|
|
|
ProxyCheckEnabled bool `env:"PIXIVFE_PROXY_CHECK_ENABLED,overwrite"`
|
|
ProxyCheckInterval time.Duration `env:"PIXIVFE_PROXY_CHECK_INTERVAL,overwrite"`
|
|
ProxyCheckTimeout time.Duration `env:"PIXIVFE_PROXY_CHECK_TIMEOUT,overwrite"`
|
|
|
|
// API response caching configuration
|
|
CacheEnabled bool `env:"PIXIVFE_CACHE_ENABLED,overwrite"`
|
|
CacheSize int `env:"PIXIVFE_CACHE_SIZE,overwrite"`
|
|
CacheTTL time.Duration `env:"PIXIVFE_CACHE_TTL,overwrite"`
|
|
CacheShortTTL time.Duration `env:"PIXIVFE_CACHE_SHORT_TTL,overwrite"`
|
|
|
|
// Cache-Control header configuration
|
|
// TODO: fine-tune the default durations to actual usage patterns
|
|
CacheControlMaxAge time.Duration `env:"PIXIVFE_CACHE_CONTROL_MAX_AGE,overwrite"`
|
|
CacheControlStaleWhileRevalidate time.Duration `env:"PIXIVFE_CACHE_CONTROL_STALE_WHILE_REVALIDATE,overwrite"`
|
|
|
|
// Feature configuration
|
|
PopularSearchEnabled bool `env:"PIXIVFE_POPULAR_SEARCH_ENABLED,overwrite"`
|
|
|
|
// Development options
|
|
InDevelopment bool `env:"PIXIVFE_DEV"`
|
|
ResponseSaveLocation string `env:"PIXIVFE_RESPONSE_SAVE_LOCATION,overwrite"`
|
|
|
|
// Logging configuration
|
|
LogLevel string `env:"PIXIVFE_LOG_LEVEL,overwrite"`
|
|
LogOutputs []string `env:"PIXIVFE_LOG_OUTPUTS,overwrite"`
|
|
LogFormat string `env:"PIXIVFE_LOG_FORMAT,overwrite"`
|
|
}
|
|
|
|
// parseRevision extracts RevisionDate, RevisionHash, and IsDirty status from the Revision string
|
|
func parseRevision(revision string) (date, hash string, isDirty bool) {
|
|
// Check if Revision is empty
|
|
if revision == "" {
|
|
return unknownRevision, unknownRevision, false
|
|
}
|
|
|
|
// Check if Revision is marked as dirty (has uncommitted changes)
|
|
isDirty = strings.HasSuffix(revision, "+dirty")
|
|
if isDirty {
|
|
revision = strings.TrimSuffix(revision, "+dirty")
|
|
}
|
|
|
|
// Split Revision into two parts, RevisionDate and RevisionHash
|
|
parts := strings.Split(revision, "-")
|
|
if len(parts) == 2 {
|
|
return parts[0], parts[1], isDirty
|
|
}
|
|
|
|
// Return unknown date, full string as hash if format doesn't match date-hash
|
|
return unknownRevision, revision, isDirty
|
|
}
|
|
|
|
func (s *ServerConfig) LoadConfig() error {
|
|
s.Version = version
|
|
s.Revision = revision
|
|
|
|
s.RevisionDate, s.RevisionHash, s.IsDirty = parseRevision(revision)
|
|
|
|
if revision == "" {
|
|
log.Printf("[WARNING] REVISION is not set. Continuing with unknown revision information.\n")
|
|
} else if s.RevisionDate == unknownRevision {
|
|
log.Printf("[WARNING] REVISION format is invalid: %s. Expected format '%s'. Continuing with full revision as hash.\n", revision, revisionFormat)
|
|
}
|
|
|
|
log.Printf("PixivFE %s, revision %s\n", s.Version, s.Revision)
|
|
|
|
s.StartingTime = time.Now().UTC().Format("2006-01-02 15:04")
|
|
|
|
// set default values with env:"...,overwrite"
|
|
s.RepoURL = defaultRepoUrl
|
|
|
|
s.AcceptLanguage = defaultAcceptLanguage
|
|
|
|
s.ProxyServer_staging = defaultProxyServerStaging
|
|
|
|
s.ProxyCheckEnabled = defaultProxyCheckEnabled
|
|
s.ProxyCheckInterval = defaultProxyCheckInterval
|
|
s.ProxyCheckTimeout = defaultProxyCheckTimeout
|
|
|
|
s.TokenLoadBalancing = defaultTokenLoadBalancing
|
|
s.TokenMaxRetries = defaultTokenMaxRetries
|
|
s.TokenBaseTimeout = defaultTokenBaseTimeout
|
|
s.TokenMaxBackoffTime = defaultTokenMaxBackoffTime
|
|
|
|
s.CacheEnabled = defaultCacheEnabled
|
|
s.CacheSize = defaultCacheSize
|
|
s.CacheTTL = defaultCacheTTL
|
|
s.CacheShortTTL = defaultCacheShortTTL
|
|
|
|
s.CacheControlMaxAge = defaultCacheControlMaxAge
|
|
s.CacheControlStaleWhileRevalidate = defaultCacheControlStaleWhileRevalidate
|
|
|
|
s.PopularSearchEnabled = defaultPopularSearchEnabled
|
|
|
|
s.ResponseSaveLocation = defaultResponseSaveLocation
|
|
|
|
s.LogLevel = defaultLogLevel
|
|
s.LogOutputs = defaultLogOutputs
|
|
s.LogFormat = defaultLogFormat
|
|
|
|
// load config from from env vars
|
|
if err := envconfig.Process(context.Background(), s); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if either Port or UnixSocket is set, if not, use defaultPort
|
|
if s.Port == "" && s.UnixSocket == "" {
|
|
s.Port = defaultPort
|
|
log.Printf("[INFO] Neither PIXIVFE_PORT nor PIXIVFE_UNIXSOCKET set. Using default port: %s\n", s.Port)
|
|
}
|
|
|
|
// Check for when both Host and UnixSocket are set
|
|
if s.Host != "" && s.UnixSocket != "" {
|
|
log.Printf("[WARNING] Both PIXIVFE_HOST and PIXIVFE_UNIXSOCKET are set. PIXIVFE_HOST will be ignored when using a Unix socket.\n")
|
|
}
|
|
|
|
// a check for tokens
|
|
if len(s.Token) < 1 {
|
|
log.Fatalln("PIXIVFE_TOKEN has to be set. Visit https://pixivfe-docs.pages.dev/hosting/hosting-pixivfe for more details.")
|
|
return i18n.Error("PIXIVFE_TOKEN has to be set. Visit https://pixivfe-docs.pages.dev/hosting/hosting-pixivfe for more details")
|
|
}
|
|
|
|
// Validate proxy server URL, but skip validation if it already points to the built-in proxy
|
|
if s.ProxyServer_staging == BuiltinProxyUrl {
|
|
proxyURL, _ := url.Parse(BuiltinProxyUrl)
|
|
s.ProxyServer = *proxyURL
|
|
log.Printf("Using built-in proxy server URL: %s\n", BuiltinProxyUrl)
|
|
} else {
|
|
proxyURL, err := utils.ValidateURL(s.ProxyServer_staging, "Proxy server")
|
|
if err != nil {
|
|
log.Printf("[WARNING] Invalid proxy server URL: %v. Falling back to built-in proxy URL.", err)
|
|
proxyURL, _ := url.Parse(BuiltinProxyUrl)
|
|
s.ProxyServer = *proxyURL
|
|
log.Printf("Proxy server set to: %s\n", BuiltinProxyUrl)
|
|
} else {
|
|
s.ProxyServer = *proxyURL
|
|
log.Printf("Proxy server set to: %s\n", proxyURL.String())
|
|
}
|
|
}
|
|
log.Printf("Proxy check interval set to: %v\n", s.ProxyCheckInterval)
|
|
|
|
// Validate repo URL
|
|
repoURL, err := utils.ValidateURL(s.RepoURL, "Repo")
|
|
if err != nil {
|
|
log.Printf("[WARNING] Invalid repo URL: %v. Using default repo URL.", err)
|
|
s.RepoURL = "https://codeberg.org/VnPower/PixivFE" // Use a default value
|
|
} else {
|
|
s.RepoURL = repoURL.String()
|
|
log.Printf("Repo URL set to: %s\n", s.RepoURL)
|
|
}
|
|
|
|
// Validate TokenLoadBalancing
|
|
switch s.TokenLoadBalancing {
|
|
case "round-robin", "random", "least-recently-used":
|
|
// Valid options
|
|
default:
|
|
log.Printf("[WARNING] Invalid PIXIVFE_TOKEN_LOAD_BALANCING value: %s. Defaulting to 'round-robin'.\n", s.TokenLoadBalancing)
|
|
s.TokenLoadBalancing = "round-robin"
|
|
}
|
|
|
|
// Initialize TokenManager
|
|
s.TokenManager = token_manager.NewTokenManager(s.Token, s.TokenMaxRetries, s.TokenBaseTimeout, s.TokenMaxBackoffTime, s.TokenLoadBalancing)
|
|
log.Printf("Token manager initialized with %d tokens\n", len(s.Token))
|
|
log.Printf("Token manager settings: Max retries: %d, Base timeout: %v, Max backoff time: %v\n", s.TokenMaxRetries, s.TokenBaseTimeout, s.TokenMaxBackoffTime)
|
|
log.Printf("Token load balancing method: %s\n", s.TokenLoadBalancing)
|
|
|
|
// Print cache configuration
|
|
if s.CacheEnabled {
|
|
log.Println("API response cache is enabled")
|
|
} else {
|
|
log.Println("API response cache is disabled")
|
|
}
|
|
|
|
if s.PopularSearchEnabled {
|
|
log.Println("Tag search by popularity is enabled")
|
|
} else {
|
|
log.Println("Tag search by popularity is disabled")
|
|
}
|
|
|
|
// Only print ResponseSaveLocation if InDevelopment is set
|
|
if s.InDevelopment {
|
|
log.Printf("Response save location: %s\n", s.ResponseSaveLocation)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServerConfig) GetToken() string {
|
|
token := s.TokenManager.GetToken()
|
|
if token == nil {
|
|
log.Println("[WARNING] All tokens are timed out. Using the first available token.")
|
|
return s.Token[0]
|
|
}
|
|
return token.Value
|
|
}
|