1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-04 10:06:24 -07:00

Add more comments to config struct fields, rename ListenIP to ListenAddress and SiteDomain to SiteHost, since they are more accurate.

This commit is contained in:
Eggbertx 2025-03-02 12:44:42 -08:00
parent f27deaad34
commit 94f2deadec
14 changed files with 134 additions and 93 deletions

View file

@ -38,7 +38,6 @@ func cleanup() {
func main() {
fmt.Printf("Starting gochan v%s\n", versionStr)
config.InitConfig(versionStr)
config.SetVerbose(true)
uid, gid := config.GetUser()
systemCritical := config.GetSystemCriticalConfig()
@ -103,7 +102,6 @@ func main() {
for _, board := range gcsql.AllBoards {
if _, err = board.DeleteOldThreads(); err != nil {
fmt.Printf("Error deleting old threads for board /%s/: %s\n", board.Dir, err)
cleanup()
gcutil.LogFatal().Err(err).Caller().
Str("board", board.Dir).

View file

@ -24,7 +24,7 @@ func initServer() {
var listener net.Listener
var err error
systemCritical := config.GetSystemCriticalConfig()
listenAddr := net.JoinHostPort(systemCritical.ListenIP, strconv.Itoa(systemCritical.Port))
listenAddr := net.JoinHostPort(systemCritical.ListenAddress, strconv.Itoa(systemCritical.Port))
router := server.GetRouter()
router.GET(config.WebPath("/captcha"), bunrouter.HTTPHandlerFunc(posting.ServeCaptcha))
@ -42,12 +42,9 @@ func initServer() {
if systemCritical.UseFastCGI {
listener, err = net.Listen("tcp", listenAddr)
if err != nil {
if !systemCritical.Verbose {
fmt.Printf("Failed listening on %s:%d: %s", systemCritical.ListenIP, systemCritical.Port, err.Error())
}
gcutil.LogFatal().Err(err).Caller().
Str("ListenIP", systemCritical.ListenIP).
Int("Port", systemCritical.Port).Send()
Str("ListenAddress", systemCritical.ListenAddress).
Int("Port", systemCritical.Port).Msg("Failed listening on address/port")
}
err = fcgi.Serve(listener, router)
} else {
@ -60,9 +57,6 @@ func initServer() {
}
if err != nil {
if !systemCritical.Verbose {
fmt.Println("Error initializing server:", err.Error())
}
gcutil.LogFatal().Err(err).Caller().
Msg("Error initializing server")
}

View file

@ -2,7 +2,7 @@
See [gochan.example.json](examples/configs/gochan.example.json) for an example gochan.json.
## Server-critical stuff
* You'll need to edit some of the values like `ListenIP` and `UseFastCGI` based on your system's setup. For an example nginx configuration, see [gochan-fastcgi.nginx](examples/configs/gochan-fastcgi.nginx) for FastCGI and [gochan-http.nginx](examples/configs/gochan-http.nginx) for passing through HTTP.
* You'll need to edit some of the values like `ListenAddress` and `UseFastCGI` based on your system's setup. For an example nginx configuration, see [gochan-fastcgi.nginx](examples/configs/gochan-fastcgi.nginx) for FastCGI and [gochan-http.nginx](examples/configs/gochan-http.nginx) for passing through HTTP.
* `DocumentRoot` refers to the root directory on your filesystem where gochan will look for requested files.
* `TemplateDir` refers to the directory where gochan will load the templates from.
* `LogDir` refers to the directory where gochan will write the logs to.
@ -13,14 +13,14 @@ See [gochan.example.json](examples/configs/gochan.example.json) for an example g
Valid `DBtype` values are "mysql" and "postgres" (sqlite3 is no longer supported for stability reasons, though that may or may not come back).
1. To connect to a MySQL database, set `DBhost` to "x.x.x.x:3306" (replacing x.x.x.x with your database server's IP or domain) or a different port, if necessary. You can also use a UNIX socket if you have it set up, like "unix(/var/run/mysqld/mysqld.sock)".
2. To connect to a PostgreSQL database, set `DBhost` to the IP address or hostname. Using a UNIX socket may work as well, but it is currently untested.
3. Set `SiteDomain`, since these are necessary in order to post and log in as a staff member.
3. Set `SiteHost`, since these are necessary in order to post and log in as a staff member.
3. If you want to see debugging info/noncritical warnings, set verbosity to 1.
4. If `DBprefix` is set (not required), all gochan table names will be prefixed with the `DBprefix` value. Once you run gochan for the first time, you really shouldn't edit this value, since gochan will assume the tables are missing.
## Website configuration
* `SiteName` is used for the name displayed on the home page.
* `SiteSlogan` is used for the slogan (if set) on the home page.
* `SiteDomain` is used for links throughout the site.
* `SiteHost` is used for links throughout the site.
* `WebRoot` is used as the prefix for boards, files, and pretty much everything on the site. If it isn't set, "/" will be used.
## GeoIP/Flag configuration

View file

@ -1,5 +1,5 @@
{
"ListenIP": "gochan-server",
"ListenAddress": "gochan-server",
"Port": 80,
"UseFastCGI": false,
"DocumentRoot": "/var/www/gochan",
@ -9,7 +9,7 @@
"PluginSettings": null,
"SiteHeaderURL": "",
"WebRoot": "/",
"SiteDomain": "127.0.0.1",
"SiteHost": "127.0.0.1",
"DBtype": "mysql",
"DBhost": "gochan-mariadb:3306",
"DBname": "gochan",

View file

@ -1,5 +1,5 @@
{
"ListenIP": "127.0.0.1",
"ListenAddress": "127.0.0.1",
"Port": 8080,
"FirstPage": ["index.html","firstrun.html","1.html"],
"Username": "",
@ -27,7 +27,7 @@
"SiteName": "Gochan",
"SiteSlogan": "",
"SiteDomain": "127.0.0.1",
"SiteHost": "127.0.0.1",
"WebRoot": "/",
"FingerprintVideoThumbnails": false,

View file

@ -13,7 +13,7 @@ base_headers["Content-Type"] = "application/x-www-form-urlencoded"
local key = "" -- read from akismet_key.txt
local function check_api_key()
local form = "blog=" .. url.query_escape("http://" .. config.system_critical_config().SiteDomain) ..
local form = "blog=" .. url.query_escape("http://" .. config.system_critical_config().SiteHost) ..
"&key=" .. key
local resp, err = http.post(check_key_url, {
form = form,
@ -51,7 +51,7 @@ local function check_akismet(post, user_agent, referrer)
comment_type = "forum-post"
end
local form = "blog=" .. url.query_escape("http://" .. config.system_critical_config().SiteDomain) ..
local form = "blog=" .. url.query_escape("http://" .. config.system_critical_config().SiteHost) ..
"&user_ip=" .. url.query_escape(post.IP) ..
"&user_agent=" .. url.query_escape(user_agent) ..
"&referrer=" .. url.query_escape(referrer) ..

View file

@ -39,15 +39,25 @@ type GochanConfig struct {
}
// ValidateValues checks to make sure that the configuration options are usable
// (e.g., ListenIP is a valid IP address, Port isn't a negative number, etc)
// (e.g., ListenAddress is a valid IP address, Port isn't a negative number, etc)
func (gcfg *GochanConfig) ValidateValues() error {
changed := false
if gcfg.SiteDomain == "" {
return &InvalidValueError{Field: "SiteDomain", Value: gcfg.SiteDomain, Details: "must be set"}
if gcfg.ListenIP != "" && gcfg.ListenAddress == "" {
gcfg.ListenAddress = gcfg.ListenIP
changed = true
}
if strings.Contains(gcfg.SiteDomain, " ") || strings.Contains(gcfg.SiteDomain, "://") {
return &InvalidValueError{Field: "SiteDomain", Value: gcfg.SiteDomain, Details: "must be a host (port optional)"}
if gcfg.SiteDomain != "" && gcfg.SiteHost == "" {
gcfg.SiteHost = gcfg.SiteDomain
changed = true
}
if gcfg.SiteHost == "" {
return &InvalidValueError{Field: "SiteHost", Value: gcfg.SiteHost, Details: "must be set"}
}
if strings.Contains(gcfg.SiteHost, " ") || strings.Contains(gcfg.SiteHost, "://") {
return &InvalidValueError{Field: "SiteHost", Value: gcfg.SiteHost, Details: "must be a valid host (port optional)"}
}
_, err := durationutil.ParseLongerDuration(gcfg.CookieMaxAge)
@ -134,110 +144,152 @@ func (gcfg *GochanConfig) Write() error {
type SQLConfig struct {
// DBtype is the type of SQL database to use. Currently supported values are "mysql", "postgres", and "sqlite3"
DBtype string
// DBhost is the hostname or IP address of the SQL server, or the path to the SQLite database file
DBhost string
// DBname is the name of the SQL database to connect to
DBname string
// DBusername is the username to use when authenticating with the SQL server
DBusername string
// DBpassword is the password to use when authenticating with the SQL server
DBpassword string
// DBprefix is the prefix to add to table names in the database
// DBprefix is the prefix to add to table names in the database. It is not requried but may be useful if you need to share a database.
DBprefix string
// DBTimeoutSeconds sets the timeout for SQL queries in seconds, 0 means no timeout
// default: 15
// DBTimeoutSeconds sets the timeout for SQL queries in seconds, 0 means no timeout.
// Default: 15
DBTimeoutSeconds int
// DBMaxOpenConnections is the maximum number of open connections to the database connection pool
// default: 10
// DBMaxOpenConnections is the maximum number of open connections to the database connection pool.
// Default: 10
DBMaxOpenConnections int
// DBMaxIdleConnections is the maximum number of idle connections to the database connection pool
// default: 10
// DBMaxIdleConnections is the maximum number of idle connections to the database connection pool.
// Default: 10
DBMaxIdleConnections int
// DBConnMaxLifetimeMin is the maximum lifetime of a connection in minutes
// default: 3
// DBConnMaxLifetimeMin is the maximum lifetime of a connection in minutes.
// Default: 3
DBConnMaxLifetimeMin int
}
/*
SystemCriticalConfig contains configuration options that are extremely important, and fucking with them while
the server is running could have site breaking consequences. It should only be changed by modifying the configuration
file and restarting the server.
*/
// SystemCriticalConfig contains configuration options that are extremely important, and fucking with them while
// the server is running could have site breaking consequences. It should only be changed by modifying the configuration
// file and restarting the server.
type SystemCriticalConfig struct {
// ListenIP is the IP address that the server will listen on
// ListenAddress is the IP address or domain name that the server will listen on
ListenAddress string
// ListenIP is an alias for the ListenAddress field.
// Deprecated: Use ListenAddress instead
ListenIP string
// Port is the port that the server will listen on
Port int
// UseFastCGI tells the server to listen on FastCGI instead of HTTP if true
UseFastCGI bool
// DocumentRoot is the path to the directory that contains the served static files
DocumentRoot string
// TemplateDir is the path to the directory that contains the template files
TemplateDir string
// LogDir is the path to the directory that contains the log files. It must be writable by the server and will be created if it doesn't exist
LogDir string
// Plugins is a list of paths to plugins to be loaded on startup. In Windows, only .lua plugins are supported. In Unix, .so plugins are also supported,
// but they must be compiled with the same Go version as the server and must be compiled in plugin mode
Plugins []string
PluginSettings map[string]any
Plugins []string
// WebRoot is the base URL path that the server will serve files and generated pages from.
// default: /
// Default: /
WebRoot string
// SiteDomain is the domain name of the site, e.g. "example.com"
// SiteHost is the publicly accessible domain name or IP address of the site, e.g. "example.com" used for anti-spam checking
SiteHost string
// SiteDomain is an alias for the the SiteHost field.
//
// Deprecated: Use SiteHost instead
SiteDomain string
SQLConfig
// CheckRequestReferer tells the server to validate the Referer header from requests to prevent CSRF attacks.
// default: true
// Default: true
CheckRequestReferer bool
Verbose bool `json:"DebugMode"`
// Verbose currently is not used and may be removed, to be replaced with more granular logging options
Verbose bool `json:"DebugMode"`
// RandomSeed is a random string used for generating secure tokens. It will be generated if not set and must not be changed
RandomSeed string
Version *GochanVersion `json:"-"`
TimeZone int `json:"-"`
Version *GochanVersion `json:"-"`
TimeZone int `json:"-"`
}
// SiteConfig contains information about the site/community, e.g. the name of the site, the slogan (if set),
// the first page to look for if a directory is requested, etc
type SiteConfig struct {
// FirstPage is a list of possible filenames to look for if a directory is requested
// default: ["index.html", "firstrun.html", "1.html"]
// Default: ["index.html", "firstrun.html", "1.html"]
FirstPage []string
// Username is the name of the user that the server will run as, if set, or the current user if empty or unset. It must be a valid user on the system if it is set
// Username is the name of the user that the server will run as, if set, or the current user if empty or unset.
// It must be a valid user on the system if it is set
Username string
// CookieMaxAge is the parsed max age duration of cookies, e.g. "1 year 2 months 3 days 4 hours" or "1y2mo3d4h"
// default: 1y
CookieMaxAge string
// CookieMaxAge is the parsed max age duration of cookies, e.g. "1 year 2 months 3 days 4 hours" or "1y2mo3d4h".
// Default: 1y
CookieMaxAge string
// StaffSessionDuration is the parsed max age duration of staff session cookies, e.g. "1 year 2 months 3 days 4 hours" or "1y2mo3d4h".
// Default: 3mo
StaffSessionDuration string
// Lockdown prevents users from posting if true
// default: false
// Default: false
Lockdown bool
// LockdownMessage is the message displayed to users if they try to cretae a post when the site is in lockdown
// default: This imageboard has temporarily disabled posting. We apologize for the inconvenience
// Default: This imageboard has temporarily disabled posting. We apologize for the inconvenience
LockdownMessage string
// SiteName is the name of the site, displayed in the title and front page header
// default: Gochan
SiteName string
// Default: Gochan
SiteName string
// SiteSlogan is the community slogan displayed on the front page below the site name
SiteSlogan string
// Modboard was intended to be the board that moderators would use to discuss moderation, but it is not currently used.
// Deprecated: This field is not currently used and may be removed in the future
Modboard string
// MaxRecentPosts is the number of recent posts to display on the front page
// default: 15
// Default: 15
MaxRecentPosts int
// RecentPostsWithNoFile determines whether to include posts with no file in the recent posts list
// default: false
// Default: false
RecentPostsWithNoFile bool
// EnableAppeals determines whether to allow users to appeal bans
// default: true
// Default: true
EnableAppeals bool
// MinifyHTML tells the server to minify HTML output before sending it to the client
// default: true
// Default: true
MinifyHTML bool
// MinifyJS tells the server to minify JavaScript and JSON output before sending it to the client
// default: true
// Default: true
MinifyJS bool
// GeoIPType is the type of GeoIP database to use. Currently only "mmdb" is supported, though other types may be provided by plugins
GeoIPType string
@ -246,20 +298,24 @@ type SiteConfig struct {
Captcha CaptchaConfig
// FingerprintVideoThumbnails determines whether to use video thumbnails for image fingerprinting. If false, the video file will not be checked by fingerprinting filters
// default: false
// Default: false
FingerprintVideoThumbnails bool
// FingerprintHashLength is the length of the hash used for image fingerprinting
// default: 16
// Default: 16
FingerprintHashLength int
}
type CaptchaConfig struct {
// Type is the type of captcha to use. Currently only "hcaptcha" is supported
Type string
// OnlyNeededForThreads determines whether to require a captcha only when creating a new thread, or for all posts
OnlyNeededForThreads bool
// SiteKey is the public key for the captcha service. Usage depends on the captcha service
SiteKey string
// AccountSecret is the secret key for the captcha service. Usage depends on the captcha service
AccountSecret string
}
@ -281,14 +337,15 @@ type PageBanner struct {
}
// BoardConfig contains information about a specific board to be stored in /path/to/board/board.json
// If a board doesn't have board.json, the site's default board config (with values set in gochan.json) will be used
// or all boards if it is stored in the main gochan.json file. If a board doesn't have board.json,
// the site's default board config (with values set in gochan.json) will be used
type BoardConfig struct {
// InheritGlobalStyles determines whether to use the global styles in addition to the board's styles, as opposed to only the board's styles
InheritGlobalStyles bool
// Styles is a list of Gochan themes with Name and Filename fields, choosable by the user
Styles []Style
// DefaultStyle is the filename of the default style to use for the board or the site
// default: pipes.css
// DefaultStyle is the filename of the default style to use for the board or the site.
// Default: pipes.css
DefaultStyle string
// Banners is a list of banners to display on the board's front page, with Filename, Width, and Height fields
Banners []PageBanner
@ -459,14 +516,6 @@ func DeleteBoardConfig(dir string) {
delete(boardConfigs, dir)
}
func VerboseMode() bool {
return cfg.testing || cfg.SystemCriticalConfig.Verbose
}
func SetVerbose(verbose bool) {
cfg.Verbose = verbose
}
func GetVersion() *GochanVersion {
return cfg.Version
}

View file

@ -6,7 +6,7 @@ const (
// the bare minimum fields required to pass GochanConfig.validate.
// this doesn't mean that the values are valid, just that they exist
bareMinimumJSON = `{
"ListenIP": "127.0.0.1",
"ListenAddress": "127.0.0.1",
"Port": 8080,
"Username": "gochan",
"UseFastCGI": true,
@ -15,7 +15,7 @@ const (
"DBname": "gochan",
"DBusername": "gochan",
"DBpassword": "",
"SiteDomain": "127.0.0.1",
"SiteHost": "127.0.0.1",
"SiteWebFolder": "/",
"Styles": [
@ -27,7 +27,7 @@ const (
"RandomSeed": "jeiwohaeiogpehwgui"
}`
validCfgJSON = `{
"ListenIP": "127.0.0.1",
"ListenAddress": "127.0.0.1",
"Port": 8080,
"FirstPage": ["index.html","firstrun.html","1.html"],
"Username": "gochan",
@ -53,7 +53,7 @@ const (
"SiteName": "Gochan",
"SiteSlogan": "",
"SiteDomain": "127.0.0.1",
"SiteHost": "127.0.0.1",
"SiteWebFolder": "/",
"Styles": [

View file

@ -35,7 +35,7 @@ func TestPreload(t *testing.T) {
desc: "access system critical config from lua",
luaIn: `local config = require("config")
sys_cfg = config.system_critical_config()
return sys_cfg.ListenIP`,
return sys_cfg.ListenAddress`,
expectOut: lua.LString("127.0.0.1"),
},
{

View file

@ -77,7 +77,7 @@ func InitConfig(versionStr string) {
if strings.HasSuffix(os.Args[0], ".test") {
// create a dummy config for testing if we're using go test
cfg = defaultGochanConfig
cfg.ListenIP = "127.0.0.1"
cfg.ListenAddress = "127.0.0.1"
cfg.Port = 8080
cfg.UseFastCGI = true
cfg.testing = true
@ -86,7 +86,7 @@ func InitConfig(versionStr string) {
cfg.DBhost = "./testdata/gochantest.db"
cfg.DBname = "gochan"
cfg.DBusername = "gochan"
cfg.SiteDomain = "127.0.0.1"
cfg.SiteHost = "127.0.0.1"
cfg.RandomSeed = "test"
cfg.Version = ParseVersion(versionStr)
cfg.SiteSlogan = "Gochan testing"

View file

@ -35,7 +35,7 @@ events.trigger_event("newPost", "blah", 16, 3.14, true, nil)`
local system_critical_cfg = config.system_critical_config()
local site_cfg = config.site_config()
local board_cfg = config.board_config()
return { ListenIP = system_critical_cfg.ListenIP, SiteSlogan = site_cfg.SiteSlogan, DefaultStyle = board_cfg.DefaultStyle }`
return { ListenAddress = system_critical_cfg.ListenAddress, SiteSlogan = site_cfg.SiteSlogan, DefaultStyle = board_cfg.DefaultStyle }`
)
func initPluginTests() {
@ -79,7 +79,7 @@ func TestConfigModule(t *testing.T) {
err := lState.DoString(configTestingStr)
assert.NoError(t, err)
returnTable := lState.CheckTable(-1)
assert.Equal(t, "127.0.0.1", returnTable.RawGetString("ListenIP").(lua.LString).String())
assert.Equal(t, "127.0.0.1", returnTable.RawGetString("ListenAddress").(lua.LString).String())
assert.Equal(t, "Gochan testing", returnTable.RawGetString("SiteSlogan").(lua.LString).String())
assert.Equal(t, "pipes.css", returnTable.RawGetString("DefaultStyle").(lua.LString).String())
}

View file

@ -62,7 +62,7 @@ func createSession(key, username, password string, request *http.Request, writer
gcutil.LogWarning().
Int("refererResult", int(refererResult)).
Str("referer", request.Referer()).
Str("siteDomain", config.GetSystemCriticalConfig().SiteDomain).
Str("SiteHost", config.GetSystemCriticalConfig().SiteHost).
Str("staff", username).
Msg("Rejected login from possible spambot")
return serverutil.ErrSpambot

View file

@ -38,7 +38,7 @@ func CheckReferer(request *http.Request) (RefererResult, error) {
}
systemCriticalConfig := config.GetSystemCriticalConfig()
siteURLBase := url.URL{
Host: systemCriticalConfig.SiteDomain,
Host: systemCriticalConfig.SiteHost,
}
var result RefererResult = ExternalReferer
if rURL.Host == siteURLBase.Host {

View file

@ -13,30 +13,30 @@ var (
{
desc: "Internal referer",
referer: "http://gochan.org",
siteDomain: "gochan.org",
siteHost: "gochan.org",
expectedResult: InternalReferer,
},
{
desc: "External referer",
referer: "http://somesketchysite.com",
siteDomain: "gochan.com",
siteHost: "gochan.com",
expectedResult: ExternalReferer,
},
{
desc: "No referer",
siteDomain: "gochan.org",
siteHost: "gochan.org",
expectedResult: NoReferer,
},
{
desc: "Internal referer with port",
referer: "http://127.0.0.1:8080",
siteDomain: "127.0.0.1:8080",
siteHost: "127.0.0.1:8080",
expectedResult: InternalReferer,
},
{
desc: "Internal referer with port, IPv6",
referer: "http://[::1]:8080",
siteDomain: "[::1]:8080",
siteHost: "[::1]:8080",
expectedResult: InternalReferer,
},
}
@ -45,7 +45,7 @@ var (
type checkRefererTestCase struct {
desc string
referer string
siteDomain string
siteHost string
expectedResult RefererResult
}
@ -58,7 +58,7 @@ func TestCheckReferer(t *testing.T) {
}
for _, tC := range checkRefererTestCases {
t.Run(tC.desc, func(t *testing.T) {
systemCriticalConfig.SiteDomain = tC.siteDomain
systemCriticalConfig.SiteHost = tC.siteHost
config.SetSystemCriticalConfig(systemCriticalConfig)
req.Header.Set("Referer", tC.referer)
result, err := CheckReferer(req)