GoBlog/config.go

562 lines
18 KiB
Go
Raw Normal View History

2020-07-28 19:17:07 +00:00
package main
import (
2020-10-06 17:07:48 +00:00
"errors"
"log"
"net/http"
2020-12-24 10:00:16 +00:00
"net/url"
2020-10-14 19:20:17 +00:00
"strings"
2020-10-06 17:07:48 +00:00
2022-03-16 07:28:03 +00:00
"github.com/samber/lo"
2020-07-28 19:17:07 +00:00
"github.com/spf13/viper"
)
type config struct {
2020-10-15 18:54:43 +00:00
Server *configServer `mapstructure:"server"`
Db *configDb `mapstructure:"database"`
Cache *configCache `mapstructure:"cache"`
DefaultBlog string `mapstructure:"defaultblog"`
Blogs map[string]*configBlog `mapstructure:"blogs"`
User *configUser `mapstructure:"user"`
Hooks *configHooks `mapstructure:"hooks"`
Plugins []*configPlugin `mapstructure:"plugins"`
2020-10-15 18:54:43 +00:00
Micropub *configMicropub `mapstructure:"micropub"`
PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"`
ActivityPub *configActivityPub `mapstructure:"activityPub"`
Webmention *configWebmention `mapstructure:"webmention"`
Notifications *configNotifications `mapstructure:"notifications"`
2021-02-27 07:31:06 +00:00
PrivateMode *configPrivateMode `mapstructure:"privateMode"`
2022-01-24 08:43:06 +00:00
IndexNow *configIndexNow `mapstructure:"indexNow"`
2021-07-23 10:59:24 +00:00
EasterEgg *configEasterEgg `mapstructure:"easterEgg"`
2021-11-22 15:36:17 +00:00
MapTiles *configMapTiles `mapstructure:"mapTiles"`
TTS *configTTS `mapstructure:"tts"`
2022-04-16 19:42:09 +00:00
Reactions *configReactions `mapstructure:"reactions"`
Pprof *configPprof `mapstructure:"pprof"`
Debug bool `mapstructure:"debug"`
2021-12-14 16:38:36 +00:00
initialized bool
2020-07-28 19:17:07 +00:00
}
type configServer struct {
2021-10-13 07:01:54 +00:00
Logging bool `mapstructure:"logging"`
LogFile string `mapstructure:"logFile"`
Port int `mapstructure:"port"`
PublicAddress string `mapstructure:"publicAddress"`
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
MediaAddress string `mapstructure:"mediaAddress"`
PublicHTTPS bool `mapstructure:"publicHttps"`
AcmeDir string `mapstructure:"acmeDir"`
AcmeEabKid string `mapstructure:"acmeEabKid"`
AcmeEabKey string `mapstructure:"acmeEabKey"`
2021-10-13 07:01:54 +00:00
TailscaleHTTPS bool `mapstructure:"tailscaleHttps"`
Tailscale *configTailscale `mapstructure:"tailscale"`
Tor bool `mapstructure:"tor"`
2022-03-10 13:52:56 +00:00
TorSingleHop bool `mapstructure:"torSingleHop"`
2021-10-13 07:01:54 +00:00
SecurityHeaders bool `mapstructure:"securityHeaders"`
CSPDomains []string `mapstructure:"cspDomains"`
2020-12-24 10:00:16 +00:00
publicHostname string
shortPublicHostname string
mediaHostname string
2020-07-28 19:17:07 +00:00
}
2021-10-13 07:01:54 +00:00
type configTailscale struct {
Enabled bool `mapstructure:"enabled"`
AuthKey string `mapstructure:"authKey"`
Hostname string `mapstructure:"Hostname"`
}
2020-07-28 19:17:07 +00:00
type configDb struct {
2021-03-26 08:33:46 +00:00
File string `mapstructure:"file"`
DumpFile string `mapstructure:"dumpFile"`
Debug bool `mapstructure:"debug"`
2020-07-28 19:17:07 +00:00
}
2020-07-29 20:45:26 +00:00
type configCache struct {
2020-11-20 14:33:20 +00:00
Enable bool `mapstructure:"enable"`
Expiration int `mapstructure:"expiration"`
2020-07-29 20:45:26 +00:00
}
2020-07-31 13:48:01 +00:00
type configBlog struct {
Path string `mapstructure:"path"`
Lang string `mapstructure:"lang"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
Pagination int `mapstructure:"pagination"`
DefaultSection string `mapstructure:"defaultsection"`
Sections map[string]*configSection `mapstructure:"sections"`
Taxonomies []*configTaxonomy `mapstructure:"taxonomies"`
Menus map[string]*configMenu `mapstructure:"menus"`
Photos *configPhotos `mapstructure:"photos"`
Search *configSearch `mapstructure:"search"`
BlogStats *configBlogStats `mapstructure:"blogStats"`
Blogroll *configBlogroll `mapstructure:"blogroll"`
Telegram *configTelegram `mapstructure:"telegram"`
PostAsHome bool `mapstructure:"postAsHome"`
RandomPost *configRandomPost `mapstructure:"randomPost"`
OnThisDay *configOnThisDay `mapstructure:"onThisDay"`
Comments *configComments `mapstructure:"comments"`
Map *configGeoMap `mapstructure:"map"`
Contact *configContact `mapstructure:"contact"`
Announcement *configAnnouncement `mapstructure:"announcement"`
hideOldContentWarning bool
}
type configSection struct {
2020-10-06 17:07:48 +00:00
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
PathTemplate string `mapstructure:"pathtemplate"`
ShowFull bool `mapstructure:"showFull"`
Name string
}
type configTaxonomy struct {
Name string `mapstructure:"name"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
2020-07-31 13:48:01 +00:00
}
type configMenu struct {
Items []*configMenuItem `mapstructure:"items"`
2020-09-19 11:37:58 +00:00
}
type configMenuItem struct {
2020-09-19 11:37:58 +00:00
Title string `mapstructure:"title"`
Link string `mapstructure:"link"`
}
type configPhotos struct {
2020-09-21 16:03:05 +00:00
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
}
type configSearch struct {
2020-11-15 10:34:48 +00:00
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
Placeholder string `mapstructure:"placeholder"`
}
type configBlogStats struct {
2021-01-04 19:29:49 +00:00
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
}
2021-05-08 19:22:48 +00:00
type configBlogroll struct {
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
Opml string `mapstructure:"opml"`
AuthHeader string `mapstructure:"authHeader"`
AuthValue string `mapstructure:"authValue"`
Categories []string `mapstructure:"categories"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
2021-05-08 19:22:48 +00:00
}
type configRandomPost struct {
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
}
2022-01-26 20:08:46 +00:00
type configOnThisDay struct {
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
}
type configComments struct {
2021-01-23 16:24:47 +00:00
Enabled bool `mapstructure:"enabled"`
}
type configGeoMap struct {
2021-07-06 19:06:39 +00:00
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
}
2021-07-22 11:41:52 +00:00
type configContact struct {
2021-07-22 12:04:46 +00:00
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
Title string `mapstructure:"title"`
Description string `mapstructure:"description"`
PrivacyPolicy string `mapstructure:"privacyPolicy"`
SMTPHost string `mapstructure:"smtpHost"`
SMTPPort int `mapstructure:"smtpPort"`
SMTPUser string `mapstructure:"smtpUser"`
SMTPPassword string `mapstructure:"smtpPassword"`
EmailFrom string `mapstructure:"emailFrom"`
EmailTo string `mapstructure:"emailTo"`
2021-11-08 15:27:33 +00:00
EmailSubject string `mapstructure:"emailSubject"`
2021-07-22 11:41:52 +00:00
}
type configAnnouncement struct {
Text string `mapstructure:"text"`
}
type configUser struct {
Nick string `mapstructure:"nick"`
Name string `mapstructure:"name"`
Password string `mapstructure:"password"`
TOTP string `mapstructure:"totp"`
AppPasswords []*configAppPassword `mapstructure:"appPasswords"`
Picture string `mapstructure:"picture"`
Email string `mapstructure:"email"`
Link string `mapstructure:"link"`
Identities []string `mapstructure:"identities"`
2021-02-28 07:57:15 +00:00
}
type configAppPassword struct {
2021-02-28 07:57:15 +00:00
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
type configHooks struct {
Shell string `mapstructure:"shell"`
Hourly []string `mapstructure:"hourly"`
PreStart []string `mapstructure:"prestart"`
2020-10-19 18:25:30 +00:00
// Can use template
PostPost []string `mapstructure:"postpost"`
PostUpdate []string `mapstructure:"postupdate"`
PostDelete []string `mapstructure:"postdelete"`
PostUndelete []string `mapstructure:"postundelete"`
}
2020-10-06 17:07:48 +00:00
type configMicropub struct {
2020-10-14 19:20:17 +00:00
CategoryParam string `mapstructure:"categoryParam"`
ReplyParam string `mapstructure:"replyParam"`
ReplyTitleParam string `mapstructure:"replyTitleParam"`
2020-10-14 19:20:17 +00:00
LikeParam string `mapstructure:"likeParam"`
LikeTitleParam string `mapstructure:"likeTitleParam"`
2020-10-14 19:20:17 +00:00
BookmarkParam string `mapstructure:"bookmarkParam"`
AudioParam string `mapstructure:"audioParam"`
PhotoParam string `mapstructure:"photoParam"`
PhotoDescriptionParam string `mapstructure:"photoDescriptionParam"`
LocationParam string `mapstructure:"locationParam"`
2020-10-14 19:20:17 +00:00
MediaStorage *configMicropubMedia `mapstructure:"mediaStorage"`
}
type configMicropubMedia struct {
MediaURL string `mapstructure:"mediaUrl"`
// BunnyCDN
BunnyStorageKey string `mapstructure:"bunnyStorageKey"`
BunnyStorageName string `mapstructure:"bunnyStorageName"`
BunnyStorageRegion string `mapstructure:"bunnyStorageRegion"`
// FTP
FTPAddress string `mapstructure:"ftpAddress"`
FTPUser string `mapstructure:"ftpUser"`
FTPPassword string `mapstructure:"ftpPassword"`
// Tinify
TinifyKey string `mapstructure:"tinifyKey"`
// Cloudflare
CloudflareCompressionEnabled bool `mapstructure:"cloudflareCompressionEnabled"`
2022-03-24 14:19:34 +00:00
// Local
LocalCompressionEnabled bool `mapstructure:"localCompressionEnabled"`
2020-10-06 17:07:48 +00:00
}
2020-10-15 18:54:43 +00:00
type configRegexRedirect struct {
From string `mapstructure:"from"`
To string `mapstructure:"to"`
2020-11-10 06:53:08 +00:00
Type int `mapstructure:"type"`
2020-10-15 18:54:43 +00:00
}
type configActivityPub struct {
Enabled bool `mapstructure:"enabled"`
TagsTaxonomies []string `mapstructure:"tagsTaxonomies"`
}
type configNotifications struct {
Ntfy *configNtfy `mapstructure:"ntfy"`
Telegram *configTelegram `mapstructure:"telegram"`
}
type configNtfy struct {
Enabled bool `mapstructure:"enabled"`
Topic string `mapstructure:"topic"`
Server string `mapstructure:"server"`
User string `mapstructure:"user"`
Pass string `mapstructure:"pass"`
2022-10-03 18:09:18 +00:00
Email string `mapstructure:"email"`
}
type configTelegram struct {
2021-02-08 19:33:48 +00:00
Enabled bool `mapstructure:"enabled"`
ChatID string `mapstructure:"chatId"`
BotToken string `mapstructure:"botToken"`
InstantViewHash string `mapstructure:"instantViewHash"`
}
2021-02-27 07:31:06 +00:00
type configPrivateMode struct {
Enabled bool `mapstructure:"enabled"`
}
2022-01-24 08:43:06 +00:00
type configIndexNow struct {
Enabled bool `mapstructure:"enabled"`
}
2021-07-23 10:59:24 +00:00
type configEasterEgg struct {
Enabled bool `mapstructure:"enabled"`
}
type configWebmention struct {
DisableSending bool `mapstructure:"disableSending"`
DisableReceiving bool `mapstructure:"disableReceiving"`
}
2021-11-22 15:36:17 +00:00
type configMapTiles struct {
Source string `mapstructure:"source"`
Attribution string `mapstructure:"attribution"`
MinZoom int `mapstructure:"minZoom"`
MaxZoom int `mapstructure:"maxZoom"`
}
type configTTS struct {
Enabled bool `mapstructure:"enabled"`
GoogleAPIKey string `mapstructure:"googleApiKey"`
}
2022-04-16 19:42:09 +00:00
type configReactions struct {
Enabled bool `mapstructure:"enabled"`
}
type configPprof struct {
Enabled bool `mapstructure:"enabled"`
Address string `mapstructure:"address"`
}
type configPlugin struct {
Path string `mapstructure:"path"`
Type string `mapstructure:"type"`
Import string `mapstructure:"import"`
Config map[string]any `mapstructure:"config"`
}
2021-12-14 16:38:36 +00:00
func (a *goBlog) loadConfigFile(file string) error {
// Use viper to load the config file
v := viper.New()
if file != "" {
// Use config file from the flag
2021-12-14 16:38:36 +00:00
v.SetConfigFile(file)
} else {
2021-12-14 16:38:36 +00:00
// Search in default locations
v.SetConfigName("config")
v.AddConfigPath("./config/")
}
2021-12-14 16:38:36 +00:00
// Read config
if err := v.ReadInConfig(); err != nil {
2020-07-28 19:17:07 +00:00
return err
}
2020-08-04 17:42:09 +00:00
// Unmarshal config
2021-12-14 16:38:36 +00:00
a.cfg = createDefaultConfig()
return v.Unmarshal(a.cfg)
}
func (a *goBlog) initConfig(logging bool) error {
2021-12-14 16:38:36 +00:00
if a.cfg == nil {
a.cfg = createDefaultConfig()
}
if a.cfg.initialized {
return nil
2020-08-04 17:42:09 +00:00
}
// Init database
if err := a.initDatabase(logging); err != nil {
return err
}
2020-10-06 17:07:48 +00:00
// Check config
2021-12-14 16:38:36 +00:00
// Parse addresses and hostnames
if a.cfg.Server.PublicAddress == "" {
return errors.New("no public address configured")
}
publicURL, err := url.Parse(a.cfg.Server.PublicAddress)
2020-12-24 10:00:16 +00:00
if err != nil {
2021-12-14 16:38:36 +00:00
return errors.New("Invalid public address: " + err.Error())
2020-12-24 10:00:16 +00:00
}
a.cfg.Server.publicHostname = publicURL.Hostname()
if sa := a.cfg.Server.ShortPublicAddress; sa != "" {
shortPublicURL, err := url.Parse(sa)
2020-12-24 10:00:16 +00:00
if err != nil {
2021-12-14 16:38:36 +00:00
return errors.New("Invalid short public address: " + err.Error())
2020-12-24 10:00:16 +00:00
}
a.cfg.Server.shortPublicHostname = shortPublicURL.Hostname()
}
if ma := a.cfg.Server.MediaAddress; ma != "" {
mediaUrl, err := url.Parse(ma)
if err != nil {
2021-12-14 16:38:36 +00:00
return errors.New("Invalid media address: " + err.Error())
}
a.cfg.Server.mediaHostname = mediaUrl.Hostname()
}
2021-12-14 16:38:36 +00:00
// Check if any blog is configured
if a.cfg.Blogs == nil || len(a.cfg.Blogs) == 0 {
a.cfg.Blogs = map[string]*configBlog{
"default": createDefaultBlog(),
}
2020-12-15 16:40:14 +00:00
}
2021-12-14 16:38:36 +00:00
// Check if default blog is set
if a.cfg.DefaultBlog == "" {
2022-07-17 06:36:10 +00:00
// Set default blog to the only blog that is configured
a.cfg.DefaultBlog = lo.Keys(a.cfg.Blogs)[0]
2020-10-06 17:07:48 +00:00
}
2021-12-14 16:38:36 +00:00
// Check if default blog exists
if a.cfg.Blogs[a.cfg.DefaultBlog] == nil {
return errors.New("default blog does not exist")
2020-10-06 17:07:48 +00:00
}
2021-12-14 16:38:36 +00:00
// Check media storage config
if ms := a.cfg.Micropub.MediaStorage; ms != nil && ms.MediaURL != "" {
ms.MediaURL = strings.TrimSuffix(ms.MediaURL, "/")
2020-10-14 19:20:17 +00:00
}
2021-12-14 16:38:36 +00:00
// Check if webmention receiving is disabled
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
// Disable comments for all blogs
for _, b := range a.cfg.Blogs {
b.Comments = &configComments{Enabled: false}
}
}
// Check if sections already migrated to db
const sectionMigrationKey = "sections_migrated"
if val, err := a.getSettingValue(sectionMigrationKey); err != nil {
return err
} else if val == "" {
if err = a.saveAllSections(); err != nil {
return err
}
if err = a.saveSettingValue(sectionMigrationKey, "1"); err != nil {
return err
}
}
// Load db sections
if err = a.loadSections(); err != nil {
return err
}
2021-05-08 19:22:48 +00:00
// Check config for each blog
2022-07-17 06:36:10 +00:00
for blog, bc := range a.cfg.Blogs {
// Check sections and add section if none exists
if len(bc.Sections) == 0 {
bc.Sections = createDefaultSections()
if err = a.saveAllSections(); err != nil {
return err
}
}
// Check default section
if defaultSection, err := a.getSettingValue(settingNameWithBlog(blog, defaultSectionSetting)); err != nil {
// Failed to read value
return err
} else if defaultSection == "" {
// No value defined in database
if _, ok := bc.Sections[bc.DefaultSection]; !ok {
bc.DefaultSection = lo.Keys(bc.Sections)[0]
}
// Save to database
if err = a.saveSettingValue(settingNameWithBlog(blog, defaultSectionSetting), bc.DefaultSection); err != nil {
return err
}
} else {
// Set value from database
bc.DefaultSection = defaultSection
}
// Check if language is set
2022-07-17 06:36:10 +00:00
if bc.Lang == "" {
bc.Lang = "en"
}
2021-07-17 07:33:44 +00:00
// Blogroll
2022-07-17 06:36:10 +00:00
if br := bc.Blogroll; br != nil && br.Enabled && br.Opml == "" {
2021-05-08 19:22:48 +00:00
br.Enabled = false
}
// Load other settings from database
bc.hideOldContentWarning, err = a.getBooleanSettingValue(settingNameWithBlog(blog, hideOldContentWarningSetting), false)
if err != nil {
return err
}
2021-05-08 19:22:48 +00:00
}
2021-12-14 16:38:36 +00:00
// Log success
a.cfg.initialized = true
log.Println("Initialized configuration")
2020-07-28 19:17:07 +00:00
return nil
}
2021-02-20 21:45:38 +00:00
2021-12-14 16:38:36 +00:00
func createDefaultConfig() *config {
return &config{
Server: &configServer{
Port: 8080,
PublicAddress: "http://localhost:8080",
2021-12-27 20:47:12 +00:00
LogFile: "data/access.log",
2021-12-14 16:38:36 +00:00
},
Db: &configDb{
File: "data/db.sqlite",
},
Cache: &configCache{
Enable: true,
Expiration: 600,
},
User: &configUser{
Nick: "admin",
Password: "secret",
},
Hooks: &configHooks{
Shell: "/bin/bash",
},
Micropub: &configMicropub{
CategoryParam: "tags",
ReplyParam: "replylink",
ReplyTitleParam: "replytitle",
LikeParam: "likelink",
LikeTitleParam: "liketitle",
BookmarkParam: "link",
AudioParam: "audio",
PhotoParam: "images",
PhotoDescriptionParam: "imagealts",
LocationParam: "location",
},
ActivityPub: &configActivityPub{
TagsTaxonomies: []string{"tags"},
},
}
}
func createDefaultBlog() *configBlog {
return &configBlog{
Path: "/",
Lang: "en",
Title: "My Blog",
Description: "Welcome to my blog.",
Taxonomies: []*configTaxonomy{
{
Name: "tags",
Title: "Tags",
},
},
2022-07-17 06:36:10 +00:00
}
}
func createDefaultSections() map[string]*configSection {
return map[string]*configSection{
"posts": {
Title: "Posts",
},
2021-12-14 16:38:36 +00:00
}
}
2021-09-23 06:42:00 +00:00
func (a *goBlog) httpsConfigured(checkAddress bool) bool {
return a.cfg.Server.PublicHTTPS ||
a.cfg.Server.TailscaleHTTPS ||
a.cfg.Server.SecurityHeaders ||
(checkAddress && strings.HasPrefix(a.cfg.Server.PublicAddress, "https"))
2021-02-20 21:45:38 +00:00
}
func (a *goBlog) getBlog(r *http.Request) (string, *configBlog) {
if r == nil {
return a.cfg.DefaultBlog, a.cfg.Blogs[a.cfg.DefaultBlog]
}
blog := a.cfg.DefaultBlog
if ctxBlog := r.Context().Value(blogKey); ctxBlog != nil {
if ctxBlogString, ok := ctxBlog.(string); ok {
blog = ctxBlogString
}
}
return blog, a.cfg.Blogs[blog]
}
2022-07-31 17:49:58 +00:00
func (a *goBlog) getBlogFromPost(p *post) *configBlog {
return a.cfg.Blogs[defaultIfEmpty(p.Blog, a.cfg.DefaultBlog)]
}