mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-05-25 09:30:20 +00:00
170 lines
6.5 KiB
Go
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)
|
|
}
|