feat: init mirror-proxy project
Build and Push to GHCR / build-and-push (push) Has been cancelled

- Docker Hub / GHCR / GitHub reverse proxy
- Web admin panel with link management
- Dynamic admin path, user and password config
- Rate limiting per link (dual/single auth mode)
- Docker and docker-compose deployment support
- GitHub Actions workflow for auto-publish to GHCR
This commit is contained in:
Agent
2026-05-24 15:27:45 +08:00
commit 9223e35164
16 changed files with 2063 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
package config
import (
"encoding/json"
"os"
"sync"
)
type Config struct {
ListenAddr string `json:"listen_addr"`
AdminPath string `json:"admin_path"`
AdminUser string `json:"admin_user"`
AdminPass string `json:"admin_pass"`
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
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) {
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",
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()
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
}