// Copyright 2022 E99p1ant. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package conf import ( "os" "strings" "github.com/pkg/errors" "gopkg.in/ini.v1" ) // File is the configuration object. var File *ini.File func Init() error { configFile := os.Getenv("NEKOBOX_CONFIG_PATH") if configFile == "" { configFile = "conf/app.ini" } var err error File, err = ini.LoadSources(ini.LoadOptions{ IgnoreInlineComment: true, }, configFile) if err != nil { return errors.Wrapf(err, "parse %q", configFile) } if err := File.Section("app").MapTo(&App); err != nil { return errors.Wrap(err, "map 'server'") } if App.ExternalURL == "" { return errors.New("app.external_url is required") } App.ExternalURL = strings.TrimRight(App.ExternalURL, "/") if err := File.Section("security").MapTo(&Security); err != nil { return errors.Wrap(err, "map 'security'") } if err := File.Section("server").MapTo(&Server); err != nil { return errors.Wrap(err, "map 'server'") } // XSRFKey 为空会导致 CSRF token 以空密钥生成、可被预测, // 使删题/置顶/拉黑/改密等所有 POST 操作的 CSRF 防护形同虚设。启动时强制校验。 if Server.XSRFKey == "" { return errors.New("server.xsrf_key is required (used to generate CSRF tokens, must not be empty)") } // Salt 为空会让密码哈希以空盐计算(HmacSha1(password, "")), // 相同密码产生相同哈希且可被离线字典/彩虹表攻击。启动时强制校验。 if Server.Salt == "" { return errors.New("server.salt is required (used to hash user passwords, must not be empty)") } if err := File.Section("database").MapTo(&Database); err != nil { return errors.Wrap(err, "map 'database'") } if err := File.Section("redis").MapTo(&Redis); err != nil { return errors.Wrap(err, "map 'redis'") } if err := File.Section("recaptcha").MapTo(&Recaptcha); err != nil { return errors.Wrap(err, "map 'recaptcha'") } if err := File.Section("pixel").MapTo(&Pixel); err != nil { return errors.Wrap(err, "map 'pixel'") } if err := File.Section("upload").MapTo(&Upload); err != nil { return errors.Wrap(err, "map 'upload'") } if err := File.Section("limits").MapTo(&Limits); err != nil { return errors.Wrap(err, "map 'limits'") } // ini 键留空会被 int 解析为 0;<1 的值一律回退内置默认,防止上限被静默置零。 sanitizeLimits(&Limits) if err := File.Section("mail").MapTo(&Mail); err != nil { return errors.Wrap(err, "map 'mail'") } serviceSections := File.Section("service").ChildSections() for _, serviceSection := range serviceSections { serviceSection := serviceSection var backend struct { Prefix string `ini:"prefix"` ForwardURL string `ini:"forward_url"` } if err := serviceSection.MapTo(&backend); err != nil { return errors.Wrapf(err, "map 'service.%s'", serviceSection.Name()) } Service.Backends = append(Service.Backends, backend) } return nil }