mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-19 08:44:14 +00:00
147 lines
4.8 KiB
Go
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)
|
|
}
|