From f063b970b41309f83bee2c0f39fb85d495a2f381 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Mon, 26 Aug 2024 15:43:28 +0100 Subject: [PATCH] config: migrate to new yaml format config-base.yaml is almost identical to json version, except there's no "order" field, as "sections" and "settings" fields are now lists themselves and so Go can parse the correct order. As such, removed enumerate_config.py. Also, rewrote scripts/generate_ini.py in Go as scripts/ini/. Config structure in Go form is now in common/config.go, and is used by jfa-go and the ini script. app.configBase is now untouched once read from config-base.yaml, and instead copied to and patched in app.patchedConfig. Patching occurs at program start and config modification, so GetConfig is now just a couple of lines. Discord role patching still occurs in GetConfig, as the available roles can change regularly. Also added new "Disabled" field to sections, to avoid the nightmare of deleting from an array. --- Makefile | 31 +- api.go | 153 +- common/config.go | 62 + config.go | 96 ++ config/config-base.json | 2179 ----------------------------- config/config-base.yaml | 1577 +++++++++++++++++++++ config/config-json-to-new-yaml.py | 35 + config/gen-rough-schema.py | 40 + lang.go | 38 +- main.go | 10 +- models.go | 37 - scripts/enumerate_config.py | 29 - scripts/generate_ini.py | 47 - scripts/ini/go.mod | 12 + scripts/ini/go.sum | 7 + scripts/ini/main.go | 130 ++ ts/modules/settings.ts | 263 ++-- 17 files changed, 2180 insertions(+), 2566 deletions(-) create mode 100644 common/config.go delete mode 100644 config/config-base.json create mode 100644 config/config-base.yaml create mode 100644 config/config-json-to-new-yaml.py create mode 100644 config/gen-rough-schema.py delete mode 100644 scripts/enumerate_config.py delete mode 100644 scripts/generate_ini.py create mode 100644 scripts/ini/go.mod create mode 100644 scripts/ini/go.sum create mode 100644 scripts/ini/main.go diff --git a/Makefile b/Makefile index 53d646f..3a1e46f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: configuration email typescript swagger copy compile compress tailwind bundle-css inline-css variants-html install clean npm config-description config-default precompile +.PHONY: configuration email typescript swagger copy compile compress inline-css variants-html install clean npm config-description config-default precompile all: compile @@ -101,20 +101,23 @@ else SWAGINSTALL := endif -CONFIG_BASE = config/config-base.json +CONFIG_BASE = config/config-base.yaml -CONFIG_DESCRIPTION = $(DATA)/config-base.json +# CONFIG_DESCRIPTION = $(DATA)/config-base.json CONFIG_DEFAULT = $(DATA)/config-default.ini -$(CONFIG_DESCRIPTION) &: $(CONFIG_BASE) - $(info Fixing config-base) - -mkdir -p $(DATA) - python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json +# $(CONFIG_DESCRIPTION) &: $(CONFIG_BASE) +# $(info Fixing config-base) +# -mkdir -p $(DATA) +# python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json -$(CONFIG_DEFAULT) &: $(CONFIG_BASE) +$(DATA): + mkdir -p $(DATA) + +$(CONFIG_DEFAULT): $(DATA) $(CONFIG_BASE) $(info Generating config-default.ini) - python3 scripts/generate_ini.py -i config/config-base.json -o $(DATA)/config-default.ini + go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini -configuration: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) +configuration: $(CONFIG_DEFAULT) EMAIL_SRC_MJML = $(wildcard mail/*.mjml) EMAIL_SRC_TXT = $(wildcard mail/*.txt) @@ -179,8 +182,6 @@ $(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wild # mv $(CSS_BUNDLE) $(DATA)/web/css/$(CSSVERSION)bundle.css # npx postcss -o $(CSS_TARGET) $(CSS_TARGET) -bundle-css: tailwind - INLINE_SRC = html/crash.html INLINE_TARGET = $(DATA)/crash.html $(INLINE_TARGET): $(CSS_FULLTARGET) $(INLINE_SRC) @@ -197,6 +198,8 @@ COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC) COPY_TARGET = $(DATA)/jfa-go.service # $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) + $(info copying $(CONFIG_BASE)) + cp $(CONFIG_BASE) $(DATA)/ $(info copying crash page) cp $(DATA)/crash.html $(DATA)/html/ $(info copying static data) @@ -209,11 +212,11 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) cp -r lang $(DATA)/ cp LICENSE $(DATA)/ -precompile: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) +precompile: $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) GO_SRC = $(shell find ./ -name "*.go") GO_TARGET = build/jfa-go -$(GO_TARGET): $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum +$(GO_TARGET): $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum $(info Downloading deps) $(GOBINARY) mod download $(info Building) diff --git a/api.go b/api.go index c560f5c..f8f339d 100644 --- a/api.go +++ b/api.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/hrfee/jfa-go/common" lm "github.com/hrfee/jfa-go/logmessages" "github.com/hrfee/mediabrowser" "github.com/itchyny/timefmt-go" @@ -229,102 +230,15 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) { // @Summary Get jfa-go configuration. // @Produce json -// @Success 200 {object} settings "Uses the same format as config-base.json" +// @Success 200 {object} common.Config "Uses the same format as config-base.json" // @Router /config [get] // @Security Bearer // @tags Configuration func (app *appContext) GetConfig(gc *gin.Context) { - resp := app.configBase - // Load language options - formOptions := app.storage.lang.User.getOptions() - fl := resp.Sections["ui"].Settings["language-form"] - fl.Options = formOptions - fl.Value = app.config.Section("ui").Key("language-form").MustString("en-us") - pwrOptions := app.storage.lang.PasswordReset.getOptions() - pl := resp.Sections["password_resets"].Settings["language"] - pl.Options = pwrOptions - pl.Value = app.config.Section("password_resets").Key("language").MustString("en-us") - adminOptions := app.storage.lang.Admin.getOptions() - al := resp.Sections["ui"].Settings["language-admin"] - al.Options = adminOptions - al.Value = app.config.Section("ui").Key("language-admin").MustString("en-us") - emailOptions := app.storage.lang.Email.getOptions() - el := resp.Sections["email"].Settings["language"] - el.Options = emailOptions - el.Value = app.config.Section("email").Key("language").MustString("en-us") - telegramOptions := app.storage.lang.Email.getOptions() - tl := resp.Sections["telegram"].Settings["language"] - tl.Options = telegramOptions - tl.Value = app.config.Section("telegram").Key("language").MustString("en-us") - if updater == "" { - delete(resp.Sections, "updates") - for i, v := range resp.Order { - if v == "updates" { - resp.Order = append(resp.Order[:i], resp.Order[i+1:]...) - break - } - } - } - if PLATFORM == "windows" { - delete(resp.Sections["smtp"].Settings, "ssl_cert") - for i, v := range resp.Sections["smtp"].Order { - if v == "ssl_cert" { - sect := resp.Sections["smtp"] - sect.Order = append(sect.Order[:i], sect.Order[i+1:]...) - resp.Sections["smtp"] = sect - } - } - } - if !MatrixE2EE() { - delete(resp.Sections["matrix"].Settings, "encryption") - for i, v := range resp.Sections["matrix"].Order { - if v == "encryption" { - sect := resp.Sections["matrix"] - sect.Order = append(sect.Order[:i], sect.Order[i+1:]...) - resp.Sections["matrix"] = sect - } - } - } - for sectName, section := range resp.Sections { - for settingName, setting := range section.Settings { - val := app.config.Section(sectName).Key(settingName) - s := resp.Sections[sectName].Settings[settingName] - switch setting.Type { - case "list": - s.Value = val.StringsWithShadows("|") - case "text", "email", "select", "password", "note": - s.Value = val.MustString("") - case "number": - s.Value = val.MustInt(0) - case "bool": - s.Value = val.MustBool(false) - } - resp.Sections[sectName].Settings[settingName] = s - } - } if discordEnabled { - r, err := app.discord.ListRoles() - if err == nil { - roles := make([][2]string, len(r)+1) - roles[0] = [2]string{"", "None"} - for i, role := range r { - roles[i+1] = role - } - s := resp.Sections["discord"].Settings["apply_role"] - s.Options = roles - resp.Sections["discord"].Settings["apply_role"] = s - } + app.PatchConfigDiscordRoles() } - - resp.Sections["ui"].Settings["language-form"] = fl - resp.Sections["ui"].Settings["language-admin"] = al - resp.Sections["email"].Settings["language"] = el - resp.Sections["password_resets"].Settings["language"] = pl - resp.Sections["telegram"].Settings["language"] = tl - resp.Sections["discord"].Settings["language"] = tl - resp.Sections["matrix"].Settings["language"] = tl - - gc.JSON(200, resp) + gc.JSON(200, app.patchedConfig) } // @Summary Modify app config. @@ -340,35 +254,46 @@ func (app *appContext) ModifyConfig(gc *gin.Context) { gc.BindJSON(&req) // Load a new config, as we set various default values in app.config that shouldn't be stored. tempConfig, _ := ini.ShadowLoad(app.configPath) - for section, settings := range req { - if section != "restart-program" { - _, err := tempConfig.GetSection(section) - if err != nil { - tempConfig.NewSection(section) + for _, section := range app.configBase.Sections { + ns, ok := req[section.Section] + if !ok { + continue + } + newSection := ns.(map[string]any) + iniSection, err := tempConfig.GetSection(section.Section) + if err != nil { + iniSection, _ = tempConfig.NewSection(section.Section) + } + for _, setting := range section.Settings { + newValue, ok := newSection[setting.Setting] + if !ok { + continue } - for setting, value := range settings.(map[string]interface{}) { - if section == "email" && setting == "method" && value == "disabled" { - value = "" - } - if (section == "discord" || section == "matrix") && setting == "language" { - tempConfig.Section("telegram").Key("language").SetValue(value.(string)) - } else if app.configBase.Sections[section].Settings[setting].Type == "list" { - splitValues := strings.Split(value.(string), "|") - // Delete the key first to get rid of any shadow values - tempConfig.Section(section).DeleteKey(setting) - for i, v := range splitValues { - if i == 0 { - tempConfig.Section(section).Key(setting).SetValue(v) - } else { - tempConfig.Section(section).Key(setting).AddShadow(v) - } + // Patch disabled to actually be an empty string + if section.Section == "email" && setting.Setting == "method" && newValue == "disabled" { + newValue = "" + } + // Copy language preference for chatbots to root one in "telegram" + if (section.Section == "discord" || section.Section == "matrix") && setting.Setting == "language" { + iniSection.Key("language").SetValue(newValue.(string)) + } else if setting.Type == common.ListType { + splitValues := strings.Split(newValue.(string), "|") + // Delete the key first to get rid of any shadow values + iniSection.DeleteKey(setting.Setting) + for i, v := range splitValues { + if i == 0 { + iniSection.Key(setting.Setting).SetValue(v) + } else { + iniSection.Key(setting.Setting).AddShadow(v) } - } else if value.(string) != app.config.Section(section).Key(setting).MustString("") { - tempConfig.Section(section).Key(setting).SetValue(value.(string)) } + + } else if newValue.(string) != iniSection.Key(setting.Setting).MustString("") { + iniSection.Key(setting.Setting).SetValue(newValue.(string)) } } } + tempConfig.Section("").Key("first_run").SetValue("false") if err := tempConfig.SaveTo(app.configPath); err != nil { app.err.Printf(lm.FailedWriting, app.configPath, err) @@ -381,6 +306,8 @@ func (app *appContext) ModifyConfig(gc *gin.Context) { app.Restart() } app.loadConfig() + // Patch new settings for next GetConfig + app.PatchConfigBase() // Reinitialize password validator on config change, as opposed to every applicable request like in python. if _, ok := req["password_validation"]; ok { validatorConf := ValidatorConf{ diff --git a/common/config.go b/common/config.go new file mode 100644 index 0000000..7d68233 --- /dev/null +++ b/common/config.go @@ -0,0 +1,62 @@ +package common + +type SectionMeta struct { + Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section + Description string `json:"description" yaml:"description"` + Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"` + Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"` + DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"` + DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"` + WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"` +} + +type Option [2]string + +type SettingType string + +var ( + BoolType SettingType = "bool" + SelectType SettingType = "select" + TextType SettingType = "text" + PasswordType SettingType = "password" + NumberType SettingType = "number" + NoteType SettingType = "note" + EmailType SettingType = "email" + ListType SettingType = "list" +) + +type Setting struct { + Setting string `json:"setting" yaml:"setting" example:"my_setting"` + Name string `json:"name" yaml:"name" example:"My Setting"` + Description string `json:"description" yaml:"description"` + Required bool `json:"required" yaml:"required"` + RequiresRestart bool `json:"requires_restart" yaml:"requires_restart"` + Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"` + Type SettingType `json:"type" yaml:"type"` // Type (string, number, bool, etc.) + Value any `json:"value" yaml:"value"` + Options []Option `json:"options,omitempty" yaml:"options,omitempty"` + DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled. + DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue. + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"` +} + +type Section struct { + Section string `json:"section" yaml:"section" example:"my_section"` + Meta SectionMeta `json:"meta" yaml:"meta"` + Settings []Setting `json:"settings" yaml:"settings"` +} + +type Config struct { + Sections []Section `json:"sections" yaml:"sections"` +} + +func (c *Config) removeSection(section string) { + for i, v := range c.Sections { + if v.Section == section { + c.Sections = append(c.Sections[:i], c.Sections[i+1:]...) + break + } + } +} diff --git a/config.go b/config.go index a3a8d02..bc7beb2 100644 --- a/config.go +++ b/config.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/hrfee/jfa-go/common" "github.com/hrfee/jfa-go/easyproxy" lm "github.com/hrfee/jfa-go/logmessages" "gopkg.in/ini.v1" @@ -250,3 +251,98 @@ func (app *appContext) loadConfig() error { return nil } + +func (app *appContext) PatchConfigBase() { + conf := app.configBase + // Load language options + formOptions := app.storage.lang.User.getOptions() + pwrOptions := app.storage.lang.PasswordReset.getOptions() + adminOptions := app.storage.lang.Admin.getOptions() + emailOptions := app.storage.lang.Email.getOptions() + telegramOptions := app.storage.lang.Email.getOptions() + + for i, section := range app.configBase.Sections { + if section.Section == "updates" && updater == "" { + section.Meta.Disabled = true + } + for j, setting := range section.Settings { + if section.Section == "ui" { + if setting.Setting == "language-form" { + setting.Options = formOptions + setting.Value = "en-us" + } else if setting.Setting == "language-admin" { + setting.Options = adminOptions + setting.Value = "en-us" + } + } else if section.Section == "password_resets" { + if setting.Setting == "language" { + setting.Options = pwrOptions + setting.Value = "en-us" + } + } else if section.Section == "email" { + if setting.Setting == "language" { + setting.Options = emailOptions + setting.Value = "en-us" + } + } else if section.Section == "telegram" { + if setting.Setting == "language" { + setting.Options = telegramOptions + setting.Value = "en-us" + } + } else if section.Section == "smtp" { + if setting.Setting == "ssl_cert" && PLATFORM == "windows" { + // Not accurate but the effect is hiding the option, which we want. + setting.Deprecated = true + } + } else if section.Section == "matrix" { + if setting.Setting == "encryption" && !MatrixE2EE() { + // Not accurate but the effect is hiding the option, which we want. + setting.Deprecated = true + } + } + val := app.config.Section(section.Section).Key(setting.Setting) + switch setting.Type { + case "list": + setting.Value = val.StringsWithShadows("|") + case "text", "email", "select", "password", "note": + setting.Value = val.MustString("") + case "number": + setting.Value = val.MustInt(0) + case "bool": + setting.Value = val.MustBool(false) + } + section.Settings[j] = setting + } + conf.Sections[i] = section + } + app.patchedConfig = conf +} + +func (app *appContext) PatchConfigDiscordRoles() { + if !discordEnabled { + return + } + r, err := app.discord.ListRoles() + if err != nil { + return + } + roles := make([]common.Option, len(r)+1) + roles[0] = common.Option{"", "None"} + for i, role := range r { + roles[i+1] = role + } + + for i, section := range app.patchedConfig.Sections { + if section.Section != "discord" { + continue + } + for j, setting := range section.Settings { + if setting.Setting != "apply_role" { + continue + } + setting.Options = roles + section.Settings[j] = setting + } + app.patchedConfig.Sections[i] = section + } +} diff --git a/config/config-base.json b/config/config-base.json deleted file mode 100644 index 4ecac5c..0000000 --- a/config/config-base.json +++ /dev/null @@ -1,2179 +0,0 @@ -{ - "order": [], - "sections": { - "updates": { - "order": [], - "meta": { - "name": "Updates", - "description": "Settings for update notifications and release channel." - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": true, - "requires_restart": true, - "type": "bool", - "value": true, - "description": "Enable/disable updating notifications and downloading/applying updates." - }, - "channel": { - "name": "Release Channel", - "required": true, - "requires_restart": false, - "type": "select", - "options": [ - ["stable", "Stable"], - ["unstable", "Unstable"] - ], - "value": "", - "description": "Release channel for updates." - } - } - }, - "jellyfin": { - "order": [], - "meta": { - "name": "Jellyfin", - "description": "Settings for connecting to Jellyfin" - }, - "settings": { - "username": { - "name": "Jellyfin Username", - "required": true, - "requires_restart": true, - "type": "text", - "value": "username", - "description": "It is recommended to create a limited admin account for this program." - }, - "password": { - "name": "Jellyfin Password", - "required": true, - "requires_restart": true, - "type": "password", - "value": "password" - }, - "server": { - "name": "Server address", - "required": true, - "requires_restart": true, - "type": "text", - "value": "http://jellyfin.local:8096", - "description": "Jellyfin server address. Can be public, or local for security purposes." - }, - "public_server": { - "name": "Public address", - "required": false, - "requires_restart": false, - "type": "text", - "value": "https://jellyf.in:443", - "description": "Publicly accessible Jellyfin address for invite form. Leave blank to reuse the above address." - }, - "client": { - "name": "Client Name", - "required": true, - "requires_restart": true, - "advanced": true, - "type": "text", - "value": "jfa-go", - "description": "The name of the client that will show up in the Jellyfin dashboard." - }, - "cache_timeout": { - "name": "User cache timeout (minutes)", - "required": false, - "requires_restart": true, - "advanced": true, - "type": "number", - "value": 30, - "description": "Timeout of user cache in minutes. Set to 0 to disable." - }, - "type": { - "name": "Server type", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["jellyfin", "Jellyfin"], - ["emby", "Emby"] - ], - "value": "jellyfin", - "description": "Note: Emby integration works but is missing some features, such as Password Resets." - }, - "substitute_jellyfin_strings": { - "name": "Substitute occurrences of \"Jellyfin\"", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Optionally substitute occurrences of \"Jellyfin\" in the account creation form and emails with this. May result in bad grammar." - } - } - }, - "ui": { - "order": [], - "meta": { - "name": "General", - "description": "Settings related to the UI and program functionality." - }, - "settings": { - "language-form": { - "name": "Default Form Language", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default Account Form Language. Visit weblate.jfa-go.com if you'd like to translate." - }, - "language-admin": { - "name": "Default Admin Language", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default Admin page Language. Settings has not been translated. Visit weblate.jfa-go.com if you'd like to translate." - }, - "theme": { - "name": "Default Look", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["Jellyfin (Dark)", "Jellyfin (Dark)"], - ["Default (Light)", "Default (Light)"] - ], - "value": "Jellyfin (Dark)", - "description": "Default appearance for all users." - }, - "host": { - "name": "Address", - "required": true, - "requires_restart": true, - "type": "text", - "value": "0.0.0.0", - "description": "Set 0.0.0.0 to run on localhost" - }, - "port": { - "name": "Port", - "required": true, - "requires_restart": true, - "type": "number", - "value": 8056 - }, - "jellyfin_login": { - "name": "Use Jellyfin for authentication", - "required": false, - "requires_restart": true, - "type": "bool", - "value": true, - "description": "Enable this to use Jellyfin users instead of the below username and pw." - }, - "admin_only": { - "name": "Allow admin users only on \"Admin\" pages", - "required": false, - "requires_restart": true, - "depends_true": "jellyfin_login", - "type": "bool", - "value": true, - "description": "Allows only admin users on Jellyfin to access the admin page. Doesn't apply to the \"My Accounts\" page." - }, - "allow_all": { - "name": "Allow all users to login to \"Admin\" pages", - "required": false, - "requires_restart": true, - "depends_true": "jellyfin_login", - "type": "bool", - "value": false, - "description": "Allow all Jellyfin users to access jfa-go. Not recommended, add individual users in the Accounts tab instead. Doesn't apply to the \"My Accounts\" page." - }, - "username": { - "name": "Web Username", - "required": true, - "requires_restart": true, - "depends_false": "jellyfin_login", - "type": "text", - "value": "your username", - "description": "Username for admin page (Leave blank if using jellyfin_login)" - }, - "password": { - "name": "Web Password", - "required": true, - "requires_restart": true, - "depends_false": "jellyfin_login", - "type": "password", - "value": "your password", - "description": "Password for admin page (Leave blank if using jellyfin_login)" - }, - "email": { - "name": "Admin email address", - "required": false, - "requires_restart": false, - "depends_false": "jellyfin_login", - "type": "text", - "value": "example@example.com", - "description": "Address to send notifications to (Leave blank if using jellyfin_login)" - }, - "debug": { - "name": "Debug logging", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enables debug logging and exposes pprof as a route (Don't use in production!)" - }, - "contact_message": { - "name": "Contact message", - "required": false, - "requires_restart": false, - "type": "text", - "value": "Need help? contact me.", - "description": "Displayed at bottom of all pages except admin" - }, - "help_message": { - "name": "Help message", - "required": false, - "requires_restart": false, - "type": "text", - "value": "Enter your details to create an account.", - "description": "Displayed at top of invite form." - }, - "success_message": { - "name": "Success message", - "required": false, - "requires_restart": false, - "type": "text", - "value": "Your account has been created. Click below to continue to Jellyfin.", - "description": "Displayed when a user creates an account. Use the \"post-signup card\" in the Message editor for more control." - }, - "url_base": { - "name": "Reverse Proxy subfolder", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "URL base for when running jfa-go with a reverse proxy in a subfolder. include preceding /, e.g \"/accounts\"." - }, - "jfa_url": { - "name": "External jfa-go URL", - "required": true, - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "http://accounts.jellyf.in:8056", - "description": "The URL at which the jfa-go root (admin page) is accessible, including the subfolder if you use one. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself." - }, - "redirect_url": { - "name": "Form success redirect URL", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "advanced": true, - "description": "Set a different URL for the sign-up form to redirect the user to when they've signed up. Default to 'Public Server' or 'Server' in the Jellyfin tab." - }, - "auto_redirect": { - "name": "Auto redirect on success", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "advanced": true, - "description": "Navigate directly to the above URL instead of needing the user to click \"Continue\". Overrides the post-signup card." - }, - "login_appearance": { - "name": "Login screen appearance", - "required": false, - "requires_restart": false, - "type": "select", - "options": [ - ["clear", "Transparent"], - ["opaque", "Opaque"] - ], - "value": "clear", - "description": "Appearance of the Admin login screen." - } - } - }, - "advanced": { - "order": [], - "meta": { - "name": "Advanced", - "description": "Advanced settings.", - "advanced": true - }, - "settings": { - "log_ips": { - "name": "Log IPs accessing Admin Page", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Log IP addresses of admins and admin page requests in console and in activities. See notice below on legality." - }, - "log_ips_users": { - "name": "Log IPs accessing User Page", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Log IP addresses of users in console and in activities. See notice below on legality." - }, - "ip_note": { - "name": "Logging IPs:", - "type": "note", - "value": "", - "required": "false", - "description": "Logging IP addresses through jfa-go may violate GDPR or other privacy regulations, as IPs are linked to account information. Enable at your own risk." - }, - "tls": { - "name": "TLS/HTTP2", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Serve application over TLS, with HTTP2 preload.", - "wiki_link": "https://wiki.jfa-go.com/docs/tls/" - }, - "tls_port": { - "name": "TLS Port", - "depends_true": "tls", - "required": false, - "requires_restart": true, - "type": "number", - "value": 8057, - "description": "Port to run TLS server on" - }, - "tls_cert": { - "name": "Path to TLS Certificate", - "depends_true": "tls", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Path to .crt file. See jfa-go wiki for more info." - }, - "tls_key": { - "name": "Path to TLS Key file", - "depends_true": "tls", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Path to .key file. See jfa-go wiki for more info." - }, - "auth_retry_count": { - "name": "Initial auth retry count", - "required": false, - "requires_restart": true, - "type": "number", - "value": 6, - "description": "Number of times to retry initial connection to Jellyfin before failing." - }, - "auth_retry_gap": { - "name": "Initial auth retry gap (seconds)", - "required": false, - "requires_restart": true, - "type": "number", - "value": 10, - "description": "Duration in seconds to wait between connection retries." - }, - "proxy": { - "name": "Use Proxy", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Whether or not to use a HTTP/SOCKS5 Proxy." - }, - "proxy_protocol": { - "name": "Proxy Protocol", - "depends_true": "proxy", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["http", "HTTP"], - ["socks", "SOCKS5"] - ], - "value": "http", - "description": "Protocol to use for proxy connection." - }, - "proxy_address": { - "name": "Proxy Address", - "depends_true": "proxy", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Proxy address, including port." - }, - "proxy_user": { - "name": "Proxy Username", - "depends_true": "proxy", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Leave blank for no Authentication." - }, - "proxy_password": { - "name": "Proxy Password", - "depends_true": "proxy", - "required": false, - "requires_restart": true, - "type": "password", - "value": "", - "description": "Leave blank for no Authentication." - }, - "debug_log_emails": { - "name": "Debug Storage Logging: Emails", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_discord": { - "name": "Debug Storage Logging: Discord", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_telegram": { - "name": "Debug Storage Logging: Telegram", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_matrix": { - "name": "Debug Storage Logging: Matrix", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_invites": { - "name": "Debug Storage Logging: Invites", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_announcements": { - "name": "Debug Storage Logging: Announcements", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_expiries": { - "name": "Debug Storage Logging: User Expiries", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_profiles": { - "name": "Debug Storage Logging: Profiles", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - }, - "debug_log_custom_content": { - "name": "Debug Storage Logging: Custom Message Content", - "required": false, - "requires_restart": true, - "type": "select", - "options": [ - ["none", "None"], - ["all", "All Writes"], - ["deletion", "Deletion Only*"] - ], - "value": "none", - "description": "Extra debug logging for writes to the database. *: Deletion also includes blanking out major fields, e.g. an email address." - } - } - }, - "activity_log": { - "order": [], - "meta": { - "name": "Activity Log", - "description": "Settings for data retention of the activity log." - }, - "settings": { - "keep_n_records": { - "name": "Number of records to keep", - "required": false, - "requires_restart": true, - "type": "number", - "value": 1000, - "description": "How many of the most recent activities to keep. Set to 0 to disable." - }, - "delete_after_days": { - "name": "Delete activities older than (days):", - "required": false, - "requires_restart": true, - "type": "number", - "value": 90, - "description": "If an activity was created this many days ago, it will be deleted. Set to 0 to disable." - } - } - }, - "captcha": { - "order": [], - "meta": { - "name": "Captcha", - "description": "Settings related to user creation CAPTCHAs.", - "wiki_link": "https://wiki.jfa-go.com/docs/captcha/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable a CAPTCHA on the account creation form." - }, - "recaptcha": { - "name": "Use Google reCAPTCHA", - "required": false, - "requires_restart": true, - "type": "bool", - "depends_true": "enabled", - "value": false, - "description": "More reliable, but requires some setup. See jfa-go wiki for more info." - }, - "recaptcha_site_key": { - "name": "reCAPTCHA Site Key", - "required": false, - "requires_restart": true, - "type": "text", - "depends_true": "recaptcha", - "value": "", - "description": "Site Key, see jfa-go wiki for how to acquire one." - }, - "recaptcha_secret_key": { - "name": "reCAPTCHA Secret Key", - "required": false, - "requires_restart": true, - "type": "text", - "depends_true": "recaptcha", - "value": "", - "description": "Secret Key, see jfa-go wiki for how to acquire one." - }, - "recaptcha_hostname": { - "name": "Hostname", - "required": false, - "requires_restart": true, - "type": "text", - "depends_true": "recaptcha", - "value": "", - "description": "Public host-name of jfa-go, e.g. \"site.com\". Don't include any subpaths." - } - } - }, - "user_page": { - "order": [], - "meta": { - "name": "User Page/\"My Account\"", - "description": "The User Page (My Account) allows users to access and modify info directly, such as changing/adding contact methods, seeing their expiry date, sending referrals or changing their password. Password resets can also be initiated from here, given a contact method or username. Access control settings set in \"General\" do not apply to this page, nor does \"Access jfa-go\" in the Accounts tab.", - "depends_true": "ui|jellyfin_login" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": true - }, - "jellyfin_login_note": { - "name": "Note:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "Jellyfin Login must be enabled to use this feature, and password resets with a link must be enabled for self-service.", - "style": "critical" - }, - "edit_note": { - "name": "Message Cards:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "Click the edit icon next to the \"User Page\" Setting to add custom Markdown messages that will be shown to the user. Note message cards are not private, little effort is required for anyone to view them." - }, - "show_link": { - "name": "Show Link on Admin Login page", - "required": false, - "requires_restart": false, - "depends_true": "enabled", - "type": "bool", - "value": true, - "description": "Whether or not to show a link to the \"My Account\" page on the admin login screen, to direct lost users." - }, - "referrals": { - "name": "User Referrals", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": true, - "description": "Users are given their own \"invite\" to send to others." - }, - "referrals_note": { - "name": "Using Referrals:", - "type": "note", - "value": "", - "depends_true": "referrals", - "required": "false", - "description": "Create an invite with your desired settings, then either assign it to a user in the accounts tab, or to a profile in settings." - }, - "allow_pwr_username": { - "name": "Allow PWR with username", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": true, - "description": "Allow users to start a Password Reset by inputting their username." - }, - "allow_pwr_email": { - "name": "Allow PWR with email address", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": true, - "description": "Allow users to start a Password Reset by inputting their email address." - }, - "allow_pwr_contact_method": { - "name": "Allow PWR with Discord/Telegram/Matrix", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": true, - "description": "Allow users to start a Password Reset by inputting their Discord/Telegram/Matrix username/id." - }, - "pwr_note": { - "name": "PWR Methods:", - "type": "note", - "depends_true": "enabled", - "value": "", - "required": "false", - "description": "Select at least one PWR initiation method. If none are selected, all will be enabled." - } - } - }, - "password_validation": { - "order": [], - "meta": { - "name": "Password Validation", - "description": "Password validation (minimum length, etc.)" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": false, - "type": "bool", - "value": true - }, - "min_length": { - "name": "Minimum Length", - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "8" - }, - "upper": { - "name": "Minimum uppercase characters", - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "1" - }, - "lower": { - "name": "Minimum lowercase characters", - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "0" - }, - "number": { - "name": "Minimum number count", - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "1" - }, - "special": { - "name": "Minimum number of special characters", - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "0" - } - } - }, - "messages": { - "order": [], - "meta": { - "name": "Messages/Notifications", - "description": "General settings for emails/messages.", - "wiki_link": "https://wiki.jfa-go.com/docs/emails/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": true, - "requires_restart": true, - "type": "bool", - "value": true, - "description": "Enable the sending of emails/messages such as password resets, announcements, etc." - }, - "use_24h": { - "name": "Use 24h time", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "bool", - "value": true - }, - "date_format": { - "name": "Date format", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "method", - "type": "text", - "value": "%d/%m/%y", - "description": "Date format used in emails. Follows datetime.strftime format." - }, - "message": { - "name": "Help message", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "text", - "value": "Need help? contact me.", - "description": "Message displayed at bottom of emails." - }, - "edit_note": { - "name": "Customize Messages:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "Click the edit icon next to the \"Messages/Notifications\" Setting to customize the messages sent to users with Markdown." - } - } - }, - "email": { - "order": [], - "meta": { - "name": "Email", - "description": "General email settings.", - "depends_true": "messages|enabled" - }, - "settings": { - "language": { - "name": "Email Language", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default email language. Submit a PR on github if you'd like to translate." - }, - "no_username": { - "name": "Use email addresses as username", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "bool", - "value": false, - "description": "Use email address from invite form as username on Jellyfin." - }, - "method": { - "name": "Email method", - "required": false, - "requires_restart": false, - "type": "select", - "options": [ - ["", "Disabled"], - ["smtp", "SMTP"], - ["mailgun", "Mailgun"] - ], - "value": "smtp", - "description": "Method of sending email to use." - }, - "address": { - "name": "Sent from (address)", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "email", - "value": "jellyfin@jellyf.in", - "description": "Address to send emails from" - }, - "from": { - "name": "Sent from (name)", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "text", - "value": "Jellyfin", - "description": "The name of the sender" - }, - "plaintext": { - "name": "Send emails as plain text", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "method", - "type": "bool", - "value": false, - "description": "Send emails as plain text instead of HTML." - }, - "collect": { - "name": "Collect on sign-up", - "required": false, - "requires_restart": false, - "depends_true": "method", - "type": "bool", - "value": true, - "description": "Ask for an email address on the sign-up form." - }, - "required": { - "name": "Require on sign-up", - "required": false, - "requires_restart": false, - "depends_true": "collect", - "type": "bool", - "value": false, - "description": "Require an email address on sign-up." - }, - "require_unique": { - "name": "Require unique address", - "required": false, - "requires_restart": true, - "depends_true": "method", - "type": "bool", - "value": false, - "description": "Disables using the same address on multiple accounts." - } - } - }, - "mailgun": { - "order": [], - "meta": { - "name": "Mailgun (Email)", - "description": "Mailgun API connection settings", - "depends_true": "email|method" - }, - "settings": { - "api_url": { - "name": "API URL", - "required": false, - "requires_restart": false, - "type": "text", - "value": "https://api.mailgun.net..." - }, - "api_key": { - "name": "API Key", - "required": false, - "requires_restart": false, - "type": "text", - "value": "your api key" - } - } - }, - "smtp": { - "order": [], - "meta": { - "name": "SMTP (Email)", - "description": "SMTP Server connection settings.", - "depends_true": "email|method" - }, - "settings": { - "username": { - "name": "Username", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Username for SMTP. Leave blank to user send from address as username." - }, - "encryption": { - "name": "Encryption Method", - "required": false, - "requires_restart": false, - "type": "select", - "options": [ - ["ssl_tls", "SSL/TLS"], - ["starttls", "STARTTLS"] - ], - "value": "starttls", - "description": "Your email provider should provide different ports for each encryption method. Generally 465 for ssl_tls, 587 for starttls." - }, - "server": { - "name": "Server address", - "required": false, - "requires_restart": false, - "type": "text", - "value": "smtp.jellyf.in", - "description": "SMTP Server address." - }, - "port": { - "name": "Port", - "required": false, - "requires_restart": false, - "type": "number", - "value": 465 - }, - "password": { - "name": "Password", - "required": false, - "requires_restart": false, - "type": "password", - "value": "smtp password" - }, - "hello_hostname": { - "name": "HELLO Hostname", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "localhost", - "description": "Hostname sent when sending HELLO to the SMTP server. Some servers don't like the default \"localhost\" value, such as smtp-relay.gmail.com." - }, - "ssl_cert": { - "name": "Path to custom SSL certificate", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Use if your SMTP server's SSL Certificate is not trusted by the system." - }, - "cert_validation": { - "name": "Verify certificate", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "bool", - "value": true, - "description": "Warning, disabling this makes you much more vulnerable to man-in-the-middle attacks" - }, - "auth_type": { - "name": "Authentication type", - "required": false, - "requires_restart": false, - "advanced": false, - "type": "select", - "options": [ - ["0", "Plain"], - ["1", "Login"], - ["2", "CRAM-MD5"], - ["3", "None"], - ["4", "Auto"] - ], - "value": 4, - "description": "SMTP authentication method" - } - } - }, - "discord": { - "order": [], - "meta": { - "name": "Discord", - "description": "Settings for Discord invites/signup/notifications", - "wiki_link": "https://wiki.jfa-go.com/docs/bots/discord/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable signup verification through Discord and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot." - }, - "show_on_reg": { - "name": "Show on user registration", - "required": false, - "requires_restart": true, - "type": "bool", - "depends_true": "enabled", - "value": true, - "description": "Allow users to link their Discord on the registration page." - }, - "required": { - "name": "Require on sign-up", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": false, - "description": "Require Discord connection on sign-up. See the jfa-go wiki for info on setting this up." - }, - "require_unique": { - "name": "Require unique user", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Disables using the same user on multiple Jellyfin accounts." - }, - "token": { - "name": "API Token", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Discord Bot API Token." - }, - "start_command": { - "name": "Start command", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "start", - "description": "Command to start the user verification process." - }, - "channel": { - "name": "Channel to monitor", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Only listen to commands in specified channel. Leave blank to monitor all." - }, - "provide_invite": { - "name": "Provide server invite", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": false, - "description": "Generate a one-time discord server invite for the account creation form. Required Bot permission \"Create instant invite\", you may need to re-add the bot to your server after." - }, - "invite_channel": { - "name": "Invite channel", - "required": false, - "requires_restart": true, - "depends_true": "provide_invite", - "type": "text", - "value": "", - "description": "Channel to invite new users to." - }, - "apply_role": { - "name": "Apply Role on connection", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "select", - "options": [ - ["", "None"] - ], - "value": "", - "description": "Add the selected role to a user when they sign up." - }, - "disable_enable_role": { - "name": "Remove/add role on user enable/disable/deletion", - "required": false, - "requires_restart": true, - "depends_true": "apply_role", - "type": "bool", - "value": false, - "description": "When a user is disabled or deleted, remove the Discord role, and when re-enabled, add it back." - }, - "language": { - "name": "Language", - "required": false, - "requires_restart": false, - "depends_true": "enabled", - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default Discord message language. Visit weblate if you'd like to translate." - } - } - }, - "telegram": { - "order": [], - "meta": { - "name": "Telegram", - "description": "Settings for Telegram signup/notifications. See the jfa-go wiki for info on setting this up.", - "wiki_link": "https://wiki.jfa-go.com/docs/bots/telegram/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable signup verification through Telegram and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot." - }, - "show_on_reg": { - "name": "Show on user registration", - "required": false, - "requires_restart": true, - "type": "bool", - "depends_true": "enabled", - "value": true, - "description": "Allow users to link their Telegram on the registration page." - }, - "required": { - "name": "Require on sign-up", - "required": false, - "required_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": false, - "description": "Require telegram connection on sign-up." - }, - "require_unique": { - "name": "Require unique user", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Disables using the same user on multiple Jellyfin accounts." - }, - "token": { - "name": "API Token", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Telegram Bot API Token." - }, - "language": { - "name": "Language", - "required": false, - "requires_restart": false, - "depends_true": "enabled", - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default telegram message language. Visit weblate if you'd like to translate." - } - } - }, - "matrix": { - "order": [], - "meta": { - "name": "Matrix", - "description": "Settings for Matrix invites/signup/notifications. See the jfa-go wiki for info on setting this up.", - "wiki_link": "https://wiki.jfa-go.com/docs/bots/matrix/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable signup verification through Matrix and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot." - }, - "show_on_reg": { - "name": "Show on user registration", - "required": false, - "requires_restart": true, - "type": "bool", - "depends_true": "enabled", - "value": true, - "description": "Allow users to link their Matrix on the registration page." - }, - "required": { - "name": "Require on sign-up", - "required": false, - "required_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": false, - "description": "Require Matrix connection on sign-up." - }, - "require_unique": { - "name": "Require unique user", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Disables using the same user on multiple Jellyfin accounts." - }, - "homeserver": { - "name": "Home Server URL", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Matrix Home server URL." - }, - "token": { - "name": "Access Token", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Matrix Bot API Token." - }, - "user_id": { - "name": "Bot User ID", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "User ID of bot account (Example: @jfa-bot:riot.im)" - }, - "topic": { - "name": "Chat topic", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "Jellyfin notifications", - "description": "Topic of Matrix private chats." - }, - "language": { - "name": "Language", - "required": false, - "requires_restart": false, - "depends_true": "enabled", - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default Matrix message language. Visit weblate if you'd like to translate." - }, - "encryption": { - "name": "End-to-end encryption", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "advanced": false, - "type": "bool", - "value": true, - "description": "Enable end-to-end encryption for messages." - }, - "e2ee_note": { - "name": "End-to-end encryption:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "If the setting is not visible to you, your jfa-go version does not include the feature. See the wiki for more information." - } - } - }, - "password_resets": { - "order": [], - "meta": { - "name": "Password Resets", - "description": "Settings for the password reset handler.", - "depends_true": "messages|enabled", - "wiki_link": "https://wiki.jfa-go.com/docs/pwr/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": true, - "description": "Enable to store provided email addresses, monitor Jellyfin directory for pw-resets, and send reset pins" - }, - "pwr_note": { - "name": "Setup:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "There are multiple ways password resets can be set up. See the wiki page for more information." - }, - "watch_directory": { - "name": "Jellyfin directory", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "text", - "value": "/path/to/jellyfin", - "description": "Path to the folder Jellyfin puts password-reset files." - }, - "link_reset": { - "name": "Use reset link instead of PIN (Required for Ombi)", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "bool", - "value": false, - "description": "Send users a link to reset their password instead of a PIN. Must be enabled to reset Ombi password at the same time as the Jellyfin password." - }, - "set_password": { - "name": "Set password through link", - "required": false, - "requires_restart": true, - "depends_true": "link_reset", - "type": "bool", - "value": false, - "description": "Instead of automatically setting the user's password to the PIN, allow them to set a new password through the reset link." - }, - "url_base": { - "name": "External jfa-go URL", - "required": true, - "requires_restart": false, - "depends_true": "link_reset", - "type": "text", - "value": "http://accounts.jellyf.in:8056", - "description": "The URL at which the jfa-go admin page is accessible, including the subfolder if you use one. You can leave this if you have one set in \"Invite Emails\". This is necessary because using a reverse proxy means the program has no way of knowing the URL itself.", - "deprecated": true - }, - "jfa_url": { - "name": "Generating Reset Links:", - "type": "note", - "value": "", - "depends_true": "link_reset", - "required": "false", - "description": "Set the \"External jfa-go URL\" in General so that links to jfa-go can be made." - }, - "language": { - "name": "Default reset link language", - "required": false, - "requires_restart": true, - "depends_true": "link_reset", - "type": "select", - "options": [ - ["en-us", "English (US)"] - ], - "value": "en-us", - "description": "Default language for password reset success screen." - }, - "email_html": { - "name": "Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "email_text": { - "name": "Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - }, - "subject": { - "name": "Email subject", - "required": false, - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Subject of password reset emails." - } - } - }, - "invite_emails": { - "order": [], - "meta": { - "name": "Invite emails", - "description": "Settings for sending invites directly to users.", - "depends_true": "email|method" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": false, - "type": "bool", - "value": true - }, - "email_html": { - "name": "Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to custom email HTML" - }, - "email_text": { - "name": "Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - }, - "subject": { - "name": "Email subject", - "required": true, - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Subject of invite emails." - }, - "url_base": { - "name": "External jfa-go URL", - "required": true, - "requires_restart": false, - "depends_true": "enabled", - "type": "text", - "value": "http://accounts.jellyf.in:8056", - "description": "The URL at which the jfa-go root (admin page) is accessible, including the subfolder if you use one. You can leave this if you have one set in \"Invite Emails\". This is necessary because using a reverse proxy means the program has no way of knowing the URL itself.", - "deprecated": true - }, - "jfa_url": { - "name": "Generating Links:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "Set the \"External jfa-go URL\" in General so that links to jfa-go can be made." - } - } - }, - "template_email": { - "order": [], - "meta": { - "name": "Custom email template", - "description": "Settings for the template used for announcements & custom messages. HTML should include {{ .text }}, Plaintext should include {{ .plaintext }}, and either can have {{ .message }} to include the contact message.", - "advanced": true - }, - "settings": { - "email_html": { - "name": "Custom template email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to custom email HTML template for announcements/custom messages." - }, - "email_text": { - "name": "Custom template email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to custom email text template for announcements/custom messages." - } - } - }, - "notifications": { - "order": [], - "meta": { - "name": "Admin invite notifications", - "description": "Allows toggling \"user created\" and \"invite expired\" notifications to be sent to the admin per-invite.", - "depends_true": "messages|enabled" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": "false", - "requires_restart": true, - "type": "bool", - "value": true, - "description": "Enabling adds optional toggles to invites to notify on expiry and user creation." - }, - "expiry_html": { - "name": "Expiry email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to expiry notification email HTML." - }, - "expiry_text": { - "name": "Expiry email (Plaintext)", - "required": false, - "requires_restart": "false", - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to expiry notification email in plaintext." - }, - "created_html": { - "name": "User created email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to user creation notification email HTML." - }, - "created_text": { - "name": "User created email (Plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "enabled", - "type": "text", - "value": "", - "description": "Path to user creation notification email in plaintext." - } - } - }, - "ombi": { - "order": [], - "meta": { - "name": "Ombi Integration", - "description": "Connect to Ombi to automatically create both Ombi and Jellyfin accounts for new users. You'll need to add a ombi template to an existing User Profile for accounts to be created, which you can do by refreshing then checking Settings > User Profiles. To handle password resets for Ombi & Jellyfin, enable \"Use reset link instead of PIN\".", - "wiki_link": "https://wiki.jfa-go.com/docs/ombi/" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable to create an Ombi account for new Jellyfin users" - }, - "server": { - "name": "URL", - "required": false, - "requires_restart": true, - "type": "text", - "value": "localhost:5000", - "depends_true": "enabled", - "description": "Ombi server URL, including http(s)://." - }, - "api_key": { - "name": "API Key", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "depends_true": "enabled", - "description": "API Key. Get this from the first tab in Ombi settings." - } - } - }, - "jellyseerr": { - "order": [], - "meta": { - "name": "Jellyseerr Integration", - "description": "Connect to Jellyseerr to automatically trigger the import of users on account creation, and to automatically link contact methods (email, discord and telegram). A template must be added to a User Profile for accounts to be created." - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable the Jellyseerr integration." - }, - "usertype_note": { - "name": "Password Changes:", - "type": "note", - "value": "", - "depends_true": "enabled", - "required": "false", - "description": "Ensure existing users on Jellyseerr are \"Jellyfin User\"s not \"Local User\"s, as password changes are not synced with Jellyseerr." - }, - "server": { - "name": "URL", - "required": false, - "requires_restart": true, - "type": "text", - "value": "localhost:5000", - "depends_true": "enabled", - "description": "Jellyseerr server URL." - }, - "api_key": { - "name": "API Key", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "depends_true": "enabled", - "description": "API Key. Get this from the first tab in Jellyseerr's settings." - }, - "import_existing": { - "name": "Import existing users to Jellyseerr", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "depends_true": "enabled", - "description": "Existing users (and those created outside jfa-go) will have their contact info imported to Jellyseerr." - }, - "constraints_note": { - "name": "Unique Emails:", - "type": "note", - "value": "", - "depends_true": "import_existing", - "required": "false", - "description": "Jellyseerr requires email addresses to be unique. If this is not the case, you may see errors in jfa-go's logs. You can require unique addresses in Settings > Email." - } - } - }, - "backups": { - "order": [], - "meta": { - "name": "Backups", - "description": "Settings for database backups. Press the \"Backups\" button above to create, download and restore backups.", - "wiki_link": "https://wiki.jfa-go.com/docs/backups/" - }, - "settings": { - "enabled": { - "name": "Scheduled Backups", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable to generate database backups on a schedule." - }, - "path": { - "name": "Backup Path", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Path to directory to store backups in. defaults to /backups." - }, - "every_n_minutes": { - "name": "Backup frequency (Minutes)", - "required": false, - "requires_restart": true, - "depends_true": "enabled", - "type": "number", - "value": 1440, - "description": "Backup after this many minutes has passed since the last. Resets every restart." - }, - "keep_n_backups": { - "name": "Number of backups to keep", - "required": false, - "requires_restart": true, - "type": "number", - "value": 20, - "description": "Number of most recent backups to keep. Once this is hit, the oldest backup will be deleted before doing a new one." - } - } - }, - "welcome_email": { - "order": [], - "meta": { - "name": "Welcome Message", - "description": "Optionally send a welcome message to new users with the Jellyfin URL and their username.", - "depends_true": "messages|enabled" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false, - "description": "Enable to send welcome emails to new users." - }, - "subject": { - "name": "Email subject", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Subject of welcome emails." - }, - "email_html": { - "name": "Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "email_text": { - "name": "Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - } - } - }, - "email_confirmation": { - "order": [], - "meta": { - "name": "Email confirmation", - "description": "If enabled, a user will be sent an email confirmation link to ensure their password is right before they can make an account.", - "depends_true": "email|method" - }, - "settings": { - "enabled": { - "name": "Enabled", - "required": false, - "requires_restart": true, - "type": "bool", - "value": false - }, - "subject": { - "name": "Email subject", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Subject of email confirmation emails." - }, - "email_html": { - "name": "Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "email_text": { - "name": "Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - } - } - }, - "user_expiry": { - "order": [], - "meta": { - "name": "User Expiry", - "description": "When set on an invite, users will be deleted or disabled a specified amount of time after they create their account. Expiries can also be set and extended for invididual users, optionally with a message why." - }, - "settings": { - "behaviour": { - "name": "Behaviour", - "required": false, - "requires_restart": false, - "type": "select", - "options": [ - ["delete_user", "Delete user"], - ["disable_user", "Disable user"] - ], - "value": "disable_user", - "description": "Whether to delete or disable users on expiry." - }, - "delete_expired_after_days": { - "name": "Delete expired accounts after (days)", - "required": false, - "requires_restart": false, - "type": "number", - "value": 0, - "depends_true": "behaviour", - "description": "When set, user accounts will be deleted this many days after expiring (if \"Behaviour\" is \"Disable user\"). Set to 0 to disable." - }, - "send_email": { - "name": "Send email", - "required": false, - "requires_restart": false, - "type": "bool", - "value": true, - "depends_true": "messages|enabled", - "description": "Send an email when a user's account expires." - }, - "subject": { - "name": "Email subject", - "required": false, - "requires_restart": false, - "depends_true": "messages|enabled", - "type": "text", - "value": "", - "description": "Subject of user expiry emails." - }, - "email_html": { - "name": "Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "messages|enabled", - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "email_text": { - "name": "Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "messages|enabled", - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - }, - "adjustment_subject": { - "name": "Adjustment: email subject", - "required": false, - "requires_restart": false, - "depends_true": "messages|enabled", - "type": "text", - "value": "", - "description": "Subject of adjustment emails, sent optionally when setting/extending an expiry." - }, - "adjustment_email_html": { - "name": "Adjustment: Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "messages|enabled", - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "adjustment_email_text": { - "name": "Adjustment: Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "depends_true": "messages|enabled", - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - } - } - }, - "disable_enable": { - "order": [], - "meta": { - "name": "Account Disabling/Enabling", - "description": "Subject/email files for account disabling/enabling emails.", - "depends_true": "messages|enabled" - }, - "settings": { - "subject_disabled": { - "name": "Email subject (Disabled)", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Subject of account disabling emails." - }, - "subject_enabled": { - "name": "Email subject (Enabled)", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Subject of account enabling emails." - }, - "disabled_html": { - "name": "Custom disabling email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "disabled_text": { - "name": "Custom disabling email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - }, - "enabled_html": { - "name": "Custom enabling email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "enabled_text": { - "name": "Custom enabling email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - } - } - }, - "deletion": { - "order": [], - "meta": { - "name": "Account Deletion", - "description": "Subject/email files for account deletion emails.", - "depends_true": "messages|enabled" - }, - "settings": { - "subject": { - "name": "Email subject", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Subject of account deletion emails." - }, - "email_html": { - "name": "Custom email (HTML)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email html" - }, - "email_text": { - "name": "Custom email (plaintext)", - "required": false, - "requires_restart": false, - "advanced": true, - "type": "text", - "value": "", - "description": "Path to custom email in plain text" - } - } - }, - "webhooks": { - "order": [], - "meta": { - "name": "Webhooks", - "description": "jfa-go will send a POST request to these URLs when an event occurs, with relevant information. Request information is logged when debug logging is enabled.", - "wiki_link": "https://wiki.jfa-go.com/docs/webhooks/" - }, - "settings": { - "created": { - "name": "User Created", - "required": false, - "requires_restart": false, - "type": "list", - "value": "", - "description": "URLs to hit when an account is created through jfa-go. Sends a `respUser` object." - } - } - }, - "files": { - "order": [], - "meta": { - "name": "File Storage", - "description": "Optional settings for changing storage locations.", - "advanced": true - }, - "settings": { - "invites": { - "name": "Invite Storage", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Location of stored invites (json)." - }, - "password_resets": { - "name": "Password Resets", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Location of stored non-Jellyfin password resets (json)." - }, - "emails": { - "name": "Email Addresses", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Location of stored email addresses (json)." - }, - "users": { - "name": "User storage", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Stores users temporarily when a user expiry is set." - }, - "ombi_template": { - "name": "Ombi user template", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Location of stored Ombi user template." - }, - "user_profiles": { - "name": "User Profiles", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Location of stored user profiles (encompasses template and configuration and displayprefs) (json)" - }, - "html_templates": { - "name": "Custom HTML Template Directory", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "Path to directory containing custom versions of web ui pages. See wiki for more info." - }, - "lang_files": { - "name": "Custom language files directory", - "required": false, - "requires_restart": true, - "type": "text", - "value": "", - "description": "The path to a directory which following the same form as the internal 'lang/' directory. See GitHub for more info." - }, - "custom_emails": { - "name": "Custom email content", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "JSON file generated by program in settings, different from email_html/email_text. See wiki for more info." - }, - "custom_user_page_content": { - "name": "Custom user page content", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "JSON file generated by program in settings, containing user page messages. See wiki for more info." - }, - "telegram_users": { - "name": "Telegram users", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Stores telegram user IDs and language preferences." - }, - "matrix_users": { - "name": "Matrix users", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Stores matrix user IDs and language preferences." - }, - "matrix_sql": { - "name": "Matrix encryption DB", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Stores cryptographic material for Matrix end-to-end encryption." - }, - "discord_users": { - "name": "Discord users", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Stores discord user IDs and language preferences." - }, - "announcements": { - "name": "Announcement templates", - "required": false, - "requires_restart": false, - "type": "text", - "value": "", - "description": "Stores custom announcement templates." - } - } - } - } -} diff --git a/config/config-base.yaml b/config/config-base.yaml new file mode 100644 index 0000000..ff427d7 --- /dev/null +++ b/config/config-base.yaml @@ -0,0 +1,1577 @@ +sections: +- section: updates + meta: + name: Updates + description: Settings for update notifications and release channel. + settings: + - setting: enabled + name: Enabled + required: true + requires_restart: true + type: bool + value: true + description: Enable/disable updating notifications and downloading/applying updates. + - setting: channel + name: Release Channel + required: true + type: select + options: + - ["stable", "Stable"] + - ["unstable", "Unstable"] + description: Release channel for updates. +- section: jellyfin + meta: + name: Jellyfin + description: Settings for connecting to Jellyfin + settings: + - setting: username + name: Jellyfin Username + required: true + requires_restart: true + type: text + value: username + description: It is recommended to create a limited admin account for this program. + - setting: password + name: Jellyfin Password + required: true + requires_restart: true + type: password + value: password + - setting: server + name: Server address + required: true + requires_restart: true + type: text + value: http://jellyfin.local:8096 + description: Jellyfin server address. Can be public, or local for security purposes. + - setting: public_server + name: Public address + type: text + value: https://jellyf.in:443 + description: Publicly accessible Jellyfin address for invite form. Leave blank + to reuse the above address. + - setting: client + name: Client Name + required: true + requires_restart: true + advanced: true + type: text + value: jfa-go + description: The name of the client that will show up in the Jellyfin dashboard. + - setting: cache_timeout + name: User cache timeout (minutes) + requires_restart: true + advanced: true + type: number + value: 30 + description: Timeout of user cache in minutes. Set to 0 to disable. + - setting: type + name: Server type + requires_restart: true + type: select + options: + - ["jellyfin", "Jellyfin"] + - ["emby", "Emby"] + value: jellyfin + description: 'Note: Emby integration works but is missing some features, such + as Password Resets.' + - setting: substitute_jellyfin_strings + name: Substitute occurrences of "Jellyfin" + requires_restart: true + type: text + description: Optionally substitute occurrences of "Jellyfin" in the account creation + form and emails with this. May result in bad grammar. +- section: ui + meta: + name: General + description: Settings related to the UI and program functionality. + settings: + - setting: language-form + name: Default Form Language + requires_restart: true + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default Account Form Language. Visit weblate.jfa-go.com if you'd + like to translate. + - setting: language-admin + name: Default Admin Language + requires_restart: true + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default Admin page Language. Settings has not been translated. Visit + weblate.jfa-go.com if you'd like to translate. + - setting: theme + name: Default Look + requires_restart: true + type: select + options: + - ["Jellyfin (Dark)", "Jellyfin (Dark)"] + - ["Default (Light)", "Default (Light)"] + value: Jellyfin (Dark) + description: Default appearance for all users. + - setting: host + name: Address + required: true + requires_restart: true + type: text + value: 0.0.0.0 + description: Set 0.0.0.0 to run on localhost + - setting: port + name: Port + required: true + requires_restart: true + type: number + value: 8056 + - setting: jellyfin_login + name: Use Jellyfin for authentication + requires_restart: true + type: bool + value: true + description: Enable this to use Jellyfin users instead of the below username and + pw. + - setting: admin_only + name: Allow admin users only on "Admin" pages + requires_restart: true + depends_true: jellyfin_login + type: bool + value: true + description: Allows only admin users on Jellyfin to access the admin page. Doesn't + apply to the "My Accounts" page. + - setting: allow_all + name: Allow all users to login to "Admin" pages + requires_restart: true + depends_true: jellyfin_login + type: bool + value: false + description: Allow all Jellyfin users to access jfa-go. Not recommended, add individual + users in the Accounts tab instead. Doesn't apply to the "My Accounts" page. + - setting: username + name: Web Username + required: true + requires_restart: true + depends_false: jellyfin_login + type: text + value: your username + description: Username for admin page (Leave blank if using jellyfin_login) + - setting: password + name: Web Password + required: true + requires_restart: true + depends_false: jellyfin_login + type: password + value: your password + description: Password for admin page (Leave blank if using jellyfin_login) + - setting: email + name: Admin email address + depends_false: jellyfin_login + type: text + value: example@example.com + description: Address to send notifications to (Leave blank if using jellyfin_login) + - setting: debug + name: Debug logging + requires_restart: true + type: bool + value: false + description: Enables debug logging and exposes pprof as a route (Don't use in + production!) + - setting: contact_message + name: Contact message + type: text + value: Need help? contact me. + description: Displayed at bottom of all pages except admin + - setting: help_message + name: Help message + type: text + value: Enter your details to create an account. + description: Displayed at top of invite form. + - setting: success_message + name: Success message + type: text + value: Your account has been created. Click below to continue to Jellyfin. + description: Displayed when a user creates an account. Use the "post-signup card" + in the Message editor for more control. + - setting: url_base + name: Reverse Proxy subfolder + requires_restart: true + type: text + description: URL base for when running jfa-go with a reverse proxy in a subfolder. + include preceding /, e.g "/accounts". + - setting: jfa_url + name: External jfa-go URL + required: true + depends_true: enabled + type: text + value: http://accounts.jellyf.in:8056 + description: The URL at which the jfa-go root (admin page) is accessible, including + the subfolder if you use one. This is necessary because using a reverse proxy + means the program has no way of knowing the URL itself. + - setting: redirect_url + name: Form success redirect URL + type: text + advanced: true + description: Set a different URL for the sign-up form to redirect the user to + when they've signed up. Default to 'Public Server' or 'Server' in the Jellyfin + tab. + - setting: auto_redirect + name: Auto redirect on success + requires_restart: true + type: bool + value: false + advanced: true + description: Navigate directly to the above URL instead of needing the user to + click "Continue". Overrides the post-signup card. + - setting: login_appearance + name: Login screen appearance + type: select + options: + - ["clear", "Transparent"] + - ["opaque", "Opaque"] + value: clear + description: Appearance of the Admin login screen. +- section: advanced + meta: + name: Advanced + description: Advanced settings. + advanced: true + settings: + - setting: log_ips + name: Log IPs accessing Admin Page + requires_restart: true + type: bool + value: false + description: Log IP addresses of admins and admin page requests in console and + in activities. See notice below on legality. + - setting: log_ips_users + name: Log IPs accessing User Page + requires_restart: true + type: bool + value: false + description: Log IP addresses of users in console and in activities. See notice + below on legality. + - setting: ip_note + name: 'Logging IPs:' + type: note + required: false + description: Logging IP addresses through jfa-go may violate GDPR or other privacy + regulations, as IPs are linked to account information. Enable at your own risk. + - setting: tls + name: TLS/HTTP2 + requires_restart: true + type: bool + value: false + description: Serve application over TLS, with HTTP2 preload. + wiki_link: https://wiki.jfa-go.com/docs/tls/ + - setting: tls_port + name: TLS Port + depends_true: tls + requires_restart: true + type: number + value: 8057 + description: Port to run TLS server on + - setting: tls_cert + name: Path to TLS Certificate + depends_true: tls + requires_restart: true + type: text + description: Path to .crt file. See jfa-go wiki for more info. + - setting: tls_key + name: Path to TLS Key file + depends_true: tls + requires_restart: true + type: text + description: Path to .key file. See jfa-go wiki for more info. + - setting: auth_retry_count + name: Initial auth retry count + requires_restart: true + type: number + value: 6 + description: Number of times to retry initial connection to Jellyfin before failing. + - setting: auth_retry_gap + name: Initial auth retry gap (seconds) + requires_restart: true + type: number + value: 10 + description: Duration in seconds to wait between connection retries. + - setting: proxy + name: Use Proxy + requires_restart: true + type: bool + value: false + description: Whether or not to use a HTTP/SOCKS5 Proxy. + - setting: proxy_protocol + name: Proxy Protocol + depends_true: proxy + requires_restart: true + type: select + options: + - ["http", "HTTP"] + - ["socks", "SOCKS5"] + value: http + description: Protocol to use for proxy connection. + - setting: proxy_address + name: Proxy Address + depends_true: proxy + requires_restart: true + type: text + description: Proxy address, including port. + - setting: proxy_user + name: Proxy Username + depends_true: proxy + requires_restart: true + type: text + description: Leave blank for no Authentication. + - setting: proxy_password + name: Proxy Password + depends_true: proxy + requires_restart: true + type: password + description: Leave blank for no Authentication. + - setting: debug_log_emails + name: 'Debug Storage Logging: Emails' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_discord + name: 'Debug Storage Logging: Discord' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_telegram + name: 'Debug Storage Logging: Telegram' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_matrix + name: 'Debug Storage Logging: Matrix' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_invites + name: 'Debug Storage Logging: Invites' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_announcements + name: 'Debug Storage Logging: Announcements' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_expiries + name: 'Debug Storage Logging: User Expiries' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_profiles + name: 'Debug Storage Logging: Profiles' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' + - setting: debug_log_custom_content + name: 'Debug Storage Logging: Custom Message Content' + requires_restart: true + type: select + options: + - ["none", "None"] + - ["all", "All Writes"] + - ["deletion", "Deletion Only*"] + value: none + description: 'Extra debug logging for writes to the database. *: Deletion also + includes blanking out major fields, e.g. an email address.' +- section: activity_log + meta: + name: Activity Log + description: Settings for data retention of the activity log. + settings: + - setting: keep_n_records + name: Number of records to keep + requires_restart: true + type: number + value: 1000 + description: How many of the most recent activities to keep. Set to 0 to disable. + - setting: delete_after_days + name: 'Delete activities older than (days):' + requires_restart: true + type: number + value: 90 + description: If an activity was created this many days ago, it will be deleted. + Set to 0 to disable. +- section: captcha + meta: + name: Captcha + description: Settings related to user creation CAPTCHAs. + wiki_link: https://wiki.jfa-go.com/docs/captcha/ + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: Enable a CAPTCHA on the account creation form. + - setting: recaptcha + name: Use Google reCAPTCHA + requires_restart: true + type: bool + depends_true: enabled + value: false + description: More reliable, but requires some setup. See jfa-go wiki for more + info. + - setting: recaptcha_site_key + name: reCAPTCHA Site Key + requires_restart: true + type: text + depends_true: recaptcha + description: Site Key, see jfa-go wiki for how to acquire one. + - setting: recaptcha_secret_key + name: reCAPTCHA Secret Key + requires_restart: true + type: text + depends_true: recaptcha + description: Secret Key, see jfa-go wiki for how to acquire one. + - setting: recaptcha_hostname + name: Hostname + requires_restart: true + type: text + depends_true: recaptcha + description: Public host-name of jfa-go, e.g. "site.com". Don't include any subpaths. +- section: user_page + meta: + name: User Page/"My Account" + description: The User Page (My Account) allows users to access and modify info + directly, such as changing/adding contact methods, seeing their expiry date, + sending referrals or changing their password. Password resets can also be initiated + from here, given a contact method or username. Access control settings set in + "General" do not apply to this page, nor does "Access jfa-go" in the Accounts + tab. + depends_true: ui|jellyfin_login + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: true + - setting: jellyfin_login_note + name: 'Note:' + type: note + depends_true: enabled + required: false + description: Jellyfin Login must be enabled to use this feature, and password + resets with a link must be enabled for self-service. + style: critical + - setting: edit_note + name: 'Message Cards:' + type: note + depends_true: enabled + required: false + description: Click the edit icon next to the "User Page" Setting to add custom + Markdown messages that will be shown to the user. Note message cards are not + private, little effort is required for anyone to view them. + - setting: show_link + name: Show Link on Admin Login page + depends_true: enabled + type: bool + value: true + description: Whether or not to show a link to the "My Account" page on the admin + login screen, to direct lost users. + - setting: referrals + name: User Referrals + requires_restart: true + depends_true: enabled + type: bool + value: true + description: Users are given their own "invite" to send to others. + - setting: referrals_note + name: 'Using Referrals:' + type: note + depends_true: referrals + required: false + description: Create an invite with your desired settings, then either assign it + to a user in the accounts tab, or to a profile in settings. + - setting: allow_pwr_username + name: Allow PWR with username + requires_restart: true + depends_true: enabled + type: bool + value: true + description: Allow users to start a Password Reset by inputting their username. + - setting: allow_pwr_email + name: Allow PWR with email address + requires_restart: true + depends_true: enabled + type: bool + value: true + description: Allow users to start a Password Reset by inputting their email address. + - setting: allow_pwr_contact_method + name: Allow PWR with Discord/Telegram/Matrix + requires_restart: true + depends_true: enabled + type: bool + value: true + description: Allow users to start a Password Reset by inputting their Discord/Telegram/Matrix + username/id. + - setting: pwr_note + name: 'PWR Methods:' + type: note + depends_true: enabled + required: false + description: Select at least one PWR initiation method. If none are selected, + all will be enabled. +- section: password_validation + meta: + name: Password Validation + description: Password validation (minimum length, etc.) + settings: + - setting: enabled + name: Enabled + type: bool + value: true + - setting: min_length + name: Minimum Length + depends_true: enabled + type: text + value: '8' + - setting: upper + name: Minimum uppercase characters + depends_true: enabled + type: text + value: '1' + - setting: lower + name: Minimum lowercase characters + depends_true: enabled + type: text + value: '0' + - setting: number + name: Minimum number count + depends_true: enabled + type: text + value: '1' + - setting: special + name: Minimum number of special characters + depends_true: enabled + type: text + value: '0' +- section: messages + meta: + name: Messages/Notifications + description: General settings for emails/messages. + wiki_link: https://wiki.jfa-go.com/docs/emails/ + settings: + - setting: enabled + name: Enabled + required: true + requires_restart: true + type: bool + value: true + description: Enable the sending of emails/messages such as password resets, announcements, + etc. + - setting: use_24h + name: Use 24h time + depends_true: method + type: bool + value: true + - setting: date_format + name: Date format + advanced: true + depends_true: method + type: text + value: '%d/%m/%y' + description: Date format used in emails. Follows datetime.strftime format. + - setting: message + name: Help message + depends_true: method + type: text + value: Need help? contact me. + description: Message displayed at bottom of emails. + - setting: edit_note + name: 'Customize Messages:' + type: note + depends_true: enabled + required: false + description: Click the edit icon next to the "Messages/Notifications" Setting + to customize the messages sent to users with Markdown. +- section: email + meta: + name: Email + description: General email settings. + depends_true: messages|enabled + settings: + - setting: language + name: Email Language + depends_true: method + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default email language. Submit a PR on github if you'd like to translate. + - setting: no_username + name: Use email addresses as username + depends_true: method + type: bool + value: false + description: Use email address from invite form as username on Jellyfin. + - setting: method + name: Email method + type: select + options: + - ["", "Disabled"] + - ["smtp", "SMTP"] + - ["mailgun", "Mailgun"] + value: smtp + description: Method of sending email to use. + - setting: address + name: Sent from (address) + depends_true: method + type: email + value: jellyfin@jellyf.in + description: Address to send emails from + - setting: from + name: Sent from (name) + depends_true: method + type: text + value: Jellyfin + description: The name of the sender + - setting: plaintext + name: Send emails as plain text + advanced: true + depends_true: method + type: bool + value: false + description: Send emails as plain text instead of HTML. + - setting: collect + name: Collect on sign-up + depends_true: method + type: bool + value: true + description: Ask for an email address on the sign-up form. + - setting: required + name: Require on sign-up + depends_true: collect + type: bool + value: false + description: Require an email address on sign-up. + - setting: require_unique + name: Require unique address + requires_restart: true + depends_true: method + type: bool + value: false + description: Disables using the same address on multiple accounts. +- section: mailgun + meta: + name: Mailgun (Email) + description: Mailgun API connection settings + depends_true: email|method + settings: + - setting: api_url + name: API URL + type: text + value: https://api.mailgun.net... + - setting: api_key + name: API Key + type: text + value: your api key +- section: smtp + meta: + name: SMTP (Email) + description: SMTP Server connection settings. + depends_true: email|method + settings: + - setting: username + name: Username + type: text + description: Username for SMTP. Leave blank to user send from address as username. + - setting: encryption + name: Encryption Method + type: select + options: + - ["ssl_tls", "SSL/TLS"] + - ["starttls", "STARTTLS"] + value: starttls + description: Your email provider should provide different ports for each encryption + method. Generally 465 for ssl_tls, 587 for starttls. + - setting: server + name: Server address + type: text + value: smtp.jellyf.in + description: SMTP Server address. + - setting: port + name: Port + type: number + value: 465 + - setting: password + name: Password + type: password + value: smtp password + - setting: hello_hostname + name: HELLO Hostname + advanced: true + type: text + value: localhost + description: Hostname sent when sending HELLO to the SMTP server. Some servers + don't like the default "localhost" value, such as smtp-relay.gmail.com. + - setting: ssl_cert + name: Path to custom SSL certificate + advanced: true + type: text + description: Use if your SMTP server's SSL Certificate is not trusted by the system. + - setting: cert_validation + name: Verify certificate + advanced: true + type: bool + value: true + description: Warning, disabling this makes you much more vulnerable to man-in-the-middle + attacks + - setting: auth_type + name: Authentication type + advanced: false + type: select + options: + - ["0", "Plain"] + - ["1", "Login"] + - ["2", "CRAM-MD5"] + - ["3", "None"] + - ["4", "Auto"] + value: 4 + description: SMTP authentication method +- section: discord + meta: + name: Discord + description: Settings for Discord invites/signup/notifications + wiki_link: https://wiki.jfa-go.com/docs/bots/discord/ + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: "Enable signup verification through Discord and the sending of notifications + through it.\nSee the jfa-go wiki for setting up a bot." + - setting: show_on_reg + name: Show on user registration + requires_restart: true + type: bool + depends_true: enabled + value: true + description: Allow users to link their Discord on the registration page. + - setting: required + name: Require on sign-up + requires_restart: true + depends_true: enabled + type: bool + value: false + description: Require Discord connection on sign-up. See the jfa-go wiki for info + on setting this up. + - setting: require_unique + name: Require unique user + requires_restart: true + type: bool + value: false + description: Disables using the same user on multiple Jellyfin accounts. + - setting: token + name: API Token + requires_restart: true + depends_true: enabled + type: text + description: Discord Bot API Token. + - setting: start_command + name: Start command + requires_restart: true + depends_true: enabled + type: text + value: start + description: Command to start the user verification process. + - setting: channel + name: Channel to monitor + requires_restart: true + depends_true: enabled + type: text + description: Only listen to commands in specified channel. Leave blank to monitor + all. + - setting: provide_invite + name: Provide server invite + requires_restart: true + depends_true: enabled + type: bool + value: false + description: Generate a one-time discord server invite for the account creation + form. Required Bot permission "Create instant invite", you may need to re-add + the bot to your server after. + - setting: invite_channel + name: Invite channel + requires_restart: true + depends_true: provide_invite + type: text + description: Channel to invite new users to. + - setting: apply_role + name: Apply Role on connection + requires_restart: true + depends_true: enabled + type: select + options: + - ["", "None"] + description: Add the selected role to a user when they sign up. + - setting: disable_enable_role + name: Remove/add role on user enable/disable/deletion + requires_restart: true + depends_true: apply_role + type: bool + value: false + description: When a user is disabled or deleted, remove the Discord role, and + when re-enabled, add it back. + - setting: language + name: Language + depends_true: enabled + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default Discord message language. Visit weblate if you'd like to + translate. +- section: telegram + meta: + name: Telegram + description: Settings for Telegram signup/notifications. See the jfa-go wiki for + info on setting this up. + wiki_link: https://wiki.jfa-go.com/docs/bots/telegram/ + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: "Enable signup verification through Telegram and the sending of notifications + through it.\nSee the jfa-go wiki for setting up a bot." + - setting: show_on_reg + name: Show on user registration + requires_restart: true + type: bool + depends_true: enabled + value: true + description: Allow users to link their Telegram on the registration page. + - setting: required + name: Require on sign-up + requires_restart: true + depends_true: enabled + type: bool + value: false + description: Require telegram connection on sign-up. + - setting: require_unique + name: Require unique user + requires_restart: true + type: bool + value: false + description: Disables using the same user on multiple Jellyfin accounts. + - setting: token + name: API Token + requires_restart: true + depends_true: enabled + type: text + description: Telegram Bot API Token. + - setting: language + name: Language + depends_true: enabled + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default telegram message language. Visit weblate if you'd like to + translate. +- section: matrix + meta: + name: Matrix + description: Settings for Matrix invites/signup/notifications. See the jfa-go + wiki for info on setting this up. + wiki_link: https://wiki.jfa-go.com/docs/bots/matrix/ + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: "Enable signup verification through Matrix and the sending of notifications + through it.\nSee the jfa-go wiki for setting up a bot." + - setting: show_on_reg + name: Show on user registration + requires_restart: true + type: bool + depends_true: enabled + value: true + description: Allow users to link their Matrix on the registration page. + - setting: required + name: Require on sign-up + requires_restart: true + depends_true: enabled + type: bool + value: false + description: Require Matrix connection on sign-up. + - setting: require_unique + name: Require unique user + requires_restart: true + type: bool + value: false + description: Disables using the same user on multiple Jellyfin accounts. + - setting: homeserver + name: Home Server URL + requires_restart: true + depends_true: enabled + type: text + description: Matrix Home server URL. + - setting: token + name: Access Token + requires_restart: true + depends_true: enabled + type: text + description: Matrix Bot API Token. + - setting: user_id + name: Bot User ID + requires_restart: true + depends_true: enabled + type: text + description: 'User ID of bot account (Example: @jfa-bot:riot.im)' + - setting: topic + name: Chat topic + requires_restart: true + depends_true: enabled + type: text + value: Jellyfin notifications + description: Topic of Matrix private chats. + - setting: language + name: Language + depends_true: enabled + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default Matrix message language. Visit weblate if you'd like to translate. + - setting: encryption + name: End-to-end encryption + requires_restart: true + depends_true: enabled + advanced: false + type: bool + value: true + description: Enable end-to-end encryption for messages. + - setting: e2ee_note + name: 'End-to-end encryption:' + type: note + depends_true: enabled + required: false + description: If the setting is not visible to you, your jfa-go version does not + include the feature. See the wiki for more information. +- section: password_resets + meta: + name: Password Resets + description: Settings for the password reset handler. + depends_true: messages|enabled + wiki_link: https://wiki.jfa-go.com/docs/pwr/ + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: true + description: Enable to store provided email addresses, monitor Jellyfin directory + for pw-resets, and send reset pins + - setting: pwr_note + name: 'Setup:' + type: note + depends_true: enabled + required: false + description: There are multiple ways password resets can be set up. See the wiki + page for more information. + - setting: watch_directory + name: Jellyfin directory + requires_restart: true + depends_true: enabled + type: text + value: /path/to/jellyfin + description: Path to the folder Jellyfin puts password-reset files. + - setting: link_reset + name: Use reset link instead of PIN (Required for Ombi) + requires_restart: true + depends_true: enabled + type: bool + value: false + description: Send users a link to reset their password instead of a PIN. Must + be enabled to reset Ombi password at the same time as the Jellyfin password. + - setting: set_password + name: Set password through link + requires_restart: true + depends_true: link_reset + type: bool + value: false + description: Instead of automatically setting the user's password to the PIN, + allow them to set a new password through the reset link. + - setting: url_base + name: External jfa-go URL + required: true + depends_true: link_reset + type: text + value: http://accounts.jellyf.in:8056 + description: The URL at which the jfa-go admin page is accessible, including the + subfolder if you use one. You can leave this if you have one set in "Invite + Emails". This is necessary because using a reverse proxy means the program has + no way of knowing the URL itself. + deprecated: true + - setting: jfa_url + name: 'Generating Reset Links:' + type: note + depends_true: link_reset + required: false + description: Set the "External jfa-go URL" in General so that links to jfa-go + can be made. + - setting: language + name: Default reset link language + requires_restart: true + depends_true: link_reset + type: select + options: + - ["en-us", "English (US)"] + value: en-us + description: Default language for password reset success screen. + - setting: email_html + name: Custom email (HTML) + advanced: true + depends_true: enabled + type: text + description: Path to custom email html + - setting: email_text + name: Custom email (plaintext) + advanced: true + depends_true: enabled + type: text + description: Path to custom email in plain text + - setting: subject + name: Email subject + depends_true: enabled + type: text + description: Subject of password reset emails. +- section: invite_emails + meta: + name: Invite emails + description: Settings for sending invites directly to users. + depends_true: email|method + settings: + - setting: enabled + name: Enabled + type: bool + value: true + - setting: email_html + name: Custom email (HTML) + advanced: true + depends_true: enabled + type: text + description: Path to custom email HTML + - setting: email_text + name: Custom email (plaintext) + advanced: true + depends_true: enabled + type: text + description: Path to custom email in plain text + - setting: subject + name: Email subject + required: true + depends_true: enabled + type: text + description: Subject of invite emails. + - setting: url_base + name: External jfa-go URL + required: true + depends_true: enabled + type: text + value: http://accounts.jellyf.in:8056 + description: The URL at which the jfa-go root (admin page) is accessible, including + the subfolder if you use one. You can leave this if you have one set in "Invite + Emails". This is necessary because using a reverse proxy means the program has + no way of knowing the URL itself. + deprecated: true + - setting: jfa_url + name: 'Generating Links:' + type: note + depends_true: enabled + required: false + description: Set the "External jfa-go URL" in General so that links to jfa-go + can be made. +- section: template_email + meta: + name: Custom email template + description: Settings for the template used for announcements & custom messages. + HTML should include {{ .text }}, Plaintext should include {{ .plaintext }}, + and either can have {{ .message }} to include the contact message. + advanced: true + settings: + - setting: email_html + name: Custom template email (HTML) + advanced: true + depends_true: enabled + type: text + description: Path to custom email HTML template for announcements/custom messages. + - setting: email_text + name: Custom template email (plaintext) + advanced: true + depends_true: enabled + type: text + description: Path to custom email text template for announcements/custom messages. +- section: notifications + meta: + name: Admin invite notifications + description: Allows toggling "user created" and "invite expired" notifications + to be sent to the admin per-invite. + depends_true: messages|enabled + settings: + - setting: enabled + name: Enabled + required: false + requires_restart: true + type: bool + value: true + description: Enabling adds optional toggles to invites to notify on expiry and + user creation. + - setting: expiry_html + name: Expiry email (HTML) + advanced: true + depends_true: enabled + type: text + description: Path to expiry notification email HTML. + - setting: expiry_text + name: Expiry email (Plaintext) + requires_restart: false + advanced: true + depends_true: enabled + type: text + description: Path to expiry notification email in plaintext. + - setting: created_html + name: User created email (HTML) + advanced: true + depends_true: enabled + type: text + description: Path to user creation notification email HTML. + - setting: created_text + name: User created email (Plaintext) + advanced: true + depends_true: enabled + type: text + description: Path to user creation notification email in plaintext. +- section: ombi + meta: + name: Ombi Integration + description: Connect to Ombi to automatically create both Ombi and Jellyfin accounts + for new users. You'll need to add a ombi template to an existing User Profile + for accounts to be created, which you can do by refreshing then checking Settings + > User Profiles. To handle password resets for Ombi & Jellyfin, enable "Use + reset link instead of PIN". + wiki_link: https://wiki.jfa-go.com/docs/ombi/ + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: Enable to create an Ombi account for new Jellyfin users + - setting: server + name: URL + requires_restart: true + type: text + value: localhost:5000 + depends_true: enabled + description: Ombi server URL, including http(s)://. + - setting: api_key + name: API Key + requires_restart: true + type: text + depends_true: enabled + description: API Key. Get this from the first tab in Ombi settings. +- section: jellyseerr + meta: + name: Jellyseerr Integration + description: Connect to Jellyseerr to automatically trigger the import of users + on account creation, and to automatically link contact methods (email, discord + and telegram). A template must be added to a User Profile for accounts to be + created. + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: Enable the Jellyseerr integration. + - setting: usertype_note + name: 'Password Changes:' + type: note + depends_true: enabled + required: false + description: Ensure existing users on Jellyseerr are "Jellyfin User"s not "Local + User"s, as password changes are not synced with Jellyseerr. + - setting: server + name: URL + requires_restart: true + type: text + value: localhost:5000 + depends_true: enabled + description: Jellyseerr server URL. + - setting: api_key + name: API Key + requires_restart: true + type: text + depends_true: enabled + description: API Key. Get this from the first tab in Jellyseerr's settings. + - setting: import_existing + name: Import existing users to Jellyseerr + requires_restart: true + type: bool + value: false + depends_true: enabled + description: Existing users (and those created outside jfa-go) will have their + contact info imported to Jellyseerr. + - setting: constraints_note + name: 'Unique Emails:' + type: note + depends_true: import_existing + required: false + description: Jellyseerr requires email addresses to be unique. If this is not + the case, you may see errors in jfa-go's logs. You can require unique addresses + in Settings > Email. +- section: backups + meta: + name: Backups + description: Settings for database backups. Press the "Backups" button above to + create, download and restore backups. + wiki_link: https://wiki.jfa-go.com/docs/backups/ + settings: + - setting: enabled + name: Scheduled Backups + requires_restart: true + type: bool + value: false + description: Enable to generate database backups on a schedule. + - setting: path + name: Backup Path + requires_restart: true + type: text + description: Path to directory to store backups in. defaults to /backups. + - setting: every_n_minutes + name: Backup frequency (Minutes) + requires_restart: true + depends_true: enabled + type: number + value: 1440 + description: Backup after this many minutes has passed since the last. Resets + every restart. + - setting: keep_n_backups + name: Number of backups to keep + requires_restart: true + type: number + value: 20 + description: Number of most recent backups to keep. Once this is hit, the oldest + backup will be deleted before doing a new one. +- section: welcome_email + meta: + name: Welcome Message + description: Optionally send a welcome message to new users with the Jellyfin + URL and their username. + depends_true: messages|enabled + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + description: Enable to send welcome emails to new users. + - setting: subject + name: Email subject + type: text + description: Subject of welcome emails. + - setting: email_html + name: Custom email (HTML) + advanced: true + type: text + description: Path to custom email html + - setting: email_text + name: Custom email (plaintext) + advanced: true + type: text + description: Path to custom email in plain text +- section: email_confirmation + meta: + name: Email confirmation + description: If enabled, a user will be sent an email confirmation link to ensure + their password is right before they can make an account. + depends_true: email|method + settings: + - setting: enabled + name: Enabled + requires_restart: true + type: bool + value: false + - setting: subject + name: Email subject + type: text + description: Subject of email confirmation emails. + - setting: email_html + name: Custom email (HTML) + advanced: true + type: text + description: Path to custom email html + - setting: email_text + name: Custom email (plaintext) + advanced: true + type: text + description: Path to custom email in plain text +- section: user_expiry + meta: + name: User Expiry + description: When set on an invite, users will be deleted or disabled a specified + amount of time after they create their account. Expiries can also be set and + extended for invididual users, optionally with a message why. + settings: + - setting: behaviour + name: Behaviour + type: select + options: + - ["delete_user", "Delete user"] + - ["disable_user", "Disable user"] + value: disable_user + description: Whether to delete or disable users on expiry. + - setting: delete_expired_after_days + name: Delete expired accounts after (days) + type: number + value: 0 + depends_true: behaviour + description: When set, user accounts will be deleted this many days after expiring + (if "Behaviour" is "Disable user"). Set to 0 to disable. + - setting: send_email + name: Send email + type: bool + value: true + depends_true: messages|enabled + description: Send an email when a user's account expires. + - setting: subject + name: Email subject + depends_true: messages|enabled + type: text + description: Subject of user expiry emails. + - setting: email_html + name: Custom email (HTML) + advanced: true + depends_true: messages|enabled + type: text + description: Path to custom email html + - setting: email_text + name: Custom email (plaintext) + advanced: true + depends_true: messages|enabled + type: text + description: Path to custom email in plain text + - setting: adjustment_subject + name: 'Adjustment: email subject' + depends_true: messages|enabled + type: text + description: Subject of adjustment emails, sent optionally when setting/extending + an expiry. + - setting: adjustment_email_html + name: 'Adjustment: Custom email (HTML)' + advanced: true + depends_true: messages|enabled + type: text + description: Path to custom email html + - setting: adjustment_email_text + name: 'Adjustment: Custom email (plaintext)' + advanced: true + depends_true: messages|enabled + type: text + description: Path to custom email in plain text +- section: disable_enable + meta: + name: Account Disabling/Enabling + description: Subject/email files for account disabling/enabling emails. + depends_true: messages|enabled + settings: + - setting: subject_disabled + name: Email subject (Disabled) + type: text + description: Subject of account disabling emails. + - setting: subject_enabled + name: Email subject (Enabled) + type: text + description: Subject of account enabling emails. + - setting: disabled_html + name: Custom disabling email (HTML) + advanced: true + type: text + description: Path to custom email html + - setting: disabled_text + name: Custom disabling email (plaintext) + advanced: true + type: text + description: Path to custom email in plain text + - setting: enabled_html + name: Custom enabling email (HTML) + advanced: true + type: text + description: Path to custom email html + - setting: enabled_text + name: Custom enabling email (plaintext) + advanced: true + type: text + description: Path to custom email in plain text +- section: deletion + meta: + name: Account Deletion + description: Subject/email files for account deletion emails. + depends_true: messages|enabled + settings: + - setting: subject + name: Email subject + type: text + description: Subject of account deletion emails. + - setting: email_html + name: Custom email (HTML) + advanced: true + type: text + description: Path to custom email html + - setting: email_text + name: Custom email (plaintext) + advanced: true + type: text + description: Path to custom email in plain text +- section: webhooks + meta: + name: Webhooks + description: jfa-go will send a POST request to these URLs when an event occurs, + with relevant information. Request information is logged when debug logging + is enabled. + wiki_link: https://wiki.jfa-go.com/docs/webhooks/ + settings: + - setting: created + name: User Created + type: list + description: URLs to hit when an account is created through jfa-go. Sends a `respUser` + object. +- section: files + meta: + name: File Storage + description: Optional settings for changing storage locations. + advanced: true + settings: + - setting: invites + name: Invite Storage + requires_restart: true + type: text + description: Location of stored invites (json). + - setting: password_resets + name: Password Resets + requires_restart: true + type: text + description: Location of stored non-Jellyfin password resets (json). + - setting: emails + name: Email Addresses + requires_restart: true + type: text + description: Location of stored email addresses (json). + - setting: users + name: User storage + type: text + description: Stores users temporarily when a user expiry is set. + - setting: ombi_template + name: Ombi user template + type: text + description: Location of stored Ombi user template. + - setting: user_profiles + name: User Profiles + requires_restart: true + type: text + description: Location of stored user profiles (encompasses template and configuration + and displayprefs) (json) + - setting: html_templates + name: Custom HTML Template Directory + requires_restart: true + type: text + description: Path to directory containing custom versions of web ui pages. See + wiki for more info. + - setting: lang_files + name: Custom language files directory + requires_restart: true + type: text + description: The path to a directory which following the same form as the internal + 'lang/' directory. See GitHub for more info. + - setting: custom_emails + name: Custom email content + type: text + description: JSON file generated by program in settings, different from email_html/email_text. + See wiki for more info. + - setting: custom_user_page_content + name: Custom user page content + type: text + description: JSON file generated by program in settings, containing user page + messages. See wiki for more info. + - setting: telegram_users + name: Telegram users + type: text + description: Stores telegram user IDs and language preferences. + - setting: matrix_users + name: Matrix users + type: text + description: Stores matrix user IDs and language preferences. + - setting: matrix_sql + name: Matrix encryption DB + type: text + description: Stores cryptographic material for Matrix end-to-end encryption. + - setting: discord_users + name: Discord users + type: text + description: Stores discord user IDs and language preferences. + - setting: announcements + name: Announcement templates + type: text + description: Stores custom announcement templates. diff --git a/config/config-json-to-new-yaml.py b/config/config-json-to-new-yaml.py new file mode 100644 index 0000000..23800bb5 --- /dev/null +++ b/config/config-json-to-new-yaml.py @@ -0,0 +1,35 @@ +from ruamel.yaml import YAML +import json +from pathlib import Path +import sys +yaml = YAML() + +# c = yaml.load(Path(sys.argv[len(sys.argv)-1])) +with open(sys.argv[len(sys.argv)-1], 'r') as f: + c = json.load(f) + +c.pop("order") + +c1 = c.copy() +c1["sections"] = [] +for section in c["sections"]: + codeSection = { "section": section } + s = codeSection | c["sections"][section] + s.pop("order") + c1["sections"].append(s) + +c2 = c.copy() +c2["sections"] = [] + +for section in c1["sections"]: + sArray = [] + for setting in section["settings"]: + codeSetting = { "setting": setting } + s = codeSetting | section["settings"][setting] + sArray.append(s) + + section["settings"] = sArray + c2["sections"].append(section) + + +yaml.dump(c2, sys.stdout) diff --git a/config/gen-rough-schema.py b/config/gen-rough-schema.py new file mode 100644 index 0000000..fb59257 --- /dev/null +++ b/config/gen-rough-schema.py @@ -0,0 +1,40 @@ +import json +import sys + +sectionSchema = {} +metaSchema = {} +settingSchema = {} +typeValues = {} + +# c = yaml.load(Path(sys.argv[len(sys.argv)-1])) +with open(sys.argv[len(sys.argv)-1], 'r') as f: + c = json.load(f) + +for section in c["sections"]: + for key in c["sections"][section]: + sectionSchema[key] = True + + for key in c["sections"][section]["meta"]: + metaSchema[key] = c["sections"][section]["meta"][key] + + for setting in c["sections"][section]["settings"]: + for field in c["sections"][section]["settings"][setting]: + settingSchema[field] = c["sections"][section]["settings"][setting][field] + typeValues[c["sections"][section]["settings"][setting]["type"]] = True + +print("Section Content:") +for v in sectionSchema: + print(v) +print("---") +print("Meta Schema") +for v in metaSchema: + print(v, "=", type(metaSchema[v])) +print("---") +print("Setting Schema") +for v in settingSchema: + print(v, "=", type(settingSchema[v])) +print("---") +print("Possible Types") +for v in typeValues: + print(v) + diff --git a/lang.go b/lang.go index 777833c..abf20e0 100644 --- a/lang.go +++ b/lang.go @@ -1,5 +1,7 @@ package main +import "github.com/hrfee/jfa-go/common" + type langMeta struct { Name string `json:"name"` // Language to fall back on if strings are missing. Defaults to en-us. @@ -13,11 +15,11 @@ type quantityString struct { type adminLangs map[string]adminLang -func (ls *adminLangs) getOptions() [][2]string { - opts := make([][2]string, len(*ls)) +func (ls *adminLangs) getOptions() []common.Option { + opts := make([]common.Option, len(*ls)) i := 0 for key, lang := range *ls { - opts[i] = [2]string{key, lang.Meta.Name} + opts[i] = common.Option{key, lang.Meta.Name} i++ } return opts @@ -42,11 +44,11 @@ type adminLang struct { type userLangs map[string]userLang -func (ls *userLangs) getOptions() [][2]string { - opts := make([][2]string, len(*ls)) +func (ls *userLangs) getOptions() []common.Option { + opts := make([]common.Option, len(*ls)) i := 0 for key, lang := range *ls { - opts[i] = [2]string{key, lang.Meta.Name} + opts[i] = common.Option{key, lang.Meta.Name} i++ } return opts @@ -65,11 +67,11 @@ type userLang struct { type pwrLangs map[string]pwrLang -func (ls *pwrLangs) getOptions() [][2]string { - opts := make([][2]string, len(*ls)) +func (ls *pwrLangs) getOptions() []common.Option { + opts := make([]common.Option, len(*ls)) i := 0 for key, lang := range *ls { - opts[i] = [2]string{key, lang.Meta.Name} + opts[i] = common.Option{key, lang.Meta.Name} i++ } return opts @@ -82,11 +84,11 @@ type pwrLang struct { type emailLangs map[string]emailLang -func (ls *emailLangs) getOptions() [][2]string { - opts := make([][2]string, len(*ls)) +func (ls *emailLangs) getOptions() []common.Option { + opts := make([]common.Option, len(*ls)) i := 0 for key, lang := range *ls { - opts[i] = [2]string{key, lang.Meta.Name} + opts[i] = common.Option{key, lang.Meta.Name} i++ } return opts @@ -135,11 +137,11 @@ type setupLang struct { JSON string } -func (ls *setupLangs) getOptions() [][2]string { - opts := make([][2]string, len(*ls)) +func (ls *setupLangs) getOptions() []common.Option { + opts := make([]common.Option, len(*ls)) i := 0 for key, lang := range *ls { - opts[i] = [2]string{key, lang.Meta.Name} + opts[i] = common.Option{key, lang.Meta.Name} i++ } return opts @@ -152,11 +154,11 @@ type telegramLang struct { Strings langSection `json:"strings"` } -func (ts *telegramLangs) getOptions() [][2]string { - opts := make([][2]string, len(*ts)) +func (ts *telegramLangs) getOptions() []common.Option { + opts := make([]common.Option, len(*ts)) i := 0 for key, lang := range *ts { - opts[i] = [2]string{key, lang.Meta.Name} + opts[i] = common.Option{key, lang.Meta.Name} i++ } return opts diff --git a/main.go b/main.go index cf0b4f5..3b98c8c 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ import ( "github.com/hrfee/mediabrowser" "github.com/lithammer/shortuuid/v3" "gopkg.in/ini.v1" + "gopkg.in/yaml.v3" ) var ( @@ -93,7 +94,8 @@ type appContext struct { config *ini.File configPath string configBasePath string - configBase settings + configBase common.Config + patchedConfig common.Config dataPath string webFS httpFS cssClass string // Default theme, "light"|"dark". @@ -388,9 +390,11 @@ func start(asDaemon, firstCall bool) { defer app.storage.db.Close() // Read config-base for settings on web. - app.configBasePath = "config-base.json" + app.configBasePath = "config-base.yaml" configBase, _ := fs.ReadFile(localFS, app.configBasePath) - json.Unmarshal(configBase, &app.configBase) + yaml.Unmarshal(configBase, &app.configBase) + // copy it to app.patchedConfig, and patch in settings from app.config, and language stuff. + app.PatchConfigBase() secret, err := generateSecret(16) if err != nil { diff --git a/models.go b/models.go index 0d1e181..2b81672 100644 --- a/models.go +++ b/models.go @@ -212,43 +212,6 @@ type errorListDTO map[string]map[string]string type configDTO map[string]interface{} -// Below are for sending config - -type meta struct { - Name string `json:"name"` - Description string `json:"description"` - Advanced bool `json:"advanced,omitempty"` - DependsTrue string `json:"depends_true,omitempty"` - DependsFalse string `json:"depends_false,omitempty"` - WikiLink string `json:"wiki_link,omitempty"` -} - -type setting struct { - Name string `json:"name"` - Description string `json:"description"` - Required bool `json:"required"` - Advanced bool `json:"advanced,omitempty"` - RequiresRestart bool `json:"requires_restart"` - Type string `json:"type"` // Type (string, number, bool, etc.) - Value interface{} `json:"value"` - Options [][2]string `json:"options,omitempty"` - DependsTrue string `json:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled. - DependsFalse string `json:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue. - Style string `json:"style,omitempty"` - Deprecated bool `json:"deprecated,omitempty"` -} - -type section struct { - Meta meta `json:"meta"` - Order []string `json:"order"` - Settings map[string]setting `json:"settings"` -} - -type settings struct { - Order []string `json:"order"` - Sections map[string]section `json:"sections"` -} - type langDTO map[string]string type emailListDTO map[string]emailListEl diff --git a/scripts/enumerate_config.py b/scripts/enumerate_config.py deleted file mode 100644 index b46a46c..0000000 --- a/scripts/enumerate_config.py +++ /dev/null @@ -1,29 +0,0 @@ -# Since go doesn't order its json, this script adds ordered lists -# of section/setting names for the settings tab to use. -import json, argparse - -parser = argparse.ArgumentParser() -parser.add_argument("-i", "--input", help="input config base from jf-accounts") -parser.add_argument("-o", "--output", help="output config base for jfa-go") - -args = parser.parse_args() - -with open(args.input, 'r') as f: - config = json.load(f) - -newconfig = {"sections": {}, "order": []} - -for sect in config["sections"]: - newconfig["order"].append(sect) - newconfig["sections"][sect] = {} - newconfig["sections"][sect]["order"] = [] - newconfig["sections"][sect]["meta"] = config["sections"][sect]["meta"] - newconfig["sections"][sect]["settings"] = {} - for setting in config["sections"][sect]["settings"]: - newconfig["sections"][sect]["order"].append(setting) - newconfig["sections"][sect]["settings"][setting] = config["sections"][sect]["settings"][setting] - -with open(args.output, 'w') as f: - f.write(json.dumps(newconfig, indent=4)) - - diff --git a/scripts/generate_ini.py b/scripts/generate_ini.py deleted file mode 100644 index e47cc1c..0000000 --- a/scripts/generate_ini.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generates config file -import configparser -import json -import argparse -from pathlib import Path - -def fix_description(desc): - return "; " + desc.replace("\n", "\n; ") - -def generate_ini(base_file, ini_file): - """ - Generates .ini file from config-base file. - """ - with open(Path(base_file), "r") as f: - config_base = json.load(f) - - ini = configparser.RawConfigParser(allow_no_value=True) - - for section in config_base["sections"]: - ini.add_section(section) - if "meta" in config_base["sections"][section]: - ini.set(section, fix_description(config_base["sections"][section]["meta"]["description"])) - for entry in config_base["sections"][section]["settings"]: - if config_base["sections"][section]["settings"][entry]["type"] == "note": - continue - if "description" in config_base["sections"][section]["settings"][entry]: - ini.set(section, fix_description(config_base["sections"][section]["settings"][entry]["description"])) - value = config_base["sections"][section]["settings"][entry]["value"] - if isinstance(value, bool): - value = str(value).lower() - else: - value = str(value) - ini.set(section, entry, value) - - with open(Path(ini_file), "w") as config_file: - ini.write(config_file) - return True - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-i", "--input", help="input config base from jf-accounts") - parser.add_argument("-o", "--output", help="output ini") - - args = parser.parse_args() - - print(generate_ini(base_file=args.input, ini_file=args.output)) diff --git a/scripts/ini/go.mod b/scripts/ini/go.mod new file mode 100644 index 0000000..13194df --- /dev/null +++ b/scripts/ini/go.mod @@ -0,0 +1,12 @@ +module github.com/hrfee/jfa-go/scripts/ini + +replace github.com/hrfee/jfa-go/common => ../../common + +go 1.22.4 + +require ( + github.com/hrfee/jfa-go/common v0.0.0-20240824141650-fcdd4e451882 // indirect + github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/scripts/ini/go.sum b/scripts/ini/go.sum new file mode 100644 index 0000000..1e5face --- /dev/null +++ b/scripts/ini/go.sum @@ -0,0 +1,7 @@ +github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a h1:qbXZgCqb9eaPSJfLEXczQD2lxTv6jb6silMPIWW9j6o= +github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a/go.mod h1:c5HKkLayo0GrEUDlJwT12b67BL9cdPjP271Xlv/KDRQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scripts/ini/main.go b/scripts/ini/main.go new file mode 100644 index 0000000..98810fc --- /dev/null +++ b/scripts/ini/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "os" + "strings" + + "github.com/hrfee/jfa-go/common" + "gopkg.in/ini.v1" + "gopkg.in/yaml.v3" +) + +func fixDescription(desc string) string { + return "; " + strings.ReplaceAll(desc, "\n", "\n; ") +} + +func generateIni(yamlPath string, iniPath string) { + yamlFile, err := os.ReadFile(yamlPath) + if err != nil { + panic(err) + } + configBase := common.Config{} + err = yaml.Unmarshal(yamlFile, &configBase) + if err != nil { + panic(err) + } + conf := ini.Empty() + + for _, section := range configBase.Sections { + cSection, err := conf.NewSection(section.Section) + if err != nil { + panic(err) + } + if section.Meta.Description != "" { + cSection.Comment = fixDescription(section.Meta.Description) + } + for _, setting := range section.Settings { + if setting.Type == common.NoteType { + continue + } + val := "" + if setting.Value != nil { + // Easy way to convert bools and numbers to strings, + // Instead of checking setting.Type + val = fmt.Sprintf("%v", setting.Value) + } + cKey, err := cSection.NewKey(setting.Setting, val) + if err != nil { + panic(err) + } + if setting.Description != "" { + cKey.Comment = fixDescription(setting.Description) + } + // Explain how to use list type + if setting.Type == common.ListType { + if cKey.Comment != "" { + cKey.Comment += "\n" + } + cKey.Comment += `List type: duplicate and edit the line to add more entries.` + } + } + } + + err = conf.SaveTo(iniPath) + if err != nil { + panic(err) + } +} + +// Compares two inis, used to check this script does the equivalent of the old generate_ini.py. +func compareInis(p1, p2 string) { + cA, err := ini.ShadowLoad(p1) + if err != nil { + panic(err) + } + + cB, err := ini.ShadowLoad(p2) + if err != nil { + panic(err) + } + + for _, pair := range [][2]*ini.File{{cA, cB}, {cB, cA}} { + s1 := pair[0].Sections() + s2 := pair[1].Sections() + for i := range s1 { + if s1[i].Name() != s2[i].Name() { + panic(fmt.Errorf("mismatching section order: s0[i]=%s, s1[i]=%s", s1[i].Name(), s2[i].Name())) + } + // fmt.Println("Section order matches") + st1 := s1[i].Keys() + st2 := s2[i].Keys() + for i := range st1 { + if st1[i].Name() != st2[i].Name() { + panic(fmt.Errorf("mismatching setting order: st1[i]=%s, st2[i]=%s", st1[i].Name(), st2[i].Name())) + } + if st1[i].Value() != st2[i].Value() { + panic(fmt.Errorf("mismatching setting values: st1[i]=%s, st2[i]=%s", st1[i].Value(), st2[i].Value())) + } + // fmt.Println("Setting matches") + } + } + } +} + +func main() { + var yamlPath string + var iniPath string + var comparePath string + flag.StringVar(&yamlPath, "in", "", "Input of the config base in yaml.") + flag.StringVar(&iniPath, "out", "", "Output path of an ini file.") + flag.StringVar(&comparePath, "comp", "", "Path to ini file to compare against.") + + flag.Parse() + + if yamlPath == "" { + panic(errors.New("invalid yaml path")) + } + if iniPath == "" { + panic(errors.New("invalid ini path")) + } + + generateIni(yamlPath, iniPath) + + if comparePath != "" { + compareInis(iniPath, comparePath) + fmt.Println("Passed.") + } +} diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 4ee6a13..0fcfbdc 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -19,20 +19,33 @@ interface settingsChangedEvent extends Event { detail: string; } +type SettingType = string; + +const BoolType: SettingType = "bool"; +const SelectType: SettingType = "select"; +const TextType: SettingType = "text"; +const PasswordType: SettingType = "password"; +const NumberType: SettingType = "number"; +const NoteType: SettingType = "note"; +const EmailType: SettingType = "email"; +const ListType: SettingType = "list"; + interface Meta { name: string; description: string; advanced?: boolean; + disabled?: boolean; depends_true?: string; depends_false?: string; wiki_link?: string; } interface Setting { + setting: string; name: string; description: string; - required: boolean; - requires_restart: boolean; + required?: boolean; + requires_restart?: boolean; advanced?: boolean; type: string; value: string | boolean | number | string[]; @@ -67,17 +80,17 @@ class DOMSetting { protected _restart: HTMLSpanElement; protected _advanced: boolean; protected _section: string; - protected _name: string; + setting: string; hide = () => { this._hideEl.classList.add("unfocused"); - const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false }) + const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": false }) document.dispatchEvent(event); }; show = () => { this._hideEl.classList.remove("unfocused"); - const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() }) + const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() }) document.dispatchEvent(event); }; @@ -142,8 +155,8 @@ class DOMSetting { valueAsString = (): string => { return ""+this.value; }; onValueChange = () => { - const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() }) - const setEvent = new CustomEvent(`settings-set-${this._section}-${this._name}`, { "detail": this.valueAsString() }) + const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() }) + const setEvent = new CustomEvent(`settings-set-${this._section}-${this.setting}`, { "detail": this.valueAsString() }) document.dispatchEvent(event); document.dispatchEvent(setEvent); if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); } @@ -151,7 +164,7 @@ class DOMSetting { constructor(input: string, setting: Setting, section: string, name: string, inputOnTop: boolean = false) { this._section = section; - this._name = name; + this.setting = name; this._container = document.createElement("div"); this._container.classList.add("setting"); this._container.setAttribute("data-name", name); @@ -223,7 +236,7 @@ interface SText extends Setting { } class DOMText extends DOMInput implements SText { constructor(setting: Setting, section: string, name: string) { super("text", setting, section, name); } - type: string = "text"; + type: SettingType = TextType; get value(): string { return this._input.value } set value(v: string) { this._input.value = v; } } @@ -233,7 +246,7 @@ interface SPassword extends Setting { } class DOMPassword extends DOMInput implements SPassword { constructor(setting: Setting, section: string, name: string) { super("password", setting, section, name); } - type: string = "password"; + type: SettingType = PasswordType; get value(): string { return this._input.value } set value(v: string) { this._input.value = v; } } @@ -243,7 +256,7 @@ interface SEmail extends Setting { } class DOMEmail extends DOMInput implements SEmail { constructor(setting: Setting, section: string, name: string) { super("email", setting, section, name); } - type: string = "email"; + type: SettingType = EmailType; get value(): string { return this._input.value } set value(v: string) { this._input.value = v; } } @@ -253,7 +266,7 @@ interface SNumber extends Setting { } class DOMNumber extends DOMInput implements SNumber { constructor(setting: Setting, section: string, name: string) { super("number", setting, section, name); } - type: string = "number"; + type: SettingType = NumberType; get value(): number { return +this._input.value; } set value(v: number) { this._input.value = ""+v; } } @@ -263,7 +276,7 @@ interface SList extends Setting { } class DOMList extends DOMSetting implements SList { protected _inputs: HTMLDivElement; - type: string = "list"; + type: SettingType = ListType; valueAsString = (): string => { return this.value.join("|"); }; @@ -334,7 +347,7 @@ interface SBool extends Setting { value: boolean; } class DOMBool extends DOMSetting implements SBool { - type: string = "bool"; + type: SettingType = BoolType; get value(): boolean { return this._input.checked; } set value(state: boolean) { this._input.checked = state; } @@ -357,7 +370,7 @@ interface SSelect extends Setting { value: string; } class DOMSelect extends DOMSetting implements SSelect { - type: string = "bool"; + type: SettingType = SelectType; private _options: string[][]; get options(): string[][] { return this._options; } @@ -395,7 +408,7 @@ interface SNote extends Setting { class DOMNote extends DOMSetting implements SNote { private _nameEl: HTMLElement; private _description: HTMLElement; - type: string = "note"; + type: SettingType = NoteType; private _style: string; // We're a note, no one depends on us so we don't need to broadcast a state change. @@ -457,9 +470,9 @@ class DOMNote extends DOMSetting implements SNote { } interface Section { + section: string; meta: Meta; - order: string[]; - settings: { [settingName: string]: Setting }; + settings: Setting[]; } class sectionPanel { @@ -491,50 +504,49 @@ class sectionPanel { this.update(s); } update = (s: Section) => { - for (let name of s.order) { - let setting: Setting = s.settings[name]; - if (name in this._settings) { - this._settings[name].update(setting); + for (let setting of s.settings) { + if (setting.setting in this._settings) { + this._settings[setting.setting].update(setting); } else { if (setting.deprecated) continue; switch (setting.type) { - case "text": - setting = new DOMText(setting, this._sectionName, name); + case TextType: + setting = new DOMText(setting, this._sectionName, setting.setting); break; - case "password": - setting = new DOMPassword(setting, this._sectionName, name); + case PasswordType: + setting = new DOMPassword(setting, this._sectionName, setting.setting); break; - case "email": - setting = new DOMEmail(setting, this._sectionName, name); + case EmailType: + setting = new DOMEmail(setting, this._sectionName, setting.setting); break; - case "number": - setting = new DOMNumber(setting, this._sectionName, name); + case NumberType: + setting = new DOMNumber(setting, this._sectionName, setting.setting); break; - case "bool": - setting = new DOMBool(setting as SBool, this._sectionName, name); + case BoolType: + setting = new DOMBool(setting as SBool, this._sectionName, setting.setting); break; - case "select": - setting = new DOMSelect(setting as SSelect, this._sectionName, name); + case SelectType: + setting = new DOMSelect(setting as SSelect, this._sectionName, setting.setting); break; - case "note": + case NoteType: setting = new DOMNote(setting as SNote, this._sectionName); break; - case "list": - setting = new DOMList(setting as SList, this._sectionName, name); + case ListType: + setting = new DOMList(setting as SList, this._sectionName, setting.setting); break; } if (setting.type != "note") { - this.values[name] = ""+setting.value; + this.values[setting.setting] = ""+setting.value; // settings-section-name: Implies the setting changed or was shown/hidden. // settings-set-section-name: Implies the setting changed. - document.addEventListener(`settings-set-${this._sectionName}-${name}`, (event: CustomEvent) => { + document.addEventListener(`settings-set-${this._sectionName}-${setting.setting}`, (event: CustomEvent) => { // const oldValue = this.values[name]; - this.values[name] = event.detail; + this.values[setting.setting] = event.detail; document.dispatchEvent(new CustomEvent("settings-section-changed")); }); } this._section.appendChild(setting.asElement()); - this._settings[name] = setting; + this._settings[setting.setting] = setting; } } } @@ -552,8 +564,7 @@ class sectionPanel { } interface Settings { - order: string[]; - sections: { [sectionName: string]: Section }; + sections: Section[]; } export class settingsList { @@ -854,65 +865,65 @@ export class settingsList { } addLoader(this._loader, false, true); _get("/config", null, (req: XMLHttpRequest) => { - if (req.readyState == 4) { - if (req.status != 200) { - window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings")); - return; - } - this._settings = req.response as Settings; - for (let name of this._settings.order) { - if (name in this._sections) { - this._sections[name].update(this._settings.sections[name]); - } else { - if (name == "messages" || name == "user_page") { - const editButton = document.createElement("div"); - editButton.classList.add("tooltip", "left"); - editButton.innerHTML = ` - - - - - ${window.lang.get("strings", "customizeMessages")} - - `; - (editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => { - this._messageEditor.showList(name == "messages" ? "email" : "user"); - }; - this.addSection(name, this._settings.sections[name], editButton); - } else if (name == "updates") { - const icon = document.createElement("span") as HTMLSpanElement; - if (window.updater.updateAvailable) { - icon.classList.add("button", "~urge"); - icon.innerHTML = ``; - icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show); - } - this.addSection(name, this._settings.sections[name], icon); - } else if (name == "matrix" && !window.matrixEnabled) { - const addButton = document.createElement("div"); - addButton.classList.add("tooltip", "left"); - addButton.innerHTML = ` - + - - ${window.lang.strings("linkMatrix")} - - `; - (addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix; - this.addSection(name, this._settings.sections[name], addButton); - } else { - this.addSection(name, this._settings.sections[name]); + if (req.readyState != 4) return; + if (req.status != 200) { + window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings")); + return; + } + this._settings = req.response as Settings; + for (let section of this._settings.sections) { + if (section.meta.disabled) continue; + if (section.section in this._sections) { + this._sections[section.section].update(section); + } else { + if (section.section == "messages" || section.section == "user_page") { + const editButton = document.createElement("div"); + editButton.classList.add("tooltip", "left"); + editButton.innerHTML = ` + + + + + ${window.lang.get("strings", "customizeMessages")} + + `; + (editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => { + this._messageEditor.showList(section.section == "messages" ? "email" : "user"); + }; + this.addSection(section.section, section, editButton); + } else if (section.section == "updates") { + const icon = document.createElement("span") as HTMLSpanElement; + if (window.updater.updateAvailable) { + icon.classList.add("button", "~urge"); + icon.innerHTML = ``; + icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show); } + this.addSection(section.section, section, icon); + } else if (section.section == "matrix" && !window.matrixEnabled) { + const addButton = document.createElement("div"); + addButton.classList.add("tooltip", "left"); + addButton.innerHTML = ` + + + + ${window.lang.strings("linkMatrix")} + + `; + (addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix; + this.addSection(section.section, section, addButton); + } else { + this.addSection(section.section, section); } } - removeLoader(this._loader); - for (let i = 0; i < this._loader.children.length; i++) { - this._loader.children[i].classList.remove("invisible"); - } - this._showPanel(this._settings.order[0]); - document.dispatchEvent(new CustomEvent("settings-loaded")); - document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false })); - this._saveButton.classList.add("unfocused"); - this._needsRestart = false; } + removeLoader(this._loader); + for (let i = 0; i < this._loader.children.length; i++) { + this._loader.children[i].classList.remove("invisible"); + } + this._showPanel(this._settings.sections[0].section); + document.dispatchEvent(new CustomEvent("settings-loaded")); + document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false })); + this._saveButton.classList.add("unfocused"); + this._needsRestart = false; }) }; @@ -923,31 +934,31 @@ export class settingsList { if (query.replace(/\s+/g, "") == "") query = ""; let firstVisibleSection = ""; - for (let section of this._settings.order) { + for (let section of this._settings.sections) { - let dependencyCard = this._sections[section].asElement().querySelector(".settings-dependency-message"); + let dependencyCard = this._sections[section.section].asElement().querySelector(".settings-dependency-message"); if (dependencyCard) dependencyCard.remove(); dependencyCard = null; let dependencyList = null; // hide button, unhide if matched - this._buttons[section].classList.add("unfocused"); + this._buttons[section.section].classList.add("unfocused"); let matchedSection = false; - if (section.toLowerCase().includes(query) || - this._settings.sections[section].meta.name.toLowerCase().includes(query) || - this._settings.sections[section].meta.description.toLowerCase().includes(query)) { - if ((this._settings.sections[section].meta.advanced && this._advanced) || !(this._settings.sections[section].meta.advanced)) { - this._buttons[section].classList.remove("unfocused"); - firstVisibleSection = firstVisibleSection || section; + if (section.section.toLowerCase().includes(query) || + section.meta.name.toLowerCase().includes(query) || + section.meta.description.toLowerCase().includes(query)) { + if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) { + this._buttons[section.section].classList.remove("unfocused"); + firstVisibleSection = firstVisibleSection || section.section; matchedSection = true; } } - const sectionElement = this._sections[section].asElement(); - for (let setting of this._settings.sections[section].order) { - if (this._settings.sections[section].settings[setting].type == "note") continue; - const element = sectionElement.querySelector(`div[data-name="${setting}"]`) as HTMLElement; + const sectionElement = this._sections[section.section].asElement(); + for (let setting of section.settings) { + if (setting.type == "note") continue; + const element = sectionElement.querySelector(`div[data-name="${setting.setting}"]`) as HTMLElement; // If we match the whole section, don't bother searching settings. if (matchedSection) { @@ -959,17 +970,17 @@ export class settingsList { // element.classList.remove("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low"); element.classList.add("opacity-50", "pointer-events-none"); element.setAttribute("aria-disabled", "true"); - if (setting.toLowerCase().includes(query) || - this._settings.sections[section].settings[setting].name.toLowerCase().includes(query) || - this._settings.sections[section].settings[setting].description.toLowerCase().includes(query) || - String(this._settings.sections[section].settings[setting].value).toLowerCase().includes(query)) { - if ((this._settings.sections[section].meta.advanced && this._advanced) || !(this._settings.sections[section].meta.advanced)) { - this._buttons[section].classList.remove("unfocused"); - firstVisibleSection = firstVisibleSection || section; + if (setting.setting.toLowerCase().includes(query) || + setting.name.toLowerCase().includes(query) || + setting.description.toLowerCase().includes(query) || + String(setting.value).toLowerCase().includes(query)) { + if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) { + this._buttons[section.section].classList.remove("unfocused"); + firstVisibleSection = firstVisibleSection || section.section; } const shouldShow = (query != "" && - ((this._settings.sections[section].settings[setting].advanced && this._advanced) || - !(this._settings.sections[section].settings[setting].advanced))); + ((setting.advanced && this._advanced) || + !(setting.advanced))); if (shouldShow || query == "") { // element.classList.add("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low"); element.classList.remove("opacity-50", "pointer-events-none"); @@ -989,21 +1000,21 @@ export class settingsList { `; dependencyList = dependencyCard.querySelector(".settings-dependency-list") as HTMLUListElement; // Insert it right after the description - this._sections[section].asElement().insertBefore(dependencyCard, this._sections[section].asElement().querySelector(".settings-section-description").nextElementSibling); + this._sections[section.section].asElement().insertBefore(dependencyCard, this._sections[section.section].asElement().querySelector(".settings-section-description").nextElementSibling); } const li = document.createElement("li"); if (shouldShow) { - const depCode = this._settings.sections[section].settings[setting].depends_true || this._settings.sections[section].settings[setting].depends_false; - const dep = splitDependant(section, depCode); + const depCode = setting.depends_true || setting.depends_false; + const dep = splitDependant(section.section, depCode); let depName = this._settings.sections[dep[0]].settings[dep[1]].name; - if (dep[0] != section) { + if (dep[0] != section.section) { depName = this._settings.sections[dep[0]].meta.name + " > " + depName; } - li.textContent = window.lang.strings("settingsDependsOn").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`).replace("{dependency}", `"`+depName+`"`); + li.textContent = window.lang.strings("settingsDependsOn").replace("{setting}", `"`+setting.name+`"`).replace("{dependency}", `"`+depName+`"`); } else { - li.textContent = window.lang.strings("settingsAdvancedMode").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`); + li.textContent = window.lang.strings("settingsAdvancedMode").replace("{setting}", `"`+setting.name+`"`); } dependencyList.appendChild(li); }