package config import ( "log/slog" "maps" "net/url" "os" "time" "github.com/pelletier/go-toml/v2" "github.com/pkg/errors" ) var Version string type URL struct { *url.URL } func (u *URL) MarshalText() ([]byte, error) { return []byte(u.URL.String()), nil } func (u *URL) UnmarshalText(text []byte) (err error) { u.URL, err = url.Parse(string(text)) if err != nil { return errors.WithMessagef(err, "could not parse URL %s", string(text)) } return nil } type Duration struct { time.Duration } func (d *Duration) MarshalText() ([]byte, error) { return []byte(d.Duration.String()), nil } func (d *Duration) UnmarshalText(text []byte) (err error) { d.Duration, err = time.ParseDuration(string(text)) if err != nil { return errors.WithMessagef(err, "could not parse duration %s", string(text)) } return nil } func mustURL(in string) (u URL) { var err error u.URL, err = url.Parse(in) if err != nil { panic(errors.Errorf("URL cannot be parsed: %s", in)) } return u } // this type is necessary as nix's `fromTOML` doesn't support TOML date/time formats type LocalTime struct { toml.LocalTime } func (t *LocalTime) MarshalText() ([]byte, error) { b, err := t.LocalTime.MarshalText() if err != nil { return nil, errors.WithMessage(err, "could not marshal time value") } return b, nil } func (t *LocalTime) UnmarshalText(in []byte) (err error) { err = t.LocalTime.UnmarshalText(in) if err != nil { return errors.WithMessage(err, "could not parse time value") } return nil } func mustLocalTime(in string) (time LocalTime) { err := time.UnmarshalText([]byte(in)) if err != nil { panic(errors.Errorf("Could not parse time: %s", in)) } return } var nixpkgs = Repository{ Type: GitHub, Owner: "NixOS", Repo: "nixpkgs", } var defaultConfig = Config{ DataPath: "./data", Web: &Web{ ListenAddress: "localhost", Port: 3000, BaseURL: mustURL("http://localhost:3000"), Environment: "development", ContentSecurityPolicy: CSP{ DefaultSrc: []string{"'self'"}, }, Headers: map[string]string{ "x-content-type-options": "nosniff", }, }, Importer: &Importer{ Timeout: Duration{30 * time.Minute}, UpdateAt: mustLocalTime("04:00:00"), Sources: map[string]*Source{ "nixos": { Name: "NixOS", Key: "nixos", Enable: true, Importer: Options, Fetcher: Channel, Channel: "nixpkgs", URL: "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz", ImportPath: "nixos/release.nix", Attribute: "options", OutputPath: "share/doc/nixos", Timeout: Duration{5 * time.Minute}, Repo: nixpkgs, }, "darwin": { Name: "Darwin", Key: "darwin", Enable: false, Importer: Options, Fetcher: Channel, Channel: "darwin", URL: "https://github.com/LnL7/nix-darwin/archive/master.tar.gz", ImportPath: "release.nix", Attribute: "options", OutputPath: "share/doc/darwin", Timeout: Duration{5 * time.Minute}, Repo: Repository{ Type: GitHub, Owner: "LnL7", Repo: "nix-darwin", }, }, "home-manager": { Name: "Home Manager", Key: "home-manager", Enable: false, Importer: Options, Channel: "home-manager", URL: "https://github.com/nix-community/home-manager/archive/master.tar.gz", Fetcher: Channel, ImportPath: "default.nix", Attribute: "docs.json", OutputPath: "share/doc/home-manager", Timeout: Duration{5 * time.Minute}, Repo: Repository{ Type: GitHub, Owner: "nix-community", Repo: "home-manager", }, }, "nixpkgs": { Name: "Nix Packages", Key: "nixpkgs", Enable: true, Importer: Packages, Fetcher: ChannelNixpkgs, Channel: "nixos-unstable", OutputPath: "packages.json.br", Timeout: Duration{5 * time.Minute}, Repo: nixpkgs, }, }, }, } func GetDefaultConfig() string { out, err := toml.Marshal(&defaultConfig) if err != nil { panic("could not read default configuration") } return string(out) } func GetConfig(filename string) (*Config, error) { config := defaultConfig if filename != "" { slog.Debug("reading config", "filename", filename) f, err := os.Open(filename) if err != nil { return nil, errors.Wrap(err, "reading config failed") } defer f.Close() dec := toml.NewDecoder(f) dec.DisallowUnknownFields() err = dec.Decode(&config) if err != nil { var tomlError *toml.DecodeError if errors.As(err, &tomlError) { return nil, errors.WithMessage(err, tomlError.Error()) } var missingConfigError *toml.StrictMissingError if errors.As(err, &missingConfigError) { return nil, errors.Errorf("unexpected config: %s", missingConfigError.String()) } return nil, errors.Wrap(err, "config error") } } maps.DeleteFunc(config.Importer.Sources, func(_ string, v *Source) bool { return !v.Enable }) return &config, nil }