Files

147 lines
4.8 KiB
Go

// Package labeler implements the ATCR labeler service, an ATProto-compatible
// content moderation service for issuing takedown labels on container registry content.
package labeler
import (
"fmt"
"net/url"
"strings"
"github.com/spf13/viper"
"atcr.io/pkg/config"
)
// Config represents the labeler service configuration.
// It reuses the appview config YAML structure, reading from the "labeler" section.
type Config struct {
Version string `yaml:"version" comment:"Configuration format version."`
LogLevel string `yaml:"log_level" comment:"Log level: debug, info, warn, error."`
Labeler LabelerConfig `yaml:"labeler" comment:"Labeler service settings."`
Server AppviewServerConfig `yaml:"server" comment:"AppView server settings (shared config)."`
LogShipper config.LogShipperConfig `yaml:"log_shipper" comment:"Remote log shipping settings."`
}
// LabelerConfig defines labeler-specific settings.
type LabelerConfig struct {
// Enable the labeler service.
Enabled bool `yaml:"enabled" comment:"Enable the labeler service."`
// Listen address for the labeler HTTP server.
Addr string `yaml:"addr" comment:"Listen address for labeler (e.g., :5002)."`
// DID of the labeler admin. Only this DID can log into the admin panel.
OwnerDID string `yaml:"owner_did" comment:"DID of the labeler admin. Only this DID can log into the admin panel."`
// Path to labeler SQLite database.
DBPath string `yaml:"db_path" comment:"Path to labeler SQLite database."`
}
// AppviewServerConfig is a subset of the appview ServerConfig that the labeler needs.
type AppviewServerConfig struct {
BaseURL string `yaml:"base_url"`
ClientName string `yaml:"client_name"`
ClientShortName string `yaml:"client_short_name"`
TestMode bool `yaml:"test_mode"`
}
// PublicURL returns the labeler's public URL derived from the appview base URL.
// If appview is https://atcr.io, labeler is https://labeler.atcr.io.
func (c *Config) PublicURL() string {
u, err := url.Parse(c.Server.BaseURL)
if err != nil {
return ""
}
u.Host = "labeler." + u.Host
return u.String()
}
// DID returns the labeler's did:web identity derived from its public URL.
func (c *Config) DID() string {
u, err := url.Parse(c.PublicURL())
if err != nil {
return ""
}
host := u.Hostname()
if port := u.Port(); port != "" {
host += "%3A" + port
}
return "did:web:" + host
}
func setDefaults(v *viper.Viper) {
v.SetDefault("version", "0.1")
v.SetDefault("log_level", "info")
// Labeler defaults
v.SetDefault("labeler.enabled", false)
v.SetDefault("labeler.addr", ":5002")
v.SetDefault("labeler.owner_did", "")
v.SetDefault("labeler.db_path", "/var/lib/atcr-labeler/labeler.db")
// Server defaults (read from shared appview config)
v.SetDefault("server.base_url", "")
v.SetDefault("server.client_name", "AT Container Registry")
v.SetDefault("server.client_short_name", "ATCR")
v.SetDefault("server.test_mode", false)
}
// LoadConfig loads the labeler configuration from the appview config YAML.
func LoadConfig(yamlPath string) (*Config, error) {
v := config.NewViper("LABELER", yamlPath)
setDefaults(v)
cfg := &Config{}
if err := v.Unmarshal(cfg, config.UnmarshalOption()); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
// Also try ATCR_ prefix for shared server config
atcrV := config.NewViper("ATCR", yamlPath)
if baseURL := atcrV.GetString("server.base_url"); baseURL != "" && cfg.Server.BaseURL == "" {
cfg.Server.BaseURL = baseURL
}
if clientName := atcrV.GetString("server.client_name"); clientName != "" && cfg.Server.ClientName == "" {
cfg.Server.ClientName = clientName
}
if clientShortName := atcrV.GetString("server.client_short_name"); clientShortName != "" && cfg.Server.ClientShortName == "" {
cfg.Server.ClientShortName = clientShortName
}
if atcrV.GetBool("server.test_mode") {
cfg.Server.TestMode = true
}
// Validation
if cfg.Server.BaseURL == "" {
return nil, fmt.Errorf("server.base_url is required")
}
if cfg.Labeler.OwnerDID == "" {
return nil, fmt.Errorf("labeler.owner_did is required")
}
if !strings.HasPrefix(cfg.Labeler.OwnerDID, "did:") {
return nil, fmt.Errorf("labeler.owner_did must be a DID (got %q)", cfg.Labeler.OwnerDID)
}
return cfg, nil
}
// ExampleYAML generates an example labeler configuration file.
func ExampleYAML() ([]byte, error) {
cfg := &Config{
Version: "0.1",
LogLevel: "info",
Server: AppviewServerConfig{
BaseURL: "https://atcr.io",
ClientName: "AT Container Registry",
ClientShortName: "ATCR",
},
Labeler: LabelerConfig{
Enabled: true,
Addr: ":5002",
OwnerDID: "did:plc:your-did-here",
DBPath: "/var/lib/atcr-labeler/labeler.db",
},
}
return config.MarshalCommentedYAML("ATCR Labeler Configuration", cfg)
}