Browse Source

conf: overhaul server settings (#5928)

* conf: rename package

* Requires Go 1.12

* Fix lint

* Fix lint

* Overhaul

* db: fix tests

* Save my work

* Fix tests

* Server.UnixSocketPermission

* Server.LocalRootURL

* SSH settings

* Server.OfflineMode

* Save my work

* App.Version

* Remove [server] STATIC_ROOT_PATH

* Server.LandingURL
ᴜɴᴋɴᴡᴏɴ 4 years ago
parent
commit
648d9e253c
100 changed files with 1443 additions and 1306 deletions
  1. 0 1
      .travis.yml
  2. 4 0
      CHANGELOG.md
  3. 5 5
      Makefile
  4. 66 57
      conf/app.ini
  5. 1 1
      conf/locale/locale_en-US.ini
  6. 1 1
      docs/local_development.md
  7. 2 1
      go.mod
  8. 1 0
      go.sum
  9. 4 6
      gogs.go
  10. 2 2
      internal/assets/conf/conf_gen.go
  11. 1 1
      internal/assets/templates/templates_gen.go
  12. 4 4
      internal/auth/auth.go
  13. 11 10
      internal/cmd/admin.go
  14. 16 12
      internal/cmd/backup.go
  15. 6 6
      internal/cmd/hook.go
  16. 6 7
      internal/cmd/import.go
  17. 24 16
      internal/cmd/restore.go
  18. 21 19
      internal/cmd/serv.go
  19. 89 83
      internal/cmd/web.go
  20. 116 0
      internal/conf/computed.go
  21. 327 474
      internal/conf/conf.go
  22. 107 0
      internal/conf/static.go
  23. 2 2
      internal/conf/static_minwinsvc.go
  24. 27 0
      internal/conf/utils.go
  25. 6 6
      internal/context/api.go
  26. 12 12
      internal/context/auth.go
  27. 13 13
      internal/context/context.go
  28. 3 3
      internal/context/notice.go
  29. 2 2
      internal/context/org.go
  30. 3 3
      internal/context/repo.go
  31. 13 13
      internal/cron/cron.go
  32. 11 11
      internal/db/action.go
  33. 2 2
      internal/db/attachment.go
  34. 2 2
      internal/db/git_diff.go
  35. 3 3
      internal/db/issue.go
  36. 2 2
      internal/db/issue_mail.go
  37. 3 3
      internal/db/login_source.go
  38. 9 9
      internal/db/migrations/v15.go
  39. 2 2
      internal/db/migrations/v16.go
  40. 5 5
      internal/db/migrations/v18.go
  41. 3 3
      internal/db/milestone.go
  42. 3 3
      internal/db/mirror.go
  43. 10 10
      internal/db/models.go
  44. 7 6
      internal/db/pull.go
  45. 32 33
      internal/db/repo.go
  46. 4 4
      internal/db/repo_editor.go
  47. 17 17
      internal/db/ssh_key.go
  48. 2 2
      internal/db/ssh_key_test.go
  49. 3 3
      internal/db/two_factor.go
  50. 25 25
      internal/db/user.go
  51. 7 7
      internal/db/webhook.go
  52. 6 6
      internal/db/webhook_discord.go
  53. 6 6
      internal/db/webhook_slack.go
  54. 2 2
      internal/db/wiki.go
  55. 11 11
      internal/mailer/mail.go
  56. 9 9
      internal/mailer/mailer.go
  57. 11 11
      internal/markup/markdown.go
  58. 4 4
      internal/markup/markdown_test.go
  59. 4 4
      internal/markup/markup.go
  60. 2 2
      internal/markup/markup_test.go
  61. 2 2
      internal/markup/sanitizer.go
  62. 32 33
      internal/route/admin/admin.go
  63. 5 5
      internal/route/admin/auths.go
  64. 4 4
      internal/route/admin/notice.go
  65. 2 2
      internal/route/admin/orgs.go
  66. 5 5
      internal/route/admin/repos.go
  67. 12 12
      internal/route/admin/users.go
  68. 2 2
      internal/route/api/v1/admin/user.go
  69. 3 3
      internal/route/api/v1/convert/utils.go
  70. 3 3
      internal/route/api/v1/repo/commits.go
  71. 2 2
      internal/route/api/v1/repo/issue.go
  72. 2 2
      internal/route/api/v1/repo/key.go
  73. 2 2
      internal/route/api/v1/repo/repo.go
  74. 2 2
      internal/route/api/v1/user/email.go
  75. 2 2
      internal/route/api/v1/user/key.go
  76. 6 6
      internal/route/dev/template.go
  77. 8 8
      internal/route/home.go
  78. 67 52
      internal/route/install.go
  79. 2 2
      internal/route/org/members.go
  80. 2 2
      internal/route/org/org.go
  81. 4 4
      internal/route/org/setting.go
  82. 11 11
      internal/route/repo/commit.go
  83. 2 2
      internal/route/repo/download.go
  84. 13 13
      internal/route/repo/editor.go
  85. 6 6
      internal/route/repo/http.go
  86. 18 18
      internal/route/repo/issue.go
  87. 12 12
      internal/route/repo/pull.go
  88. 9 9
      internal/route/repo/release.go
  89. 7 7
      internal/route/repo/repo.go
  90. 8 8
      internal/route/repo/setting.go
  91. 4 4
      internal/route/repo/view.go
  92. 3 3
      internal/route/repo/webhook.go
  93. 35 35
      internal/route/user/auth.go
  94. 10 10
      internal/route/user/home.go
  95. 3 3
      internal/route/user/profile.go
  96. 14 14
      internal/route/user/setting.go
  97. 0 12
      internal/setting/computed.go
  98. 5 5
      internal/ssh/ssh.go
  99. 2 2
      internal/template/highlight/highlight.go
  100. 10 10
      internal/template/template.go

+ 0 - 1
.travis.yml

@@ -1,7 +1,6 @@
 os: linux
 language: go
 go:
-  - 1.11.x
   - 1.12.x
   - 1.13.x
 go_import_path: gogs.io/gogs

+ 4 - 0
CHANGELOG.md

@@ -17,6 +17,9 @@ All notable changes to Gogs are documented in this file.
 - All assets are now embedded into binary and served from memory by default. Set `[server] LOAD_ASSETS_FROM_DISK = true` to load them from disk. [#5920](https://github.com/gogs/gogs/pull/5920)
 - Application and Go versions are removed from page footer and only show in the admin dashboard.
 - Build tag for running as Windows Service has been changed from `miniwinsvc` to `minwinsvc`.
+- Configuration option `APP_NAME` is deprecated and will end support in 0.13.0, please start using `BRAND_NAME`.
+- Configuration option `[server] ROOT_URL` is deprecated and will end support in 0.13.0, please start using `[server] EXTERNAL_URL`.
+- Configuration option `[server] LANDING_PAGE` is deprecated and will end support in 0.13.0, please start using `[server] LANDING_URL`.
 
 ### Fixed
 
@@ -33,6 +36,7 @@ All notable changes to Gogs are documented in this file.
 ### Removed
 
 - Configuration option `[other] SHOW_FOOTER_VERSION`
+- Configuration option `[server] STATIC_ROOT_PATH`
 
 ---
 

+ 5 - 5
Makefile

@@ -1,5 +1,5 @@
-LDFLAGS += -X "gogs.io/gogs/internal/setting.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
-LDFLAGS += -X "gogs.io/gogs/internal/setting.BuildCommit=$(shell git rev-parse HEAD)"
+LDFLAGS += -X "gogs.io/gogs/internal/conf.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
+LDFLAGS += -X "gogs.io/gogs/internal/conf.BuildCommit=$(shell git rev-parse HEAD)"
 
 CONF_FILES := $(shell find conf | sed 's/ /\\ /g')
 TEMPLATES_FILES := $(shell find templates | sed 's/ /\\ /g')
@@ -81,11 +81,11 @@ test:
 	go test -cover -race ./...
 
 fixme:
-	grep -rnw "FIXME" cmd routers models pkg
+	grep -rnw "FIXME" internal
 
 todo:
-	grep -rnw "TODO" cmd routers models pkg
+	grep -rnw "TODO" internal
 
 # Legacy code should be remove by the time of release
 legacy:
-	grep -rnw "LEGACY" cmd routes models pkg
+	grep -rnw "\(LEGACY\|DEPRECATED\)" internal

+ 66 - 57
conf/app.ini

@@ -2,75 +2,91 @@
 # !!! PLEASE MAKE CHANGES ON CORRESPONDING CUSTOM CONFIG FILE !!!
 # !!! IF YOU ARE PACKAGING PROVIDER, PLEASE MAKE OWN COPY OF IT !!!
 
-; App name that shows on every page title
-APP_NAME = Gogs
-; The name of the system user that runs Gogs
+; The brand name of the application.
+BRAND_NAME = Gogs
+; The system user who should be running the applications. It has no effect on Windows,
+; otherwise, it should match the value of $USER environment variable.
 RUN_USER = git
-; Either "dev", "prod" or "test"
+; The running mode of the application, can be either "dev", "prod" or "test".
 RUN_MODE = dev
 
 [server]
-PROTOCOL = http
+; The public-facing URL for the application.
+EXTERNAL_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
+; The public-facing domain name for the application.
 DOMAIN = localhost
-ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
+; The protocol that is used to serve direct traffic to the application.
+; Currently supports "http", "https", "fcgi" and "unix".
+PROTOCOL = http
+; The address to be listened by the application.
 HTTP_ADDR = 0.0.0.0
+; The port number to be listened by the application.
 HTTP_PORT = 3000
-; Permission for unix socket
+; Generate steps:
+; $ ./gogs cert -ca=true -duration=8760h0m0s -host=myhost.example.com
+;
+; Or from a .pfx file exported from the Windows certificate store (do
+; not forget to export the private key):
+; $ openssl pkcs12 -in cert.pfx -out cert.pem -nokeys
+; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes
+CERT_FILE = custom/https/cert.pem
+KEY_FILE = custom/https/key.pem
+; The minimum allowed TLS version, currently supports "TLS10", "TLS11", "TLS12", and "TLS13".
+TLS_MIN_VERSION = TLS12
+; File permission when serve traffic via Unix domain socket.
 UNIX_SOCKET_PERMISSION = 666
-; Local (DMZ) URL for Gogs workers (such as SSH update) accessing web service.
+; Local (DMZ) URL for workers (e.g. SSH update) accessing web service.
 ; In most cases you do not need to change the default value.
 ; Alter it only if your SSH server node is not the same as HTTP node.
 LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
-; Disable SSH feature when not available
+
+; Whether to disable SSH access to the application entirely.
 DISABLE_SSH = false
-; Whether use builtin SSH server or not.
-START_SSH_SERVER = false
-; Domain name to be exposed in SSH clone URL
+; The domain name to be exposed in SSH clone URL.
 SSH_DOMAIN = %(DOMAIN)s
-; Port number to be exposed in SSH clone URL
+; The port number to be exposed in SSH clone URL.
 SSH_PORT = 22
-; Network interface builtin SSH server listens on
+; The path of SSH root directory, default is "$HOME/.ssh".
+SSH_ROOT_PATH =
+; The path to ssh-keygen, default is "ssh-keygen" and let shell find out which one to call.
+SSH_KEYGEN_PATH = ssh-keygen
+; The directory to create temporary files when test a public key using ssh-keygen,
+; default is the system temporary directory.
+SSH_KEY_TEST_PATH =
+; Whether to start a builtin SSH server.
+START_SSH_SERVER = false
+; The network interface for builtin SSH server to listen on.
 SSH_LISTEN_HOST = 0.0.0.0
-; Port number builtin SSH server listens on
+; The port number for builtin SSH server to listen on.
 SSH_LISTEN_PORT = %(SSH_PORT)s
-; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
-SSH_ROOT_PATH =
-; Indicate whether to rewrite authorized_keys at start, ignored when use builtin SSH server
-REWRITE_AUTHORIZED_KEYS_AT_START = false
-; Choose the ciphers to support for SSH connections
+; The list of accepted ciphers for connections to builtin SSH server.
 SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128
-; Directory to create temporary files when test publick key using ssh-keygen,
-; default is system temporary directory.
-SSH_KEY_TEST_PATH =
-; Path to ssh-keygen, default is 'ssh-keygen' and let shell find out which one to call.
-SSH_KEYGEN_PATH = ssh-keygen
-; Indicate whether to check minimum key size with corresponding type
+; Whether to check minimum public key size with corresponding type.
 MINIMUM_KEY_SIZE_CHECK = false
-; Disable CDN even in "prod" mode
+; Whether to rewrite "~/.ssh/authorized_keys" file at start, ignored when use builtin SSH server.
+REWRITE_AUTHORIZED_KEYS_AT_START = false
+
+; Whether to disable using CDN for static files regardless.
 OFFLINE_MODE = false
-DISABLE_ROUTER_LOG = false
-; Generate steps:
-; $ ./gogs cert -ca=true -duration=8760h0m0s -host=myhost.example.com
-;
-; Or from a .pfx file exported from the Windows certificate store (do
-; not forget to export the private key):
-; $ openssl pkcs12 -in cert.pfx -out cert.pem -nokeys
-; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes
-CERT_FILE = custom/https/cert.pem
-KEY_FILE = custom/https/key.pem
-; Allowed TLS version values: SSL30, TLS10, TLS11, TLS12
-TLS_MIN_VERSION = TLS10
+; Whether to disable logging in router.
+DISABLE_ROUTER_LOG = true
+; Whether to enable application level GZIP compression.
+ENABLE_GZIP = false
 
-; Enable to load assets (i.e. "conf", "templates", "public") from disk instead of embedded bindata.
-LOAD_ASSETS_FROM_DISK = false
-; The directory that contains "templates" and "public". By default, it is the working directory.
-STATIC_ROOT_PATH =
-; Default path for App data
+; The path for storing application specific data.
 APP_DATA_PATH = data
-; Application level GZIP support
-ENABLE_GZIP = false
-; Landing page for non-logged users, can be "home" or "explore"
-LANDING_PAGE = home
+; Whether to enable to load assets (i.e. "conf", "templates", "public") from disk instead of embedded bindata.
+LOAD_ASSETS_FROM_DISK = false
+
+; The landing page URL for anonymous users, can be a link to a external site.
+LANDING_URL = /
+
+; Define allowed algorithms and their minimum key length (use -1 to disable a type).
+[ssh.minimum_key_sizes]
+ED25519 = 256
+ECDSA   = 256
+RSA     = 2048
+DSA     = 1024
 
 [repository]
 ; Root path for storing repositories's data, default is "~/gogs-repositories"
@@ -156,13 +172,6 @@ ANGLED_QUOTES = true
 ; Value for Access-Control-Allow-Origin header, default is not to present
 ACCESS_CONTROL_ALLOW_ORIGIN =
 
-; Define allowed algorithms and their minimum key length (use -1 to disable a type)
-[ssh.minimum_key_sizes]
-ED25519 = 256
-ECDSA   = 256
-RSA     = 2048
-DSA     = 1024
-
 [database]
 ; Either "mysql", "postgres" or "sqlite3", you can connect to TiDB with MySQL protocol
 DB_TYPE = mysql
@@ -228,7 +237,7 @@ ENABLED = false
 ; Buffer length of channel, keep it as it is if you don't know what it is.
 SEND_BUFFER_LEN = 100
 ; Prefix prepended to the subject line
-SUBJECT_PREFIX = `[%(APP_NAME)s] `
+SUBJECT_PREFIX = `[%(BRAND_NAME)s] `
 ; Mail server
 ; Gmail: smtp.gmail.com:587
 ; QQ: smtp.qq.com:465
@@ -364,7 +373,7 @@ URL =
 ; Webhook URL
 URL =
 ; Username displayed in webhook
-USERNAME = %(APP_NAME)s
+USERNAME = %(BRAND_NAME)s
 
 [log.xorm]
 ; Enable file rotation

+ 1 - 1
conf/locale/locale_en-US.ini

@@ -121,6 +121,7 @@ run_user_not_match = Run user isn't the current user: %s -> %s
 smtp_host_missing_port = SMTP Host port missing from address.
 invalid_smtp_from = SMTP From field is invalid: %v
 save_config_failed = Failed to save configuration: %v
+init_failed = Failed to initialize application: %v
 invalid_admin_setting = Admin account setting is invalid: %v
 install_success = Welcome! We're glad that you chose Gogs, have fun and take care.
 invalid_log_root_path = Log root path is invalid: %v
@@ -1168,7 +1169,6 @@ config.offline_mode = Offline Mode
 config.disable_router_log = Disable Router Log
 config.run_user = Run User
 config.run_mode = Run Mode
-config.static_file_root_path = Static File Root Path
 config.log_file_root_path = Log File Root Path
 config.reverse_auth_user = Reverse Authentication User
 

+ 1 - 1
docs/local_development.md

@@ -23,7 +23,7 @@ Gogs is built and runs as a single binary and meant to be cross platform. Theref
 Gogs has the following dependencies:
 
 - [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) (v1.8.3 or higher)
-- [Go](https://golang.org/doc/install) (v1.11 or higher)
+- [Go](https://golang.org/doc/install) (v1.12 or higher)
 - [Less.js](http://lesscss.org/usage/#command-line-usage-installing)
 - [GNU Make](https://www.gnu.org/software/make/)
 - Database upon your choice (pick one, we choose PostgreSQL in this document):

+ 2 - 1
go.mod

@@ -1,6 +1,6 @@
 module gogs.io/gogs
 
-go 1.13
+go 1.12
 
 require (
 	github.com/bgentry/speakeasy v0.1.0 // indirect
@@ -39,6 +39,7 @@ require (
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 	github.com/niklasfasching/go-org v0.1.6
 	github.com/olekukonko/tablewriter v0.0.1 // indirect
+	github.com/pkg/errors v0.8.1
 	github.com/pquerna/otp v1.2.0
 	github.com/prometheus/client_golang v1.2.1
 	github.com/russross/blackfriday v1.5.2

+ 1 - 0
go.sum

@@ -194,6 +194,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
 github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

+ 4 - 6
gogs.go

@@ -1,4 +1,4 @@
-// +build go1.11
+// +build go1.12
 
 // Copyright 2014 The Gogs Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
@@ -14,20 +14,18 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/cmd"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
-const Version = "0.12.0+dev"
-
 func init() {
-	setting.AppVersion = Version
+	conf.App.Version = "0.12.0+dev"
 }
 
 func main() {
 	app := cli.NewApp()
 	app.Name = "Gogs"
 	app.Usage = "A painless self-hosted Git service"
-	app.Version = Version
+	app.Version = conf.App.Version
 	app.Commands = []cli.Command{
 		cmd.Web,
 		cmd.Serv,

File diff suppressed because it is too large
+ 2 - 2
internal/assets/conf/conf_gen.go


File diff suppressed because it is too large
+ 1 - 1
internal/assets/templates/templates_gen.go


+ 4 - 4
internal/auth/auth.go

@@ -15,7 +15,7 @@ import (
 
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -90,8 +90,8 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (_ *db.User, isBasic
 	uid, isTokenAuth := SignedInID(ctx, sess)
 
 	if uid <= 0 {
-		if setting.Service.EnableReverseProxyAuth {
-			webAuthUser := ctx.Req.Header.Get(setting.ReverseProxyAuthUser)
+		if conf.Service.EnableReverseProxyAuth {
+			webAuthUser := ctx.Req.Header.Get(conf.ReverseProxyAuthUser)
 			if len(webAuthUser) > 0 {
 				u, err := db.GetUserByName(webAuthUser)
 				if err != nil {
@@ -101,7 +101,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (_ *db.User, isBasic
 					}
 
 					// Check if enabled auto-registration.
-					if setting.Service.EnableReverseProxyAutoRegister {
+					if conf.Service.EnableReverseProxyAutoRegister {
 						u := &db.User{
 							Name:     webAuthUser,
 							Email:    gouuid.NewV4().String() + "@localhost",

+ 11 - 10
internal/cmd/admin.go

@@ -9,10 +9,11 @@ import (
 	"reflect"
 	"runtime"
 
+	"github.com/pkg/errors"
 	"github.com/urfave/cli"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 var (
@@ -133,18 +134,18 @@ to make automatic initialization process more smoothly`,
 
 func runCreateUser(c *cli.Context) error {
 	if !c.IsSet("name") {
-		return fmt.Errorf("Username is not specified")
+		return errors.New("Username is not specified")
 	} else if !c.IsSet("password") {
-		return fmt.Errorf("Password is not specified")
+		return errors.New("Password is not specified")
 	} else if !c.IsSet("email") {
-		return fmt.Errorf("Email is not specified")
+		return errors.New("Email is not specified")
 	}
 
-	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
+	err := conf.Init(c.String("config"))
+	if err != nil {
+		return errors.Wrap(err, "init configuration")
 	}
 
-	setting.Init()
 	db.LoadConfigs()
 	db.SetEngine()
 
@@ -164,11 +165,11 @@ func runCreateUser(c *cli.Context) error {
 
 func adminDashboardOperation(operation func() error, successMessage string) func(*cli.Context) error {
 	return func(c *cli.Context) error {
-		if c.IsSet("config") {
-			setting.CustomConf = c.String("config")
+		err := conf.Init(c.String("config"))
+		if err != nil {
+			return errors.Wrap(err, "init configuration")
 		}
 
-		setting.Init()
 		db.LoadConfigs()
 		db.SetEngine()
 

+ 16 - 12
internal/cmd/backup.go

@@ -9,16 +9,18 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
+	"path/filepath"
 	"time"
 
+	"github.com/pkg/errors"
 	"github.com/unknwon/cae/zip"
 	"github.com/unknwon/com"
 	"github.com/urfave/cli"
 	"gopkg.in/ini.v1"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 var Backup = cli.Command{
@@ -44,10 +46,12 @@ const _ARCHIVE_ROOT_DIR = "gogs-backup"
 
 func runBackup(c *cli.Context) error {
 	zip.Verbose = c.Bool("verbose")
-	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
+
+	err := conf.Init(c.String("config"))
+	if err != nil {
+		return errors.Wrap(err, "init configuration")
 	}
-	setting.Init()
+
 	db.LoadConfigs()
 	db.SetEngine()
 
@@ -66,12 +70,12 @@ func runBackup(c *cli.Context) error {
 	metadata := ini.Empty()
 	metadata.Section("").Key("VERSION").SetValue(com.ToStr(_CURRENT_BACKUP_FORMAT_VERSION))
 	metadata.Section("").Key("DATE_TIME").SetValue(time.Now().String())
-	metadata.Section("").Key("GOGS_VERSION").SetValue(setting.AppVersion)
+	metadata.Section("").Key("GOGS_VERSION").SetValue(conf.App.Version)
 	if err = metadata.SaveTo(metaFile); err != nil {
 		log.Fatal("Failed to save metadata '%s': %v", metaFile, err)
 	}
 
-	archiveName := path.Join(c.String("target"), c.String("archive-name"))
+	archiveName := filepath.Join(c.String("target"), c.String("archive-name"))
 	log.Info("Packing backup files to: %s", archiveName)
 
 	z, err := zip.Create(archiveName)
@@ -83,7 +87,7 @@ func runBackup(c *cli.Context) error {
 	}
 
 	// Database
-	dbDir := path.Join(rootDir, "db")
+	dbDir := filepath.Join(rootDir, "db")
 	if err = db.DumpDatabase(dbDir); err != nil {
 		log.Fatal("Failed to dump database: %v", err)
 	}
@@ -93,7 +97,7 @@ func runBackup(c *cli.Context) error {
 
 	// Custom files
 	if !c.Bool("database-only") {
-		if err = z.AddDir(_ARCHIVE_ROOT_DIR+"/custom", setting.CustomPath); err != nil {
+		if err = z.AddDir(_ARCHIVE_ROOT_DIR+"/custom", conf.CustomDir()); err != nil {
 			log.Fatal("Failed to include 'custom': %v", err)
 		}
 	}
@@ -101,7 +105,7 @@ func runBackup(c *cli.Context) error {
 	// Data files
 	if !c.Bool("database-only") {
 		for _, dir := range []string{"attachments", "avatars", "repo-avatars"} {
-			dirPath := path.Join(setting.AppDataPath, dir)
+			dirPath := filepath.Join(conf.Server.AppDataPath, dir)
 			if !com.IsDir(dirPath) {
 				continue
 			}
@@ -114,9 +118,9 @@ func runBackup(c *cli.Context) error {
 
 	// Repositories
 	if !c.Bool("exclude-repos") && !c.Bool("database-only") {
-		reposDump := path.Join(rootDir, "repositories.zip")
-		log.Info("Dumping repositories in '%s'", setting.RepoRootPath)
-		if err = zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
+		reposDump := filepath.Join(rootDir, "repositories.zip")
+		log.Info("Dumping repositories in '%s'", conf.RepoRootPath)
+		if err = zip.PackTo(conf.RepoRootPath, reposDump, true); err != nil {
 			log.Fatal("Failed to dump repositories: %v", err)
 		}
 		log.Info("Repositories dumped to: %s", reposDump)

+ 6 - 6
internal/cmd/hook.go

@@ -20,11 +20,11 @@ import (
 
 	"github.com/gogs/git-module"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/httplib"
 	"gogs.io/gogs/internal/mailer"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/template"
 )
 
@@ -141,7 +141,7 @@ func runHookPreReceive(c *cli.Context) error {
 	}
 
 	var hookCmd *exec.Cmd
-	if setting.IsWindows {
+	if conf.IsWindowsRuntime() {
 		hookCmd = exec.Command("bash.exe", "custom_hooks/pre-receive")
 	} else {
 		hookCmd = exec.Command(customHooksPath)
@@ -175,7 +175,7 @@ func runHookUpdate(c *cli.Context) error {
 	}
 
 	var hookCmd *exec.Cmd
-	if setting.IsWindows {
+	if conf.IsWindowsRuntime() {
 		hookCmd = exec.Command("bash.exe", append([]string{"custom_hooks/update"}, args...)...)
 	} else {
 		hookCmd = exec.Command(customHooksPath, args...)
@@ -198,7 +198,7 @@ func runHookPostReceive(c *cli.Context) error {
 
 	// Post-receive hook does more than just gather Git information,
 	// so we need to setup additional services for email notifications.
-	setting.NewPostReceiveHookServices()
+	conf.NewPostReceiveHookServices()
 	mailer.NewContext()
 
 	isWiki := strings.Contains(os.Getenv(db.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")
@@ -233,7 +233,7 @@ func runHookPostReceive(c *cli.Context) error {
 		}
 
 		// Ask for running deliver hook and test pull request tasks
-		reqURL := setting.LocalURL + options.RepoUserName + "/" + options.RepoName + "/tasks/trigger?branch=" +
+		reqURL := conf.Server.LocalRootURL + options.RepoUserName + "/" + options.RepoName + "/tasks/trigger?branch=" +
 			template.EscapePound(strings.TrimPrefix(options.RefFullName, git.BRANCH_PREFIX)) +
 			"&secret=" + os.Getenv(db.ENV_REPO_OWNER_SALT_MD5) +
 			"&pusher=" + os.Getenv(db.ENV_AUTH_USER_ID)
@@ -258,7 +258,7 @@ func runHookPostReceive(c *cli.Context) error {
 	}
 
 	var hookCmd *exec.Cmd
-	if setting.IsWindows {
+	if conf.IsWindowsRuntime() {
 		hookCmd = exec.Command("bash.exe", "custom_hooks/post-receive")
 	} else {
 		hookCmd = exec.Command(customHooksPath)

+ 6 - 7
internal/cmd/import.go

@@ -7,16 +7,16 @@ package cmd
 import (
 	"bufio"
 	"bytes"
-	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
 	"time"
 
+	"github.com/pkg/errors"
 	"github.com/unknwon/com"
 	"github.com/urfave/cli"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 var (
@@ -54,12 +54,11 @@ func runImportLocale(c *cli.Context) error {
 		return fmt.Errorf("target directory %q does not exist or is not a directory", c.String("target"))
 	}
 
-	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
+	err := conf.Init(c.String("config"))
+	if err != nil {
+		return errors.Wrap(err, "init configuration")
 	}
 
-	setting.Init()
-
 	now := time.Now()
 
 	line := make([]byte, 0, 100)
@@ -67,7 +66,7 @@ func runImportLocale(c *cli.Context) error {
 	escapedQuotes := []byte(`\"`)
 	regularQuotes := []byte(`"`)
 	// Cut out en-US.
-	for _, lang := range setting.Langs[1:] {
+	for _, lang := range conf.Langs[1:] {
 		name := fmt.Sprintf("locale_%s.ini", lang)
 		source := filepath.Join(c.String("source"), name)
 		target := filepath.Join(c.String("target"), name)

+ 24 - 16
internal/cmd/restore.go

@@ -7,16 +7,18 @@ package cmd
 import (
 	"os"
 	"path"
+	"path/filepath"
 
 	"github.com/mcuadros/go-version"
+	"github.com/pkg/errors"
 	"github.com/unknwon/cae/zip"
 	"github.com/unknwon/com"
 	"github.com/urfave/cli"
 	"gopkg.in/ini.v1"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 var Restore = cli.Command{
@@ -59,7 +61,7 @@ func runRestore(c *cli.Context) error {
 	defer os.RemoveAll(archivePath)
 
 	// Check backup version
-	metaFile := path.Join(archivePath, "metadata.ini")
+	metaFile := filepath.Join(archivePath, "metadata.ini")
 	if !com.IsExist(metaFile) {
 		log.Fatal("File 'metadata.ini' is missing")
 	}
@@ -68,8 +70,8 @@ func runRestore(c *cli.Context) error {
 		log.Fatal("Failed to load metadata '%s': %v", metaFile, err)
 	}
 	backupVersion := metadata.Section("").Key("GOGS_VERSION").MustString("999.0")
-	if version.Compare(setting.AppVersion, backupVersion, "<") {
-		log.Fatal("Current Gogs version is lower than backup version: %s < %s", setting.AppVersion, backupVersion)
+	if version.Compare(conf.App.Version, backupVersion, "<") {
+		log.Fatal("Current Gogs version is lower than backup version: %s < %s", conf.App.Version, backupVersion)
 	}
 	formatVersion := metadata.Section("").Key("VERSION").MustInt()
 	if formatVersion == 0 {
@@ -82,15 +84,21 @@ func runRestore(c *cli.Context) error {
 
 	// If config file is not present in backup, user must set this file via flag.
 	// Otherwise, it's optional to set config file flag.
-	configFile := path.Join(archivePath, "custom/conf/app.ini")
+	configFile := filepath.Join(archivePath, "custom", "conf", "app.ini")
+	var customConf string
 	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
+		customConf = c.String("config")
 	} else if !com.IsExist(configFile) {
 		log.Fatal("'--config' is not specified and custom config file is not found in backup")
 	} else {
-		setting.CustomConf = configFile
+		customConf = configFile
 	}
-	setting.Init()
+
+	err = conf.Init(customConf)
+	if err != nil {
+		return errors.Wrap(err, "init configuration")
+	}
+
 	db.LoadConfigs()
 	db.SetEngine()
 
@@ -102,27 +110,27 @@ func runRestore(c *cli.Context) error {
 
 	// Custom files
 	if !c.Bool("database-only") {
-		if com.IsExist(setting.CustomPath) {
-			if err = os.Rename(setting.CustomPath, setting.CustomPath+".bak"); err != nil {
+		if com.IsExist(conf.CustomDir()) {
+			if err = os.Rename(conf.CustomDir(), conf.CustomDir()+".bak"); err != nil {
 				log.Fatal("Failed to backup current 'custom': %v", err)
 			}
 		}
-		if err = os.Rename(path.Join(archivePath, "custom"), setting.CustomPath); err != nil {
+		if err = os.Rename(filepath.Join(archivePath, "custom"), conf.CustomDir()); err != nil {
 			log.Fatal("Failed to import 'custom': %v", err)
 		}
 	}
 
 	// Data files
 	if !c.Bool("database-only") {
-		os.MkdirAll(setting.AppDataPath, os.ModePerm)
+		_ = os.MkdirAll(conf.Server.AppDataPath, os.ModePerm)
 		for _, dir := range []string{"attachments", "avatars", "repo-avatars"} {
 			// Skip if backup archive does not have corresponding data
-			srcPath := path.Join(archivePath, "data", dir)
+			srcPath := filepath.Join(archivePath, "data", dir)
 			if !com.IsDir(srcPath) {
 				continue
 			}
 
-			dirPath := path.Join(setting.AppDataPath, dir)
+			dirPath := filepath.Join(conf.Server.AppDataPath, dir)
 			if com.IsExist(dirPath) {
 				if err = os.Rename(dirPath, dirPath+".bak"); err != nil {
 					log.Fatal("Failed to backup current 'data': %v", err)
@@ -135,9 +143,9 @@ func runRestore(c *cli.Context) error {
 	}
 
 	// Repositories
-	reposPath := path.Join(archivePath, "repositories.zip")
+	reposPath := filepath.Join(archivePath, "repositories.zip")
 	if !c.Bool("exclude-repos") && !c.Bool("database-only") && com.IsExist(reposPath) {
-		if err := zip.ExtractTo(reposPath, path.Dir(setting.RepoRootPath)); err != nil {
+		if err := zip.ExtractTo(reposPath, filepath.Dir(conf.RepoRootPath)); err != nil {
 			log.Fatal("Failed to extract 'repositories.zip': %v", err)
 		}
 	}

+ 21 - 19
internal/cmd/serv.go

@@ -16,9 +16,9 @@ import (
 	"github.com/urfave/cli"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -39,7 +39,7 @@ func fail(userMessage, logMessage string, args ...interface{}) {
 	fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
 
 	if len(logMessage) > 0 {
-		if !setting.ProdMode {
+		if !conf.IsProdMode() {
 			fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
 		}
 		log.Fatal(logMessage, args...)
@@ -49,22 +49,26 @@ func fail(userMessage, logMessage string, args ...interface{}) {
 }
 
 func setup(c *cli.Context, logPath string, connectDB bool) {
+	var customConf string
 	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
+		customConf = c.String("config")
 	} else if c.GlobalIsSet("config") {
-		setting.CustomConf = c.GlobalString("config")
+		customConf = c.GlobalString("config")
 	}
 
-	setting.Init()
+	err := conf.Init(customConf)
+	if err != nil {
+		fail("Internal error", "Failed to init configuration: %v", err)
+	}
 
 	level := log.LevelTrace
-	if setting.ProdMode {
+	if conf.IsProdMode() {
 		level = log.LevelError
 	}
 
-	err := log.NewFile(log.FileConfig{
+	err = log.NewFile(log.FileConfig{
 		Level:    level,
-		Filename: filepath.Join(setting.LogRootPath, logPath),
+		Filename: filepath.Join(conf.LogRootPath, logPath),
 		FileRotationConfig: log.FileRotationConfig{
 			Rotate:  true,
 			Daily:   true,
@@ -72,8 +76,7 @@ func setup(c *cli.Context, logPath string, connectDB bool) {
 		},
 	})
 	if err != nil {
-		log.Fatal("Failed to init file logger: %v", err)
-		return
+		fail("Internal error", "Failed to init file logger: %v", err)
 	}
 	log.Remove(log.DefaultConsoleName) // Remove the primary logger
 
@@ -83,13 +86,12 @@ func setup(c *cli.Context, logPath string, connectDB bool) {
 
 	db.LoadConfigs()
 
-	if setting.UseSQLite3 {
-		workDir, _ := setting.WorkDir()
-		os.Chdir(workDir)
+	if conf.UseSQLite3 {
+		_ = os.Chdir(conf.WorkDir())
 	}
 
 	if err := db.SetEngine(); err != nil {
-		fail("Internal error", "SetEngine: %v", err)
+		fail("Internal error", "Failed to set database engine: %v", err)
 	}
 }
 
@@ -130,7 +132,7 @@ var (
 func runServ(c *cli.Context) error {
 	setup(c, "serv.log", true)
 
-	if setting.SSH.Disabled {
+	if conf.SSH.Disabled {
 		println("Gogs: SSH has been disabled")
 		return nil
 	}
@@ -220,12 +222,12 @@ func runServ(c *cli.Context) error {
 			}
 		}
 	} else {
-		setting.NewService()
+		conf.NewService()
 		// Check if the key can access to the repository in case of it is a deploy key (a deploy keys != user key).
 		// A deploy key doesn't represent a signed in user, so in a site with Service.RequireSignInView activated
 		// we should give read access only in repositories where this deploy key is in use. In other case, a server
 		// or system using an active deploy key can get read access to all the repositories in a Gogs service.
-		if key.IsDeployKey() && setting.Service.RequireSignInView {
+		if key.IsDeployKey() && conf.Service.RequireSignInView {
 			checkDeployKey(key, repo)
 		}
 	}
@@ -244,7 +246,7 @@ func runServ(c *cli.Context) error {
 	}
 
 	// Special handle for Windows.
-	if setting.IsWindows {
+	if conf.IsWindowsRuntime() {
 		verb = strings.Replace(verb, "-", " ", 1)
 	}
 
@@ -265,7 +267,7 @@ func runServ(c *cli.Context) error {
 			RepoPath:  repo.RepoPath(),
 		})...)
 	}
-	gitCmd.Dir = setting.RepoRootPath
+	gitCmd.Dir = conf.RepoRootPath
 	gitCmd.Stdout = os.Stdout
 	gitCmd.Stdin = os.Stdin
 	gitCmd.Stderr = os.Stderr

+ 89 - 83
internal/cmd/web.go

@@ -12,7 +12,7 @@ import (
 	"net/http"
 	"net/http/fcgi"
 	"os"
-	"path"
+	"path/filepath"
 	"strings"
 
 	"github.com/go-macaron/binding"
@@ -29,9 +29,9 @@ import (
 	"gopkg.in/macaron.v1"
 	log "unknwon.dev/clog/v2"
 
-	"gogs.io/gogs/internal/assets/conf"
 	"gogs.io/gogs/internal/assets/public"
 	"gogs.io/gogs/internal/assets/templates"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/form"
@@ -42,7 +42,6 @@ import (
 	"gogs.io/gogs/internal/route/org"
 	"gogs.io/gogs/internal/route/repo"
 	"gogs.io/gogs/internal/route/user"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/template"
 )
 
@@ -61,58 +60,58 @@ and it takes care of all the other things for you`,
 // newMacaron initializes Macaron instance.
 func newMacaron() *macaron.Macaron {
 	m := macaron.New()
-	if !setting.DisableRouterLog {
+	if !conf.Server.DisableRouterLog {
 		m.Use(macaron.Logger())
 	}
 	m.Use(macaron.Recovery())
-	if setting.EnableGzip {
+	if conf.Server.EnableGzip {
 		m.Use(gzip.Gziper())
 	}
-	if setting.Protocol == setting.SCHEME_FCGI {
-		m.SetURLPrefix(setting.AppSubURL)
+	if conf.Server.Protocol == "fcgi" {
+		m.SetURLPrefix(conf.Server.Subpath)
 	}
 
 	// Register custom middleware first to make it possible to override files under "public".
 	m.Use(macaron.Static(
-		path.Join(setting.CustomPath, "public"),
+		filepath.Join(conf.CustomDir(), "public"),
 		macaron.StaticOptions{
-			SkipLogging: setting.DisableRouterLog,
+			SkipLogging: conf.Server.DisableRouterLog,
 		},
 	))
 	var publicFs http.FileSystem
-	if !setting.LoadAssetsFromDisk {
+	if !conf.Server.LoadAssetsFromDisk {
 		publicFs = public.NewFileSystem()
 	}
 	m.Use(macaron.Static(
-		path.Join(setting.StaticRootPath, "public"),
+		filepath.Join(conf.WorkDir(), "public"),
 		macaron.StaticOptions{
-			SkipLogging: setting.DisableRouterLog,
+			SkipLogging: conf.Server.DisableRouterLog,
 			FileSystem:  publicFs,
 		},
 	))
 
 	m.Use(macaron.Static(
-		setting.AvatarUploadPath,
+		conf.AvatarUploadPath,
 		macaron.StaticOptions{
 			Prefix:      db.USER_AVATAR_URL_PREFIX,
-			SkipLogging: setting.DisableRouterLog,
+			SkipLogging: conf.Server.DisableRouterLog,
 		},
 	))
 	m.Use(macaron.Static(
-		setting.RepositoryAvatarUploadPath,
+		conf.RepositoryAvatarUploadPath,
 		macaron.StaticOptions{
 			Prefix:      db.REPO_AVATAR_URL_PREFIX,
-			SkipLogging: setting.DisableRouterLog,
+			SkipLogging: conf.Server.DisableRouterLog,
 		},
 	))
 
 	renderOpt := macaron.RenderOptions{
-		Directory:         path.Join(setting.StaticRootPath, "templates"),
-		AppendDirectories: []string{path.Join(setting.CustomPath, "templates")},
+		Directory:         filepath.Join(conf.WorkDir(), "templates"),
+		AppendDirectories: []string{filepath.Join(conf.CustomDir(), "templates")},
 		Funcs:             template.FuncMap(),
 		IndentJSON:        macaron.Env != macaron.PROD,
 	}
-	if !setting.LoadAssetsFromDisk {
+	if !conf.Server.LoadAssetsFromDisk {
 		renderOpt.TemplateFileSystem = templates.NewTemplateFileSystem("", renderOpt.AppendDirectories[0])
 	}
 	m.Use(macaron.Renderer(renderOpt))
@@ -121,34 +120,34 @@ func newMacaron() *macaron.Macaron {
 	if err != nil {
 		log.Fatal("Failed to list locale files: %v", err)
 	}
-	localFiles := make(map[string][]byte)
+	localeFiles := make(map[string][]byte)
 	for _, name := range localeNames {
-		localFiles[name] = conf.MustAsset("conf/locale/" + name)
+		localeFiles[name] = conf.MustAsset("conf/locale/" + name)
 	}
 	m.Use(i18n.I18n(i18n.Options{
-		SubURL:          setting.AppSubURL,
-		Files:           localFiles,
-		CustomDirectory: path.Join(setting.CustomPath, "conf/locale"),
-		Langs:           setting.Langs,
-		Names:           setting.Names,
+		SubURL:          conf.Server.Subpath,
+		Files:           localeFiles,
+		CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
+		Langs:           conf.Langs,
+		Names:           conf.Names,
 		DefaultLang:     "en-US",
 		Redirect:        true,
 	}))
 	m.Use(cache.Cacher(cache.Options{
-		Adapter:       setting.CacheAdapter,
-		AdapterConfig: setting.CacheConn,
-		Interval:      setting.CacheInterval,
+		Adapter:       conf.CacheAdapter,
+		AdapterConfig: conf.CacheConn,
+		Interval:      conf.CacheInterval,
 	}))
 	m.Use(captcha.Captchaer(captcha.Options{
-		SubURL: setting.AppSubURL,
+		SubURL: conf.Server.Subpath,
 	}))
-	m.Use(session.Sessioner(setting.SessionConfig))
+	m.Use(session.Sessioner(conf.SessionConfig))
 	m.Use(csrf.Csrfer(csrf.Options{
-		Secret:     setting.SecretKey,
-		Cookie:     setting.CSRFCookieName,
+		Secret:     conf.SecretKey,
+		Cookie:     conf.CSRFCookieName,
 		SetCookie:  true,
 		Header:     "X-Csrf-Token",
-		CookiePath: setting.AppSubURL,
+		CookiePath: conf.Server.Subpath,
 	}))
 	m.Use(toolbox.Toolboxer(m, toolbox.Options{
 		HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
@@ -163,15 +162,15 @@ func newMacaron() *macaron.Macaron {
 }
 
 func runWeb(c *cli.Context) error {
-	if c.IsSet("config") {
-		setting.CustomConf = c.String("config")
+	err := route.GlobalInit(c.String("config"))
+	if err != nil {
+		log.Fatal("Failed to initialize application: %v", err)
 	}
-	route.GlobalInit()
 
 	m := newMacaron()
 
 	reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
-	ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
+	ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: conf.Service.RequireSignInView})
 	ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
 	reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
 
@@ -185,7 +184,7 @@ func runWeb(c *cli.Context) error {
 	m.Get("/", ignSignIn, route.Home)
 	m.Group("/explore", func() {
 		m.Get("", func(c *context.Context) {
-			c.Redirect(setting.AppSubURL + "/explore/repos")
+			c.Redirect(conf.Server.Subpath + "/explore/repos")
 		})
 		m.Get("/repos", route.ExploreRepos)
 		m.Get("/users", route.ExploreUsers)
@@ -570,7 +569,7 @@ func runWeb(c *cli.Context) error {
 				m.Post("/upload-file", repo.UploadFileToServer)
 				m.Post("/upload-remove", bindIgnErr(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer)
 			}, func(c *context.Context) {
-				if !setting.Repository.Upload.Enabled {
+				if !conf.Repository.Upload.Enabled {
 					c.NotFound()
 					return
 				}
@@ -658,21 +657,21 @@ func runWeb(c *cli.Context) error {
 	}, ignSignIn)
 
 	m.Group("/-", func() {
-		if setting.Prometheus.Enabled {
+		if conf.Prometheus.Enabled {
 			m.Get("/metrics", func(c *context.Context) {
-				if !setting.Prometheus.EnableBasicAuth {
+				if !conf.Prometheus.EnableBasicAuth {
 					return
 				}
 
-				c.RequireBasicAuth(setting.Prometheus.BasicAuthUsername, setting.Prometheus.BasicAuthPassword)
+				c.RequireBasicAuth(conf.Prometheus.BasicAuthUsername, conf.Prometheus.BasicAuthPassword)
 			}, promhttp.Handler())
 		}
 	})
 
 	// robots.txt
 	m.Get("/robots.txt", func(c *context.Context) {
-		if setting.HasRobotsTxt {
-			c.ServeFileContent(path.Join(setting.CustomPath, "robots.txt"))
+		if conf.HasRobotsTxt {
+			c.ServeFileContent(filepath.Join(conf.CustomDir(), "robots.txt"))
 		} else {
 			c.NotFound()
 		}
@@ -683,69 +682,76 @@ func runWeb(c *cli.Context) error {
 
 	// Flag for port number in case first time run conflict.
 	if c.IsSet("port") {
-		setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, c.String("port"), 1)
-		setting.HTTPPort = c.String("port")
+		conf.Server.URL.Host = strings.Replace(conf.Server.URL.Host, conf.Server.URL.Port(), c.String("port"), 1)
+		conf.Server.ExternalURL = conf.Server.URL.String()
+		conf.Server.HTTPPort = c.String("port")
 	}
 
 	var listenAddr string
-	if setting.Protocol == setting.SCHEME_UNIX_SOCKET {
-		listenAddr = fmt.Sprintf("%s", setting.HTTPAddr)
+	if conf.Server.Protocol == "unix" {
+		listenAddr = conf.Server.HTTPAddr
 	} else {
-		listenAddr = fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.HTTPPort)
+		listenAddr = fmt.Sprintf("%s:%s", conf.Server.HTTPAddr, conf.Server.HTTPPort)
 	}
-	log.Info("Listen on %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
+	log.Info("Listen on %v://%s%s", conf.Server.Protocol, listenAddr, conf.Server.Subpath)
 
-	var err error
-	switch setting.Protocol {
-	case setting.SCHEME_HTTP:
+	switch conf.Server.Protocol {
+	case "http":
 		err = http.ListenAndServe(listenAddr, m)
-	case setting.SCHEME_HTTPS:
-		var tlsMinVersion uint16
-		switch setting.TLSMinVersion {
-		case "SSL30":
-			tlsMinVersion = tls.VersionSSL30
+
+	case "https":
+		tlsMinVersion := tls.VersionTLS12
+		switch conf.Server.TLSMinVersion {
+		case "TLS13":
+			tlsMinVersion = tls.VersionTLS13
 		case "TLS12":
 			tlsMinVersion = tls.VersionTLS12
 		case "TLS11":
 			tlsMinVersion = tls.VersionTLS11
 		case "TLS10":
-			fallthrough
-		default:
 			tlsMinVersion = tls.VersionTLS10
 		}
-		server := &http.Server{Addr: listenAddr, TLSConfig: &tls.Config{
-			MinVersion:               tlsMinVersion,
-			CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521},
-			PreferServerCipherSuites: true,
-			CipherSuites: []uint16{
-				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
-				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
-				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
-				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
-				tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
-				tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
-			},
-		}, Handler: m}
-		err = server.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
-	case setting.SCHEME_FCGI:
+		server := &http.Server{
+			Addr: listenAddr,
+			TLSConfig: &tls.Config{
+				MinVersion:               uint16(tlsMinVersion),
+				CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521},
+				PreferServerCipherSuites: true,
+				CipherSuites: []uint16{
+					tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+					tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+					tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+					tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+					tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+					tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+				},
+			}, Handler: m}
+		err = server.ListenAndServeTLS(conf.Server.CertFile, conf.Server.KeyFile)
+
+	case "fcgi":
 		err = fcgi.Serve(nil, m)
-	case setting.SCHEME_UNIX_SOCKET:
-		os.Remove(listenAddr)
+
+	case "unix":
+		err = os.Remove(listenAddr)
+		if err != nil {
+			log.Fatal("Failed to remove existing Unix domain socket: %v", err)
+		}
 
 		var listener *net.UnixListener
-		listener, err = net.ListenUnix("unix", &net.UnixAddr{listenAddr, "unix"})
+		listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: listenAddr, Net: "unix"})
 		if err != nil {
-			break // Handle error after switch
+			log.Fatal("Failed to listen on Unix networks: %v", err)
 		}
 
 		// FIXME: add proper implementation of signal capture on all protocols
 		// execute this on SIGTERM or SIGINT: listener.Close()
-		if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
-			log.Fatal("Failed to set permission of unix socket: %v", err)
+		if err = os.Chmod(listenAddr, conf.Server.UnixSocketMode); err != nil {
+			log.Fatal("Failed to change permission of Unix domain socket: %v", err)
 		}
 		err = http.Serve(listener, m)
+
 	default:
-		log.Fatal("Invalid protocol: %s", setting.Protocol)
+		log.Fatal("Unexpected server protocol: %s", conf.Server.Protocol)
 	}
 
 	if err != nil {

+ 116 - 0
internal/conf/computed.go

@@ -0,0 +1,116 @@
+// Copyright 2020 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package conf
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"sync"
+)
+
+// ℹ️ README: This file contains configuration values that require computation to be useful.
+
+// IsWindowsRuntime returns true if the current runtime in Windows.
+func IsWindowsRuntime() bool {
+	return runtime.GOOS == "windows"
+}
+
+// IsProdMode returns true if the application is running in production mode.
+func IsProdMode() bool {
+	return strings.EqualFold(App.RunMode, "prod")
+}
+
+var (
+	appPath     string
+	appPathOnce sync.Once
+)
+
+// AppPath returns the absolute path of the application's binary.
+func AppPath() string {
+	appPathOnce.Do(func() {
+		var err error
+		appPath, err = exec.LookPath(os.Args[0])
+		if err != nil {
+			panic("look executable path: " + err.Error())
+		}
+
+		appPath, err = filepath.Abs(appPath)
+		if err != nil {
+			panic("get absolute executable path: " + err.Error())
+		}
+	})
+
+	return appPath
+}
+
+var (
+	workDir     string
+	workDirOnce sync.Once
+)
+
+// WorkDir returns the absolute path of work directory. It reads the value of envrionment
+// variable GOGS_WORK_DIR. When not set, it uses the directory where the application's
+// binary is located.
+func WorkDir() string {
+	workDirOnce.Do(func() {
+		workDir = os.Getenv("GOGS_WORK_DIR")
+		if workDir != "" {
+			return
+		}
+
+		workDir = filepath.Dir(AppPath())
+	})
+
+	return workDir
+}
+
+var (
+	customDir     string
+	customDirOnce sync.Once
+)
+
+// CustomDir returns the absolute path of the custom directory that contains local overrides.
+// It reads the value of envrionment variable GOGS_CUSTOM. When not set, it uses the work
+// directory returned by WorkDir fucntion.
+func CustomDir() string {
+	customDirOnce.Do(func() {
+		customDir = os.Getenv("GOGS_CUSTOM")
+		if customDir != "" {
+			return
+		}
+
+		customDir = filepath.Join(WorkDir(), "custom")
+	})
+
+	return customDir
+}
+
+var (
+	homeDir     string
+	homeDirOnce sync.Once
+)
+
+// HomeDir returns the home directory by reading environment variables. It may return empty
+// string when environment variables are not set.
+func HomeDir() string {
+	homeDirOnce.Do(func() {
+		if !IsWindowsRuntime() {
+			homeDir = os.Getenv("HOME")
+			return
+		}
+
+		homeDir = os.Getenv("USERPROFILE")
+		if homeDir != "" {
+			return
+		}
+
+		homeDir = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+	})
+
+	return homeDir
+}

+ 327 - 474
internal/setting/setting.go → internal/conf/conf.go

@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package setting
+package conf
 
 import (
 	"net/mail"
 	"net/url"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
-	"runtime"
 	"strconv"
 	"strings"
 	"time"
@@ -21,83 +19,325 @@ import (
 	"github.com/go-macaron/session"
 	_ "github.com/go-macaron/session/redis"
 	"github.com/mcuadros/go-version"
-	"github.com/unknwon/com"
+	"github.com/pkg/errors"
 	"gopkg.in/ini.v1"
 	log "unknwon.dev/clog/v2"
 
 	"github.com/gogs/go-libravatar"
 
 	"gogs.io/gogs/internal/assets/conf"
-	"gogs.io/gogs/internal/process"
+	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/user"
 )
 
-type Scheme string
+func init() {
+	// Initialize the primary logger until logging service is up.
+	err := log.NewConsole()
+	if err != nil {
+		panic("init console logger: " + err.Error())
+	}
+}
 
-const (
-	SCHEME_HTTP        Scheme = "http"
-	SCHEME_HTTPS       Scheme = "https"
-	SCHEME_FCGI        Scheme = "fcgi"
-	SCHEME_UNIX_SOCKET Scheme = "unix"
-)
+// Asset is a wrapper for getting conf assets.
+func Asset(name string) ([]byte, error) {
+	return conf.Asset(name)
+}
 
-type LandingPage string
+// AssetDir is a wrapper for getting conf assets.
+func AssetDir(name string) ([]string, error) {
+	return conf.AssetDir(name)
+}
 
-const (
-	LANDING_PAGE_HOME    LandingPage = "/"
-	LANDING_PAGE_EXPLORE LandingPage = "/explore"
-)
+// MustAsset is a wrapper for getting conf assets.
+func MustAsset(name string) []byte {
+	return conf.MustAsset(name)
+}
 
-var (
-	// Build information should only be set by -ldflags.
-	BuildTime   string
-	BuildCommit string
-
-	// App settings
-	AppVersion     string
-	AppName        string
-	AppURL         string
-	AppSubURL      string
-	AppSubURLDepth int // Number of slashes
-	AppPath        string
-	AppDataPath    string
-	HostAddress    string // AppURL without protocol and slashes
-
-	// Server settings
-	Protocol             Scheme
-	Domain               string
-	HTTPAddr             string
-	HTTPPort             string
-	LocalURL             string
-	OfflineMode          bool
-	DisableRouterLog     bool
-	CertFile             string
-	KeyFile              string
-	TLSMinVersion        string
-	LoadAssetsFromDisk   bool
-	StaticRootPath       string
-	EnableGzip           bool
-	LandingPageURL       LandingPage
-	UnixSocketPermission uint32
+// File is the configuration object.
+var File *ini.File
+
+// Init initializes configuration from conf assets and given custom configuration file.
+// If `customConf` is empty, it falls back to default location, i.e. "<WORK DIR>/custom".
+// It is safe to call this function multiple times with desired `customConf`, but it is
+// not concurrent safe.
+//
+// ⚠️ WARNING: Do not print anything in this function other than wanrings.
+func Init(customConf string) error {
+	var err error
+	File, err = ini.LoadSources(ini.LoadOptions{
+		IgnoreInlineComment: true,
+	}, conf.MustAsset("conf/app.ini"))
+	if err != nil {
+		return errors.Wrap(err, "parse 'conf/app.ini'")
+	}
+	File.NameMapper = ini.SnackCase
 
-	HTTP struct {
-		AccessControlAllowOrigin string
+	customConf, err = filepath.Abs(customConf)
+	if err != nil {
+		return errors.Wrap(err, "get absolute path")
+	}
+	if customConf == "" {
+		customConf = filepath.Join(CustomDir(), "conf/app.ini")
+	}
+	CustomConf = customConf
+
+	if osutil.IsFile(customConf) {
+		if err = File.Append(customConf); err != nil {
+			return errors.Wrapf(err, "append %q", customConf)
+		}
+	} else {
+		log.Warn("Custom config %q not found. Ignore this warning if you're running for the first time", customConf)
+	}
+
+	if err = File.Section(ini.DefaultSection).MapTo(&App); err != nil {
+		return errors.Wrap(err, "mapping default section")
+	}
+
+	// ***************************
+	// ----- Server settings -----
+	// ***************************
+
+	if err = File.Section("server").MapTo(&Server); err != nil {
+		return errors.Wrap(err, "mapping [server] section")
+	}
+
+	if !strings.HasSuffix(Server.ExternalURL, "/") {
+		Server.ExternalURL += "/"
+	}
+	Server.URL, err = url.Parse(Server.ExternalURL)
+	if err != nil {
+		return errors.Wrapf(err, "parse '[server] EXTERNAL_URL' %q", err)
+	}
+
+	// Subpath should start with '/' and end without '/', i.e. '/{subpath}'.
+	Server.Subpath = strings.TrimRight(Server.URL.Path, "/")
+	Server.SubpathDepth = strings.Count(Server.Subpath, "/")
+
+	unixSocketMode, err := strconv.ParseUint(Server.UnixSocketPermission, 8, 32)
+	if err != nil {
+		return errors.Wrapf(err, "parse '[server] UNIX_SOCKET_PERMISSION' %q", Server.UnixSocketPermission)
+	}
+	if unixSocketMode > 0777 {
+		unixSocketMode = 0666
+	}
+	Server.UnixSocketMode = os.FileMode(unixSocketMode)
+
+	// ************************
+	// ----- SSH settings -----
+	// ************************
+
+	if err = File.Section("server").MapTo(&SSH); err != nil {
+		return errors.Wrap(err, "mapping SSH settings from [server] section")
+	}
+
+	if !SSH.Disabled {
+		if !SSH.StartBuiltinServer {
+			SSH.RootPath = filepath.Join(HomeDir(), ".ssh")
+			SSH.KeyTestPath = os.TempDir()
+
+			if err := os.MkdirAll(SSH.RootPath, 0700); err != nil {
+				return errors.Wrap(err, "create SSH root directory")
+			} else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
+				return errors.Wrap(err, "create SSH key test directory")
+			}
+		} else {
+			SSH.RewriteAuthorizedKeysAtStart = false
+		}
+
+		// Check if server is eligible for minimum key size check when user choose to enable.
+		// Windows server and OpenSSH version lower than 5.1 are forced to be disabled because
+		// the "ssh-keygen" in Windows does not print key type.
+		// See https://github.com/gogs/gogs/issues/4507.
+		if SSH.MinimumKeySizeCheck {
+			sshVersion, err := openSSHVersion()
+			if err != nil {
+				return errors.Wrap(err, "get OpenSSH version")
+			}
+
+			if IsWindowsRuntime() || version.Compare(sshVersion, "5.1", "<") {
+				log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
+	1. Windows server
+	2. OpenSSH version is lower than 5.1`)
+			} else {
+				SSH.MinimumKeySizes = map[string]int{}
+				for _, key := range File.Section("ssh.minimum_key_sizes").Keys() {
+					if key.MustInt() != -1 {
+						SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt()
+					}
+				}
+			}
+		}
 	}
 
-	SSH struct {
-		Disabled                     bool           `ini:"DISABLE_SSH"`
-		StartBuiltinServer           bool           `ini:"START_SSH_SERVER"`
-		Domain                       string         `ini:"SSH_DOMAIN"`
-		Port                         int            `ini:"SSH_PORT"`
-		ListenHost                   string         `ini:"SSH_LISTEN_HOST"`
-		ListenPort                   int            `ini:"SSH_LISTEN_PORT"`
-		RootPath                     string         `ini:"SSH_ROOT_PATH"`
-		RewriteAuthorizedKeysAtStart bool           `ini:"REWRITE_AUTHORIZED_KEYS_AT_START"`
-		ServerCiphers                []string       `ini:"SSH_SERVER_CIPHERS"`
-		KeyTestPath                  string         `ini:"SSH_KEY_TEST_PATH"`
-		KeygenPath                   string         `ini:"SSH_KEYGEN_PATH"`
-		MinimumKeySizeCheck          bool           `ini:"MINIMUM_KEY_SIZE_CHECK"`
-		MinimumKeySizes              map[string]int `ini:"-"`
+	transferDeprecated()
+
+	// TODO
+
+	sec := File.Section("security")
+	InstallLock = sec.Key("INSTALL_LOCK").MustBool()
+	SecretKey = sec.Key("SECRET_KEY").String()
+	LoginRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt()
+	CookieUserName = sec.Key("COOKIE_USERNAME").String()
+	CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String()
+	CookieSecure = sec.Key("COOKIE_SECURE").MustBool(false)
+	ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
+	EnableLoginStatusCookie = sec.Key("ENABLE_LOGIN_STATUS_COOKIE").MustBool(false)
+	LoginStatusCookieName = sec.Key("LOGIN_STATUS_COOKIE_NAME").MustString("login_status")
+
+	// Does not check run user when the install lock is off.
+	if InstallLock {
+		currentUser, match := IsRunUserMatchCurrentUser(App.RunUser)
+		if !match {
+			log.Fatal("The user configured to run Gogs is %q, but the current user is %q", App.RunUser, currentUser)
+		}
+	}
+
+	sec = File.Section("attachment")
+	AttachmentPath = sec.Key("PATH").MustString(filepath.Join(Server.AppDataPath, "attachments"))
+	if !filepath.IsAbs(AttachmentPath) {
+		AttachmentPath = path.Join(workDir, AttachmentPath)
+	}
+	AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1)
+	AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
+	AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
+	AttachmentEnabled = sec.Key("ENABLED").MustBool(true)
+
+	TimeFormat = map[string]string{
+		"ANSIC":       time.ANSIC,
+		"UnixDate":    time.UnixDate,
+		"RubyDate":    time.RubyDate,
+		"RFC822":      time.RFC822,
+		"RFC822Z":     time.RFC822Z,
+		"RFC850":      time.RFC850,
+		"RFC1123":     time.RFC1123,
+		"RFC1123Z":    time.RFC1123Z,
+		"RFC3339":     time.RFC3339,
+		"RFC3339Nano": time.RFC3339Nano,
+		"Kitchen":     time.Kitchen,
+		"Stamp":       time.Stamp,
+		"StampMilli":  time.StampMilli,
+		"StampMicro":  time.StampMicro,
+		"StampNano":   time.StampNano,
+	}[File.Section("time").Key("FORMAT").MustString("RFC1123")]
+
+	// Determine and create root git repository path.
+	sec = File.Section("repository")
+	RepoRootPath = sec.Key("ROOT").MustString(filepath.Join(HomeDir(), "gogs-repositories"))
+	if !filepath.IsAbs(RepoRootPath) {
+		RepoRootPath = path.Join(workDir, RepoRootPath)
+	} else {
+		RepoRootPath = path.Clean(RepoRootPath)
+	}
+	ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
+	if err = File.Section("repository").MapTo(&Repository); err != nil {
+		log.Fatal("Failed to map Repository settings: %v", err)
+	} else if err = File.Section("repository.editor").MapTo(&Repository.Editor); err != nil {
+		log.Fatal("Failed to map Repository.Editor settings: %v", err)
+	} else if err = File.Section("repository.upload").MapTo(&Repository.Upload); err != nil {
+		log.Fatal("Failed to map Repository.Upload settings: %v", err)
+	}
+
+	if !filepath.IsAbs(Repository.Upload.TempPath) {
+		Repository.Upload.TempPath = path.Join(workDir, Repository.Upload.TempPath)
+	}
+
+	sec = File.Section("picture")
+	AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(filepath.Join(Server.AppDataPath, "avatars"))
+	if !filepath.IsAbs(AvatarUploadPath) {
+		AvatarUploadPath = path.Join(workDir, AvatarUploadPath)
+	}
+	RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(filepath.Join(Server.AppDataPath, "repo-avatars"))
+	if !filepath.IsAbs(RepositoryAvatarUploadPath) {
+		RepositoryAvatarUploadPath = path.Join(workDir, RepositoryAvatarUploadPath)
+	}
+	switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
+	case "duoshuo":
+		GravatarSource = "http://gravatar.duoshuo.com/avatar/"
+	case "gravatar":
+		GravatarSource = "https://secure.gravatar.com/avatar/"
+	case "libravatar":
+		GravatarSource = "https://seccdn.libravatar.org/avatar/"
+	default:
+		GravatarSource = source
+	}
+	DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
+	EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(true)
+	if Server.OfflineMode {
+		DisableGravatar = true
+		EnableFederatedAvatar = false
+	}
+	if DisableGravatar {
+		EnableFederatedAvatar = false
+	}
+
+	if EnableFederatedAvatar {
+		LibravatarService = libravatar.New()
+		parts := strings.Split(GravatarSource, "/")
+		if len(parts) >= 3 {
+			if parts[0] == "https:" {
+				LibravatarService.SetUseHTTPS(true)
+				LibravatarService.SetSecureFallbackHost(parts[2])
+			} else {
+				LibravatarService.SetUseHTTPS(false)
+				LibravatarService.SetFallbackHost(parts[2])
+			}
+		}
+	}
+
+	if err = File.Section("http").MapTo(&HTTP); err != nil {
+		log.Fatal("Failed to map HTTP settings: %v", err)
+	} else if err = File.Section("webhook").MapTo(&Webhook); err != nil {
+		log.Fatal("Failed to map Webhook settings: %v", err)
+	} else if err = File.Section("release.attachment").MapTo(&Release.Attachment); err != nil {
+		log.Fatal("Failed to map Release.Attachment settings: %v", err)
+	} else if err = File.Section("markdown").MapTo(&Markdown); err != nil {
+		log.Fatal("Failed to map Markdown settings: %v", err)
+	} else if err = File.Section("smartypants").MapTo(&Smartypants); err != nil {
+		log.Fatal("Failed to map Smartypants settings: %v", err)
+	} else if err = File.Section("admin").MapTo(&Admin); err != nil {
+		log.Fatal("Failed to map Admin settings: %v", err)
+	} else if err = File.Section("cron").MapTo(&Cron); err != nil {
+		log.Fatal("Failed to map Cron settings: %v", err)
+	} else if err = File.Section("git").MapTo(&Git); err != nil {
+		log.Fatal("Failed to map Git settings: %v", err)
+	} else if err = File.Section("mirror").MapTo(&Mirror); err != nil {
+		log.Fatal("Failed to map Mirror settings: %v", err)
+	} else if err = File.Section("api").MapTo(&API); err != nil {
+		log.Fatal("Failed to map API settings: %v", err)
+	} else if err = File.Section("ui").MapTo(&UI); err != nil {
+		log.Fatal("Failed to map UI settings: %v", err)
+	} else if err = File.Section("prometheus").MapTo(&Prometheus); err != nil {
+		log.Fatal("Failed to map Prometheus settings: %v", err)
+	}
+
+	if Mirror.DefaultInterval <= 0 {
+		Mirror.DefaultInterval = 24
+	}
+
+	Langs = File.Section("i18n").Key("LANGS").Strings(",")
+	Names = File.Section("i18n").Key("NAMES").Strings(",")
+	dateLangs = File.Section("i18n.datelang").KeysHash()
+
+	ShowFooterBranding = File.Section("other").Key("SHOW_FOOTER_BRANDING").MustBool()
+	ShowFooterTemplateLoadTime = File.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool()
+
+	HasRobotsTxt = osutil.IsFile(path.Join(CustomDir(), "robots.txt"))
+	return nil
+}
+
+// MustInit panics if configuration initialization failed.
+func MustInit(customConf string) {
+	err := Init(customConf)
+	if err != nil {
+		panic(err)
+	}
+}
+
+// TODO
+
+var (
+	HTTP struct {
+		AccessControlAllowOrigin string
 	}
 
 	// Security settings
@@ -316,12 +556,6 @@ var (
 	ShowFooterTemplateLoadTime bool
 
 	// Global setting objects
-	Cfg          *ini.File
-	CustomPath   string // Custom directory path
-	CustomConf   string
-	ProdMode     bool
-	RunUser      string
-	IsWindows    bool
 	HasRobotsTxt bool
 )
 
@@ -334,59 +568,12 @@ func DateLang(lang string) string {
 	return "en"
 }
 
-// execPath returns the executable path.
-func execPath() (string, error) {
-	file, err := exec.LookPath(os.Args[0])
-	if err != nil {
-		return "", err
-	}
-	return filepath.Abs(file)
-}
-
-func init() {
-	IsWindows = runtime.GOOS == "windows"
-
-	err := log.NewConsole()
-	if err != nil {
-		panic("init console logger: " + err.Error())
-	}
-
-	AppPath, err = execPath()
-	if err != nil {
-		log.Fatal("Failed to get executable path: %v", err)
-	}
-
-	// NOTE: we don't use path.Dir here because it does not handle case
-	// which path starts with two "/" in Windows: "//psf/Home/..."
-	AppPath = strings.Replace(AppPath, "\\", "/", -1)
-}
-
-// WorkDir returns absolute path of work directory.
-func WorkDir() (string, error) {
-	wd := os.Getenv("GOGS_WORK_DIR")
-	if len(wd) > 0 {
-		return wd, nil
-	}
-
-	i := strings.LastIndex(AppPath, "/")
-	if i == -1 {
-		return AppPath, nil
-	}
-	return AppPath[:i], nil
-}
-
-func forcePathSeparator(path string) {
-	if strings.Contains(path, "\\") {
-		log.Fatal("Do not use '\\' or '\\\\' in paths, please use '/' in all places")
-	}
-}
-
 // IsRunUserMatchCurrentUser returns false if configured run user does not match
 // actual user that runs the app. The first return value is the actual user name.
 // This check is ignored under Windows since SSH remote login is not the main
 // method to login on Windows.
 func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
-	if IsWindows {
+	if IsWindowsRuntime() {
 		return "", true
 	}
 
@@ -394,351 +581,17 @@ func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
 	return currentUser, runUser == currentUser
 }
 
-// getOpenSSHVersion parses and returns string representation of OpenSSH version
-// returned by command "ssh -V".
-func getOpenSSHVersion() string {
-	// NOTE: Somehow the version is printed to stderr.
-	_, stderr, err := process.Exec("setting.getOpenSSHVersion", "ssh", "-V")
-	if err != nil {
-		log.Fatal("Failed to get OpenSSH version: %v - %s", err, stderr)
-	}
-
-	// Trim unused information: https://github.com/gogs/gogs/issues/4507#issuecomment-305150441
-	version := strings.TrimRight(strings.Fields(stderr)[0], ",1234567890")
-	version = strings.TrimSuffix(strings.TrimPrefix(version, "OpenSSH_"), "p")
-	return version
-}
-
-// Init initializes configuration by loading from sources.
-// ⚠️ WARNING: Do not print anything in this function other than wanrings or errors.
-func Init() {
-	workDir, err := WorkDir()
-	if err != nil {
-		log.Fatal("Failed to get work directory: %v", err)
-		return
-	}
-
-	Cfg, err = ini.LoadSources(ini.LoadOptions{
-		IgnoreInlineComment: true,
-	}, conf.MustAsset("conf/app.ini"))
-	if err != nil {
-		log.Fatal("Failed to parse 'conf/app.ini': %v", err)
-		return
-	}
-
-	CustomPath = os.Getenv("GOGS_CUSTOM")
-	if len(CustomPath) == 0 {
-		CustomPath = workDir + "/custom"
-	}
-
-	if len(CustomConf) == 0 {
-		CustomConf = CustomPath + "/conf/app.ini"
-	}
-
-	if com.IsFile(CustomConf) {
-		if err = Cfg.Append(CustomConf); err != nil {
-			log.Fatal("Failed to load custom conf %q: %v", CustomConf, err)
-			return
-		}
-	} else {
-		log.Warn("Custom config '%s' not found, ignore this warning if you're running the first time", CustomConf)
-	}
-	Cfg.NameMapper = ini.SnackCase
-
-	homeDir, err := com.HomeDir()
-	if err != nil {
-		log.Fatal("Failed to get home directory: %v", err)
-		return
-	}
-	homeDir = strings.Replace(homeDir, "\\", "/", -1)
-
-	LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(workDir, "log"))
-	forcePathSeparator(LogRootPath)
-
-	sec := Cfg.Section("server")
-	AppName = Cfg.Section("").Key("APP_NAME").MustString("Gogs")
-	AppURL = sec.Key("ROOT_URL").MustString("http://localhost:3000/")
-	if AppURL[len(AppURL)-1] != '/' {
-		AppURL += "/"
-	}
-
-	// Check if has app suburl.
-	url, err := url.Parse(AppURL)
-	if err != nil {
-		log.Fatal("Failed to parse ROOT_URL %q: %s", AppURL, err)
-		return
-	}
-	// Suburl should start with '/' and end without '/', such as '/{subpath}'.
-	// This value is empty if site does not have sub-url.
-	AppSubURL = strings.TrimSuffix(url.Path, "/")
-	AppSubURLDepth = strings.Count(AppSubURL, "/")
-	HostAddress = url.Host
-
-	Protocol = SCHEME_HTTP
-	if sec.Key("PROTOCOL").String() == "https" {
-		Protocol = SCHEME_HTTPS
-		CertFile = sec.Key("CERT_FILE").String()
-		KeyFile = sec.Key("KEY_FILE").String()
-		TLSMinVersion = sec.Key("TLS_MIN_VERSION").String()
-	} else if sec.Key("PROTOCOL").String() == "fcgi" {
-		Protocol = SCHEME_FCGI
-	} else if sec.Key("PROTOCOL").String() == "unix" {
-		Protocol = SCHEME_UNIX_SOCKET
-		UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
-		UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32)
-		if err != nil || UnixSocketPermissionParsed > 0777 {
-			log.Fatal("Failed to parse unixSocketPermission %q: %v", UnixSocketPermissionRaw, err)
-			return
-		}
-		UnixSocketPermission = uint32(UnixSocketPermissionParsed)
-	}
-	Domain = sec.Key("DOMAIN").MustString("localhost")
-	HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
-	HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
-	LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(string(Protocol) + "://localhost:" + HTTPPort + "/")
-	OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
-	DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
-	LoadAssetsFromDisk = sec.Key("LOAD_ASSETS_FROM_DISK").MustBool()
-	StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir)
-	AppDataPath = sec.Key("APP_DATA_PATH").MustString("data")
-	EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
-
-	switch sec.Key("LANDING_PAGE").MustString("home") {
-	case "explore":
-		LandingPageURL = LANDING_PAGE_EXPLORE
-	default:
-		LandingPageURL = LANDING_PAGE_HOME
-	}
-
-	SSH.RootPath = path.Join(homeDir, ".ssh")
-	SSH.RewriteAuthorizedKeysAtStart = sec.Key("REWRITE_AUTHORIZED_KEYS_AT_START").MustBool()
-	SSH.ServerCiphers = sec.Key("SSH_SERVER_CIPHERS").Strings(",")
-	SSH.KeyTestPath = os.TempDir()
-	if err = Cfg.Section("server").MapTo(&SSH); err != nil {
-		log.Fatal("Failed to map SSH settings: %v", err)
-		return
-	}
-	if SSH.Disabled {
-		SSH.StartBuiltinServer = false
-		SSH.MinimumKeySizeCheck = false
-	}
-
-	if !SSH.Disabled && !SSH.StartBuiltinServer {
-		if err := os.MkdirAll(SSH.RootPath, 0700); err != nil {
-			log.Fatal("Failed to create '%s': %v", SSH.RootPath, err)
-			return
-		} else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
-			log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err)
-			return
-		}
-	}
-
-	if SSH.StartBuiltinServer {
-		SSH.RewriteAuthorizedKeysAtStart = false
-	}
-
-	// Check if server is eligible for minimum key size check when user choose to enable.
-	// Windows server and OpenSSH version lower than 5.1 (https://gogs.io/gogs/issues/4507)
-	// are forced to be disabled because the "ssh-keygen" in Windows does not print key type.
-	if SSH.MinimumKeySizeCheck &&
-		(IsWindows || version.Compare(getOpenSSHVersion(), "5.1", "<")) {
-		SSH.MinimumKeySizeCheck = false
-		log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
-1. Windows server
-2. OpenSSH version is lower than 5.1`)
-	}
-
-	if SSH.MinimumKeySizeCheck {
-		SSH.MinimumKeySizes = map[string]int{}
-		for _, key := range Cfg.Section("ssh.minimum_key_sizes").Keys() {
-			if key.MustInt() != -1 {
-				SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt()
-			}
-		}
-	}
-
-	sec = Cfg.Section("security")
-	InstallLock = sec.Key("INSTALL_LOCK").MustBool()
-	SecretKey = sec.Key("SECRET_KEY").String()
-	LoginRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt()
-	CookieUserName = sec.Key("COOKIE_USERNAME").String()
-	CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String()
-	CookieSecure = sec.Key("COOKIE_SECURE").MustBool(false)
-	ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
-	EnableLoginStatusCookie = sec.Key("ENABLE_LOGIN_STATUS_COOKIE").MustBool(false)
-	LoginStatusCookieName = sec.Key("LOGIN_STATUS_COOKIE_NAME").MustString("login_status")
-
-	sec = Cfg.Section("attachment")
-	AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
-	if !filepath.IsAbs(AttachmentPath) {
-		AttachmentPath = path.Join(workDir, AttachmentPath)
-	}
-	AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1)
-	AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
-	AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
-	AttachmentEnabled = sec.Key("ENABLED").MustBool(true)
-
-	TimeFormat = map[string]string{
-		"ANSIC":       time.ANSIC,
-		"UnixDate":    time.UnixDate,
-		"RubyDate":    time.RubyDate,
-		"RFC822":      time.RFC822,
-		"RFC822Z":     time.RFC822Z,
-		"RFC850":      time.RFC850,
-		"RFC1123":     time.RFC1123,
-		"RFC1123Z":    time.RFC1123Z,
-		"RFC3339":     time.RFC3339,
-		"RFC3339Nano": time.RFC3339Nano,
-		"Kitchen":     time.Kitchen,
-		"Stamp":       time.Stamp,
-		"StampMilli":  time.StampMilli,
-		"StampMicro":  time.StampMicro,
-		"StampNano":   time.StampNano,
-	}[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")]
-
-	RunUser = Cfg.Section("").Key("RUN_USER").String()
-	// Does not check run user when the install lock is off.
-	if InstallLock {
-		currentUser, match := IsRunUserMatchCurrentUser(RunUser)
-		if !match {
-			log.Fatal("The user configured to run Gogs is %q, but the current user is %q", RunUser, currentUser)
-			return
-		}
-	}
-
-	ProdMode = Cfg.Section("").Key("RUN_MODE").String() == "prod"
-
-	// Determine and create root git repository path.
-	sec = Cfg.Section("repository")
-	RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gogs-repositories"))
-	forcePathSeparator(RepoRootPath)
-	if !filepath.IsAbs(RepoRootPath) {
-		RepoRootPath = path.Join(workDir, RepoRootPath)
-	} else {
-		RepoRootPath = path.Clean(RepoRootPath)
-	}
-	ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
-	if err = Cfg.Section("repository").MapTo(&Repository); err != nil {
-		log.Fatal("Failed to map Repository settings: %v", err)
-		return
-	} else if err = Cfg.Section("repository.editor").MapTo(&Repository.Editor); err != nil {
-		log.Fatal("Failed to map Repository.Editor settings: %v", err)
-		return
-	} else if err = Cfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil {
-		log.Fatal("Failed to map Repository.Upload settings: %v", err)
-		return
-	}
-
-	if !filepath.IsAbs(Repository.Upload.TempPath) {
-		Repository.Upload.TempPath = path.Join(workDir, Repository.Upload.TempPath)
-	}
-
-	sec = Cfg.Section("picture")
-	AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
-	forcePathSeparator(AvatarUploadPath)
-	if !filepath.IsAbs(AvatarUploadPath) {
-		AvatarUploadPath = path.Join(workDir, AvatarUploadPath)
-	}
-	RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars"))
-	forcePathSeparator(RepositoryAvatarUploadPath)
-	if !filepath.IsAbs(RepositoryAvatarUploadPath) {
-		RepositoryAvatarUploadPath = path.Join(workDir, RepositoryAvatarUploadPath)
-	}
-	switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
-	case "duoshuo":
-		GravatarSource = "http://gravatar.duoshuo.com/avatar/"
-	case "gravatar":
-		GravatarSource = "https://secure.gravatar.com/avatar/"
-	case "libravatar":
-		GravatarSource = "https://seccdn.libravatar.org/avatar/"
-	default:
-		GravatarSource = source
-	}
-	DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
-	EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(true)
-	if OfflineMode {
-		DisableGravatar = true
-		EnableFederatedAvatar = false
-	}
-	if DisableGravatar {
-		EnableFederatedAvatar = false
-	}
-
-	if EnableFederatedAvatar {
-		LibravatarService = libravatar.New()
-		parts := strings.Split(GravatarSource, "/")
-		if len(parts) >= 3 {
-			if parts[0] == "https:" {
-				LibravatarService.SetUseHTTPS(true)
-				LibravatarService.SetSecureFallbackHost(parts[2])
-			} else {
-				LibravatarService.SetUseHTTPS(false)
-				LibravatarService.SetFallbackHost(parts[2])
-			}
-		}
-	}
-
-	if err = Cfg.Section("http").MapTo(&HTTP); err != nil {
-		log.Fatal("Failed to map HTTP settings: %v", err)
-		return
-	} else if err = Cfg.Section("webhook").MapTo(&Webhook); err != nil {
-		log.Fatal("Failed to map Webhook settings: %v", err)
-		return
-	} else if err = Cfg.Section("release.attachment").MapTo(&Release.Attachment); err != nil {
-		log.Fatal("Failed to map Release.Attachment settings: %v", err)
-		return
-	} else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
-		log.Fatal("Failed to map Markdown settings: %v", err)
-		return
-	} else if err = Cfg.Section("smartypants").MapTo(&Smartypants); err != nil {
-		log.Fatal("Failed to map Smartypants settings: %v", err)
-		return
-	} else if err = Cfg.Section("admin").MapTo(&Admin); err != nil {
-		log.Fatal("Failed to map Admin settings: %v", err)
-		return
-	} else if err = Cfg.Section("cron").MapTo(&Cron); err != nil {
-		log.Fatal("Failed to map Cron settings: %v", err)
-		return
-	} else if err = Cfg.Section("git").MapTo(&Git); err != nil {
-		log.Fatal("Failed to map Git settings: %v", err)
-		return
-	} else if err = Cfg.Section("mirror").MapTo(&Mirror); err != nil {
-		log.Fatal("Failed to map Mirror settings: %v", err)
-		return
-	} else if err = Cfg.Section("api").MapTo(&API); err != nil {
-		log.Fatal("Failed to map API settings: %v", err)
-		return
-	} else if err = Cfg.Section("ui").MapTo(&UI); err != nil {
-		log.Fatal("Failed to map UI settings: %v", err)
-		return
-	} else if err = Cfg.Section("prometheus").MapTo(&Prometheus); err != nil {
-		log.Fatal("Failed to map Prometheus settings: %v", err)
-		return
-	}
-
-	if Mirror.DefaultInterval <= 0 {
-		Mirror.DefaultInterval = 24
-	}
-
-	Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
-	Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
-	dateLangs = Cfg.Section("i18n.datelang").KeysHash()
-
-	ShowFooterBranding = Cfg.Section("other").Key("SHOW_FOOTER_BRANDING").MustBool()
-	ShowFooterTemplateLoadTime = Cfg.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool()
-
-	HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))
-}
-
-// InitLogging initializes the logging infrastructure of the application.
+// InitLogging initializes the logging service of the application.
 func InitLogging() {
+	LogRootPath = File.Section("log").Key("ROOT_PATH").MustString(filepath.Join(WorkDir(), "log"))
+
 	// Because we always create a console logger as the primary logger at init time,
 	// we need to remove it in case the user doesn't configure to use it after the
-	// logging infrastructure is initalized.
+	// logging service is initalized.
 	hasConsole := false
 
 	// Iterate over [log.*] sections to initialize individual logger.
-	LogModes = strings.Split(Cfg.Section("log").Key("MODE").MustString("console"), ",")
+	LogModes = strings.Split(File.Section("log").Key("MODE").MustString("console"), ",")
 	LogConfigs = make([]interface{}, len(LogModes))
 	levelMappings := map[string]log.Level{
 		"trace": log.LevelTrace,
@@ -755,7 +608,7 @@ func InitLogging() {
 	for i, mode := range LogModes {
 		mode = strings.ToLower(strings.TrimSpace(mode))
 		secName := "log." + mode
-		sec, err := Cfg.GetSection(secName)
+		sec, err := File.GetSection(secName)
 		if err != nil {
 			log.Fatal("Missing configuration section [%s] for %q logger", secName, mode)
 			return
@@ -852,7 +705,7 @@ var Service struct {
 }
 
 func newService() {
-	sec := Cfg.Section("service")
+	sec := File.Section("service")
 	Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
 	Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
 	Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()
@@ -864,12 +717,12 @@ func newService() {
 }
 
 func newCacheService() {
-	CacheAdapter = Cfg.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
+	CacheAdapter = File.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
 	switch CacheAdapter {
 	case "memory":
-		CacheInterval = Cfg.Section("cache").Key("INTERVAL").MustInt(60)
+		CacheInterval = File.Section("cache").Key("INTERVAL").MustInt(60)
 	case "redis", "memcache":
-		CacheConn = strings.Trim(Cfg.Section("cache").Key("HOST").String(), "\" ")
+		CacheConn = strings.Trim(File.Section("cache").Key("HOST").String(), "\" ")
 	default:
 		log.Fatal("Unrecognized cache adapter %q", CacheAdapter)
 		return
@@ -879,15 +732,15 @@ func newCacheService() {
 }
 
 func newSessionService() {
-	SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
+	SessionConfig.Provider = File.Section("session").Key("PROVIDER").In("memory",
 		[]string{"memory", "file", "redis", "mysql"})
-	SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").String(), "\" ")
-	SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gogs")
-	SessionConfig.CookiePath = AppSubURL
-	SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool()
-	SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(3600)
-	SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
-	CSRFCookieName = Cfg.Section("session").Key("CSRF_COOKIE_NAME").MustString("_csrf")
+	SessionConfig.ProviderConfig = strings.Trim(File.Section("session").Key("PROVIDER_CONFIG").String(), "\" ")
+	SessionConfig.CookieName = File.Section("session").Key("COOKIE_NAME").MustString("i_like_gogs")
+	SessionConfig.CookiePath = Server.Subpath
+	SessionConfig.Secure = File.Section("session").Key("COOKIE_SECURE").MustBool()
+	SessionConfig.Gclifetime = File.Section("session").Key("GC_INTERVAL_TIME").MustInt64(3600)
+	SessionConfig.Maxlifetime = File.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
+	CSRFCookieName = File.Section("session").Key("CSRF_COOKIE_NAME").MustString("_csrf")
 
 	log.Trace("Session service is enabled")
 }
@@ -916,14 +769,14 @@ var (
 // newMailService initializes mail service options from configuration.
 // No non-error log will be printed in hook mode.
 func newMailService() {
-	sec := Cfg.Section("mailer")
+	sec := File.Section("mailer")
 	if !sec.Key("ENABLED").MustBool() {
 		return
 	}
 
 	MailService = &Mailer{
 		QueueLength:     sec.Key("SEND_BUFFER_LEN").MustInt(100),
-		SubjectPrefix:   sec.Key("SUBJECT_PREFIX").MustString("[" + AppName + "] "),
+		SubjectPrefix:   sec.Key("SUBJECT_PREFIX").MustString("[" + App.BrandName + "] "),
 		Host:            sec.Key("HOST").String(),
 		User:            sec.Key("USER").String(),
 		Passwd:          sec.Key("PASSWD").String(),
@@ -954,7 +807,7 @@ func newMailService() {
 }
 
 func newRegisterMailService() {
-	if !Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
+	if !File.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
 		return
 	} else if MailService == nil {
 		log.Warn("Email confirmation is not enabled due to the mail service is not available")
@@ -967,7 +820,7 @@ func newRegisterMailService() {
 // newNotifyMailService initializes notification email service options from configuration.
 // No non-error log will be printed in hook mode.
 func newNotifyMailService() {
-	if !Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
+	if !File.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
 		return
 	} else if MailService == nil {
 		log.Warn("Email notification is not enabled due to the mail service is not available")

+ 107 - 0
internal/conf/static.go

@@ -0,0 +1,107 @@
+// Copyright 2020 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package conf
+
+import (
+	"net/url"
+	"os"
+)
+
+// ℹ️ README: This file contains static values that should only be set at initialization time.
+
+// HasMinWinSvc is whether the application is built with Windows Service support.
+var HasMinWinSvc bool
+
+// Build information should only be set by -ldflags.
+var (
+	BuildTime   string
+	BuildCommit string
+)
+
+// CustomConf returns the absolute path of custom configuration file that is used.
+var CustomConf string
+
+var (
+	// Application settings
+	App struct {
+		// ⚠️ WARNING: Should only be set by gogs.go.
+		Version string `ini:"-"`
+
+		BrandName string
+		RunUser   string
+		RunMode   string
+
+		// Deprecated: Use BrandName instead, will be removed in 0.13.
+		AppName string
+	}
+
+	// Server settings
+	Server struct {
+		ExternalURL          string `ini:"EXTERNAL_URL"`
+		Domain               string
+		Protocol             string
+		HTTPAddr             string `ini:"HTTP_ADDR"`
+		HTTPPort             string `ini:"HTTP_PORT"`
+		CertFile             string
+		KeyFile              string
+		TLSMinVersion        string `ini:"TLS_MIN_VERSION"`
+		UnixSocketPermission string
+		LocalRootURL         string `ini:"LOCAL_ROOT_URL"`
+
+		OfflineMode      bool
+		DisableRouterLog bool
+		EnableGzip       bool
+
+		AppDataPath        string
+		LoadAssetsFromDisk bool
+
+		LandingURL string `ini:"LANDING_URL"`
+
+		// Derived from other static values
+		URL            *url.URL    `ini:"-"` // Parsed URL object of ExternalURL.
+		Subpath        string      `ini:"-"` // Subpath found the ExternalURL. Should be empty when not found.
+		SubpathDepth   int         `ini:"-"` // The number of slashes found in the Subpath.
+		UnixSocketMode os.FileMode `ini:"-"` // Parsed file mode of UnixSocketPermission.
+
+		// Deprecated: Use ExternalURL instead, will be removed in 0.13.
+		RootURL string `ini:"ROOT_URL"`
+		// Deprecated: Use LandingURL instead, will be removed in 0.13.
+		LangdingPage string `ini:"LANDING_PAGE"`
+	}
+
+	// SSH settings
+	SSH struct {
+		Disabled                     bool           `ini:"DISABLE_SSH"`
+		Domain                       string         `ini:"SSH_DOMAIN"`
+		Port                         int            `ini:"SSH_PORT"`
+		RootPath                     string         `ini:"SSH_ROOT_PATH"`
+		KeygenPath                   string         `ini:"SSH_KEYGEN_PATH"`
+		KeyTestPath                  string         `ini:"SSH_KEY_TEST_PATH"`
+		StartBuiltinServer           bool           `ini:"START_SSH_SERVER"`
+		ListenHost                   string         `ini:"SSH_LISTEN_HOST"`
+		ListenPort                   int            `ini:"SSH_LISTEN_PORT"`
+		ServerCiphers                []string       `ini:"SSH_SERVER_CIPHERS"`
+		MinimumKeySizeCheck          bool           `ini:"MINIMUM_KEY_SIZE_CHECK"`
+		MinimumKeySizes              map[string]int `ini:"-"` // Load from [ssh.minimum_key_sizes]
+		RewriteAuthorizedKeysAtStart bool           `ini:"REWRITE_AUTHORIZED_KEYS_AT_START"`
+	}
+)
+
+// transferDeprecated transfers deprecated values to the new ones when set.
+func transferDeprecated() {
+	if App.AppName != "" {
+		App.BrandName = App.AppName
+		App.AppName = ""
+	}
+
+	if Server.RootURL != "" {
+		Server.ExternalURL = Server.RootURL
+		Server.RootURL = ""
+	}
+	if Server.LangdingPage == "explore" {
+		Server.LandingURL = "/explore"
+		Server.LangdingPage = ""
+	}
+}

+ 2 - 2
internal/setting/computed_minwinsvc.go → internal/conf/static_minwinsvc.go

@@ -4,12 +4,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package setting
+package conf
 
 import (
 	_ "github.com/gogs/minwinsvc"
 )
 
 func init() {
-	supportWindowsService = true
+	HasMinWinSvc = true
 }

+ 27 - 0
internal/conf/utils.go

@@ -0,0 +1,27 @@
+// Copyright 2020 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package conf
+
+import (
+	"strings"
+
+	"github.com/pkg/errors"
+
+	"gogs.io/gogs/internal/process"
+)
+
+// openSSHVersion returns string representation of OpenSSH version via command "ssh -V".
+func openSSHVersion() (string, error) {
+	// NOTE: Somehow the version is printed to stderr.
+	_, stderr, err := process.Exec("conf.openSSHVersion", "ssh", "-V")
+	if err != nil {
+		return "", errors.Wrap(err, stderr)
+	}
+
+	// Trim unused information, see https://github.com/gogs/gogs/issues/4507#issuecomment-305150441.
+	v := strings.TrimRight(strings.Fields(stderr)[0], ",1234567890")
+	v = strings.TrimSuffix(strings.TrimPrefix(v, "OpenSSH_"), "p")
+	return v, nil
+}

+ 6 - 6
internal/context/api.go

@@ -13,7 +13,7 @@ import (
 	"gopkg.in/macaron.v1"
 	log "unknwon.dev/clog/v2"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 type APIContext struct {
@@ -79,16 +79,16 @@ func (c *APIContext) SetLinkHeader(total, pageSize int) {
 	page := paginater.New(total, pageSize, c.QueryInt("page"), 0)
 	links := make([]string, 0, 4)
 	if page.HasNext() {
-		links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", setting.AppURL, c.Req.URL.Path[1:], page.Next()))
+		links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", conf.Server.ExternalURL, c.Req.URL.Path[1:], page.Next()))
 	}
 	if !page.IsLast() {
-		links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", setting.AppURL, c.Req.URL.Path[1:], page.TotalPages()))
+		links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", conf.Server.ExternalURL, c.Req.URL.Path[1:], page.TotalPages()))
 	}
 	if !page.IsFirst() {
-		links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", setting.AppURL, c.Req.URL.Path[1:]))
+		links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", conf.Server.ExternalURL, c.Req.URL.Path[1:]))
 	}
 	if page.HasPrevious() {
-		links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", setting.AppURL, c.Req.URL.Path[1:], page.Previous()))
+		links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", conf.Server.ExternalURL, c.Req.URL.Path[1:], page.Previous()))
 	}
 
 	if len(links) > 0 {
@@ -100,7 +100,7 @@ func APIContexter() macaron.Handler {
 	return func(ctx *Context) {
 		c := &APIContext{
 			Context: ctx,
-			BaseURL: setting.AppURL + "api/v1",
+			BaseURL: conf.Server.ExternalURL + "api/v1",
 		}
 		ctx.Map(c)
 	}

+ 12 - 12
internal/context/auth.go

@@ -13,7 +13,7 @@ import (
 	"gopkg.in/macaron.v1"
 
 	"gogs.io/gogs/internal/auth"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -27,8 +27,8 @@ type ToggleOptions struct {
 func Toggle(options *ToggleOptions) macaron.Handler {
 	return func(c *Context) {
 		// Cannot view any page before installation.
-		if !setting.InstallLock {
-			c.Redirect(setting.AppSubURL + "/install")
+		if !conf.InstallLock {
+			c.Redirect(conf.Server.Subpath + "/install")
 			return
 		}
 
@@ -40,14 +40,14 @@ func Toggle(options *ToggleOptions) macaron.Handler {
 		}
 
 		// Check non-logged users landing page.
-		if !c.IsLogged && c.Req.RequestURI == "/" && setting.LandingPageURL != setting.LANDING_PAGE_HOME {
-			c.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
+		if !c.IsLogged && c.Req.RequestURI == "/" && conf.Server.LandingURL != "/" {
+			c.Redirect(conf.Server.LandingURL)
 			return
 		}
 
 		// Redirect to dashboard if user tries to visit any non-login page.
 		if options.SignOutRequired && c.IsLogged && c.Req.RequestURI != "/" {
-			c.Redirect(setting.AppSubURL + "/")
+			c.Redirect(conf.Server.Subpath + "/")
 			return
 		}
 
@@ -68,10 +68,10 @@ func Toggle(options *ToggleOptions) macaron.Handler {
 					return
 				}
 
-				c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL)
-				c.Redirect(setting.AppSubURL + "/user/login")
+				c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
+				c.Redirect(conf.Server.Subpath + "/user/login")
 				return
-			} else if !c.User.IsActive && setting.Service.RegisterEmailConfirm {
+			} else if !c.User.IsActive && conf.Service.RegisterEmailConfirm {
 				c.Data["Title"] = c.Tr("auth.active_your_account")
 				c.HTML(200, "user/auth/activate")
 				return
@@ -80,9 +80,9 @@ func Toggle(options *ToggleOptions) macaron.Handler {
 
 		// Redirect to log in page if auto-signin info is provided and has not signed in.
 		if !options.SignOutRequired && !c.IsLogged && !auth.IsAPIPath(c.Req.URL.Path) &&
-			len(c.GetCookie(setting.CookieUserName)) > 0 {
-			c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL)
-			c.Redirect(setting.AppSubURL + "/user/login")
+			len(c.GetCookie(conf.CookieUserName)) > 0 {
+			c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
+			c.Redirect(conf.Server.Subpath + "/user/login")
 			return
 		}
 

+ 13 - 13
internal/context/context.go

@@ -21,10 +21,10 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/auth"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/template"
 )
 
@@ -151,9 +151,9 @@ func (c *Context) Redirect(location string, status ...int) {
 }
 
 // SubURLRedirect responses redirection wtih given location and status.
-// It prepends setting.AppSubURL to the location string.
+// It prepends setting.Server.Subpath to the location string.
 func (c *Context) SubURLRedirect(location string, status ...int) {
-	c.Redirect(setting.AppSubURL+location, status...)
+	c.Redirect(conf.Server.Subpath+location, status...)
 }
 
 // RenderWithErr used for page has form validation but need to prompt error to users.
@@ -174,7 +174,7 @@ func (c *Context) Handle(status int, msg string, err error) {
 	case http.StatusInternalServerError:
 		c.Data["Title"] = "Internal Server Error"
 		log.Error("%s: %v", msg, err)
-		if !setting.ProdMode || (c.IsLogged && c.User.IsAdmin) {
+		if !conf.IsProdMode() || (c.IsLogged && c.User.IsAdmin) {
 			c.Data["ErrorMsg"] = err
 		}
 	}
@@ -233,7 +233,7 @@ func Contexter() macaron.Handler {
 			csrf:    x,
 			Flash:   f,
 			Session: sess,
-			Link:    setting.AppSubURL + strings.TrimSuffix(ctx.Req.URL.Path, "/"),
+			Link:    conf.Server.Subpath + strings.TrimSuffix(ctx.Req.URL.Path, "/"),
 			Repo: &Repository{
 				PullRequest: &PullRequest{},
 			},
@@ -263,9 +263,9 @@ func Contexter() macaron.Handler {
 				branchName = repo.DefaultBranch
 			}
 
-			prefix := setting.AppURL + path.Join(ownerName, repoName, "src", branchName)
+			prefix := conf.Server.ExternalURL + path.Join(ownerName, repoName, "src", branchName)
 			insecureFlag := ""
-			if !strings.HasPrefix(setting.AppURL, "https://") {
+			if !strings.HasPrefix(conf.Server.ExternalURL, "https://") {
 				insecureFlag = "--insecure "
 			}
 			c.PlainText(http.StatusOK, []byte(com.Expand(`<!doctype html>
@@ -279,7 +279,7 @@ func Contexter() macaron.Handler {
 	</body>
 </html>
 `, map[string]string{
-				"GoGetImport":    path.Join(setting.HostAddress, setting.AppSubURL, repo.FullName()),
+				"GoGetImport":    path.Join(conf.Server.URL.Host, conf.Server.Subpath, repo.FullName()),
 				"CloneLink":      db.ComposeHTTPSCloneURL(ownerName, repoName),
 				"GoDocDirectory": prefix + "{/dir}",
 				"GoDocFile":      prefix + "{/dir}/{file}#L{line}",
@@ -288,8 +288,8 @@ func Contexter() macaron.Handler {
 			return
 		}
 
-		if len(setting.HTTP.AccessControlAllowOrigin) > 0 {
-			c.Header().Set("Access-Control-Allow-Origin", setting.HTTP.AccessControlAllowOrigin)
+		if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
+			c.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
 			c.Header().Set("'Access-Control-Allow-Credentials' ", "true")
 			c.Header().Set("Access-Control-Max-Age", "3600")
 			c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
@@ -312,7 +312,7 @@ func Contexter() macaron.Handler {
 
 		// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
 		if c.Req.Method == "POST" && strings.Contains(c.Req.Header.Get("Content-Type"), "multipart/form-data") {
-			if err := c.Req.ParseMultipartForm(setting.AttachmentMaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
+			if err := c.Req.ParseMultipartForm(conf.AttachmentMaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
 				c.ServerError("ParseMultipartForm", err)
 				return
 			}
@@ -323,8 +323,8 @@ func Contexter() macaron.Handler {
 		log.Trace("Session ID: %s", sess.ID())
 		log.Trace("CSRF Token: %v", c.Data["CSRFToken"])
 
-		c.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
-		c.Data["ShowFooterBranding"] = setting.ShowFooterBranding
+		c.Data["ShowRegistrationButton"] = conf.Service.ShowRegistrationButton
+		c.Data["ShowFooterBranding"] = conf.ShowFooterBranding
 
 		c.renderNoticeBanner()
 

+ 3 - 3
internal/context/notice.go

@@ -6,20 +6,20 @@ package context
 
 import (
 	"os"
-	"path"
+	"path/filepath"
 
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
 // renderNoticeBanner checks if a notice banner file exists and loads the message to display
 // on all pages.
 func (c *Context) renderNoticeBanner() {
-	fpath := path.Join(setting.CustomPath, "notice", "banner.md")
+	fpath := filepath.Join(conf.CustomDir(), "notice", "banner.md")
 	if !com.IsExist(fpath) {
 		return
 	}

+ 2 - 2
internal/context/org.go

@@ -9,9 +9,9 @@ import (
 
 	"gopkg.in/macaron.v1"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 type Organization struct {
@@ -91,7 +91,7 @@ func HandleOrgAssignment(c *Context, args ...bool) {
 	c.Data["IsOrganizationOwner"] = c.Org.IsOwner
 	c.Data["IsOrganizationMember"] = c.Org.IsMember
 
-	c.Org.OrgLink = setting.AppSubURL + "/org/" + org.Name
+	c.Org.OrgLink = conf.Server.Subpath + "/org/" + org.Name
 	c.Data["OrgLink"] = c.Org.OrgLink
 
 	// Team.

+ 3 - 3
internal/context/repo.go

@@ -17,7 +17,7 @@ import (
 
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 type PullRequest struct {
@@ -248,8 +248,8 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 		c.Data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
 		c.Data["IsRepositoryWriter"] = c.Repo.IsWriter()
 
-		c.Data["DisableSSH"] = setting.SSH.Disabled
-		c.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
+		c.Data["DisableSSH"] = conf.SSH.Disabled
+		c.Data["DisableHTTP"] = conf.Repository.DisableHTTPGit
 		c.Data["CloneLink"] = repo.CloneLink()
 		c.Data["WikiCloneLink"] = repo.WikiCloneLink()
 

+ 13 - 13
internal/cron/cron.go

@@ -12,7 +12,7 @@ import (
 	"github.com/gogs/cron"
 
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 var c = cron.New()
@@ -22,45 +22,45 @@ func NewContext() {
 		entry *cron.Entry
 		err   error
 	)
-	if setting.Cron.UpdateMirror.Enabled {
-		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, db.MirrorUpdate)
+	if conf.Cron.UpdateMirror.Enabled {
+		entry, err = c.AddFunc("Update mirrors", conf.Cron.UpdateMirror.Schedule, db.MirrorUpdate)
 		if err != nil {
 			log.Fatal("Cron.(update mirrors): %v", err)
 		}
-		if setting.Cron.UpdateMirror.RunAtStart {
+		if conf.Cron.UpdateMirror.RunAtStart {
 			entry.Prev = time.Now()
 			entry.ExecTimes++
 			go db.MirrorUpdate()
 		}
 	}
-	if setting.Cron.RepoHealthCheck.Enabled {
-		entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, db.GitFsck)
+	if conf.Cron.RepoHealthCheck.Enabled {
+		entry, err = c.AddFunc("Repository health check", conf.Cron.RepoHealthCheck.Schedule, db.GitFsck)
 		if err != nil {
 			log.Fatal("Cron.(repository health check): %v", err)
 		}
-		if setting.Cron.RepoHealthCheck.RunAtStart {
+		if conf.Cron.RepoHealthCheck.RunAtStart {
 			entry.Prev = time.Now()
 			entry.ExecTimes++
 			go db.GitFsck()
 		}
 	}
-	if setting.Cron.CheckRepoStats.Enabled {
-		entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, db.CheckRepoStats)
+	if conf.Cron.CheckRepoStats.Enabled {
+		entry, err = c.AddFunc("Check repository statistics", conf.Cron.CheckRepoStats.Schedule, db.CheckRepoStats)
 		if err != nil {
 			log.Fatal("Cron.(check repository statistics): %v", err)
 		}
-		if setting.Cron.CheckRepoStats.RunAtStart {
+		if conf.Cron.CheckRepoStats.RunAtStart {
 			entry.Prev = time.Now()
 			entry.ExecTimes++
 			go db.CheckRepoStats()
 		}
 	}
-	if setting.Cron.RepoArchiveCleanup.Enabled {
-		entry, err = c.AddFunc("Repository archive cleanup", setting.Cron.RepoArchiveCleanup.Schedule, db.DeleteOldRepositoryArchives)
+	if conf.Cron.RepoArchiveCleanup.Enabled {
+		entry, err = c.AddFunc("Repository archive cleanup", conf.Cron.RepoArchiveCleanup.Schedule, db.DeleteOldRepositoryArchives)
 		if err != nil {
 			log.Fatal("Cron.(repository archive cleanup): %v", err)
 		}
-		if setting.Cron.RepoArchiveCleanup.RunAtStart {
+		if conf.Cron.RepoArchiveCleanup.RunAtStart {
 			entry.Prev = time.Now()
 			entry.ExecTimes++
 			go db.DeleteOldRepositoryArchives()

+ 11 - 11
internal/db/action.go

@@ -19,9 +19,9 @@ import (
 	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/lazyregexp"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -134,8 +134,8 @@ func (a *Action) ShortRepoPath() string {
 }
 
 func (a *Action) GetRepoLink() string {
-	if len(setting.AppSubURL) > 0 {
-		return path.Join(setting.AppSubURL, a.GetRepoPath())
+	if conf.Server.Subpath != "" {
+		return path.Join(conf.Server.Subpath, a.GetRepoPath())
 	}
 	return "/" + a.GetRepoPath()
 }
@@ -495,8 +495,8 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		}
 	}
 
-	if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum {
-		opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum]
+	if len(opts.Commits.Commits) > conf.UI.FeedMaxCommitNum {
+		opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
 	}
 
 	data, err := jsoniter.Marshal(opts.Commits)
@@ -540,7 +540,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 			return nil
 		}
 
-		compareURL := setting.AppURL + opts.Commits.CompareURL
+		compareURL := conf.Server.ExternalURL + opts.Commits.CompareURL
 		if isNewRef {
 			compareURL = ""
 			if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
@@ -692,8 +692,8 @@ type MirrorSyncPushActionOptions struct {
 
 // MirrorSyncPushAction adds new action for mirror synchronization of pushed commits.
 func MirrorSyncPushAction(repo *Repository, opts MirrorSyncPushActionOptions) error {
-	if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum {
-		opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum]
+	if len(opts.Commits.Commits) > conf.UI.FeedMaxCommitNum {
+		opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
 	}
 
 	apiCommits, err := opts.Commits.ToApiPayloadCommits(repo.RepoPath(), repo.HTMLURL())
@@ -707,7 +707,7 @@ func MirrorSyncPushAction(repo *Repository, opts MirrorSyncPushActionOptions) er
 		Ref:        opts.RefName,
 		Before:     opts.OldCommitID,
 		After:      opts.NewCommitID,
-		CompareURL: setting.AppURL + opts.Commits.CompareURL,
+		CompareURL: conf.Server.ExternalURL + opts.Commits.CompareURL,
 		Commits:    apiCommits,
 		Repo:       repo.APIFormat(nil),
 		Pusher:     apiPusher,
@@ -738,8 +738,8 @@ func MirrorSyncDeleteAction(repo *Repository, refName string) error {
 // actorID is the user who's requesting, ctxUserID is the user/org that is requested.
 // actorID can be -1 when isProfile is true or to skip the permission check.
 func GetFeeds(ctxUser *User, actorID, afterID int64, isProfile bool) ([]*Action, error) {
-	actions := make([]*Action, 0, setting.UI.User.NewsFeedPagingNum)
-	sess := x.Limit(setting.UI.User.NewsFeedPagingNum).Where("user_id = ?", ctxUser.ID).Desc("id")
+	actions := make([]*Action, 0, conf.UI.User.NewsFeedPagingNum)
+	sess := x.Limit(conf.UI.User.NewsFeedPagingNum).Where("user_id = ?", ctxUser.ID).Desc("id")
 	if afterID > 0 {
 		sess.And("id < ?", afterID)
 	}

+ 2 - 2
internal/db/attachment.go

@@ -15,7 +15,7 @@ import (
 	gouuid "github.com/satori/go.uuid"
 	"xorm.io/xorm"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 // Attachment represent a attachment of issue/comment/release.
@@ -44,7 +44,7 @@ func (a *Attachment) AfterSet(colName string, _ xorm.Cell) {
 
 // AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
 func AttachmentLocalPath(uuid string) string {
-	return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
+	return path.Join(conf.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
 }
 
 // LocalPath returns where attachment is stored in local file system.

+ 2 - 2
internal/db/git_diff.go

@@ -17,7 +17,7 @@ import (
 
 	"github.com/gogs/git-module"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/template/highlight"
 	"gogs.io/gogs/internal/tool"
 )
@@ -69,7 +69,7 @@ func init() {
 
 // ComputedInlineDiffFor computes inline diff for the given line.
 func (diffSection *DiffSection) ComputedInlineDiffFor(diffLine *git.DiffLine) template.HTML {
-	if setting.Git.DisableDiffHighlight {
+	if conf.Git.DisableDiffHighlight {
 		return template.HTML(html.EscapeString(diffLine.Content[1:]))
 	}
 	var (

+ 3 - 3
internal/db/issue.go

@@ -16,7 +16,7 @@ import (
 	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -969,9 +969,9 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
 		return make([]*Issue, 0), nil
 	}
 
-	sess.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum)
+	sess.Limit(conf.UI.IssuePagingNum, (opts.Page-1)*conf.UI.IssuePagingNum)
 
-	issues := make([]*Issue, 0, setting.UI.IssuePagingNum)
+	issues := make([]*Issue, 0, conf.UI.IssuePagingNum)
 	if err := sess.Find(&issues); err != nil {
 		return nil, fmt.Errorf("Find: %v", err)
 	}

+ 2 - 2
internal/db/issue_mail.go

@@ -12,7 +12,7 @@ import (
 
 	"gogs.io/gogs/internal/mailer"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func (issue *Issue) MailSubject() string {
@@ -95,7 +95,7 @@ func NewMailerIssue(issue *Issue) mailer.Issue {
 // 1. Repository watchers, users who participated in comments and the assignee.
 // 2. Users who are not in 1. but get mentioned in current issue/comment.
 func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error {
-	if !setting.Service.EnableNotifyMail {
+	if !conf.Service.EnableNotifyMail {
 		return nil
 	}
 

+ 3 - 3
internal/db/login_source.go

@@ -11,7 +11,7 @@ import (
 	"net/smtp"
 	"net/textproto"
 	"os"
-	"path"
+	"path/filepath"
 	"strings"
 	"sync"
 	"time"
@@ -27,8 +27,8 @@ import (
 	"gogs.io/gogs/internal/auth/github"
 	"gogs.io/gogs/internal/auth/ldap"
 	"gogs.io/gogs/internal/auth/pam"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 type LoginType int
@@ -462,7 +462,7 @@ var localLoginSources = &LocalLoginSources{}
 // LoadAuthSources loads authentication sources from local files
 // and converts them into login sources.
 func LoadAuthSources() {
-	authdPath := path.Join(setting.CustomPath, "conf/auth.d")
+	authdPath := filepath.Join(conf.CustomDir(), "conf", "auth.d")
 	if !com.IsDir(authdPath) {
 		return
 	}

+ 9 - 9
internal/db/migrations/v15.go

@@ -15,8 +15,8 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/osutil"
-	"gogs.io/gogs/internal/setting"
 )
 
 func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
@@ -32,18 +32,18 @@ func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
 	var (
 		hookNames = []string{"pre-receive", "update", "post-receive"}
 		hookTpls  = []string{
-			fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
-			fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
-			fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
+			fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", conf.ScriptType, conf.AppPath(), conf.CustomConf),
+			fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", conf.ScriptType, conf.AppPath(), conf.CustomConf),
+			fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", conf.ScriptType, conf.AppPath(), conf.CustomConf),
 		}
 	)
 
 	// Cleanup old update.log and http.log files.
-	filepath.Walk(setting.LogRootPath, func(path string, info os.FileInfo, err error) error {
+	_ = filepath.Walk(conf.LogRootPath, func(path string, info os.FileInfo, err error) error {
 		if !info.IsDir() &&
 			(strings.HasPrefix(filepath.Base(path), "update.log") ||
 				strings.HasPrefix(filepath.Base(path), "http.log")) {
-			os.Remove(path)
+			_ = os.Remove(path)
 		}
 		return nil
 	})
@@ -63,7 +63,7 @@ func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
 				return nil
 			}
 
-			repoBase := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name))
+			repoBase := filepath.Join(conf.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name))
 			repoPath := repoBase + ".git"
 			wikiPath := repoBase + ".wiki.git"
 			log.Trace("[%04d]: %s", idx, repoPath)
@@ -82,7 +82,7 @@ func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
 				// In case user runs this migration multiple times, and custom hook exists,
 				// we assume it's been migrated already.
 				if hookName != "update" && osutil.IsFile(oldHookPath) && !com.IsExist(customHookDir) {
-					os.MkdirAll(customHookDir, os.ModePerm)
+					_ = os.MkdirAll(customHookDir, os.ModePerm)
 					if err = os.Rename(oldHookPath, newHookPath); err != nil {
 						return fmt.Errorf("move hook file to custom directory '%s' -> '%s': %v", oldHookPath, newHookPath, err)
 					}
@@ -93,7 +93,7 @@ func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
 				}
 
 				if com.IsDir(wikiPath) {
-					os.MkdirAll(wikiHookDir, os.ModePerm)
+					_ = os.MkdirAll(wikiHookDir, os.ModePerm)
 					wikiHookPath := filepath.Join(wikiHookDir, hookName)
 					if err = ioutil.WriteFile(wikiHookPath, []byte(hookTpls[i]), os.ModePerm); err != nil {
 						return fmt.Errorf("write wiki hook file '%s': %v", wikiHookPath, err)

+ 2 - 2
internal/db/migrations/v16.go

@@ -14,7 +14,7 @@ import (
 
 	"github.com/gogs/git-module"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func updateRepositorySizes(x *xorm.Engine) (err error) {
@@ -60,7 +60,7 @@ func updateRepositorySizes(x *xorm.Engine) (err error) {
 				continue
 			}
 
-			repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
+			repoPath := filepath.Join(conf.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
 			countObject, err := git.GetRepoSize(repoPath)
 			if err != nil {
 				log.Warn("GetRepoSize: %v", err)

+ 5 - 5
internal/db/migrations/v18.go

@@ -9,7 +9,7 @@ import (
 
 	"xorm.io/xorm"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func updateRepositoryDescriptionField(x *xorm.Engine) error {
@@ -20,13 +20,13 @@ func updateRepositoryDescriptionField(x *xorm.Engine) error {
 		return nil
 	}
 	switch {
-		case setting.UseMySQL:
+		case conf.UseMySQL:
 			_, err = x.Exec("ALTER TABLE `repository` MODIFY `description` VARCHAR(512);")
-		case setting.UseMSSQL:
+		case conf.UseMSSQL:
 			_, err = x.Exec("ALTER TABLE `repository` ALTER COLUMN `description` VARCHAR(512);")
-		case setting.UsePostgreSQL:
+		case conf.UsePostgreSQL:
 			_, err = x.Exec("ALTER TABLE `repository` ALTER COLUMN `description` TYPE VARCHAR(512);")
-		case setting.UseSQLite3:
+		case conf.UseSQLite3:
 			// Sqlite3 uses TEXT type by default for any string type field.
 			// Keep this comment to mention that we don't missed any option.
 	}

+ 3 - 3
internal/db/milestone.go

@@ -13,7 +13,7 @@ import (
 
 	api "github.com/gogs/go-gogs-client"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 // Milestone represents a milestone of repository.
@@ -157,10 +157,10 @@ func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) {
 
 // GetMilestones returns a list of milestones of given repository and status.
 func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) {
-	miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
+	miles := make([]*Milestone, 0, conf.UI.IssuePagingNum)
 	sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed)
 	if page > 0 {
-		sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
+		sess = sess.Limit(conf.UI.IssuePagingNum, (page-1)*conf.UI.IssuePagingNum)
 	}
 	return miles, sess.Find(&miles)
 }

+ 3 - 3
internal/db/mirror.go

@@ -20,11 +20,11 @@ import (
 
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/process"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/sync"
 )
 
-var MirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength)
+var MirrorQueue = sync.NewUniqueQueue(conf.Repository.MirrorQueueLength)
 
 // Mirror represents mirror information of a repository.
 type Mirror struct {
@@ -258,7 +258,7 @@ func parseRemoteUpdateOutput(output string) []*mirrorSyncResult {
 func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 	repoPath := m.Repo.RepoPath()
 	wikiPath := m.Repo.WikiPath()
-	timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
+	timeout := time.Duration(conf.Git.Timeout.Mirror) * time.Second
 
 	// Do a fast-fail testing against on repository URL to ensure it is accessible under
 	// good condition to prevent long blocking on URL resolution without syncing anything.

+ 10 - 10
internal/db/models.go

@@ -24,8 +24,8 @@ import (
 	"xorm.io/core"
 	"xorm.io/xorm"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/migrations"
-	"gogs.io/gogs/internal/setting"
 )
 
 // Engine represents a XORM engine or session.
@@ -75,17 +75,17 @@ func init() {
 }
 
 func LoadConfigs() {
-	sec := setting.Cfg.Section("database")
+	sec := conf.File.Section("database")
 	DbCfg.Type = sec.Key("DB_TYPE").String()
 	switch DbCfg.Type {
 	case "sqlite3":
-		setting.UseSQLite3 = true
+		conf.UseSQLite3 = true
 	case "mysql":
-		setting.UseMySQL = true
+		conf.UseMySQL = true
 	case "postgres":
-		setting.UsePostgreSQL = true
+		conf.UsePostgreSQL = true
 	case "mssql":
-		setting.UseMSSQL = true
+		conf.UseMSSQL = true
 	}
 	DbCfg.Host = sec.Key("HOST").String()
 	DbCfg.Name = sec.Key("NAME").String()
@@ -189,8 +189,8 @@ func SetEngine() (err error) {
 
 	// WARNING: for serv command, MUST remove the output to os.stdout,
 	// so use log file to instead print to stdout.
-	sec := setting.Cfg.Section("log.xorm")
-	logger, err := log.NewFileWriter(path.Join(setting.LogRootPath, "xorm.log"),
+	sec := conf.File.Section("log.xorm")
+	logger, err := log.NewFileWriter(path.Join(conf.LogRootPath, "xorm.log"),
 		log.FileRotationConfig{
 			Rotate:  sec.Key("ROTATE").MustBool(true),
 			Daily:   sec.Key("ROTATE_DAILY").MustBool(true),
@@ -206,7 +206,7 @@ func SetEngine() (err error) {
 	x.SetMaxIdleConns(0)
 	x.SetConnMaxLifetime(time.Second)
 
-	if setting.ProdMode {
+	if conf.IsProdMode() {
 		x.SetLogger(xorm.NewSimpleLogger3(logger, xorm.DEFAULT_LOG_PREFIX, xorm.DEFAULT_LOG_FLAG, core.LOG_WARNING))
 	} else {
 		x.SetLogger(xorm.NewSimpleLogger(logger))
@@ -389,7 +389,7 @@ func ImportDatabase(dirPath string, verbose bool) (err error) {
 		}
 
 		// PostgreSQL needs manually reset table sequence for auto increment keys
-		if setting.UsePostgreSQL {
+		if conf.UsePostgreSQL {
 			rawTableName := snakeMapper.Obj2Table(tableName)
 			seqName := rawTableName + "_id_seq"
 			if _, err = x.Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false);`, seqName, rawTableName)); err != nil {

+ 7 - 6
internal/db/pull.go

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"os"
 	"path"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -18,14 +19,14 @@ import (
 	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/process"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/sync"
 )
 
-var PullRequestQueue = sync.NewUniqueQueue(setting.Repository.PullRequestQueueLength)
+var PullRequestQueue = sync.NewUniqueQueue(conf.Repository.PullRequestQueueLength)
 
 type PullRequestType int
 
@@ -218,9 +219,9 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 
 	// Create temporary directory to store temporary copy of the base repository,
 	// and clean it up when operation finished regardless of succeed or not.
-	tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
-	os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
-	defer os.RemoveAll(path.Dir(tmpBasePath))
+	tmpBasePath := filepath.Join(conf.Server.AppDataPath, "tmp", "repos", com.ToStr(time.Now().Nanosecond())+".git")
+	os.MkdirAll(filepath.Dir(tmpBasePath), os.ModePerm)
+	defer os.RemoveAll(filepath.Dir(tmpBasePath))
 
 	// Clone the base repository to the defined temporary directory,
 	// and checks out to base branch directly.
@@ -378,7 +379,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 		Ref:        git.BRANCH_PREFIX + pr.BaseBranch,
 		Before:     pr.MergeBase,
 		After:      mergeCommit.ID.String(),
-		CompareURL: setting.AppURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
+		CompareURL: conf.Server.ExternalURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
 		Commits:    commits,
 		Repo:       pr.BaseRepo.APIFormat(nil),
 		Pusher:     pr.HeadRepo.MustOwner().APIFormat(),

+ 32 - 33
internal/db/repo.go

@@ -30,13 +30,12 @@ import (
 	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
-	"gogs.io/gogs/internal/assets/conf"
 	"gogs.io/gogs/internal/avatar"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/markup"
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/process"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/sync"
 )
 
@@ -61,7 +60,7 @@ func LoadRepoConfig() {
 		if err != nil {
 			log.Fatal("Failed to get %s files: %v", t, err)
 		}
-		customPath := path.Join(setting.CustomPath, "conf", t)
+		customPath := filepath.Join(conf.CustomDir(), "conf", t)
 		if com.IsDir(customPath) {
 			customFiles, err := com.StatDir(customPath)
 			if err != nil {
@@ -88,13 +87,13 @@ func LoadRepoConfig() {
 
 	// Filter out invalid names and promote preferred licenses.
 	sortedLicenses := make([]string, 0, len(Licenses))
-	for _, name := range setting.Repository.PreferredLicenses {
+	for _, name := range conf.Repository.PreferredLicenses {
 		if com.IsSliceContainsStr(Licenses, name) {
 			sortedLicenses = append(sortedLicenses, name)
 		}
 	}
 	for _, name := range Licenses {
-		if !com.IsSliceContainsStr(setting.Repository.PreferredLicenses, name) {
+		if !com.IsSliceContainsStr(conf.Repository.PreferredLicenses, name) {
 			sortedLicenses = append(sortedLicenses, name)
 		}
 	}
@@ -111,18 +110,18 @@ func NewRepoContext() {
 
 	// Check Git version.
 	var err error
-	setting.Git.Version, err = git.BinVersion()
+	conf.Git.Version, err = git.BinVersion()
 	if err != nil {
 		log.Fatal("Failed to get Git version: %v", err)
 	}
 
-	log.Trace("Git version: %s", setting.Git.Version)
-	if version.Compare("1.8.3", setting.Git.Version, ">") {
+	log.Trace("Git version: %s", conf.Git.Version)
+	if version.Compare("1.8.3", conf.Git.Version, ">") {
 		log.Fatal("Gogs requires Git version greater or equal to 1.8.3")
 	}
 	git.HookDir = "custom_hooks"
 	git.HookSampleDir = "hooks"
-	git.DefaultCommitsPageSize = setting.UI.User.CommitsPagingNum
+	git.DefaultCommitsPageSize = conf.UI.User.CommitsPagingNum
 
 	// Git requires setting user.name and user.email in order to commit changes.
 	for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} {
@@ -145,7 +144,7 @@ func NewRepoContext() {
 		log.Fatal("Failed to execute 'git config --global core.quotepath false': %v - %s", err, stderr)
 	}
 
-	RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
+	RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(conf.Server.AppDataPath, "tmp"))
 }
 
 // Repository contains information of a repository.
@@ -292,12 +291,12 @@ func (repo *Repository) FullName() string {
 }
 
 func (repo *Repository) HTMLURL() string {
-	return setting.AppURL + repo.FullName()
+	return conf.Server.ExternalURL + repo.FullName()
 }
 
 // CustomAvatarPath returns repository custom avatar file path.
 func (repo *Repository) CustomAvatarPath() string {
-	return filepath.Join(setting.RepositoryAvatarUploadPath, com.ToStr(repo.ID))
+	return filepath.Join(conf.RepositoryAvatarUploadPath, com.ToStr(repo.ID))
 }
 
 // RelAvatarLink returns relative avatar link to the site domain,
@@ -308,14 +307,14 @@ func (repo *Repository) RelAvatarLink() string {
 	if !com.IsExist(repo.CustomAvatarPath()) {
 		return defaultImgUrl
 	}
-	return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, REPO_AVATAR_URL_PREFIX, repo.ID)
+	return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, REPO_AVATAR_URL_PREFIX, repo.ID)
 }
 
 // AvatarLink returns repository avatar absolute link.
 func (repo *Repository) AvatarLink() string {
 	link := repo.RelAvatarLink()
 	if link[0] == '/' && link[1] != '/' {
-		return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
+		return conf.Server.ExternalURL + strings.TrimPrefix(link, conf.Server.Subpath)[1:]
 	}
 	return link
 }
@@ -328,7 +327,7 @@ func (repo *Repository) UploadAvatar(data []byte) error {
 		return fmt.Errorf("decode image: %v", err)
 	}
 
-	os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm)
+	_ = os.MkdirAll(conf.RepositoryAvatarUploadPath, os.ModePerm)
 	fw, err := os.Create(repo.CustomAvatarPath())
 	if err != nil {
 		return fmt.Errorf("create custom avatar directory: %v", err)
@@ -546,7 +545,7 @@ func (repo *Repository) RelLink() string {
 }
 
 func (repo *Repository) Link() string {
-	return setting.AppSubURL + "/" + repo.FullName()
+	return conf.Server.Subpath + "/" + repo.FullName()
 }
 
 func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
@@ -593,7 +592,7 @@ func (repo *Repository) NextIssueIndex() int64 {
 }
 
 func (repo *Repository) LocalCopyPath() string {
-	return path.Join(setting.AppDataPath, "tmp/local-repo", com.ToStr(repo.ID))
+	return filepath.Join(conf.Server.AppDataPath, "tmp", "local-repo", com.ToStr(repo.ID))
 }
 
 // UpdateLocalCopy fetches latest changes of given branch from repoPath to localPath.
@@ -609,7 +608,7 @@ func UpdateLocalCopyBranch(repoPath, localPath, branch string, isWiki bool) (err
 			branch = ""
 		}
 		if err = git.Clone(repoPath, localPath, git.CloneRepoOptions{
-			Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second,
+			Timeout: time.Duration(conf.Git.Timeout.Clone) * time.Second,
 			Branch:  branch,
 		}); err != nil {
 			return fmt.Errorf("git clone %s: %v", branch, err)
@@ -685,7 +684,7 @@ type CloneLink struct {
 
 // ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
 func ComposeHTTPSCloneURL(owner, repo string) string {
-	return fmt.Sprintf("%s%s/%s.git", setting.AppURL, owner, repo)
+	return fmt.Sprintf("%s%s/%s.git", conf.Server.ExternalURL, owner, repo)
 }
 
 func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
@@ -696,10 +695,10 @@ func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
 
 	repo.Owner = repo.MustOwner()
 	cl := new(CloneLink)
-	if setting.SSH.Port != 22 {
-		cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName)
+	if conf.SSH.Port != 22 {
+		cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", conf.App.RunUser, conf.SSH.Domain, conf.SSH.Port, repo.Owner.Name, repoName)
 	} else {
-		cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSH.Domain, repo.Owner.Name, repoName)
+		cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", conf.App.RunUser, conf.SSH.Domain, repo.Owner.Name, repoName)
 	}
 	cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName)
 	return cl
@@ -764,7 +763,7 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 		repo.NumWatches = 1
 	}
 
-	migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
+	migrateTimeout := time.Duration(conf.Git.Timeout.Migrate) * time.Second
 
 	RemoveAllWithNotice("Repository path erase before creation", repoPath)
 	if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{
@@ -820,9 +819,9 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 	if opts.IsMirror {
 		if _, err = x.InsertOne(&Mirror{
 			RepoID:      repo.ID,
-			Interval:    setting.Mirror.DefaultInterval,
+			Interval:    conf.Mirror.DefaultInterval,
 			EnablePrune: true,
-			NextSync:    time.Now().Add(time.Duration(setting.Mirror.DefaultInterval) * time.Hour),
+			NextSync:    time.Now().Add(time.Duration(conf.Mirror.DefaultInterval) * time.Hour),
 		}); err != nil {
 			return repo, fmt.Errorf("InsertOne: %v", err)
 		}
@@ -858,7 +857,7 @@ func createDelegateHooks(repoPath string) (err error) {
 	for _, name := range git.HookNames {
 		hookPath := filepath.Join(repoPath, "hooks", name)
 		if err = ioutil.WriteFile(hookPath,
-			[]byte(fmt.Sprintf(hooksTpls[name], setting.ScriptType, setting.AppPath, setting.CustomConf)),
+			[]byte(fmt.Sprintf(hooksTpls[name], conf.ScriptType, conf.AppPath(), conf.CustomConf)),
 			os.ModePerm); err != nil {
 			return fmt.Errorf("create delegate hook '%s': %v", hookPath, err)
 		}
@@ -929,7 +928,7 @@ func getRepoInitFile(tp, name string) ([]byte, error) {
 	relPath := path.Join("conf", tp, strings.TrimLeft(path.Clean("/"+name), "/"))
 
 	// Use custom file when available.
-	customPath := path.Join(setting.CustomPath, relPath)
+	customPath := filepath.Join(conf.CustomDir(), relPath)
 	if osutil.IsFile(customPath) {
 		return ioutil.ReadFile(customPath)
 	}
@@ -1768,7 +1767,7 @@ func DeleteOldRepositoryArchives() {
 	log.Trace("Doing: DeleteOldRepositoryArchives")
 
 	formats := []string{"zip", "targz"}
-	oldestTime := time.Now().Add(-setting.Cron.RepoArchiveCleanup.OlderThan)
+	oldestTime := time.Now().Add(-conf.Cron.RepoArchiveCleanup.OlderThan)
 	if err := x.Where("id > 0").Iterate(new(Repository),
 		func(idx int, bean interface{}) error {
 			repo := bean.(*Repository)
@@ -1931,7 +1930,7 @@ func GitFsck() {
 		func(idx int, bean interface{}) error {
 			repo := bean.(*Repository)
 			repoPath := repo.RepoPath()
-			if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil {
+			if err := git.Fsck(repoPath, conf.Cron.RepoHealthCheck.Timeout, conf.Cron.RepoHealthCheck.Args...); err != nil {
 				desc := fmt.Sprintf("Failed to perform health check on repository '%s': %v", repoPath, err)
 				log.Warn(desc)
 				if err = CreateRepositoryNotice(desc); err != nil {
@@ -1945,7 +1944,7 @@ func GitFsck() {
 }
 
 func GitGcRepos() error {
-	args := append([]string{"gc"}, setting.Git.GCArgs...)
+	args := append([]string{"gc"}, conf.Git.GCArgs...)
 	return x.Where("id > 0").Iterate(new(Repository),
 		func(idx int, bean interface{}) error {
 			repo := bean.(*Repository)
@@ -1953,7 +1952,7 @@ func GitGcRepos() error {
 				return err
 			}
 			_, stderr, err := process.ExecDir(
-				time.Duration(setting.Git.Timeout.GC)*time.Second,
+				time.Duration(conf.Git.Timeout.GC)*time.Second,
 				RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection",
 				"git", args...)
 			if err != nil {
@@ -2235,7 +2234,7 @@ func GetWatchers(repoID int64) ([]*Watch, error) {
 func (repo *Repository) GetWatchers(page int) ([]*User, error) {
 	users := make([]*User, 0, ItemsPerPage)
 	sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("watch.repo_id=?", repo.ID)
-	if setting.UsePostgreSQL {
+	if conf.UsePostgreSQL {
 		sess = sess.Join("LEFT", "watch", `"user".id=watch.user_id`)
 	} else {
 		sess = sess.Join("LEFT", "watch", "user.id=watch.user_id")
@@ -2326,7 +2325,7 @@ func IsStaring(userID, repoID int64) bool {
 func (repo *Repository) GetStargazers(page int) ([]*User, error) {
 	users := make([]*User, 0, ItemsPerPage)
 	sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("star.repo_id=?", repo.ID)
-	if setting.UsePostgreSQL {
+	if conf.UsePostgreSQL {
 		sess = sess.Join("LEFT", "star", `"user".id=star.uid`)
 	} else {
 		sess = sess.Join("LEFT", "star", "user.id=star.uid")

+ 4 - 4
internal/db/repo_editor.go

@@ -24,7 +24,7 @@ import (
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/process"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -95,7 +95,7 @@ func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error {
 // checkoutNewBranch checks out to a new branch from the a branch name.
 func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error {
 	if err := git.Checkout(localPath, git.CheckoutOptions{
-		Timeout:   time.Duration(setting.Git.Timeout.Pull) * time.Second,
+		Timeout:   time.Duration(conf.Git.Timeout.Pull) * time.Second,
 		Branch:    newBranch,
 		OldBranch: oldBranch,
 	}); err != nil {
@@ -231,7 +231,7 @@ func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *
 	pid := process.Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", repo.RepoPath()), cmd)
 	defer process.Remove(pid)
 
-	diff, err = ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout)
+	diff, err = ParsePatch(conf.Git.MaxGitDiffLines, conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles, stdout)
 	if err != nil {
 		return nil, fmt.Errorf("parse path: %v", err)
 	}
@@ -318,7 +318,7 @@ type Upload struct {
 
 // UploadLocalPath returns where uploads is stored in local file system based on given UUID.
 func UploadLocalPath(uuid string) string {
-	return path.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid)
+	return path.Join(conf.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid)
 }
 
 // LocalPath returns where uploads are temporarily stored in local file system.

+ 17 - 17
internal/db/ssh_key.go

@@ -23,8 +23,8 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/process"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -84,7 +84,7 @@ func (k *PublicKey) OmitEmail() string {
 
 // AuthorizedString returns formatted public key string for authorized_keys file.
 func (k *PublicKey) AuthorizedString() string {
-	return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, k.ID, setting.CustomConf, k.Content)
+	return fmt.Sprintf(_TPL_PUBLICK_KEY, conf.AppPath(), k.ID, conf.CustomConf, k.Content)
 }
 
 // IsDeployKey returns true if the public key is used as deploy key.
@@ -179,7 +179,7 @@ func parseKeyString(content string) (string, error) {
 // writeTmpKeyFile writes key content to a temporary file
 // and returns the name of that file, along with any possible errors.
 func writeTmpKeyFile(content string) (string, error) {
-	tmpFile, err := ioutil.TempFile(setting.SSH.KeyTestPath, "gogs_keytest")
+	tmpFile, err := ioutil.TempFile(conf.SSH.KeyTestPath, "gogs_keytest")
 	if err != nil {
 		return "", fmt.Errorf("TempFile: %v", err)
 	}
@@ -199,7 +199,7 @@ func SSHKeyGenParsePublicKey(key string) (string, int, error) {
 	}
 	defer os.Remove(tmpName)
 
-	stdout, stderr, err := process.Exec("SSHKeyGenParsePublicKey", setting.SSH.KeygenPath, "-lf", tmpName)
+	stdout, stderr, err := process.Exec("SSHKeyGenParsePublicKey", conf.SSH.KeygenPath, "-lf", tmpName)
 	if err != nil {
 		return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr)
 	}
@@ -274,7 +274,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
 // CheckPublicKeyString checks if the given public key string is recognized by SSH.
 // It returns the actual public key line on success.
 func CheckPublicKeyString(content string) (_ string, err error) {
-	if setting.SSH.Disabled {
+	if conf.SSH.Disabled {
 		return "", errors.New("SSH is disabled")
 	}
 
@@ -291,7 +291,7 @@ func CheckPublicKeyString(content string) (_ string, err error) {
 	// Remove any unnecessary whitespace
 	content = strings.TrimSpace(content)
 
-	if !setting.SSH.MinimumKeySizeCheck {
+	if !conf.SSH.MinimumKeySizeCheck {
 		return content, nil
 	}
 
@@ -300,7 +300,7 @@ func CheckPublicKeyString(content string) (_ string, err error) {
 		keyType string
 		length  int
 	)
-	if setting.SSH.StartBuiltinServer {
+	if conf.SSH.StartBuiltinServer {
 		fnName = "SSHNativeParsePublicKey"
 		keyType, length, err = SSHNativeParsePublicKey(content)
 	} else {
@@ -310,9 +310,9 @@ func CheckPublicKeyString(content string) (_ string, err error) {
 	if err != nil {
 		return "", fmt.Errorf("%s: %v", fnName, err)
 	}
-	log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length)
+	log.Trace("Key info [native: %v]: %s-%d", conf.SSH.StartBuiltinServer, keyType, length)
 
-	if minLen, found := setting.SSH.MinimumKeySizes[keyType]; found && length >= minLen {
+	if minLen, found := conf.SSH.MinimumKeySizes[keyType]; found && length >= minLen {
 		return content, nil
 	} else if found && length < minLen {
 		return "", fmt.Errorf("key length is not enough: got %d, needs %d", length, minLen)
@@ -325,7 +325,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
 	sshOpLocker.Lock()
 	defer sshOpLocker.Unlock()
 
-	fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
+	fpath := filepath.Join(conf.SSH.RootPath, "authorized_keys")
 	f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
 	if err != nil {
 		return err
@@ -333,7 +333,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
 	defer f.Close()
 
 	// Note: chmod command does not support in Windows.
-	if !setting.IsWindows {
+	if !conf.IsWindowsRuntime() {
 		fi, err := f.Stat()
 		if err != nil {
 			return err
@@ -375,12 +375,12 @@ func addKey(e Engine, key *PublicKey) (err error) {
 	// Calculate fingerprint.
 	tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
 		"id_rsa.pub"), "\\", "/", -1)
-	os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
+	_ = os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
 	if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
 		return err
 	}
 
-	stdout, stderr, err := process.Exec("AddPublicKey", setting.SSH.KeygenPath, "-lf", tmpPath)
+	stdout, stderr, err := process.Exec("AddPublicKey", conf.SSH.KeygenPath, "-lf", tmpPath)
 	if err != nil {
 		return fmt.Errorf("fail to parse public key: %s - %s", err, stderr)
 	} else if len(stdout) < 2 {
@@ -394,7 +394,7 @@ func addKey(e Engine, key *PublicKey) (err error) {
 	}
 
 	// Don't need to rewrite this file if builtin SSH server is enabled.
-	if setting.SSH.StartBuiltinServer {
+	if conf.SSH.StartBuiltinServer {
 		return nil
 	}
 	return appendAuthorizedKeysToFile(key)
@@ -523,8 +523,8 @@ func RewriteAuthorizedKeys() error {
 
 	log.Trace("Doing: RewriteAuthorizedKeys")
 
-	os.MkdirAll(setting.SSH.RootPath, os.ModePerm)
-	fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
+	_ = os.MkdirAll(conf.SSH.RootPath, os.ModePerm)
+	fpath := filepath.Join(conf.SSH.RootPath, "authorized_keys")
 	tmpPath := fpath + ".tmp"
 	f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
 	if err != nil {
@@ -536,7 +536,7 @@ func RewriteAuthorizedKeys() error {
 		_, err = f.WriteString((bean.(*PublicKey)).AuthorizedString())
 		return err
 	})
-	f.Close()
+	_ = f.Close()
 	if err != nil {
 		return err
 	}

+ 2 - 2
internal/db/ssh_key_test.go

@@ -11,11 +11,11 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func init() {
-	setting.Init()
+	conf.MustInit("")
 }
 
 func Test_SSHParsePublicKey(t *testing.T) {

+ 3 - 3
internal/db/two_factor.go

@@ -16,7 +16,7 @@ import (
 	"xorm.io/xorm"
 
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -47,7 +47,7 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
 	if err != nil {
 		return false, fmt.Errorf("DecodeString: %v", err)
 	}
-	decryptSecret, err := com.AESGCMDecrypt(tool.MD5Bytes(setting.SecretKey), secret)
+	decryptSecret, err := com.AESGCMDecrypt(tool.MD5Bytes(conf.SecretKey), secret)
 	if err != nil {
 		return false, fmt.Errorf("AESGCMDecrypt: %v", err)
 	}
@@ -85,7 +85,7 @@ func NewTwoFactor(userID int64, secret string) error {
 	}
 
 	// Encrypt secret
-	encryptSecret, err := com.AESGCMEncrypt(tool.MD5Bytes(setting.SecretKey), []byte(secret))
+	encryptSecret, err := com.AESGCMEncrypt(tool.MD5Bytes(conf.SecretKey), []byte(secret))
 	if err != nil {
 		return fmt.Errorf("AESGCMEncrypt: %v", err)
 	}

+ 25 - 25
internal/db/user.go

@@ -30,8 +30,8 @@ import (
 	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/avatar"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -152,23 +152,23 @@ func (u *User) HasForkedRepo(repoID int64) bool {
 
 func (u *User) RepoCreationNum() int {
 	if u.MaxRepoCreation <= -1 {
-		return setting.Repository.MaxCreationLimit
+		return conf.Repository.MaxCreationLimit
 	}
 	return u.MaxRepoCreation
 }
 
 func (u *User) CanCreateRepo() bool {
 	if u.MaxRepoCreation <= -1 {
-		if setting.Repository.MaxCreationLimit <= -1 {
+		if conf.Repository.MaxCreationLimit <= -1 {
 			return true
 		}
-		return u.NumRepos < setting.Repository.MaxCreationLimit
+		return u.NumRepos < conf.Repository.MaxCreationLimit
 	}
 	return u.NumRepos < u.MaxRepoCreation
 }
 
 func (u *User) CanCreateOrganization() bool {
-	return !setting.Admin.DisableRegularOrgCreation || u.IsAdmin
+	return !conf.Admin.DisableRegularOrgCreation || u.IsAdmin
 }
 
 // CanEditGitHook returns true if user can edit Git hooks.
@@ -178,31 +178,31 @@ func (u *User) CanEditGitHook() bool {
 
 // CanImportLocal returns true if user can migrate repository by local path.
 func (u *User) CanImportLocal() bool {
-	return setting.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal)
+	return conf.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal)
 }
 
 // DashboardLink returns the user dashboard page link.
 func (u *User) DashboardLink() string {
 	if u.IsOrganization() {
-		return setting.AppSubURL + "/org/" + u.Name + "/dashboard/"
+		return conf.Server.Subpath + "/org/" + u.Name + "/dashboard/"
 	}
-	return setting.AppSubURL + "/"
+	return conf.Server.Subpath + "/"
 }
 
 // HomeLink returns the user or organization home page link.
 func (u *User) HomeLink() string {
-	return setting.AppSubURL + "/" + u.Name
+	return conf.Server.Subpath + "/" + u.Name
 }
 
 func (u *User) HTMLURL() string {
-	return setting.AppURL + u.Name
+	return conf.Server.ExternalURL + u.Name
 }
 
 // GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
 func (u *User) GenerateEmailActivateCode(email string) string {
 	code := tool.CreateTimeLimitCode(
 		com.ToStr(u.ID)+email+u.LowerName+u.Passwd+u.Rands,
-		setting.Service.ActiveCodeLives, nil)
+		conf.Service.ActiveCodeLives, nil)
 
 	// Add tail hex username
 	code += hex.EncodeToString([]byte(u.LowerName))
@@ -216,7 +216,7 @@ func (u *User) GenerateActivateCode() string {
 
 // CustomAvatarPath returns user custom avatar file path.
 func (u *User) CustomAvatarPath() string {
-	return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.ID))
+	return filepath.Join(conf.AvatarUploadPath, com.ToStr(u.ID))
 }
 
 // GenerateRandomAvatar generates a random avatar for user.
@@ -251,7 +251,7 @@ func (u *User) GenerateRandomAvatar() error {
 // which includes app sub-url as prefix. However, it is possible
 // to return full URL if user enables Gravatar-like service.
 func (u *User) RelAvatarLink() string {
-	defaultImgUrl := setting.AppSubURL + "/img/avatar_default.png"
+	defaultImgUrl := conf.Server.Subpath + "/img/avatar_default.png"
 	if u.ID == -1 {
 		return defaultImgUrl
 	}
@@ -261,15 +261,15 @@ func (u *User) RelAvatarLink() string {
 		if !com.IsExist(u.CustomAvatarPath()) {
 			return defaultImgUrl
 		}
-		return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, USER_AVATAR_URL_PREFIX, u.ID)
-	case setting.DisableGravatar, setting.OfflineMode:
+		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, USER_AVATAR_URL_PREFIX, u.ID)
+	case conf.DisableGravatar:
 		if !com.IsExist(u.CustomAvatarPath()) {
 			if err := u.GenerateRandomAvatar(); err != nil {
 				log.Error("GenerateRandomAvatar: %v", err)
 			}
 		}
 
-		return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, USER_AVATAR_URL_PREFIX, u.ID)
+		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, USER_AVATAR_URL_PREFIX, u.ID)
 	}
 	return tool.AvatarLink(u.AvatarEmail)
 }
@@ -278,7 +278,7 @@ func (u *User) RelAvatarLink() string {
 func (u *User) AvatarLink() string {
 	link := u.RelAvatarLink()
 	if link[0] == '/' && link[1] != '/' {
-		return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
+		return conf.Server.ExternalURL + strings.TrimPrefix(link, conf.Server.Subpath)[1:]
 	}
 	return link
 }
@@ -287,7 +287,7 @@ func (u *User) AvatarLink() string {
 func (u *User) GetFollowers(page int) ([]*User, error) {
 	users := make([]*User, 0, ItemsPerPage)
 	sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.follow_id=?", u.ID)
-	if setting.UsePostgreSQL {
+	if conf.UsePostgreSQL {
 		sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`)
 	} else {
 		sess = sess.Join("LEFT", "follow", "user.id=follow.user_id")
@@ -303,7 +303,7 @@ func (u *User) IsFollowing(followID int64) bool {
 func (u *User) GetFollowing(page int) ([]*User, error) {
 	users := make([]*User, 0, ItemsPerPage)
 	sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.user_id=?", u.ID)
-	if setting.UsePostgreSQL {
+	if conf.UsePostgreSQL {
 		sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`)
 	} else {
 		sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id")
@@ -341,7 +341,7 @@ func (u *User) UploadAvatar(data []byte) error {
 		return fmt.Errorf("decode image: %v", err)
 	}
 
-	os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
+	_ = os.MkdirAll(conf.AvatarUploadPath, os.ModePerm)
 	fw, err := os.Create(u.CustomAvatarPath())
 	if err != nil {
 		return fmt.Errorf("create custom avatar directory: %v", err)
@@ -617,7 +617,7 @@ func parseUserFromCode(code string) (user *User) {
 
 // verify active code when active account
 func VerifyUserActiveCode(code string) (user *User) {
-	minutes := setting.Service.ActiveCodeLives
+	minutes := conf.Service.ActiveCodeLives
 
 	if user = parseUserFromCode(code); user != nil {
 		// time limit code
@@ -633,7 +633,7 @@ func VerifyUserActiveCode(code string) (user *User) {
 
 // verify active code when active account
 func VerifyActiveEmailCode(code, email string) *EmailAddress {
-	minutes := setting.Service.ActiveCodeLives
+	minutes := conf.Service.ActiveCodeLives
 
 	if user := parseUserFromCode(code); user != nil {
 		// time limit code
@@ -877,7 +877,7 @@ func DeleteInactivateUsers() (err error) {
 
 // UserPath returns the path absolute path of user repositories.
 func UserPath(userName string) string {
-	return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
+	return filepath.Join(conf.RepoRootPath, strings.ToLower(userName))
 }
 
 func GetUserByKeyID(keyID int64) (*User, error) {
@@ -1049,8 +1049,8 @@ func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error)
 	}
 	opts.Keyword = strings.ToLower(opts.Keyword)
 
-	if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum {
-		opts.PageSize = setting.UI.ExplorePagingNum
+	if opts.PageSize <= 0 || opts.PageSize > conf.UI.ExplorePagingNum {
+		opts.PageSize = conf.UI.ExplorePagingNum
 	}
 	if opts.Page <= 0 {
 		opts.Page = 1

+ 7 - 7
internal/db/webhook.go

@@ -21,13 +21,13 @@ import (
 
 	api "github.com/gogs/go-gogs-client"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/httplib"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/sync"
 )
 
-var HookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength)
+var HookQueue = sync.NewUniqueQueue(conf.Webhook.QueueLength)
 
 type HookContentType int
 
@@ -99,7 +99,7 @@ type Webhook struct {
 	ContentType  HookContentType
 	Secret       string     `xorm:"TEXT"`
 	Events       string     `xorm:"TEXT"`
-	*HookEvent   `xorm:"-"` // LEGACY [1.0]: Cannot ignore JSON here, it breaks old backup archive
+	*HookEvent   `xorm:"-"` // LEGACY [1.0]: Cannot ignore JSON (i.e. json:"-") here, it breaks old backup archive
 	IsSSL        bool       `xorm:"is_ssl"`
 	IsActive     bool
 	HookTaskType HookTaskType
@@ -482,8 +482,8 @@ func (t *HookTask) MarshalJSON(v interface{}) string {
 
 // HookTasks returns a list of hook tasks by given conditions.
 func HookTasks(hookID int64, page int) ([]*HookTask, error) {
-	tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
-	return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
+	tasks := make([]*HookTask, 0, conf.Webhook.PagingNum)
+	return tasks, x.Limit(conf.Webhook.PagingNum, (page-1)*conf.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
 }
 
 // createHookTask creates a new hook task,
@@ -652,14 +652,14 @@ func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhook
 func (t *HookTask) deliver() {
 	t.IsDelivered = true
 
-	timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
+	timeout := time.Duration(conf.Webhook.DeliverTimeout) * time.Second
 	req := httplib.Post(t.URL).SetTimeout(timeout, timeout).
 		Header("X-Github-Delivery", t.UUID).
 		Header("X-Github-Event", string(t.EventType)).
 		Header("X-Gogs-Delivery", t.UUID).
 		Header("X-Gogs-Signature", t.Signature).
 		Header("X-Gogs-Event", string(t.EventType)).
-		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
+		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Webhook.SkipTLSVerify})
 
 	switch t.ContentType {
 	case JSON:

+ 6 - 6
internal/db/webhook_discord.go

@@ -14,7 +14,7 @@ import (
 	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 type DiscordEmbedFooterObject struct {
@@ -78,7 +78,7 @@ func getDiscordCreatePayload(p *api.CreatePayload) (*DiscordPayload, error) {
 	return &DiscordPayload{
 		Embeds: []*DiscordEmbedObject{{
 			Description: content,
-			URL:         setting.AppURL + p.Sender.UserName,
+			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
 				IconURL: p.Sender.AvatarUrl,
@@ -95,7 +95,7 @@ func getDiscordDeletePayload(p *api.DeletePayload) (*DiscordPayload, error) {
 	return &DiscordPayload{
 		Embeds: []*DiscordEmbedObject{{
 			Description: content,
-			URL:         setting.AppURL + p.Sender.UserName,
+			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
 				IconURL: p.Sender.AvatarUrl,
@@ -112,7 +112,7 @@ func getDiscordForkPayload(p *api.ForkPayload) (*DiscordPayload, error) {
 	return &DiscordPayload{
 		Embeds: []*DiscordEmbedObject{{
 			Description: content,
-			URL:         setting.AppURL + p.Sender.UserName,
+			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
 				IconURL: p.Sender.AvatarUrl,
@@ -160,7 +160,7 @@ func getDiscordPushPayload(p *api.PushPayload, slack *SlackMeta) (*DiscordPayloa
 		AvatarURL: slack.IconURL,
 		Embeds: []*DiscordEmbedObject{{
 			Description: content,
-			URL:         setting.AppURL + p.Sender.UserName,
+			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Color:       int(color),
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
@@ -361,7 +361,7 @@ func getDiscordReleasePayload(p *api.ReleasePayload) (*DiscordPayload, error) {
 	return &DiscordPayload{
 		Embeds: []*DiscordEmbedObject{{
 			Description: content,
-			URL:         setting.AppURL + p.Sender.UserName,
+			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
 				IconURL: p.Sender.AvatarUrl,

+ 6 - 6
internal/db/webhook_slack.go

@@ -13,7 +13,7 @@ import (
 	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 type SlackMeta struct {
@@ -147,7 +147,7 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
 }
 
 func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*SlackPayload, error) {
-	senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
+	senderLink := SlackLinkFormatter(conf.Server.ExternalURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index),
 		fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
 	var text, title, attachmentText string
@@ -165,7 +165,7 @@ func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*SlackPayloa
 		attachmentText = SlackTextFormatter(p.Issue.Body)
 	case api.HOOK_ISSUE_ASSIGNED:
 		text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName,
-			SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
+			SlackLinkFormatter(conf.Server.ExternalURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
 			titleLink, senderLink)
 	case api.HOOK_ISSUE_UNASSIGNED:
 		text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
@@ -193,7 +193,7 @@ func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*SlackPayloa
 }
 
 func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
-	senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
+	senderLink := SlackLinkFormatter(conf.Server.ExternalURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)),
 		fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
 	var text, title, attachmentText string
@@ -227,7 +227,7 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (
 }
 
 func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
-	senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
+	senderLink := SlackLinkFormatter(conf.Server.ExternalURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
 		fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
 	var text, title, attachmentText string
@@ -249,7 +249,7 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
 		attachmentText = SlackTextFormatter(p.PullRequest.Body)
 	case api.HOOK_ISSUE_ASSIGNED:
 		text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
-			SlackLinkFormatter(setting.AppURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName),
+			SlackLinkFormatter(conf.Server.ExternalURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName),
 			titleLink, senderLink)
 	case api.HOOK_ISSUE_UNASSIGNED:
 		text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)

+ 2 - 2
internal/db/wiki.go

@@ -17,7 +17,7 @@ import (
 
 	"github.com/gogs/git-module"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/sync"
 )
 
@@ -71,7 +71,7 @@ func (repo *Repository) InitWiki() error {
 }
 
 func (repo *Repository) LocalWikiPath() string {
-	return path.Join(setting.AppDataPath, "tmp/local-wiki", com.ToStr(repo.ID))
+	return filepath.Join(conf.Server.AppDataPath, "tmp", "local-wiki", com.ToStr(repo.ID))
 }
 
 // UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.

+ 11 - 11
internal/mailer/mail.go

@@ -7,7 +7,7 @@ package mailer
 import (
 	"fmt"
 	"html/template"
-	"path"
+	"path/filepath"
 	"sync"
 	"time"
 
@@ -16,8 +16,8 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/assets/templates"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -41,15 +41,15 @@ var (
 func render(tpl string, data map[string]interface{}) (string, error) {
 	tplRenderOnce.Do(func() {
 		opt := &macaron.RenderOptions{
-			Directory:         path.Join(setting.StaticRootPath, "templates/mail"),
-			AppendDirectories: []string{path.Join(setting.CustomPath, "templates/mail")},
+			Directory:         filepath.Join(conf.WorkDir(), "templates", "mail"),
+			AppendDirectories: []string{filepath.Join(conf.CustomDir(), "templates", "mail")},
 			Extensions:        []string{".tmpl", ".html"},
 			Funcs: []template.FuncMap{map[string]interface{}{
 				"AppName": func() string {
-					return setting.AppName
+					return conf.App.BrandName
 				},
 				"AppURL": func() string {
-					return setting.AppURL
+					return conf.Server.ExternalURL
 				},
 				"Year": func() int {
 					return time.Now().Year()
@@ -59,7 +59,7 @@ func render(tpl string, data map[string]interface{}) (string, error) {
 				},
 			}},
 		}
-		if !setting.LoadAssetsFromDisk {
+		if !conf.Server.LoadAssetsFromDisk {
 			opt.TemplateFileSystem = templates.NewTemplateFileSystem("mail", opt.AppendDirectories[0])
 		}
 
@@ -105,8 +105,8 @@ type Issue interface {
 func SendUserMail(c *macaron.Context, u User, tpl, code, subject, info string) {
 	data := map[string]interface{}{
 		"Username":          u.DisplayName(),
-		"ActiveCodeLives":   setting.Service.ActiveCodeLives / 60,
-		"ResetPwdCodeLives": setting.Service.ResetPwdCodeLives / 60,
+		"ActiveCodeLives":   conf.Service.ActiveCodeLives / 60,
+		"ResetPwdCodeLives": conf.Service.ResetPwdCodeLives / 60,
 		"Code":              code,
 	}
 	body, err := render(tpl, data)
@@ -133,7 +133,7 @@ func SendResetPasswordMail(c *macaron.Context, u User) {
 func SendActivateEmailMail(c *macaron.Context, u User, email string) {
 	data := map[string]interface{}{
 		"Username":        u.DisplayName(),
-		"ActiveCodeLives": setting.Service.ActiveCodeLives / 60,
+		"ActiveCodeLives": conf.Service.ActiveCodeLives / 60,
 		"Code":            u.GenerateEmailActivateCode(email),
 		"Email":           email,
 	}
@@ -204,7 +204,7 @@ func composeIssueMessage(issue Issue, repo Repository, doer User, tplName string
 	if err != nil {
 		log.Error("HTMLString (%s): %v", tplName, err)
 	}
-	from := gomail.NewMessage().FormatAddress(setting.MailService.FromEmail, doer.DisplayName())
+	from := gomail.NewMessage().FormatAddress(conf.MailService.FromEmail, doer.DisplayName())
 	msg := NewMessageFrom(tos, from, subject, content)
 	msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
 	return msg

+ 9 - 9
internal/mailer/mailer.go

@@ -18,7 +18,7 @@ import (
 	"gopkg.in/gomail.v2"
 	log "unknwon.dev/clog/v2"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 type Message struct {
@@ -34,13 +34,13 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
 	msg := gomail.NewMessage()
 	msg.SetHeader("From", from)
 	msg.SetHeader("To", to...)
-	msg.SetHeader("Subject", setting.MailService.SubjectPrefix+subject)
+	msg.SetHeader("Subject", conf.MailService.SubjectPrefix+subject)
 	msg.SetDateHeader("Date", time.Now())
 
 	contentType := "text/html"
 	body := htmlBody
 	switchedToPlaintext := false
-	if setting.MailService.UsePlainText || setting.MailService.AddPlainTextAlt {
+	if conf.MailService.UsePlainText || conf.MailService.AddPlainTextAlt {
 		plainBody, err := html2text.FromString(htmlBody)
 		if err != nil {
 			log.Error("html2text.FromString: %v", err)
@@ -51,7 +51,7 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
 		}
 	}
 	msg.SetBody(contentType, body)
-	if switchedToPlaintext && setting.MailService.AddPlainTextAlt && !setting.MailService.UsePlainText {
+	if switchedToPlaintext && conf.MailService.AddPlainTextAlt && !conf.MailService.UsePlainText {
 		// The AddAlternative method name is confusing - adding html as an "alternative" will actually cause mail
 		// clients to show it as first priority, and the text "main body" is the 2nd priority fallback.
 		// See: https://godoc.org/gopkg.in/gomail.v2#Message.AddAlternative
@@ -65,7 +65,7 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
 
 // NewMessage creates new mail message object with default From header.
 func NewMessage(to []string, subject, body string) *Message {
-	return NewMessageFrom(to, setting.MailService.From, subject, body)
+	return NewMessageFrom(to, conf.MailService.From, subject, body)
 }
 
 type loginAuth struct {
@@ -99,7 +99,7 @@ type Sender struct {
 }
 
 func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
-	opts := setting.MailService
+	opts := conf.MailService
 
 	host, port, err := net.SplitHostPort(opts.Host)
 	if err != nil {
@@ -225,11 +225,11 @@ func NewContext() {
 	// Need to check if mailQueue is nil because in during reinstall (user had installed
 	// before but swithed install lock off), this function will be called again
 	// while mail queue is already processing tasks, and produces a race condition.
-	if setting.MailService == nil || mailQueue != nil {
+	if conf.MailService == nil || mailQueue != nil {
 		return
 	}
 
-	mailQueue = make(chan *Message, setting.MailService.QueueLength)
+	mailQueue = make(chan *Message, conf.MailService.QueueLength)
 	go processMailQueue()
 }
 
@@ -239,7 +239,7 @@ func NewContext() {
 func Send(msg *Message) {
 	mailQueue <- msg
 
-	if setting.HookMode {
+	if conf.HookMode {
 		<-msg.confirmChan
 		return
 	}

+ 11 - 11
internal/markup/markdown.go

@@ -13,15 +13,15 @@ import (
 
 	"github.com/russross/blackfriday"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/lazyregexp"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
 // IsMarkdownFile reports whether name looks like a Markdown file based on its extension.
 func IsMarkdownFile(name string) bool {
 	extension := strings.ToLower(filepath.Ext(name))
-	for _, ext := range setting.Markdown.FileExtensions {
+	for _, ext := range conf.Markdown.FileExtensions {
 		if strings.ToLower(ext) == extension {
 			return true
 		}
@@ -63,7 +63,7 @@ func (r *MarkdownRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
 
 	// Since this method could only possibly serve one link at a time,
 	// we do not need to find all.
-	if bytes.HasPrefix(link, []byte(setting.AppURL)) {
+	if bytes.HasPrefix(link, []byte(conf.Server.ExternalURL)) {
 		m := CommitPattern.Find(link)
 		if m != nil {
 			m = bytes.TrimSpace(m)
@@ -86,14 +86,14 @@ func (r *MarkdownRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
 			}
 
 			index := string(m[i+7 : j])
-			fullRepoURL := setting.AppURL + strings.TrimPrefix(r.urlPrefix, "/")
+			fullRepoURL := conf.Server.ExternalURL + strings.TrimPrefix(r.urlPrefix, "/")
 			var link string
 			if strings.HasPrefix(string(m), fullRepoURL) {
 				// Use a short issue reference if the URL refers to this repository
 				link = fmt.Sprintf(`<a href="%s">#%s</a>`, m, index)
 			} else {
 				// Use a cross-repository issue reference if the URL refers to a different repository
-				repo := string(m[len(setting.AppURL) : i-1])
+				repo := string(m[len(conf.Server.ExternalURL) : i-1])
 				link = fmt.Sprintf(`<a href="%s">%s#%s</a>`, m, repo, index)
 			}
 			out.WriteString(link)
@@ -122,18 +122,18 @@ func RawMarkdown(body []byte, urlPrefix string) []byte {
 	htmlFlags |= blackfriday.HTML_SKIP_STYLE
 	htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
 
-	if setting.Smartypants.Enabled {
+	if conf.Smartypants.Enabled {
 		htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
-		if setting.Smartypants.Fractions {
+		if conf.Smartypants.Fractions {
 			htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
 		}
-		if setting.Smartypants.Dashes {
+		if conf.Smartypants.Dashes {
 			htmlFlags |= blackfriday.HTML_SMARTYPANTS_DASHES
 		}
-		if setting.Smartypants.LatexDashes {
+		if conf.Smartypants.LatexDashes {
 			htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
 		}
-		if setting.Smartypants.AngledQuotes {
+		if conf.Smartypants.AngledQuotes {
 			htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES
 		}
 	}
@@ -153,7 +153,7 @@ func RawMarkdown(body []byte, urlPrefix string) []byte {
 	extensions |= blackfriday.EXTENSION_SPACE_HEADERS
 	extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
 
-	if setting.Markdown.EnableHardLineBreak {
+	if conf.Markdown.EnableHardLineBreak {
 		extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
 	}
 

+ 4 - 4
internal/markup/markdown_test.go

@@ -12,12 +12,12 @@ import (
 	"github.com/russross/blackfriday"
 	. "github.com/smartystreets/goconvey/convey"
 
+	"gogs.io/gogs/internal/conf"
 	. "gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
 )
 
 func Test_IsMarkdownFile(t *testing.T) {
-	setting.Markdown.FileExtensions = strings.Split(".md,.markdown,.mdown,.mkd", ",")
+	conf.Markdown.FileExtensions = strings.Split(".md,.markdown,.mdown,.mkd", ",")
 	Convey("Detect Markdown file extension", t, func() {
 		testCases := []struct {
 			ext   string
@@ -40,7 +40,7 @@ func Test_IsMarkdownFile(t *testing.T) {
 
 func Test_Markdown(t *testing.T) {
 	Convey("Rendering an issue URL", t, func() {
-		setting.AppURL = "http://localhost:3000/"
+		conf.Server.ExternalURL = "http://localhost:3000/"
 		htmlFlags := 0
 		htmlFlags |= blackfriday.HTML_SKIP_STYLE
 		htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
@@ -82,7 +82,7 @@ func Test_Markdown(t *testing.T) {
 	})
 
 	Convey("Rendering a commit URL", t, func() {
-		setting.AppURL = "http://localhost:3000/"
+		conf.Server.ExternalURL = "http://localhost:3000/"
 		htmlFlags := 0
 		htmlFlags |= blackfriday.HTML_SKIP_STYLE
 		htmlFlags |= blackfriday.HTML_OMIT_CONTENTS

+ 4 - 4
internal/markup/markup.go

@@ -13,8 +13,8 @@ import (
 	"github.com/unknwon/com"
 	"golang.org/x/net/html"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/lazyregexp"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -78,7 +78,7 @@ func cutoutVerbosePrefix(prefix string) string {
 		if prefix[i] == '/' {
 			count++
 		}
-		if count >= 3+setting.AppSubURLDepth {
+		if count >= 3+conf.Server.SubpathDepth {
 			return prefix[:i]
 		}
 	}
@@ -133,7 +133,7 @@ func RenderCrossReferenceIssueIndexPattern(rawBytes []byte, urlPrefix string, me
 		repo := string(m[:delimIdx])
 		index := string(m[delimIdx+1:])
 
-		link := fmt.Sprintf(`<a href="%s%s/issues/%s">%s</a>`, setting.AppURL, repo, index, m)
+		link := fmt.Sprintf(`<a href="%s%s/issues/%s">%s</a>`, conf.Server.ExternalURL, repo, index, m)
 		rawBytes = bytes.Replace(rawBytes, m, []byte(link), 1)
 	}
 	return rawBytes
@@ -155,7 +155,7 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string, metas map[string]strin
 	for _, m := range ms {
 		m = m[bytes.Index(m, []byte("@")):]
 		rawBytes = bytes.Replace(rawBytes, m,
-			[]byte(fmt.Sprintf(`<a href="%s/%s">%s</a>`, setting.AppSubURL, m[1:], m)), -1)
+			[]byte(fmt.Sprintf(`<a href="%s/%s">%s</a>`, conf.Server.Subpath, m[1:], m)), -1)
 	}
 
 	rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix, metas)

+ 2 - 2
internal/markup/markup_test.go

@@ -10,8 +10,8 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
+	"gogs.io/gogs/internal/conf"
 	. "gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
 )
 
 func Test_IsReadmeFile(t *testing.T) {
@@ -62,7 +62,7 @@ func Test_RenderIssueIndexPattern(t *testing.T) {
 			urlPrefix                   = "/prefix"
 			metas     map[string]string = nil
 		)
-		setting.AppSubURLDepth = 0
+		conf.Server.SubpathDepth = 0
 
 		Convey("To the internal issue tracker", func() {
 			Convey("It should not render anything when there are no mentions", func() {

+ 2 - 2
internal/markup/sanitizer.go

@@ -10,7 +10,7 @@ import (
 	"github.com/microcosm-cc/bluemonday"
 
 	"gogs.io/gogs/internal/lazyregexp"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 // Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
@@ -40,7 +40,7 @@ func NewSanitizer() {
 		sanitizer.policy.AllowURLSchemes("data")
 
 		// Custom URL-Schemes
-		sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
+		sanitizer.policy.AllowURLSchemes(conf.Markdown.CustomURLSchemes...)
 	})
 }
 

+ 32 - 33
internal/route/admin/admin.go

@@ -14,12 +14,12 @@ import (
 	"github.com/unknwon/com"
 	"gopkg.in/macaron.v1"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/cron"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/mailer"
 	"gogs.io/gogs/internal/process"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -168,10 +168,10 @@ func Dashboard(c *context.Context) {
 		return
 	}
 
-	c.Data["GitVersion"] = setting.Git.Version
+	c.Data["GitVersion"] = conf.Git.Version
 	c.Data["GoVersion"] = runtime.Version()
-	c.Data["BuildTime"] = setting.BuildTime
-	c.Data["BuildCommit"] = setting.BuildCommit
+	c.Data["BuildTime"] = conf.BuildTime
+	c.Data["BuildCommit"] = conf.BuildCommit
 
 	c.Data["Stats"] = db.GetStatistic()
 	// FIXME: update periodically
@@ -189,7 +189,7 @@ func SendTestMail(c *context.Context) {
 		c.Flash.Info(c.Tr("admin.config.test_mail_sent", email))
 	}
 
-	c.Redirect(setting.AppSubURL + "/admin/config")
+	c.Redirect(conf.Server.Subpath + "/admin/config")
 }
 
 func Config(c *context.Context) {
@@ -197,54 +197,53 @@ func Config(c *context.Context) {
 	c.Data["PageIsAdmin"] = true
 	c.Data["PageIsAdminConfig"] = true
 
-	c.Data["AppURL"] = setting.AppURL
-	c.Data["Domain"] = setting.Domain
-	c.Data["OfflineMode"] = setting.OfflineMode
-	c.Data["DisableRouterLog"] = setting.DisableRouterLog
-	c.Data["RunUser"] = setting.RunUser
+	c.Data["AppURL"] = conf.Server.ExternalURL
+	c.Data["Domain"] = conf.Server.Domain
+	c.Data["OfflineMode"] = conf.Server.OfflineMode
+	c.Data["DisableRouterLog"] = conf.Server.DisableRouterLog
+	c.Data["RunUser"] = conf.App.RunUser
 	c.Data["RunMode"] = strings.Title(macaron.Env)
-	c.Data["StaticRootPath"] = setting.StaticRootPath
-	c.Data["LogRootPath"] = setting.LogRootPath
-	c.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser
+	c.Data["LogRootPath"] = conf.LogRootPath
+	c.Data["ReverseProxyAuthUser"] = conf.ReverseProxyAuthUser
 
-	c.Data["SSH"] = setting.SSH
+	c.Data["SSH"] = conf.SSH
 
-	c.Data["RepoRootPath"] = setting.RepoRootPath
-	c.Data["ScriptType"] = setting.ScriptType
-	c.Data["Repository"] = setting.Repository
-	c.Data["HTTP"] = setting.HTTP
+	c.Data["RepoRootPath"] = conf.RepoRootPath
+	c.Data["ScriptType"] = conf.ScriptType
+	c.Data["Repository"] = conf.Repository
+	c.Data["HTTP"] = conf.HTTP
 
 	c.Data["DbCfg"] = db.DbCfg
-	c.Data["Service"] = setting.Service
-	c.Data["Webhook"] = setting.Webhook
+	c.Data["Service"] = conf.Service
+	c.Data["Webhook"] = conf.Webhook
 
 	c.Data["MailerEnabled"] = false
-	if setting.MailService != nil {
+	if conf.MailService != nil {
 		c.Data["MailerEnabled"] = true
-		c.Data["Mailer"] = setting.MailService
+		c.Data["Mailer"] = conf.MailService
 	}
 
-	c.Data["CacheAdapter"] = setting.CacheAdapter
-	c.Data["CacheInterval"] = setting.CacheInterval
-	c.Data["CacheConn"] = setting.CacheConn
+	c.Data["CacheAdapter"] = conf.CacheAdapter
+	c.Data["CacheInterval"] = conf.CacheInterval
+	c.Data["CacheConn"] = conf.CacheConn
 
-	c.Data["SessionConfig"] = setting.SessionConfig
+	c.Data["SessionConfig"] = conf.SessionConfig
 
-	c.Data["DisableGravatar"] = setting.DisableGravatar
-	c.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar
+	c.Data["DisableGravatar"] = conf.DisableGravatar
+	c.Data["EnableFederatedAvatar"] = conf.EnableFederatedAvatar
 
-	c.Data["Git"] = setting.Git
+	c.Data["Git"] = conf.Git
 
 	type logger struct {
 		Mode, Config string
 	}
-	loggers := make([]*logger, len(setting.LogModes))
-	for i := range setting.LogModes {
+	loggers := make([]*logger, len(conf.LogModes))
+	for i := range conf.LogModes {
 		loggers[i] = &logger{
-			Mode: strings.Title(setting.LogModes[i]),
+			Mode: strings.Title(conf.LogModes[i]),
 		}
 
-		result, _ := jsoniter.MarshalIndent(setting.LogConfigs[i], "", "  ")
+		result, _ := jsoniter.MarshalIndent(conf.LogConfigs[i], "", "  ")
 		loggers[i].Config = string(result)
 	}
 	c.Data["Loggers"] = loggers

+ 5 - 5
internal/route/admin/auths.go

@@ -14,10 +14,10 @@ import (
 	"xorm.io/core"
 
 	"gogs.io/gogs/internal/auth/ldap"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -175,7 +175,7 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) {
 	log.Trace("Authentication created by admin(%s): %s", c.User.Name, f.Name)
 
 	c.Flash.Success(c.Tr("admin.auths.new_success", f.Name))
-	c.Redirect(setting.AppSubURL + "/admin/auths")
+	c.Redirect(conf.Server.Subpath + "/admin/auths")
 }
 
 func EditAuthSource(c *context.Context) {
@@ -248,7 +248,7 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) {
 	log.Trace("Authentication changed by admin '%s': %d", c.User.Name, source.ID)
 
 	c.Flash.Success(c.Tr("admin.auths.update_success"))
-	c.Redirect(setting.AppSubURL + "/admin/auths/" + com.ToStr(f.ID))
+	c.Redirect(conf.Server.Subpath + "/admin/auths/" + com.ToStr(f.ID))
 }
 
 func DeleteAuthSource(c *context.Context) {
@@ -265,7 +265,7 @@ func DeleteAuthSource(c *context.Context) {
 			c.Flash.Error(fmt.Sprintf("DeleteSource: %v", err))
 		}
 		c.JSONSuccess(map[string]interface{}{
-			"redirect": setting.AppSubURL + "/admin/auths/" + c.Params(":authid"),
+			"redirect": conf.Server.Subpath + "/admin/auths/" + c.Params(":authid"),
 		})
 		return
 	}
@@ -273,6 +273,6 @@ func DeleteAuthSource(c *context.Context) {
 
 	c.Flash.Success(c.Tr("admin.auths.deletion_success"))
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/admin/auths",
+		"redirect": conf.Server.Subpath + "/admin/auths",
 	})
 }

+ 4 - 4
internal/route/admin/notice.go

@@ -9,9 +9,9 @@ import (
 	"github.com/unknwon/paginater"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -28,9 +28,9 @@ func Notices(c *context.Context) {
 	if page <= 1 {
 		page = 1
 	}
-	c.Data["Page"] = paginater.New(int(total), setting.UI.Admin.NoticePagingNum, page, 5)
+	c.Data["Page"] = paginater.New(int(total), conf.UI.Admin.NoticePagingNum, page, 5)
 
-	notices, err := db.Notices(page, setting.UI.Admin.NoticePagingNum)
+	notices, err := db.Notices(page, conf.UI.Admin.NoticePagingNum)
 	if err != nil {
 		c.Handle(500, "Notices", err)
 		return
@@ -68,5 +68,5 @@ func EmptyNotices(c *context.Context) {
 
 	log.Trace("System notices deleted by admin (%s): [start: %d]", c.User.Name, 0)
 	c.Flash.Success(c.Tr("admin.notices.delete_success"))
-	c.Redirect(setting.AppSubURL + "/admin/notices")
+	c.Redirect(conf.Server.Subpath + "/admin/notices")
 }

+ 2 - 2
internal/route/admin/orgs.go

@@ -8,7 +8,7 @@ import (
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/route"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 const (
@@ -24,7 +24,7 @@ func Organizations(c *context.Context) {
 		Type:     db.USER_TYPE_ORGANIZATION,
 		Counter:  db.CountOrganizations,
 		Ranger:   db.Organizations,
-		PageSize: setting.UI.Admin.OrgPagingNum,
+		PageSize: conf.UI.Admin.OrgPagingNum,
 		OrderBy:  "id ASC",
 		TplName:  ORGS,
 	})

+ 5 - 5
internal/route/admin/repos.go

@@ -8,9 +8,9 @@ import (
 	"github.com/unknwon/paginater"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -35,7 +35,7 @@ func Repos(c *context.Context) {
 
 	keyword := c.Query("q")
 	if len(keyword) == 0 {
-		repos, err = db.Repositories(page, setting.UI.Admin.RepoPagingNum)
+		repos, err = db.Repositories(page, conf.UI.Admin.RepoPagingNum)
 		if err != nil {
 			c.Handle(500, "Repositories", err)
 			return
@@ -47,7 +47,7 @@ func Repos(c *context.Context) {
 			OrderBy:  "id ASC",
 			Private:  true,
 			Page:     page,
-			PageSize: setting.UI.Admin.RepoPagingNum,
+			PageSize: conf.UI.Admin.RepoPagingNum,
 		})
 		if err != nil {
 			c.Handle(500, "SearchRepositoryByName", err)
@@ -56,7 +56,7 @@ func Repos(c *context.Context) {
 	}
 	c.Data["Keyword"] = keyword
 	c.Data["Total"] = count
-	c.Data["Page"] = paginater.New(int(count), setting.UI.Admin.RepoPagingNum, page, 5)
+	c.Data["Page"] = paginater.New(int(count), conf.UI.Admin.RepoPagingNum, page, 5)
 
 	if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
 		c.Handle(500, "LoadAttributes", err)
@@ -82,6 +82,6 @@ func DeleteRepo(c *context.Context) {
 
 	c.Flash.Success(c.Tr("repo.settings.deletion_success"))
 	c.JSON(200, map[string]interface{}{
-		"redirect": setting.AppSubURL + "/admin/repos?page=" + c.Query("page"),
+		"redirect": conf.Server.Subpath + "/admin/repos?page=" + c.Query("page"),
 	})
 }

+ 12 - 12
internal/route/admin/users.go

@@ -10,12 +10,12 @@ import (
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/mailer"
 	"gogs.io/gogs/internal/route"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -33,7 +33,7 @@ func Users(c *context.Context) {
 		Type:     db.USER_TYPE_INDIVIDUAL,
 		Counter:  db.CountUsers,
 		Ranger:   db.Users,
-		PageSize: setting.UI.Admin.UserPagingNum,
+		PageSize: conf.UI.Admin.UserPagingNum,
 		OrderBy:  "id ASC",
 		TplName:  USERS,
 	})
@@ -53,7 +53,7 @@ func NewUser(c *context.Context) {
 	}
 	c.Data["Sources"] = sources
 
-	c.Data["CanSendEmail"] = setting.MailService != nil
+	c.Data["CanSendEmail"] = conf.MailService != nil
 	c.HTML(200, USER_NEW)
 }
 
@@ -69,7 +69,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
 	}
 	c.Data["Sources"] = sources
 
-	c.Data["CanSendEmail"] = setting.MailService != nil
+	c.Data["CanSendEmail"] = conf.MailService != nil
 
 	if c.HasError() {
 		c.HTML(200, USER_NEW)
@@ -115,12 +115,12 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
 	log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name)
 
 	// Send email notification.
-	if f.SendNotify && setting.MailService != nil {
+	if f.SendNotify && conf.MailService != nil {
 		mailer.SendRegisterNotifyMail(c.Context, db.NewMailerUser(u))
 	}
 
 	c.Flash.Success(c.Tr("admin.users.new_success", u.Name))
-	c.Redirect(setting.AppSubURL + "/admin/users/" + com.ToStr(u.ID))
+	c.Redirect(conf.Server.Subpath + "/admin/users/" + com.ToStr(u.ID))
 }
 
 func prepareUserInfo(c *context.Context) *db.User {
@@ -155,7 +155,7 @@ func EditUser(c *context.Context) {
 	c.Data["Title"] = c.Tr("admin.users.edit_account")
 	c.Data["PageIsAdmin"] = true
 	c.Data["PageIsAdminUsers"] = true
-	c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration
+	c.Data["EnableLocalPathMigration"] = conf.Repository.EnableLocalPathMigration
 
 	prepareUserInfo(c)
 	if c.Written() {
@@ -169,7 +169,7 @@ func EditUserPost(c *context.Context, f form.AdminEditUser) {
 	c.Data["Title"] = c.Tr("admin.users.edit_account")
 	c.Data["PageIsAdmin"] = true
 	c.Data["PageIsAdminUsers"] = true
-	c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration
+	c.Data["EnableLocalPathMigration"] = conf.Repository.EnableLocalPathMigration
 
 	u := prepareUserInfo(c)
 	if c.Written() {
@@ -226,7 +226,7 @@ func EditUserPost(c *context.Context, f form.AdminEditUser) {
 	log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name)
 
 	c.Flash.Success(c.Tr("admin.users.update_profile_success"))
-	c.Redirect(setting.AppSubURL + "/admin/users/" + c.Params(":userid"))
+	c.Redirect(conf.Server.Subpath + "/admin/users/" + c.Params(":userid"))
 }
 
 func DeleteUser(c *context.Context) {
@@ -241,12 +241,12 @@ func DeleteUser(c *context.Context) {
 		case db.IsErrUserOwnRepos(err):
 			c.Flash.Error(c.Tr("admin.users.still_own_repo"))
 			c.JSON(200, map[string]interface{}{
-				"redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"),
+				"redirect": conf.Server.Subpath + "/admin/users/" + c.Params(":userid"),
 			})
 		case db.IsErrUserHasOrgs(err):
 			c.Flash.Error(c.Tr("admin.users.still_has_org"))
 			c.JSON(200, map[string]interface{}{
-				"redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"),
+				"redirect": conf.Server.Subpath + "/admin/users/" + c.Params(":userid"),
 			})
 		default:
 			c.Handle(500, "DeleteUser", err)
@@ -257,6 +257,6 @@ func DeleteUser(c *context.Context) {
 
 	c.Flash.Success(c.Tr("admin.users.deletion_success"))
 	c.JSON(200, map[string]interface{}{
-		"redirect": setting.AppSubURL + "/admin/users",
+		"redirect": conf.Server.Subpath + "/admin/users",
 	})
 }

+ 2 - 2
internal/route/api/v1/admin/user.go

@@ -16,7 +16,7 @@ import (
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/mailer"
 	"gogs.io/gogs/internal/route/api/v1/user"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginName string) {
@@ -68,7 +68,7 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) {
 	log.Trace("Account created by admin %q: %s", c.User.Name, u.Name)
 
 	// Send email notification.
-	if form.SendNotify && setting.MailService != nil {
+	if form.SendNotify && conf.MailService != nil {
 		mailer.SendRegisterNotifyMail(c.Context.Context, db.NewMailerUser(u))
 	}
 

+ 3 - 3
internal/route/api/v1/convert/utils.go

@@ -5,15 +5,15 @@
 package convert
 
 import (
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 // ToCorrectPageSize makes sure page size is in allowed range.
 func ToCorrectPageSize(size int) int {
 	if size <= 0 {
 		size = 10
-	} else if size > setting.API.MaxResponseItems {
-		size = setting.API.MaxResponseItems
+	} else if size > conf.API.MaxResponseItems {
+		size = conf.API.MaxResponseItems
 	}
 	return size
 }

+ 3 - 3
internal/route/api/v1/repo/commits.go

@@ -12,10 +12,10 @@ import (
 	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 func GetSingleCommit(c *context.APIContext) {
@@ -70,12 +70,12 @@ func GetSingleCommit(c *context.APIContext) {
 
 	c.JSONSuccess(&api.Commit{
 		CommitMeta: &api.CommitMeta{
-			URL: setting.AppURL + c.Link[1:],
+			URL: conf.Server.ExternalURL + c.Link[1:],
 			SHA: commit.ID.String(),
 		},
 		HTMLURL: c.Repo.Repository.HTMLURL() + "/commits/" + commit.ID.String(),
 		RepoCommit: &api.RepoCommit{
-			URL: setting.AppURL + c.Link[1:],
+			URL: conf.Server.ExternalURL + c.Link[1:],
 			Author: &api.CommitUser{
 				Name:  commit.Author.Name,
 				Email: commit.Author.Email,

+ 2 - 2
internal/route/api/v1/repo/issue.go

@@ -14,7 +14,7 @@ import (
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func listIssues(c *context.APIContext, opts *db.IssuesOptions) {
@@ -40,7 +40,7 @@ func listIssues(c *context.APIContext, opts *db.IssuesOptions) {
 		apiIssues[i] = issues[i].APIFormat()
 	}
 
-	c.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
+	c.SetLinkHeader(int(count), conf.UI.IssuePagingNum)
 	c.JSONSuccess(&apiIssues)
 }
 

+ 2 - 2
internal/route/api/v1/repo/key.go

@@ -10,13 +10,13 @@ import (
 
 	api "github.com/gogs/go-gogs-client"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 func composeDeployKeysAPILink(repoPath string) string {
-	return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/"
+	return conf.Server.ExternalURL + "api/v1/repos/" + repoPath + "/keys/"
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys

+ 2 - 2
internal/route/api/v1/repo/repo.go

@@ -18,7 +18,7 @@ import (
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/route/api/v1/convert"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func Search(c *context.APIContext) {
@@ -263,7 +263,7 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 	repo, err := db.MigrateRepository(c.User, ctxUser, db.MigrateRepoOptions{
 		Name:        f.RepoName,
 		Description: f.Description,
-		IsPrivate:   f.Private || setting.Repository.ForcePrivate,
+		IsPrivate:   f.Private || conf.Repository.ForcePrivate,
 		IsMirror:    f.Mirror,
 		RemoteAddr:  remoteAddr,
 	})

+ 2 - 2
internal/route/api/v1/user/email.go

@@ -12,7 +12,7 @@ import (
 
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 func ListEmails(c *context.APIContext) {
@@ -39,7 +39,7 @@ func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
 		emails[i] = &db.EmailAddress{
 			UID:         c.User.ID,
 			Email:       form.Emails[i],
-			IsActivated: !setting.Service.RegisterEmailConfirm,
+			IsActivated: !conf.Service.RegisterEmailConfirm,
 		}
 	}
 

+ 2 - 2
internal/route/api/v1/user/key.go

@@ -10,10 +10,10 @@ import (
 	repo2 "gogs.io/gogs/internal/route/api/v1/repo"
 	"net/http"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 func GetUserByParamsName(c *context.APIContext, name string) *db.User {
@@ -31,7 +31,7 @@ func GetUserByParams(c *context.APIContext) *db.User {
 }
 
 func composePublicKeysAPILink() string {
-	return setting.AppURL + "api/v1/user/keys/"
+	return conf.Server.ExternalURL + "api/v1/user/keys/"
 }
 
 func listPublicKeys(c *context.APIContext, uid int64) {

+ 6 - 6
internal/route/dev/template.go

@@ -5,19 +5,19 @@
 package dev
 
 import (
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 func TemplatePreview(c *context.Context) {
 	c.Data["User"] = db.User{Name: "Unknown"}
-	c.Data["AppName"] = setting.AppName
-	c.Data["AppVersion"] = setting.AppVersion
-	c.Data["AppURL"] = setting.AppURL
+	c.Data["AppName"] = conf.App.BrandName
+	c.Data["AppVersion"] = conf.App.Version
+	c.Data["AppURL"] = conf.Server.ExternalURL
 	c.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374"
-	c.Data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
-	c.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
+	c.Data["ActiveCodeLives"] = conf.Service.ActiveCodeLives / 60
+	c.Data["ResetPwdCodeLives"] = conf.Service.ResetPwdCodeLives / 60
 	c.Data["CurDbValue"] = ""
 
 	c.HTML(200, (c.Params("*")))

+ 8 - 8
internal/route/home.go

@@ -8,9 +8,9 @@ import (
 	"github.com/unknwon/paginater"
 	user2 "gogs.io/gogs/internal/route/user"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -22,7 +22,7 @@ const (
 
 func Home(c *context.Context) {
 	if c.IsLogged {
-		if !c.User.IsActive && setting.Service.RegisterEmailConfirm {
+		if !c.User.IsActive && conf.Service.RegisterEmailConfirm {
 			c.Data["Title"] = c.Tr("auth.active_your_account")
 			c.Success(user2.ACTIVATE)
 		} else {
@@ -32,9 +32,9 @@ func Home(c *context.Context) {
 	}
 
 	// Check auto-login.
-	uname := c.GetCookie(setting.CookieUserName)
+	uname := c.GetCookie(conf.CookieUserName)
 	if len(uname) != 0 {
-		c.Redirect(setting.AppSubURL + "/user/login")
+		c.Redirect(conf.Server.Subpath + "/user/login")
 		return
 	}
 
@@ -58,7 +58,7 @@ func ExploreRepos(c *context.Context) {
 		UserID:   c.UserID(),
 		OrderBy:  "updated_unix DESC",
 		Page:     page,
-		PageSize: setting.UI.ExplorePagingNum,
+		PageSize: conf.UI.ExplorePagingNum,
 	})
 	if err != nil {
 		c.ServerError("SearchRepositoryByName", err)
@@ -66,7 +66,7 @@ func ExploreRepos(c *context.Context) {
 	}
 	c.Data["Keyword"] = keyword
 	c.Data["Total"] = count
-	c.Data["Page"] = paginater.New(int(count), setting.UI.ExplorePagingNum, page, 5)
+	c.Data["Page"] = paginater.New(int(count), conf.UI.ExplorePagingNum, page, 5)
 
 	if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
 		c.ServerError("RepositoryList.LoadAttributes", err)
@@ -136,7 +136,7 @@ func ExploreUsers(c *context.Context) {
 		Type:     db.USER_TYPE_INDIVIDUAL,
 		Counter:  db.CountUsers,
 		Ranger:   db.Users,
-		PageSize: setting.UI.ExplorePagingNum,
+		PageSize: conf.UI.ExplorePagingNum,
 		OrderBy:  "updated_unix DESC",
 		TplName:  EXPLORE_USERS,
 	})
@@ -151,7 +151,7 @@ func ExploreOrganizations(c *context.Context) {
 		Type:     db.USER_TYPE_ORGANIZATION,
 		Counter:  db.CountOrganizations,
 		Ranger:   db.Organizations,
-		PageSize: setting.UI.ExplorePagingNum,
+		PageSize: conf.UI.ExplorePagingNum,
 		OrderBy:  "updated_unix DESC",
 		TplName:  EXPLORE_ORGANIZATIONS,
 	})

+ 67 - 52
internal/route/install.go

@@ -11,6 +11,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/pkg/errors"
 	"github.com/unknwon/com"
 	"gopkg.in/ini.v1"
 	"gopkg.in/macaron.v1"
@@ -19,13 +20,14 @@ import (
 
 	"github.com/gogs/git-module"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/cron"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/mailer"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/ssh"
 	"gogs.io/gogs/internal/template/highlight"
 	"gogs.io/gogs/internal/tool"
@@ -37,7 +39,7 @@ const (
 )
 
 func checkRunMode() {
-	if setting.ProdMode {
+	if conf.IsProdMode() {
 		macaron.Env = macaron.PROD
 		macaron.ColorLog = false
 	} else {
@@ -47,20 +49,26 @@ func checkRunMode() {
 }
 
 // GlobalInit is for global configuration reload-able.
-func GlobalInit() {
-	setting.Init()
-	setting.InitLogging()
-	log.Info("%s %s", setting.AppName, setting.AppVersion)
-	log.Trace("Custom path: %s", setting.CustomPath)
-	log.Trace("Log path: %s", setting.LogRootPath)
-	log.Trace("Build time: %s", setting.BuildTime)
-	log.Trace("Build commit: %s", setting.BuildCommit)
+func GlobalInit(customConf string) error {
+	err := conf.Init(customConf)
+	if err != nil {
+		return errors.Wrap(err, "init configuration")
+	}
+
+	conf.InitLogging()
+	log.Info("%s %s", conf.App.BrandName, conf.App.Version)
+	log.Trace("Work directory: %s", conf.WorkDir())
+	log.Trace("Custom path: %s", conf.CustomDir())
+	log.Trace("Custom config: %s", conf.CustomConf)
+	log.Trace("Log path: %s", conf.LogRootPath)
+	log.Trace("Build time: %s", conf.BuildTime)
+	log.Trace("Build commit: %s", conf.BuildCommit)
 
 	db.LoadConfigs()
-	setting.NewServices()
+	conf.NewServices()
 	mailer.NewContext()
 
-	if setting.InstallLock {
+	if conf.InstallLock {
 		highlight.NewContext()
 		markup.NewSanitizer()
 		if err := db.NewEngine(); err != nil {
@@ -81,33 +89,35 @@ func GlobalInit() {
 	if db.EnableSQLite3 {
 		log.Info("SQLite3 is supported")
 	}
-	if setting.SupportWindowsService() {
+	if conf.HasMinWinSvc {
 		log.Info("Builtin Windows Service is supported")
 	}
-	if setting.LoadAssetsFromDisk {
+	if conf.Server.LoadAssetsFromDisk {
 		log.Trace("Assets are loaded from disk")
 	}
 	checkRunMode()
 
-	if !setting.InstallLock {
-		return
+	if !conf.InstallLock {
+		return nil
 	}
 
-	if setting.SSH.StartBuiltinServer {
-		ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers)
-		log.Info("SSH server started on %s:%v", setting.SSH.ListenHost, setting.SSH.ListenPort)
-		log.Trace("SSH server cipher list: %v", setting.SSH.ServerCiphers)
+	if conf.SSH.StartBuiltinServer {
+		ssh.Listen(conf.SSH.ListenHost, conf.SSH.ListenPort, conf.SSH.ServerCiphers)
+		log.Info("SSH server started on %s:%v", conf.SSH.ListenHost, conf.SSH.ListenPort)
+		log.Trace("SSH server cipher list: %v", conf.SSH.ServerCiphers)
 	}
 
-	if setting.SSH.RewriteAuthorizedKeysAtStart {
+	if conf.SSH.RewriteAuthorizedKeysAtStart {
 		if err := db.RewriteAuthorizedKeys(); err != nil {
 			log.Warn("Failed to rewrite authorized_keys file: %v", err)
 		}
 	}
+
+	return nil
 }
 
 func InstallInit(c *context.Context) {
-	if setting.InstallLock {
+	if conf.InstallLock {
 		c.NotFound()
 		return
 	}
@@ -144,40 +154,40 @@ func Install(c *context.Context) {
 	}
 
 	// Application general settings
-	f.AppName = setting.AppName
-	f.RepoRootPath = setting.RepoRootPath
+	f.AppName = conf.App.BrandName
+	f.RepoRootPath = conf.RepoRootPath
 
 	// Note(unknwon): it's hard for Windows users change a running user,
 	// 	so just use current one if config says default.
-	if setting.IsWindows && setting.RunUser == "git" {
+	if conf.IsWindowsRuntime() && conf.App.RunUser == "git" {
 		f.RunUser = user.CurrentUsername()
 	} else {
-		f.RunUser = setting.RunUser
+		f.RunUser = conf.App.RunUser
 	}
 
-	f.Domain = setting.Domain
-	f.SSHPort = setting.SSH.Port
-	f.UseBuiltinSSHServer = setting.SSH.StartBuiltinServer
-	f.HTTPPort = setting.HTTPPort
-	f.AppUrl = setting.AppURL
-	f.LogRootPath = setting.LogRootPath
+	f.Domain = conf.Server.Domain
+	f.SSHPort = conf.SSH.Port
+	f.UseBuiltinSSHServer = conf.SSH.StartBuiltinServer
+	f.HTTPPort = conf.Server.HTTPPort
+	f.AppUrl = conf.Server.ExternalURL
+	f.LogRootPath = conf.LogRootPath
 
 	// E-mail service settings
-	if setting.MailService != nil {
-		f.SMTPHost = setting.MailService.Host
-		f.SMTPFrom = setting.MailService.From
-		f.SMTPUser = setting.MailService.User
+	if conf.MailService != nil {
+		f.SMTPHost = conf.MailService.Host
+		f.SMTPFrom = conf.MailService.From
+		f.SMTPUser = conf.MailService.User
 	}
-	f.RegisterConfirm = setting.Service.RegisterEmailConfirm
-	f.MailNotify = setting.Service.EnableNotifyMail
+	f.RegisterConfirm = conf.Service.RegisterEmailConfirm
+	f.MailNotify = conf.Service.EnableNotifyMail
 
 	// Server and other services settings
-	f.OfflineMode = setting.OfflineMode
-	f.DisableGravatar = setting.DisableGravatar
-	f.EnableFederatedAvatar = setting.EnableFederatedAvatar
-	f.DisableRegistration = setting.Service.DisableRegistration
-	f.EnableCaptcha = setting.Service.EnableCaptcha
-	f.RequireSignInView = setting.Service.RequireSignInView
+	f.OfflineMode = conf.Server.OfflineMode
+	f.DisableGravatar = conf.DisableGravatar
+	f.EnableFederatedAvatar = conf.EnableFederatedAvatar
+	f.DisableRegistration = conf.Service.DisableRegistration
+	f.EnableCaptcha = conf.Service.EnableCaptcha
+	f.RequireSignInView = conf.Service.RequireSignInView
 
 	form.Assign(f, c.Data)
 	c.Success(INSTALL)
@@ -251,7 +261,7 @@ func InstallPost(c *context.Context, f form.Install) {
 		return
 	}
 
-	currentUser, match := setting.IsRunUserMatchCurrentUser(f.RunUser)
+	currentUser, match := conf.IsRunUserMatchCurrentUser(f.RunUser)
 	if !match {
 		c.FormErr("RunUser")
 		c.RenderWithErr(c.Tr("install.run_user_not_match", f.RunUser, currentUser), INSTALL, &f)
@@ -300,10 +310,10 @@ func InstallPost(c *context.Context, f form.Install) {
 
 	// Save settings.
 	cfg := ini.Empty()
-	if com.IsFile(setting.CustomConf) {
+	if osutil.IsFile(conf.CustomConf) {
 		// Keeps custom settings if there is already something.
-		if err := cfg.Append(setting.CustomConf); err != nil {
-			log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
+		if err := cfg.Append(conf.CustomConf); err != nil {
+			log.Error("Failed to load custom conf %q: %v", conf.CustomConf, err)
 		}
 	}
 	cfg.Section("database").Key("DB_TYPE").SetValue(db.DbCfg.Type)
@@ -368,13 +378,18 @@ func InstallPost(c *context.Context, f form.Install) {
 	}
 	cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey)
 
-	os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm)
-	if err := cfg.SaveTo(setting.CustomConf); err != nil {
+	_ = os.MkdirAll(filepath.Dir(conf.CustomConf), os.ModePerm)
+	if err := cfg.SaveTo(conf.CustomConf); err != nil {
 		c.RenderWithErr(c.Tr("install.save_config_failed", err), INSTALL, &f)
 		return
 	}
 
-	GlobalInit()
+	// NOTE: We reuse the current value because this handler does not have access to CLI flags.
+	err = GlobalInit(conf.CustomConf)
+	if err != nil {
+		c.RenderWithErr(c.Tr("install.init_failed", err), INSTALL, &f)
+		return
+	}
 
 	// Create admin account
 	if len(f.AdminName) > 0 {
@@ -387,7 +402,7 @@ func InstallPost(c *context.Context, f form.Install) {
 		}
 		if err := db.CreateUser(u); err != nil {
 			if !db.IsErrUserAlreadyExist(err) {
-				setting.InstallLock = false
+				conf.InstallLock = false
 				c.FormErr("AdminName", "AdminEmail")
 				c.RenderWithErr(c.Tr("install.invalid_admin_setting", err), INSTALL, &f)
 				return

+ 2 - 2
internal/route/org/members.go

@@ -8,10 +8,10 @@ import (
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -87,7 +87,7 @@ func MembersAction(c *context.Context) {
 	if c.Params(":action") != "leave" {
 		c.Redirect(c.Org.OrgLink + "/members")
 	} else {
-		c.Redirect(setting.AppSubURL + "/")
+		c.Redirect(conf.Server.Subpath + "/")
 	}
 }
 

+ 2 - 2
internal/route/org/org.go

@@ -7,10 +7,10 @@ package org
 import (
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -52,5 +52,5 @@ func CreatePost(c *context.Context, f form.CreateOrg) {
 	}
 	log.Trace("Organization created: %s", org.Name)
 
-	c.Redirect(setting.AppSubURL + "/org/" + f.OrgName + "/dashboard")
+	c.Redirect(conf.Server.Subpath + "/org/" + f.OrgName + "/dashboard")
 }

+ 4 - 4
internal/route/org/setting.go

@@ -9,12 +9,12 @@ import (
 
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/route/user"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -63,7 +63,7 @@ func SettingsPost(c *context.Context, f form.UpdateOrgSetting) {
 			return
 		}
 		// reset c.org.OrgLink with new name
-		c.Org.OrgLink = setting.AppSubURL + "/org/" + f.Name
+		c.Org.OrgLink = conf.Server.Subpath + "/org/" + f.Name
 		log.Trace("Organization name changed: %s -> %s", org.Name, f.Name)
 	}
 	// In case it's just a case change.
@@ -130,7 +130,7 @@ func SettingsDelete(c *context.Context) {
 			}
 		} else {
 			log.Trace("Organization deleted: %s", org.Name)
-			c.Redirect(setting.AppSubURL + "/")
+			c.Redirect(conf.Server.Subpath + "/")
 		}
 		return
 	}
@@ -143,7 +143,7 @@ func Webhooks(c *context.Context) {
 	c.Data["PageIsSettingsHooks"] = true
 	c.Data["BaseLink"] = c.Org.OrgLink
 	c.Data["Description"] = c.Tr("org.settings.hooks_desc")
-	c.Data["Types"] = setting.Webhook.Types
+	c.Data["Types"] = conf.Webhook.Types
 
 	ws, err := db.GetWebhooksByOrgID(c.Org.Organization.ID)
 	if err != nil {

+ 11 - 11
internal/route/repo/commit.go

@@ -10,9 +10,9 @@ import (
 
 	"github.com/gogs/git-module"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -135,8 +135,8 @@ func Diff(c *context.Context) {
 	}
 
 	diff, err := db.GetDiffCommit(db.RepoPath(userName, repoName),
-		commitID, setting.Git.MaxGitDiffLines,
-		setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
+		commitID, conf.Git.MaxGitDiffLines,
+		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
 	if err != nil {
 		c.NotFoundOrServerError("get diff commit", git.IsErrNotExist, err)
 		return
@@ -168,11 +168,11 @@ func Diff(c *context.Context) {
 	c.Data["Diff"] = diff
 	c.Data["Parents"] = parents
 	c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
-	c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", commitID)
+	c.Data["SourcePath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "src", commitID)
 	if commit.ParentCount() > 0 {
-		c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", parents[0])
+		c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "src", parents[0])
 	}
-	c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", commitID)
+	c.Data["RawPath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "raw", commitID)
 	c.Success(DIFF)
 }
 
@@ -202,8 +202,8 @@ func CompareDiff(c *context.Context) {
 	}
 
 	diff, err := db.GetDiffRange(db.RepoPath(userName, repoName), beforeCommitID,
-		afterCommitID, setting.Git.MaxGitDiffLines,
-		setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
+		afterCommitID, conf.Git.MaxGitDiffLines,
+		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
 	if err != nil {
 		c.Handle(404, "GetDiffRange", err)
 		return
@@ -229,8 +229,8 @@ func CompareDiff(c *context.Context) {
 	c.Data["Commit"] = commit
 	c.Data["Diff"] = diff
 	c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
-	c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", afterCommitID)
-	c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", beforeCommitID)
-	c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", afterCommitID)
+	c.Data["SourcePath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "src", afterCommitID)
+	c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "src", beforeCommitID)
+	c.Data["RawPath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "raw", afterCommitID)
 	c.HTML(200, DIFF)
 }

+ 2 - 2
internal/route/repo/download.go

@@ -13,7 +13,7 @@ import (
 	"github.com/gogs/git-module"
 
 	"gogs.io/gogs/internal/context"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -35,7 +35,7 @@ func serveData(c *context.Context, name string, r io.Reader) error {
 			c.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
 			c.Resp.Header().Set("Content-Transfer-Encoding", "binary")
 		}
-	} else if !setting.Repository.EnableRawFileRenderMode || !c.QueryBool("render") {
+	} else if !conf.Repository.EnableRawFileRenderMode || !c.QueryBool("render") {
 		c.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
 	}
 

+ 13 - 13
internal/route/repo/editor.go

@@ -14,12 +14,12 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"github.com/gogs/git-module"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/pathutil"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/template"
 	"gogs.io/gogs/internal/tool"
 )
@@ -110,10 +110,10 @@ func editFile(c *context.Context, isNewFile bool) {
 	c.Data["commit_choice"] = "direct"
 	c.Data["new_branch_name"] = ""
 	c.Data["last_commit"] = c.Repo.Commit.ID
-	c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
-	c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
-	c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
-	c.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, c.Repo.Repository.FullName())
+	c.Data["MarkdownFileExts"] = strings.Join(conf.Markdown.FileExtensions, ",")
+	c.Data["LineWrapExtensions"] = strings.Join(conf.Repository.Editor.LineWrapExtensions, ",")
+	c.Data["PreviewableFileModes"] = strings.Join(conf.Repository.Editor.PreviewableFileModes, ",")
+	c.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", conf.Server.Subpath, c.Repo.Repository.FullName())
 
 	c.Success(EDIT_FILE)
 }
@@ -156,9 +156,9 @@ func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
 	c.Data["commit_choice"] = f.CommitChoice
 	c.Data["new_branch_name"] = branchName
 	c.Data["last_commit"] = f.LastCommit
-	c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
-	c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
-	c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
+	c.Data["MarkdownFileExts"] = strings.Join(conf.Markdown.FileExtensions, ",")
+	c.Data["LineWrapExtensions"] = strings.Join(conf.Repository.Editor.LineWrapExtensions, ",")
+	c.Data["PreviewableFileModes"] = strings.Join(conf.Repository.Editor.PreviewableFileModes, ",")
 
 	if c.HasError() {
 		c.Success(EDIT_FILE)
@@ -400,9 +400,9 @@ func DeleteFilePost(c *context.Context, f form.DeleteRepoFile) {
 
 func renderUploadSettings(c *context.Context) {
 	c.RequireDropzone()
-	c.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",")
-	c.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
-	c.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
+	c.Data["UploadAllowedTypes"] = strings.Join(conf.Repository.Upload.AllowedTypes, ",")
+	c.Data["UploadMaxSize"] = conf.Repository.Upload.FileMaxSize
+	c.Data["UploadMaxFiles"] = conf.Repository.Upload.MaxFiles
 }
 
 func UploadFile(c *context.Context) {
@@ -533,9 +533,9 @@ func UploadFileToServer(c *context.Context) {
 	}
 	fileType := http.DetectContentType(buf)
 
-	if len(setting.Repository.Upload.AllowedTypes) > 0 {
+	if len(conf.Repository.Upload.AllowedTypes) > 0 {
 		allowed := false
-		for _, t := range setting.Repository.Upload.AllowedTypes {
+		for _, t := range conf.Repository.Upload.AllowedTypes {
 			t := strings.Trim(t, " ")
 			if t == "*/*" || t == fileType {
 				allowed = true

+ 6 - 6
internal/route/repo/http.go

@@ -23,7 +23,7 @@ import (
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/lazyregexp"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -44,9 +44,9 @@ func askCredentials(c *context.Context, status int, text string) {
 
 func HTTPContexter() macaron.Handler {
 	return func(c *context.Context) {
-		if len(setting.HTTP.AccessControlAllowOrigin) > 0 {
+		if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
 			// Set CORS headers for browser-based git clients
-			c.Resp.Header().Set("Access-Control-Allow-Origin", setting.HTTP.AccessControlAllowOrigin)
+			c.Resp.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
 			c.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
 
 			// Handle preflight OPTIONS request
@@ -77,7 +77,7 @@ func HTTPContexter() macaron.Handler {
 		}
 
 		// Authentication is not required for pulling from public repositories.
-		if isPull && !repo.IsPrivate && !setting.Service.RequireSignInView {
+		if isPull && !repo.IsPrivate && !conf.Service.RequireSignInView {
 			c.Map(&HTTPContext{
 				Context: c,
 			})
@@ -368,7 +368,7 @@ func getGitRepoPath(dir string) (string, error) {
 		dir += ".git"
 	}
 
-	filename := path.Join(setting.RepoRootPath, dir)
+	filename := path.Join(conf.RepoRootPath, dir)
 	if _, err := os.Stat(filename); os.IsNotExist(err) {
 		return "", err
 	}
@@ -387,7 +387,7 @@ func HTTP(c *HTTPContext) {
 		// We perform check here because route matched in cmd/web.go is wider than needed,
 		// but we only want to output this message only if user is really trying to access
 		// Git HTTP endpoints.
-		if setting.Repository.DisableHTTPGit {
+		if conf.Repository.DisableHTTPGit {
 			c.HandleText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is not disabled")
 			return
 		}

+ 18 - 18
internal/route/repo/issue.go

@@ -17,12 +17,12 @@ import (
 	"github.com/unknwon/paginater"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -116,8 +116,8 @@ func issues(c *context.Context, isPullList bool) {
 
 	// Must sign in to see issues about you.
 	if viewType != "all" && !c.IsLogged {
-		c.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL)
-		c.Redirect(setting.AppSubURL + "/user/login")
+		c.SetCookie("redirect_to", "/"+url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
+		c.Redirect(conf.Server.Subpath + "/user/login")
 		return
 	}
 
@@ -167,7 +167,7 @@ func issues(c *context.Context, isPullList bool) {
 	} else {
 		total = int(issueStats.ClosedCount)
 	}
-	pager := paginater.New(total, setting.UI.IssuePagingNum, page, 5)
+	pager := paginater.New(total, conf.UI.IssuePagingNum, page, 5)
 	c.Data["Page"] = pager
 
 	issues, err := db.Issues(&db.IssuesOptions{
@@ -256,10 +256,10 @@ func Pulls(c *context.Context) {
 
 func renderAttachmentSettings(c *context.Context) {
 	c.Data["RequireDropzone"] = true
-	c.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled
-	c.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes
-	c.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize
-	c.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles
+	c.Data["IsAttachmentEnabled"] = conf.AttachmentEnabled
+	c.Data["AttachmentAllowedTypes"] = conf.AttachmentAllowedTypes
+	c.Data["AttachmentMaxSize"] = conf.AttachmentMaxSize
+	c.Data["AttachmentMaxFiles"] = conf.AttachmentMaxFiles
 }
 
 func RetrieveRepoMilestonesAndAssignees(c *context.Context, repo *db.Repository) {
@@ -429,7 +429,7 @@ func NewIssuePost(c *context.Context, f form.NewIssue) {
 	}
 
 	var attachments []string
-	if setting.AttachmentEnabled {
+	if conf.AttachmentEnabled {
 		attachments = f.Files
 	}
 
@@ -493,12 +493,12 @@ func uploadAttachment(c *context.Context, allowedTypes []string) {
 }
 
 func UploadIssueAttachment(c *context.Context) {
-	if !setting.AttachmentEnabled {
+	if !conf.AttachmentEnabled {
 		c.NotFound()
 		return
 	}
 
-	uploadAttachment(c, strings.Split(setting.AttachmentAllowedTypes, ","))
+	uploadAttachment(c, strings.Split(conf.AttachmentAllowedTypes, ","))
 }
 
 func viewIssue(c *context.Context, isPullList bool) {
@@ -669,7 +669,7 @@ func viewIssue(c *context.Context, isPullList bool) {
 	c.Data["NumParticipants"] = len(participants)
 	c.Data["Issue"] = issue
 	c.Data["IsIssueOwner"] = c.Repo.IsWriter() || (c.IsLogged && issue.IsPoster(c.User.ID))
-	c.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + c.Data["Link"].(string)
+	c.Data["SignInLink"] = conf.Server.Subpath + "/user/login?redirect_to=" + c.Data["Link"].(string)
 	c.HTML(200, ISSUE_VIEW)
 }
 
@@ -845,7 +845,7 @@ func NewComment(c *context.Context, f form.CreateComment) {
 	}
 
 	var attachments []string
-	if setting.AttachmentEnabled {
+	if conf.AttachmentEnabled {
 		attachments = f.Files
 	}
 
@@ -1098,7 +1098,7 @@ func Milestones(c *context.Context) {
 	} else {
 		total = int(closedCount)
 	}
-	c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
+	c.Data["Page"] = paginater.New(total, conf.UI.IssuePagingNum, page, 5)
 
 	miles, err := db.GetMilestones(c.Repo.Repository.ID, page, isShowClosed)
 	if err != nil {
@@ -1130,7 +1130,7 @@ func NewMilestone(c *context.Context) {
 	c.Data["PageIsIssueList"] = true
 	c.Data["PageIsMilestones"] = true
 	c.Data["RequireDatetimepicker"] = true
-	c.Data["DateLang"] = setting.DateLang(c.Locale.Language())
+	c.Data["DateLang"] = conf.DateLang(c.Locale.Language())
 	c.HTML(200, MILESTONE_NEW)
 }
 
@@ -1139,7 +1139,7 @@ func NewMilestonePost(c *context.Context, f form.CreateMilestone) {
 	c.Data["PageIsIssueList"] = true
 	c.Data["PageIsMilestones"] = true
 	c.Data["RequireDatetimepicker"] = true
-	c.Data["DateLang"] = setting.DateLang(c.Locale.Language())
+	c.Data["DateLang"] = conf.DateLang(c.Locale.Language())
 
 	if c.HasError() {
 		c.HTML(200, MILESTONE_NEW)
@@ -1175,7 +1175,7 @@ func EditMilestone(c *context.Context) {
 	c.Data["PageIsMilestones"] = true
 	c.Data["PageIsEditMilestone"] = true
 	c.Data["RequireDatetimepicker"] = true
-	c.Data["DateLang"] = setting.DateLang(c.Locale.Language())
+	c.Data["DateLang"] = conf.DateLang(c.Locale.Language())
 
 	m, err := db.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
@@ -1199,7 +1199,7 @@ func EditMilestonePost(c *context.Context, f form.CreateMilestone) {
 	c.Data["PageIsMilestones"] = true
 	c.Data["PageIsEditMilestone"] = true
 	c.Data["RequireDatetimepicker"] = true
-	c.Data["DateLang"] = setting.DateLang(c.Locale.Language())
+	c.Data["DateLang"] = conf.DateLang(c.Locale.Language())
 
 	if c.HasError() {
 		c.HTML(200, MILESTONE_NEW)

+ 12 - 12
internal/route/repo/pull.go

@@ -14,11 +14,11 @@ import (
 
 	"github.com/gogs/git-module"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -354,8 +354,8 @@ func ViewPullFiles(c *context.Context) {
 	}
 
 	diff, err := db.GetDiffRange(diffRepoPath,
-		startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
-		setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
+		startCommitID, endCommitID, conf.Git.MaxGitDiffLines,
+		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
 	if err != nil {
 		c.ServerError("GetDiffRange", err)
 		return
@@ -383,9 +383,9 @@ func ViewPullFiles(c *context.Context) {
 		c.Data["Reponame"] = pull.HeadRepo.Name
 
 		headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name)
-		c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", endCommitID)
-		c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", startCommitID)
-		c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", endCommitID)
+		c.Data["SourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", endCommitID)
+		c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", startCommitID)
+		c.Data["RawPath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "raw", endCommitID)
 	}
 
 	c.Data["RequireHighlightJS"] = true
@@ -570,8 +570,8 @@ func PrepareCompareDiff(
 	}
 
 	diff, err := db.GetDiffRange(db.RepoPath(headUser.Name, headRepo.Name),
-		prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines,
-		setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
+		prInfo.MergeBase, headCommitID, conf.Git.MaxGitDiffLines,
+		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
 	if err != nil {
 		c.ServerError("GetDiffRange", err)
 		return false
@@ -593,9 +593,9 @@ func PrepareCompareDiff(
 	c.Data["IsImageFile"] = headCommit.IsImageFile
 
 	headTarget := path.Join(headUser.Name, repo.Name)
-	c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", headCommitID)
-	c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", prInfo.MergeBase)
-	c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", headCommitID)
+	c.Data["SourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", headCommitID)
+	c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", prInfo.MergeBase)
+	c.Data["RawPath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "raw", headCommitID)
 	return false
 }
 
@@ -677,7 +677,7 @@ func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) {
 		return
 	}
 
-	if setting.AttachmentEnabled {
+	if conf.AttachmentEnabled {
 		attachments = f.Files
 	}
 

+ 9 - 9
internal/route/repo/release.go

@@ -14,7 +14,7 @@ import (
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 const (
@@ -151,10 +151,10 @@ func Releases(c *context.Context) {
 
 func renderReleaseAttachmentSettings(c *context.Context) {
 	c.Data["RequireDropzone"] = true
-	c.Data["IsAttachmentEnabled"] = setting.Release.Attachment.Enabled
-	c.Data["AttachmentAllowedTypes"] = strings.Join(setting.Release.Attachment.AllowedTypes, ",")
-	c.Data["AttachmentMaxSize"] = setting.Release.Attachment.MaxSize
-	c.Data["AttachmentMaxFiles"] = setting.Release.Attachment.MaxFiles
+	c.Data["IsAttachmentEnabled"] = conf.Release.Attachment.Enabled
+	c.Data["AttachmentAllowedTypes"] = strings.Join(conf.Release.Attachment.AllowedTypes, ",")
+	c.Data["AttachmentMaxSize"] = conf.Release.Attachment.MaxSize
+	c.Data["AttachmentMaxFiles"] = conf.Release.Attachment.MaxFiles
 }
 
 func NewRelease(c *context.Context) {
@@ -203,7 +203,7 @@ func NewReleasePost(c *context.Context, f form.NewRelease) {
 	}
 
 	var attachments []string
-	if setting.Release.Attachment.Enabled {
+	if conf.Release.Attachment.Enabled {
 		attachments = f.Files
 	}
 
@@ -295,7 +295,7 @@ func EditReleasePost(c *context.Context, f form.EditRelease) {
 	}
 
 	var attachments []string
-	if setting.Release.Attachment.Enabled {
+	if conf.Release.Attachment.Enabled {
 		attachments = f.Files
 	}
 
@@ -312,11 +312,11 @@ func EditReleasePost(c *context.Context, f form.EditRelease) {
 }
 
 func UploadReleaseAttachment(c *context.Context) {
-	if !setting.Release.Attachment.Enabled {
+	if !conf.Release.Attachment.Enabled {
 		c.NotFound()
 		return
 	}
-	uploadAttachment(c, setting.Release.Attachment.AllowedTypes)
+	uploadAttachment(c, conf.Release.Attachment.AllowedTypes)
 }
 
 func DeleteRelease(c *context.Context) {

+ 7 - 7
internal/route/repo/repo.go

@@ -15,11 +15,11 @@ import (
 
 	"github.com/gogs/git-module"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -75,7 +75,7 @@ func Create(c *context.Context) {
 	c.Data["Readmes"] = db.Readmes
 	c.Data["readme"] = "Default"
 	c.Data["private"] = c.User.LastRepoVisibility
-	c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
+	c.Data["IsForcedPrivate"] = conf.Repository.ForcePrivate
 
 	ctxUser := checkContextUser(c, c.QueryInt64("org"))
 	if c.Written() {
@@ -128,12 +128,12 @@ func CreatePost(c *context.Context, f form.CreateRepo) {
 		Gitignores:  f.Gitignores,
 		License:     f.License,
 		Readme:      f.Readme,
-		IsPrivate:   f.Private || setting.Repository.ForcePrivate,
+		IsPrivate:   f.Private || conf.Repository.ForcePrivate,
 		AutoInit:    f.AutoInit,
 	})
 	if err == nil {
 		log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
-		c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
+		c.Redirect(conf.Server.Subpath + "/" + ctxUser.Name + "/" + repo.Name)
 		return
 	}
 
@@ -149,7 +149,7 @@ func CreatePost(c *context.Context, f form.CreateRepo) {
 func Migrate(c *context.Context) {
 	c.Data["Title"] = c.Tr("new_migrate")
 	c.Data["private"] = c.User.LastRepoVisibility
-	c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
+	c.Data["IsForcedPrivate"] = conf.Repository.ForcePrivate
 	c.Data["mirror"] = c.Query("mirror") == "1"
 
 	ctxUser := checkContextUser(c, c.QueryInt64("org"))
@@ -199,13 +199,13 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
 	repo, err := db.MigrateRepository(c.User, ctxUser, db.MigrateRepoOptions{
 		Name:        f.RepoName,
 		Description: f.Description,
-		IsPrivate:   f.Private || setting.Repository.ForcePrivate,
+		IsPrivate:   f.Private || conf.Repository.ForcePrivate,
 		IsMirror:    f.Mirror,
 		RemoteAddr:  remoteAddr,
 	})
 	if err == nil {
 		log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, f.RepoName)
-		c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + f.RepoName)
+		c.Redirect(conf.Server.Subpath + "/" + ctxUser.Name + "/" + f.RepoName)
 		return
 	}
 

+ 8 - 8
internal/route/repo/setting.go

@@ -14,12 +14,12 @@ import (
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/mailer"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -196,7 +196,7 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
 		}
 		log.Trace("Repository converted from mirror to regular: %s/%s", c.Repo.Owner.Name, repo.Name)
 		c.Flash.Success(c.Tr("repo.settings.convert_succeed"))
-		c.Redirect(setting.AppSubURL + "/" + c.Repo.Owner.Name + "/" + repo.Name)
+		c.Redirect(conf.Server.Subpath + "/" + c.Repo.Owner.Name + "/" + repo.Name)
 
 	case "transfer":
 		if !c.Repo.IsOwner() {
@@ -235,7 +235,7 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
 		}
 		log.Trace("Repository transfered: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newOwner)
 		c.Flash.Success(c.Tr("repo.settings.transfer_succeed"))
-		c.Redirect(setting.AppSubURL + "/" + newOwner + "/" + repo.Name)
+		c.Redirect(conf.Server.Subpath + "/" + newOwner + "/" + repo.Name)
 
 	case "delete":
 		if !c.Repo.IsOwner() {
@@ -371,7 +371,7 @@ func SettingsCollaboration(c *context.Context) {
 func SettingsCollaborationPost(c *context.Context) {
 	name := strings.ToLower(c.Query("collaborator"))
 	if len(name) == 0 || c.Repo.Owner.LowerName == name {
-		c.Redirect(setting.AppSubURL + c.Req.URL.Path)
+		c.Redirect(conf.Server.Subpath + c.Req.URL.Path)
 		return
 	}
 
@@ -379,7 +379,7 @@ func SettingsCollaborationPost(c *context.Context) {
 	if err != nil {
 		if errors.IsUserNotExist(err) {
 			c.Flash.Error(c.Tr("form.user_not_exist"))
-			c.Redirect(setting.AppSubURL + c.Req.URL.Path)
+			c.Redirect(conf.Server.Subpath + c.Req.URL.Path)
 		} else {
 			c.Handle(500, "GetUserByName", err)
 		}
@@ -389,7 +389,7 @@ func SettingsCollaborationPost(c *context.Context) {
 	// Organization is not allowed to be added as a collaborator
 	if u.IsOrganization() {
 		c.Flash.Error(c.Tr("repo.settings.org_not_allowed_to_be_collaborator"))
-		c.Redirect(setting.AppSubURL + c.Req.URL.Path)
+		c.Redirect(conf.Server.Subpath + c.Req.URL.Path)
 		return
 	}
 
@@ -398,12 +398,12 @@ func SettingsCollaborationPost(c *context.Context) {
 		return
 	}
 
-	if setting.Service.EnableNotifyMail {
+	if conf.Service.EnableNotifyMail {
 		mailer.SendCollaboratorMail(db.NewMailerUser(u), db.NewMailerUser(c.User), db.NewMailerRepo(c.Repo.Repository))
 	}
 
 	c.Flash.Success(c.Tr("repo.settings.add_collaborator_success"))
-	c.Redirect(setting.AppSubURL + c.Req.URL.Path)
+	c.Redirect(conf.Server.Subpath + c.Req.URL.Path)
 }
 
 func ChangeCollaborationAccessMode(c *context.Context) {

+ 4 - 4
internal/route/repo/view.go

@@ -20,7 +20,7 @@ import (
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/template"
 	"gogs.io/gogs/internal/template/highlight"
 	"gogs.io/gogs/internal/tool"
@@ -47,7 +47,7 @@ func renderDirectory(c *context.Context, treeLink string) {
 	}
 	entries.Sort()
 
-	c.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(c.Repo.Commit, c.Repo.TreePath, setting.Repository.CommitsFetchConcurrency)
+	c.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(c.Repo.Commit, c.Repo.TreePath, conf.Repository.CommitsFetchConcurrency)
 	if err != nil {
 		c.ServerError("GetCommitsInfoWithCustomConcurrency", err)
 		return
@@ -118,7 +118,7 @@ func renderDirectory(c *context.Context, treeLink string) {
 
 	if c.Repo.CanEnableEditor() {
 		c.Data["CanAddFile"] = true
-		c.Data["CanUploadFile"] = setting.Repository.Upload.Enabled
+		c.Data["CanUploadFile"] = conf.Repository.Upload.Enabled
 	}
 }
 
@@ -152,7 +152,7 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 	canEnableEditor := c.Repo.CanEnableEditor()
 	switch {
 	case isTextFile:
-		if blob.Size() >= setting.UI.MaxDisplayFileSize {
+		if blob.Size() >= conf.UI.MaxDisplayFileSize {
 			c.Data["IsFileTooLarge"] = true
 			break
 		}

+ 3 - 3
internal/route/repo/webhook.go

@@ -18,7 +18,7 @@ import (
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 const (
@@ -32,7 +32,7 @@ func Webhooks(c *context.Context) {
 	c.Data["PageIsSettingsHooks"] = true
 	c.Data["BaseLink"] = c.Repo.RepoLink
 	c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://github.com/gogs/docs-api/blob/master/Repositories/Webhooks.md")
-	c.Data["Types"] = setting.Webhook.Types
+	c.Data["Types"] = conf.Webhook.Types
 
 	ws, err := db.GetWebhooksByRepoID(c.Repo.Repository.ID)
 	if err != nil {
@@ -76,7 +76,7 @@ func getOrgRepoCtx(c *context.Context) (*OrgRepoCtx, error) {
 
 func checkHookType(c *context.Context) string {
 	hookType := strings.ToLower(c.Params(":type"))
-	if !com.IsSliceContainsStr(setting.Webhook.Types, hookType) {
+	if !com.IsSliceContainsStr(conf.Webhook.Types, hookType) {
 		c.Handle(404, "checkHookType", nil)
 		return ""
 	}

+ 35 - 35
internal/route/user/auth.go

@@ -11,12 +11,12 @@ import (
 	"github.com/go-macaron/captcha"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/mailer"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -36,7 +36,7 @@ func AutoLogin(c *context.Context) (bool, error) {
 		return false, nil
 	}
 
-	uname := c.GetCookie(setting.CookieUserName)
+	uname := c.GetCookie(conf.CookieUserName)
 	if len(uname) == 0 {
 		return false, nil
 	}
@@ -45,9 +45,9 @@ func AutoLogin(c *context.Context) (bool, error) {
 	defer func() {
 		if !isSucceed {
 			log.Trace("auto-login cookie cleared: %s", uname)
-			c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL)
-			c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL)
-			c.SetCookie(setting.LoginStatusCookieName, "", -1, setting.AppSubURL)
+			c.SetCookie(conf.CookieUserName, "", -1, conf.Server.Subpath)
+			c.SetCookie(conf.CookieRememberName, "", -1, conf.Server.Subpath)
+			c.SetCookie(conf.LoginStatusCookieName, "", -1, conf.Server.Subpath)
 		}
 	}()
 
@@ -59,16 +59,16 @@ func AutoLogin(c *context.Context) (bool, error) {
 		return false, nil
 	}
 
-	if val, ok := c.GetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName); !ok || val != u.Name {
+	if val, ok := c.GetSuperSecureCookie(u.Rands+u.Passwd, conf.CookieRememberName); !ok || val != u.Name {
 		return false, nil
 	}
 
 	isSucceed = true
 	c.Session.Set("uid", u.ID)
 	c.Session.Set("uname", u.Name)
-	c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
-	if setting.EnableLoginStatusCookie {
-		c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL)
+	c.SetCookie(conf.CSRFCookieName, "", -1, conf.Server.Subpath)
+	if conf.EnableLoginStatusCookie {
+		c.SetCookie(conf.LoginStatusCookieName, "true", 0, conf.Server.Subpath)
 	}
 	return true, nil
 }
@@ -85,7 +85,7 @@ func Login(c *context.Context) {
 
 	redirectTo := c.Query("redirect_to")
 	if len(redirectTo) > 0 {
-		c.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL)
+		c.SetCookie("redirect_to", redirectTo, 0, conf.Server.Subpath)
 	} else {
 		redirectTo, _ = url.QueryUnescape(c.GetCookie("redirect_to"))
 	}
@@ -96,7 +96,7 @@ func Login(c *context.Context) {
 		} else {
 			c.SubURLRedirect("/")
 		}
-		c.SetCookie("redirect_to", "", -1, setting.AppSubURL)
+		c.SetCookie("redirect_to", "", -1, conf.Server.Subpath)
 		return
 	}
 
@@ -119,9 +119,9 @@ func Login(c *context.Context) {
 
 func afterLogin(c *context.Context, u *db.User, remember bool) {
 	if remember {
-		days := 86400 * setting.LoginRememberDays
-		c.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true)
-		c.SetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true)
+		days := 86400 * conf.LoginRememberDays
+		c.SetCookie(conf.CookieUserName, u.Name, days, conf.Server.Subpath, "", conf.CookieSecure, true)
+		c.SetSuperSecureCookie(u.Rands+u.Passwd, conf.CookieRememberName, u.Name, days, conf.Server.Subpath, "", conf.CookieSecure, true)
 	}
 
 	c.Session.Set("uid", u.ID)
@@ -130,13 +130,13 @@ func afterLogin(c *context.Context, u *db.User, remember bool) {
 	c.Session.Delete("twoFactorUserID")
 
 	// Clear whatever CSRF has right now, force to generate a new one
-	c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
-	if setting.EnableLoginStatusCookie {
-		c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL)
+	c.SetCookie(conf.CSRFCookieName, "", -1, conf.Server.Subpath)
+	if conf.EnableLoginStatusCookie {
+		c.SetCookie(conf.LoginStatusCookieName, "true", 0, conf.Server.Subpath)
 	}
 
 	redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to"))
-	c.SetCookie("redirect_to", "", -1, setting.AppSubURL)
+	c.SetCookie("redirect_to", "", -1, conf.Server.Subpath)
 	if tool.IsSameSiteURLPath(redirectTo) {
 		c.Redirect(redirectTo)
 		return
@@ -283,18 +283,18 @@ func LoginTwoFactorRecoveryCodePost(c *context.Context) {
 func SignOut(c *context.Context) {
 	c.Session.Flush()
 	c.Session.Destory(c.Context)
-	c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL)
-	c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL)
-	c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
+	c.SetCookie(conf.CookieUserName, "", -1, conf.Server.Subpath)
+	c.SetCookie(conf.CookieRememberName, "", -1, conf.Server.Subpath)
+	c.SetCookie(conf.CSRFCookieName, "", -1, conf.Server.Subpath)
 	c.SubURLRedirect("/")
 }
 
 func SignUp(c *context.Context) {
 	c.Title("sign_up")
 
-	c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
+	c.Data["EnableCaptcha"] = conf.Service.EnableCaptcha
 
-	if setting.Service.DisableRegistration {
+	if conf.Service.DisableRegistration {
 		c.Data["DisableRegistration"] = true
 		c.Success(SIGNUP)
 		return
@@ -306,9 +306,9 @@ func SignUp(c *context.Context) {
 func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 	c.Title("sign_up")
 
-	c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
+	c.Data["EnableCaptcha"] = conf.Service.EnableCaptcha
 
-	if setting.Service.DisableRegistration {
+	if conf.Service.DisableRegistration {
 		c.Status(403)
 		return
 	}
@@ -318,7 +318,7 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 		return
 	}
 
-	if setting.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) {
+	if conf.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) {
 		c.FormErr("Captcha")
 		c.RenderWithErr(c.Tr("form.captcha_incorrect"), SIGNUP, &f)
 		return
@@ -334,7 +334,7 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 		Name:     f.UserName,
 		Email:    f.Email,
 		Passwd:   f.Password,
-		IsActive: !setting.Service.RegisterEmailConfirm,
+		IsActive: !conf.Service.RegisterEmailConfirm,
 	}
 	if err := db.CreateUser(u); err != nil {
 		switch {
@@ -368,11 +368,11 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 	}
 
 	// Send confirmation email, no need for social account.
-	if setting.Service.RegisterEmailConfirm && u.ID > 1 {
+	if conf.Service.RegisterEmailConfirm && u.ID > 1 {
 		mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(u))
 		c.Data["IsSendRegisterMail"] = true
 		c.Data["Email"] = u.Email
-		c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
+		c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
 		c.Success(ACTIVATE)
 
 		if err := c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil {
@@ -393,11 +393,11 @@ func Activate(c *context.Context) {
 			return
 		}
 		// Resend confirmation email.
-		if setting.Service.RegisterEmailConfirm {
+		if conf.Service.RegisterEmailConfirm {
 			if c.Cache.IsExist(c.User.MailResendCacheKey()) {
 				c.Data["ResendLimited"] = true
 			} else {
-				c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
+				c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
 				mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(c.User))
 
 				if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil {
@@ -457,7 +457,7 @@ func ActivateEmail(c *context.Context) {
 func ForgotPasswd(c *context.Context) {
 	c.Title("auth.forgot_password")
 
-	if setting.MailService == nil {
+	if conf.MailService == nil {
 		c.Data["IsResetDisable"] = true
 		c.Success(FORGOT_PASSWORD)
 		return
@@ -470,7 +470,7 @@ func ForgotPasswd(c *context.Context) {
 func ForgotPasswdPost(c *context.Context) {
 	c.Title("auth.forgot_password")
 
-	if setting.MailService == nil {
+	if conf.MailService == nil {
 		c.Status(403)
 		return
 	}
@@ -482,7 +482,7 @@ func ForgotPasswdPost(c *context.Context) {
 	u, err := db.GetUserByEmail(email)
 	if err != nil {
 		if errors.IsUserNotExist(err) {
-			c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
+			c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
 			c.Data["IsResetSent"] = true
 			c.Success(FORGOT_PASSWORD)
 			return
@@ -509,7 +509,7 @@ func ForgotPasswdPost(c *context.Context) {
 		log.Error("Failed to put cache key 'mail resend': %v", err)
 	}
 
-	c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
+	c.Data["Hours"] = conf.Service.ActiveCodeLives / 60
 	c.Data["IsResetSent"] = true
 	c.Success(FORGOT_PASSWORD)
 }

+ 10 - 10
internal/route/user/home.go

@@ -11,10 +11,10 @@ import (
 	"github.com/unknwon/com"
 	"github.com/unknwon/paginater"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
-	"gogs.io/gogs/internal/setting"
 )
 
 const (
@@ -110,7 +110,7 @@ func Dashboard(c *context.Context) {
 
 	// Only user can have collaborative repositories.
 	if !ctxUser.IsOrganization() {
-		collaborateRepos, err := c.User.GetAccessibleRepositories(setting.UI.User.RepoPagingNum)
+		collaborateRepos, err := c.User.GetAccessibleRepositories(conf.UI.User.RepoPagingNum)
 		if err != nil {
 			c.Handle(500, "GetAccessibleRepositories", err)
 			return
@@ -125,7 +125,7 @@ func Dashboard(c *context.Context) {
 	var repos, mirrors []*db.Repository
 	var repoCount int64
 	if ctxUser.IsOrganization() {
-		repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, setting.UI.User.RepoPagingNum)
+		repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, conf.UI.User.RepoPagingNum)
 		if err != nil {
 			c.Handle(500, "GetUserRepositories", err)
 			return
@@ -137,7 +137,7 @@ func Dashboard(c *context.Context) {
 			return
 		}
 	} else {
-		if err = ctxUser.GetRepositories(1, setting.UI.User.RepoPagingNum); err != nil {
+		if err = ctxUser.GetRepositories(1, conf.UI.User.RepoPagingNum); err != nil {
 			c.Handle(500, "GetRepositories", err)
 			return
 		}
@@ -152,7 +152,7 @@ func Dashboard(c *context.Context) {
 	}
 	c.Data["Repos"] = repos
 	c.Data["RepoCount"] = repoCount
-	c.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum
+	c.Data["MaxShowRepoNum"] = conf.UI.User.RepoPagingNum
 
 	if err := db.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil {
 		c.Handle(500, "MirrorRepositoryList.LoadAttributes", err)
@@ -328,7 +328,7 @@ func Issues(c *context.Context) {
 
 	c.Data["Issues"] = issues
 	c.Data["Repos"] = showRepos
-	c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
+	c.Data["Page"] = paginater.New(total, conf.UI.IssuePagingNum, page, 5)
 	c.Data["IssueStats"] = issueStats
 	c.Data["ViewType"] = string(filterMode)
 	c.Data["SortType"] = sortType
@@ -380,7 +380,7 @@ func showOrgProfile(c *context.Context) {
 		err   error
 	)
 	if c.IsLogged && !c.User.IsAdmin {
-		repos, count, err = org.GetUserRepositories(c.User.ID, page, setting.UI.User.RepoPagingNum)
+		repos, count, err = org.GetUserRepositories(c.User.ID, page, conf.UI.User.RepoPagingNum)
 		if err != nil {
 			c.Handle(500, "GetUserRepositories", err)
 			return
@@ -392,7 +392,7 @@ func showOrgProfile(c *context.Context) {
 			UserID:   org.ID,
 			Private:  showPrivate,
 			Page:     page,
-			PageSize: setting.UI.User.RepoPagingNum,
+			PageSize: conf.UI.User.RepoPagingNum,
 		})
 		if err != nil {
 			c.Handle(500, "GetRepositories", err)
@@ -401,7 +401,7 @@ func showOrgProfile(c *context.Context) {
 		c.Data["Repos"] = repos
 		count = db.CountUserRepositories(org.ID, showPrivate)
 	}
-	c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5)
+	c.Data["Page"] = paginater.New(int(count), conf.UI.User.RepoPagingNum, page, 5)
 
 	if err := org.GetMembers(); err != nil {
 		c.Handle(500, "GetMembers", err)
@@ -420,5 +420,5 @@ func Email2User(c *context.Context) {
 		c.NotFoundOrServerError("GetUserByEmail", errors.IsUserNotExist, err)
 		return
 	}
-	c.Redirect(setting.AppSubURL + "/user/" + u.Name)
+	c.Redirect(conf.Server.Subpath + "/user/" + u.Name)
 }

+ 3 - 3
internal/route/user/profile.go

@@ -13,7 +13,7 @@ import (
 
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -70,7 +70,7 @@ func Profile(c *context.Context, puser *context.ParamsUser) {
 			UserID:   puser.ID,
 			Private:  showPrivate,
 			Page:     page,
-			PageSize: setting.UI.User.RepoPagingNum,
+			PageSize: conf.UI.User.RepoPagingNum,
 		})
 		if err != nil {
 			c.ServerError("GetRepositories", err)
@@ -78,7 +78,7 @@ func Profile(c *context.Context, puser *context.ParamsUser) {
 		}
 
 		count := db.CountUserRepositories(puser.ID, showPrivate)
-		c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5)
+		c.Data["Page"] = paginater.New(int(count), conf.UI.User.RepoPagingNum, page, 5)
 	}
 
 	c.Success(PROFILE)

+ 14 - 14
internal/route/user/setting.go

@@ -18,12 +18,12 @@ import (
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/mailer"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -262,7 +262,7 @@ func SettingsEmailPost(c *context.Context, f form.AddEmail) {
 	email := &db.EmailAddress{
 		UID:         c.User.ID,
 		Email:       f.Email,
-		IsActivated: !setting.Service.RegisterEmailConfirm,
+		IsActivated: !conf.Service.RegisterEmailConfirm,
 	}
 	if err := db.AddEmailAddress(email); err != nil {
 		if db.IsErrEmailAlreadyUsed(err) {
@@ -274,13 +274,13 @@ func SettingsEmailPost(c *context.Context, f form.AddEmail) {
 	}
 
 	// Send confirmation email
-	if setting.Service.RegisterEmailConfirm {
+	if conf.Service.RegisterEmailConfirm {
 		mailer.SendActivateEmailMail(c.Context, db.NewMailerUser(c.User), email.Email)
 
 		if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil {
 			log.Error("Set cache 'MailResendLimit' failed: %v", err)
 		}
-		c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60))
+		c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, conf.Service.ActiveCodeLives/60))
 	} else {
 		c.Flash.Success(c.Tr("settings.add_email_success"))
 	}
@@ -299,7 +299,7 @@ func DeleteEmail(c *context.Context) {
 
 	c.Flash.Success(c.Tr("settings.email_deletion_success"))
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/user/settings/email",
+		"redirect": conf.Server.Subpath + "/user/settings/email",
 	})
 }
 
@@ -371,7 +371,7 @@ func DeleteSSHKey(c *context.Context) {
 	}
 
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/user/settings/ssh",
+		"redirect": conf.Server.Subpath + "/user/settings/ssh",
 	})
 }
 
@@ -406,7 +406,7 @@ func SettingsTwoFactorEnable(c *context.Context) {
 	}
 	if key == nil {
 		key, err = totp.Generate(totp.GenerateOpts{
-			Issuer:      setting.AppName,
+			Issuer:      conf.App.BrandName,
 			AccountName: c.User.Email,
 		})
 		if err != nil {
@@ -506,7 +506,7 @@ func SettingsTwoFactorDisable(c *context.Context) {
 
 	c.Flash.Success(c.Tr("settings.two_factor_disable_success"))
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/user/settings/security",
+		"redirect": conf.Server.Subpath + "/user/settings/security",
 	})
 }
 
@@ -542,7 +542,7 @@ func SettingsLeaveRepo(c *context.Context) {
 
 	c.Flash.Success(c.Tr("settings.repos.leave_success", repo.FullName()))
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/user/settings/repositories",
+		"redirect": conf.Server.Subpath + "/user/settings/repositories",
 	})
 }
 
@@ -571,7 +571,7 @@ func SettingsLeaveOrganization(c *context.Context) {
 	}
 
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/user/settings/organizations",
+		"redirect": conf.Server.Subpath + "/user/settings/organizations",
 	})
 }
 
@@ -632,7 +632,7 @@ func SettingsDeleteApplication(c *context.Context) {
 	}
 
 	c.JSONSuccess(map[string]interface{}{
-		"redirect": setting.AppSubURL + "/user/settings/applications",
+		"redirect": conf.Server.Subpath + "/user/settings/applications",
 	})
 }
 
@@ -654,16 +654,16 @@ func SettingsDelete(c *context.Context) {
 			switch {
 			case db.IsErrUserOwnRepos(err):
 				c.Flash.Error(c.Tr("form.still_own_repo"))
-				c.Redirect(setting.AppSubURL + "/user/settings/delete")
+				c.Redirect(conf.Server.Subpath + "/user/settings/delete")
 			case db.IsErrUserHasOrgs(err):
 				c.Flash.Error(c.Tr("form.still_has_org"))
-				c.Redirect(setting.AppSubURL + "/user/settings/delete")
+				c.Redirect(conf.Server.Subpath + "/user/settings/delete")
 			default:
 				c.ServerError("DeleteUser", err)
 			}
 		} else {
 			log.Trace("Account deleted: %s", c.User.Name)
-			c.Redirect(setting.AppSubURL + "/")
+			c.Redirect(conf.Server.Subpath + "/")
 		}
 		return
 	}

+ 0 - 12
internal/setting/computed.go

@@ -1,12 +0,0 @@
-// Copyright 2020 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package setting
-
-var supportWindowsService bool
-
-// SupportWindowsService returns true if running as Windows Service is supported.
-func SupportWindowsService() bool {
-	return supportWindowsService
-}

+ 5 - 5
internal/ssh/ssh.go

@@ -18,8 +18,8 @@ import (
 	"golang.org/x/crypto/ssh"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
-	"gogs.io/gogs/internal/setting"
 )
 
 func cleanCommand(cmd string) string {
@@ -64,9 +64,9 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 					cmdName := strings.TrimLeft(payload, "'()")
 					log.Trace("SSH: Payload: %v", cmdName)
 
-					args := []string{"serv", "key-" + keyID, "--config=" + setting.CustomConf}
+					args := []string{"serv", "key-" + keyID, "--config=" + conf.CustomConf}
 					log.Trace("SSH: Arguments: %v", args)
-					cmd := exec.Command(setting.AppPath, args...)
+					cmd := exec.Command(conf.AppPath(), args...)
 					cmd.Env = append(os.Environ(), "SSH_ORIGINAL_COMMAND="+cmdName)
 
 					stdout, err := cmd.StdoutPipe()
@@ -163,10 +163,10 @@ func Listen(host string, port int, ciphers []string) {
 		},
 	}
 
-	keyPath := filepath.Join(setting.AppDataPath, "ssh/gogs.rsa")
+	keyPath := filepath.Join(conf.Server.AppDataPath, "ssh", "gogs.rsa")
 	if !com.IsExist(keyPath) {
 		os.MkdirAll(filepath.Dir(keyPath), os.ModePerm)
-		_, stderr, err := com.ExecCmd(setting.SSH.KeygenPath, "-f", keyPath, "-t", "rsa", "-m", "PEM", "-N", "")
+		_, stderr, err := com.ExecCmd(conf.SSH.KeygenPath, "-f", keyPath, "-t", "rsa", "-m", "PEM", "-N", "")
 		if err != nil {
 			panic(fmt.Sprintf("Failed to generate private key: %v - %s", err, stderr))
 		}

+ 2 - 2
internal/template/highlight/highlight.go

@@ -8,7 +8,7 @@ import (
 	"path"
 	"strings"
 
-	"gogs.io/gogs/internal/setting"
+	"gogs.io/gogs/internal/conf"
 )
 
 var (
@@ -72,7 +72,7 @@ var (
 )
 
 func NewContext() {
-	keys := setting.Cfg.Section("highlight.mapping").Keys()
+	keys := conf.File.Section("highlight.mapping").Keys()
 	for i := range keys {
 		highlightMapping[keys[i].Name()] = keys[i].Value()
 	}

+ 10 - 10
internal/template/template.go

@@ -21,9 +21,9 @@ import (
 	"golang.org/x/text/transform"
 	log "unknwon.dev/clog/v2"
 
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/markup"
-	"gogs.io/gogs/internal/setting"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -40,28 +40,28 @@ func FuncMap() []template.FuncMap {
 				return time.Now().Year()
 			},
 			"UseHTTPS": func() bool {
-				return strings.HasPrefix(setting.AppURL, "https")
+				return conf.Server.URL.Scheme == "https"
 			},
 			"AppName": func() string {
-				return setting.AppName
+				return conf.App.BrandName
 			},
 			"AppSubURL": func() string {
-				return setting.AppSubURL
+				return conf.Server.Subpath
 			},
 			"AppURL": func() string {
-				return setting.AppURL
+				return conf.Server.ExternalURL
 			},
 			"AppVer": func() string {
-				return setting.AppVersion
+				return conf.App.Version
 			},
 			"AppDomain": func() string {
-				return setting.Domain
+				return conf.Server.Domain
 			},
 			"DisableGravatar": func() bool {
-				return setting.DisableGravatar
+				return conf.DisableGravatar
 			},
 			"ShowFooterTemplateLoadTime": func() bool {
-				return setting.ShowFooterTemplateLoadTime
+				return conf.ShowFooterTemplateLoadTime
 			},
 			"LoadTimes": func(startTime time.Time) string {
 				return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
@@ -111,7 +111,7 @@ func FuncMap() []template.FuncMap {
 			"EscapePound":           EscapePound,
 			"RenderCommitMessage":   RenderCommitMessage,
 			"ThemeColorMetaTag": func() string {
-				return setting.UI.ThemeColorMetaTag
+				return conf.UI.ThemeColorMetaTag
 			},
 			"FilenameIsImage": func(filename string) bool {
 				mimeType := mime.TypeByExtension(filepath.Ext(filename))

Some files were not shown because too many files changed in this diff