package config import ( "fmt" "os" "path/filepath" mcdslconfig "git.wntrmute.dev/kyle/mcdsl/config" ) // Config is the top-level MCR configuration. It embeds config.Base for // the standard Metacircular sections and adds MCR-specific sections. type Config struct { mcdslconfig.Base Storage StorageConfig `toml:"storage"` Web WebConfig `toml:"web"` } // StorageConfig holds blob/layer storage settings. type StorageConfig struct { LayersPath string `toml:"layers_path"` UploadsPath string `toml:"uploads_path"` } // WebConfig holds the web UI server settings. type WebConfig struct { ListenAddr string `toml:"listen_addr"` GRPCAddr string `toml:"grpc_addr"` CACert string `toml:"ca_cert"` } // Load reads a TOML config file, applies environment variable overrides // (MCR_ prefix), sets defaults, and validates required fields. func Load(path string) (*Config, error) { cfg, err := mcdslconfig.Load[Config](path, "MCR") if err != nil { return nil, err } return cfg, nil } // Validate implements the mcdsl config.Validator interface. It checks // MCR-specific required fields and constraints beyond what config.Base // validates. func (c *Config) Validate() error { if c.Database.Path == "" { return fmt.Errorf("database.path is required") } if c.Storage.LayersPath == "" { return fmt.Errorf("storage.layers_path is required") } if c.MCIAS.ServerURL == "" { return fmt.Errorf("mcias.server_url is required") } // Default uploads path to sibling of layers path. if c.Storage.UploadsPath == "" && c.Storage.LayersPath != "" { c.Storage.UploadsPath = filepath.Join(filepath.Dir(c.Storage.LayersPath), "uploads") } return validateSameFilesystem(c.Storage.LayersPath, c.Storage.UploadsPath) } // validateSameFilesystem checks that two paths reside on the same filesystem // by comparing device IDs. If either path does not exist yet, it checks the // nearest existing parent directory. func validateSameFilesystem(layersPath, uploadsPath string) error { layersDev, err := deviceID(layersPath) if err != nil { return fmt.Errorf("stat layers_path: %w", err) } uploadsDev, err := deviceID(uploadsPath) if err != nil { return fmt.Errorf("stat uploads_path: %w", err) } if layersDev != uploadsDev { return fmt.Errorf("storage.layers_path and storage.uploads_path must be on the same filesystem") } return nil } // deviceID returns the device ID for the given path. If the path does not // exist, it walks up to the nearest existing parent. func deviceID(path string) (uint64, error) { p := filepath.Clean(path) for { info, err := os.Stat(p) if err == nil { return extractDeviceID(info) } if !os.IsNotExist(err) { return 0, err } parent := filepath.Dir(p) if parent == p { return 0, fmt.Errorf("no existing parent for %s", path) } p = parent } }