Files
2026-05-03 20:49:56 -05:00

170 lines
6.5 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"
"path/filepath"
"strings"
"time"
"github.com/spf13/viper"
"atcr.io/pkg/config"
)
// Config represents the labeler service configuration. It is fully self-contained:
// no fields are inherited from or shared with the appview config.
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."`
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)."`
// PublicURL is the externally reachable URL of the labeler. Required.
PublicURL string `yaml:"public_url" comment:"Externally reachable labeler URL (required, e.g. https://labeler.example.com)."`
// ClientName is the OAuth client display name shown to PDS users on consent screens.
ClientName string `yaml:"client_name" comment:"OAuth client display name (e.g., \"ATCR Labeler\")."`
// ClientShortName is a shorter brand label used in UI copy.
ClientShortName string `yaml:"client_short_name" comment:"Short brand label used in UI copy (e.g., \"ATCR\")."`
// 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."`
// Directory for labeler state: SQLite database, signing key, did.txt.
DataDir string `yaml:"data_dir" comment:"Directory for labeler state (database, signing key, did.txt)."`
// DID method: "plc" (recommended, portable) or "web" (hostname-bound).
DIDMethod string `yaml:"did_method" comment:"DID method: \"plc\" (recommended) or \"web\"."`
// Explicit DID for did:plc adoption/recovery (optional).
DID string `yaml:"did" comment:"Explicit did:plc identifier for adoption/recovery (optional)."`
// Signing key path (defaults to <DataDir>/signing.key).
KeyPath string `yaml:"key_path" comment:"Path to K-256 signing key (defaults to <data_dir>/signing.key)."`
// Rotation key multibase (K-256 or P-256). Required to update the PLC document.
RotationKey string `yaml:"rotation_key" comment:"Multibase-encoded rotation key (K-256 or P-256). Required to update the PLC document."`
// PLC directory URL (default https://plc.directory).
PLCDirectoryURL string `yaml:"plc_directory_url" comment:"PLC directory URL (default https://plc.directory)."`
// LibsqlSyncURL enables embedded-replica sync to a remote libSQL/Bunny database when set.
// Empty = local-only mode (default).
LibsqlSyncURL string `yaml:"libsql_sync_url" comment:"Optional libSQL/Bunny remote sync URL. Empty = local-only."`
// LibsqlAuthToken is the auth token for the remote libSQL database.
LibsqlAuthToken string `yaml:"libsql_auth_token" comment:"Auth token for libsql_sync_url."`
// LibsqlSyncInterval is how often the embedded replica pulls from the remote.
LibsqlSyncInterval time.Duration `yaml:"libsql_sync_interval" comment:"Embedded-replica pull interval (e.g. 30s). 0 = manual sync only."`
}
// PublicURL returns the labeler's externally reachable URL.
func (c *Config) PublicURL() string {
return c.Labeler.PublicURL
}
// DBPath returns the path to the SQLite database file inside the data dir.
func (c *Config) DBPath() string {
return filepath.Join(c.Labeler.DataDir, "labeler.db")
}
// SigningKeyPath returns the configured signing key path or the default inside DataDir.
func (c *Config) SigningKeyPath() string {
if c.Labeler.KeyPath != "" {
return c.Labeler.KeyPath
}
return filepath.Join(c.Labeler.DataDir, "signing.key")
}
// PLCDirectoryURL returns the configured PLC directory URL or the canonical default.
func (c *Config) PLCDirectoryURL() string {
if c.Labeler.PLCDirectoryURL != "" {
return c.Labeler.PLCDirectoryURL
}
return "https://plc.directory"
}
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.public_url", "")
v.SetDefault("labeler.client_name", "ATCR Labeler")
v.SetDefault("labeler.client_short_name", "ATCR")
v.SetDefault("labeler.owner_did", "")
v.SetDefault("labeler.data_dir", "/var/lib/atcr-labeler")
v.SetDefault("labeler.did_method", "plc")
v.SetDefault("labeler.did", "")
v.SetDefault("labeler.key_path", "")
v.SetDefault("labeler.rotation_key", "")
v.SetDefault("labeler.plc_directory_url", "https://plc.directory")
v.SetDefault("labeler.libsql_sync_url", "")
v.SetDefault("labeler.libsql_auth_token", "")
v.SetDefault("labeler.libsql_sync_interval", 0)
}
// LoadConfig loads the labeler configuration from a YAML file.
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)
}
// Validation
if cfg.Labeler.PublicURL == "" {
return nil, fmt.Errorf("labeler.public_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)
}
switch cfg.Labeler.DIDMethod {
case "plc", "web":
default:
return nil, fmt.Errorf("labeler.did_method must be \"plc\" or \"web\" (got %q)", cfg.Labeler.DIDMethod)
}
return cfg, nil
}
// ExampleYAML generates an example labeler configuration file.
func ExampleYAML() ([]byte, error) {
cfg := &Config{
Version: "0.1",
LogLevel: "info",
Labeler: LabelerConfig{
Enabled: true,
Addr: ":5002",
PublicURL: "https://labeler.example.com",
ClientName: "ATCR Labeler",
ClientShortName: "ATCR",
OwnerDID: "did:plc:your-did-here",
DataDir: "/var/lib/atcr-labeler",
DIDMethod: "plc",
PLCDirectoryURL: "https://plc.directory",
},
}
return config.MarshalCommentedYAML("ATCR Labeler Configuration", cfg)
}