Files
Mirror-Proxy/internal/config/config.go
T
AgentandHAPI d6623fc150
Build and Push to GHCR / build-and-push (push) Has been cancelled
feat: per-link IP whitelist, clipboard fallback, and link editing
- Add `allowed_ips` to Link model with IPv4/IPv6 and CIDR support
- Validate client IP in proxy auth middleware against link whitelist
- Extract client IP from X-Forwarded-For / X-Real-Ip headers
- Fix copy button for non-HTTPS contexts via execCommand fallback
- Allow editing existing links (name, type, auth mode, rate limit, IPs)
- Add dedicated IP whitelist modal for quick editing

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <[email protected]>
2026-05-24 17:14:35 +08:00

149 lines
3.3 KiB
Go

package config
import (
"encoding/json"
"os"
"path/filepath"
"sync"
)
var configFilePath string = "config.json"
func SetConfigFilePath(path string) {
configFilePath = path
}
func GetConfigFilePath() string {
return configFilePath
}
type Config struct {
ListenAddr string `json:"listen_addr"`
AdminPath string `json:"admin_path"`
AdminUser string `json:"admin_user"`
AdminPass string `json:"admin_pass"`
SessionTimeout int `json:"session_timeout"` // hours
DockerHubHost string `json:"docker_hub_host"`
GHCRCacheEnabled bool `json:"ghcr_cache_enabled"`
CacheDir string `json:"cache_dir"`
MaxCacheSize int64 `json:"max_cache_size"`
Links map[string]*Link `json:"links"`
mu sync.RWMutex
}
type Link struct {
ID string `json:"id"`
Name string `json:"name"`
Token string `json:"token"`
Type string `json:"type"` // docker, ghcr, github
AuthMode string `json:"auth_mode"` // single, dual
Enabled bool `json:"enabled"`
RateLimit int `json:"rate_limit"` // requests per minute
AllowedIPs []string `json:"allowed_ips"` // empty = allow all
CreatedAt int64 `json:"created_at"`
AccessCount int64 `json:"access_count"`
LastAccess int64 `json:"last_access"`
}
var (
cfg *Config
once sync.Once
)
func Load(path string) (*Config, error) {
configFilePath = path
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
cfg = defaultConfig()
return cfg, cfg.Save(path)
}
return nil, err
}
cfg = defaultConfig()
if err := json.Unmarshal(data, cfg); err != nil {
return nil, err
}
if cfg.Links == nil {
cfg.Links = make(map[string]*Link)
}
return cfg, nil
}
func defaultConfig() *Config {
return &Config{
ListenAddr: ":8080",
AdminPath: "/admin",
AdminUser: "admin",
AdminPass: "admin123",
SessionTimeout: 24,
DockerHubHost: "registry-1.docker.io",
GHCRCacheEnabled: true,
CacheDir: "./cache",
MaxCacheSize: 10 * 1024 * 1024 * 1024, // 10GB
Links: make(map[string]*Link),
}
}
func (c *Config) Save(path string) error {
c.mu.RLock()
defer c.mu.RUnlock()
dir := filepath.Dir(path)
if dir != "" && dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
}
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
func (c *Config) GetLink(id string) (*Link, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
link, ok := c.Links[id]
return link, ok
}
func (c *Config) GetLinkByToken(token string) (*Link, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
for _, link := range c.Links {
if link.Token == token {
return link, true
}
}
return nil, false
}
func (c *Config) SetLink(link *Link) {
c.mu.Lock()
defer c.mu.Unlock()
c.Links[link.ID] = link
}
func (c *Config) DeleteLink(id string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.Links, id)
}
func (c *Config) ListLinks() []*Link {
c.mu.RLock()
defer c.mu.RUnlock()
links := make([]*Link, 0, len(c.Links))
for _, l := range c.Links {
links = append(links, l)
}
return links
}
func Get() *Config {
return cfg
}