add global config struct

This commit is contained in:
wjqserver 2025-06-25 20:02:53 +08:00
parent cc429c44f9
commit cd1e1a42f3
9 changed files with 251 additions and 214 deletions

View file

@ -4,7 +4,7 @@ import (
"caddydash/apic" "caddydash/apic"
"caddydash/config" "caddydash/config"
"caddydash/db" "caddydash/db"
"caddydash/user" "caddydash/gen"
"github.com/infinite-iroha/touka" "github.com/infinite-iroha/touka"
) )
@ -21,40 +21,43 @@ func ApiGroup(v0 touka.IRouter, cdb *db.ConfigDB, cfg *config.Config) {
}) })
// 配置参数相关 // 配置参数相关
api.GET("/config/file/:filename", GetConfig(cdb)) // 读取配置(与写入一致) cfgr := api.Group("/config")
api.PUT("/config/file/:filename", PutConfig(cdb, cfg)) // 写入配置 {
api.DELETE("/config/file/:filename", DeleteConfig(cdb, cfg)) //删除配置 cfgr.GET("/file/:filename", GetConfig(cdb)) // 读取配置(与写入一致)
cfgr.PUT("/file/:filename", PutConfig(cdb, cfg)) // 写入配置
cfgr.DELETE("/file/:filename", DeleteConfig(cdb, cfg)) //删除配置
api.GET("/config/files/params", FilesParams(cdb)) // 获取所有配置, 需进行decode cfgr.GET("/files/params", FilesParams(cdb)) // 获取所有配置, 需进行decode
api.GET("/config/files/templates", FilesTemplates(cdb)) // 获取所有模板 cfgr.GET("/files/templates", FilesTemplates(cdb)) // 获取所有模板
api.GET("/config/files/rendered", FilesRendered(cdb)) // 获取所有渲染产物 cfgr.GET("/files/rendered", FilesRendered(cdb)) // 获取所有渲染产物
api.GET("/config/templates", GetTemplates(cdb)) // 获取可用模板名称 cfgr.GET("/templates", GetTemplates(cdb)) // 获取可用模板名称
api.GET("/config/headers-presets", func(c *touka.Context) { cfgr.GET("/headers-presets", func(c *touka.Context) {
c.JSON(200, GetHeaderSetMetadataList()) c.JSON(200, GetHeaderSetMetadataList())
}) })
cfgr.GET("/headers-presets/:name", GetHeadersPreset())
api.GET("/config/headers-presets/:name", func(c *touka.Context) { glbr := api.Group("/global")
presetName := c.Param("name") {
if presetName == "" { glbr.GET("/log/levels", func(c *touka.Context) {
c.JSON(400, touka.H{"error": "presetName is required"}) c.JSON(200, gen.LogLevelList)
return })
glbr.GET("/tls/providers", func(c *touka.Context) {
c.JSON(200, gen.ProviderList)
})
} }
preset, found := GetHeaderSetByID(presetName) }
if !found {
c.JSON(404, touka.H{"error": "preset not found"})
return
}
c.JSON(200, preset)
})
// caddy实例相关 // caddy实例相关
api.POST("/caddy/stop", apic.StopCaddy()) // 无需payload {
api.POST("/caddy/run", apic.StartCaddy(cfg)) api.POST("/caddy/stop", apic.StopCaddy()) // 无需payload
api.POST("/caddy/restart", apic.RestartCaddy(cfg)) api.POST("/caddy/run", apic.StartCaddy(cfg))
api.GET("/caddy/status", apic.IsCaddyRunning()) api.POST("/caddy/restart", apic.RestartCaddy(cfg))
api.GET("/caddy/status", apic.IsCaddyRunning())
}
// 鉴权相关
auth := api.Group("/auth") auth := api.Group("/auth")
{ {
auth.POST("/login", func(c *touka.Context) { auth.POST("/login", func(c *touka.Context) {
@ -66,31 +69,8 @@ func ApiGroup(v0 touka.IRouter, cdb *db.ConfigDB, cfg *config.Config) {
auth.GET("/logout", func(c *touka.Context) { auth.GET("/logout", func(c *touka.Context) {
AuthLogout(c) AuthLogout(c)
}) })
auth.GET("/init", func(c *touka.Context) { auth.GET("/init", AuthInitStatus())
// 返回是否init管理员 auth.POST("/init", AuthInitHandle(cdb))
isInit := user.IsAdminInit()
if isInit {
c.JSON(200, touka.H{"admin_init": true})
} else {
c.JSON(200, touka.H{"admin_init": false})
}
})
auth.POST("/init", func(c *touka.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
// 验证是否为空
if username == "" || password == "" {
c.JSON(400, touka.H{"error": "username and password are required"})
return
}
// 初始化管理员
err := user.InitAdminUser(username, password, cdb)
if err != nil {
c.JSON(500, touka.H{"error": err.Error()})
return
}
c.JSON(200, touka.H{"message": "admin initialized"})
})
auth.POST("resetpwd", ResetPassword(cdb)) auth.POST("resetpwd", ResetPassword(cdb))
} }
} }

View file

@ -183,3 +183,34 @@ func ResetPassword(cdb *db.ConfigDB) touka.HandlerFunc {
AuthLogout(c) AuthLogout(c)
} }
} }
func AuthInitHandle(cdb *db.ConfigDB) touka.HandlerFunc {
return func(c *touka.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
// 验证是否为空
if username == "" || password == "" {
c.JSON(400, touka.H{"error": "username and password are required"})
return
}
// 初始化管理员
err := user.InitAdminUser(username, password, cdb)
if err != nil {
c.JSON(500, touka.H{"error": err.Error()})
return
}
c.JSON(200, touka.H{"message": "admin initialized"})
}
}
func AuthInitStatus() touka.HandlerFunc {
return func(c *touka.Context) {
// 返回是否init管理员
isInit := user.IsAdminInit()
if isInit {
c.JSON(200, touka.H{"admin_init": true})
} else {
c.JSON(200, touka.H{"admin_init": false})
}
}
}

View file

@ -119,107 +119,18 @@ func DeleteConfig(cdb *db.ConfigDB, cfg *config.Config) touka.HandlerFunc {
} }
} }
/* func GetHeadersPreset() touka.HandlerFunc {
func PutConfig(cdb *db.ConfigDB, cfg *config.Config) touka.HandlerFunc {
return func(c *touka.Context) { return func(c *touka.Context) {
filename := c.Param("filename") presetName := c.Param("name")
var config gen.CaddyUniConfig if presetName == "" {
err := c.ShouldBindJSON(&config) c.JSON(400, touka.H{"error": "presetName is required"})
if err != nil {
c.JSON(500, touka.H{"error": err.Error()})
return return
} }
var ( preset, found := GetHeaderSetByID(presetName)
paramsGOB []byte if !found {
paramsOrigin []byte c.JSON(404, touka.H{"error": "preset not found"})
)
switch config.TmplType {
case "file_server":
caddyfscfg := gen.CaddyFileServerConfig{
Domain: config.Domain,
FileDirPath: config.FileServer.FileDirPath,
EnableBrowser: config.FileServer.EnableBrowser,
Headers: gen.HeadersMapToHeadersUp(config.Headers),
EnableLog: config.Log.EnableLog,
LogDomain: config.Log.LogDomain,
EnableErrorPage: config.ErrorPage.EnableErrorPage,
EnableEncode: config.Encode.EnableEncode,
}
paramsGOB, err = gen.EncodeGobConfig(caddyfscfg)
if err != nil {
c.Warnf("encode gob config error: %v", err)
c.JSON(500, touka.H{"error": err.Error()})
return
}
// 把json变为gob []byte
paramsOrigin, err = gen.EncodeGobConfig(config)
if err != nil {
c.Warnf("encode origin config error: %v", err)
c.JSON(500, touka.H{"error": err.Error()})
return
}
paramsEntry := db.ParamsEntry{
Filename: filename,
TemplateType: config.TmplType,
ParamsGOB: paramsGOB,
ParamsOrigin: paramsOrigin,
}
err = WriteConfig(cdb, paramsEntry, cfg, filename)
if err != nil {
c.Warnf("write config error: %v", err)
c.JSON(500, touka.H{"error": err.Error()})
return
}
c.JSON(200, touka.H{"message": "config saved and rendered"})
return
case "reverse_proxy":
caddyrpCfg := gen.CaddyReverseProxyConfig{
Domain: config.Domain,
ReverseProxy: config.UpStream.UpStream,
Headers: gen.HeadersMapToHeadersUp(config.Headers),
HeadersUp: gen.HeadersMapToHeadersUp(config.UpStream.UpStreamHeaders),
EnableLog: config.Log.EnableLog,
LogDomain: config.Log.LogDomain,
EnableErrorPage: config.ErrorPage.EnableErrorPage,
EnableEncode: config.Encode.EnableEncode,
}
paramsGOB, err = gen.EncodeGobConfig(caddyrpCfg)
if err != nil {
c.Warnf("encode gob config error: %v", err)
c.JSON(500, touka.H{"error": err.Error()})
return
}
// 把json变为gob []byte
paramsOrigin, err = gen.EncodeGobConfig(config)
if err != nil {
c.Warnf("encode origin config error: %v", err)
c.JSON(500, touka.H{"error": err.Error()})
return
}
paramsEntry := db.ParamsEntry{
Filename: filename,
TemplateType: config.TmplType,
ParamsGOB: paramsGOB,
ParamsOrigin: paramsOrigin,
}
err = WriteConfig(cdb, paramsEntry, cfg, filename)
if err != nil {
c.Warnf("write config error: %v", err)
c.JSON(500, touka.H{"error": err.Error()})
return
}
c.JSON(200, touka.H{"message": "config saved and rendered"})
return
default:
c.JSON(500, touka.H{"error": "unknown template type"})
return return
} }
c.JSON(200, preset)
} }
} }
*/

1
api/global.go Normal file
View file

@ -0,0 +1 @@
package api

View file

@ -1,29 +1,5 @@
package gen package gen
/*
type CaddyReverseProxyConfig struct {
Domain string // 域名; 例如 example.com
ReverseProxy string // 反向代理目标; 例如 127.0.0.1:8080 (这里简化为单个目标)
Headers []string // 自定义响应Header
HeadersUp []string // 自定义请求头列表; 例如 ["XXX0 XX", "XXX1 XXX"]
EnableLog bool // 是否导入 log 指令
LogDomain string // log 指令的域名参数
EnableErrorPage bool // 是否导入 error_page 指令
EnableEncode bool // 是否导入 encode 指令
}
type CaddyFileServerConfig struct {
Domain string // 域名; 例如 example.com
FileDirPath string // 文件目录
EnableBrowser bool // 是否导入 browse 指令
Headers []string //
EnableLog bool // 是否导入 log 指令
LogDomain string // log 指令的域名参数
EnableErrorPage bool // 是否导入 error_page 指令
EnableEncode bool // 是否导入 encode 指令
}
*/
func HeadersMapToHeadersUp(headers map[string][]string) []string { func HeadersMapToHeadersUp(headers map[string][]string) []string {
var headersUp []string var headersUp []string
for key, values := range headers { for key, values := range headers {
@ -77,3 +53,46 @@ type CaddyUniErrorPageConfig struct {
type CaddyUniEncodeConfig struct { type CaddyUniEncodeConfig struct {
EnableEncode bool `json:"enable_encode"` EnableEncode bool `json:"enable_encode"`
} }
type CaddyGlobalConfig struct {
Debug bool `json:"debug"`
PortsConfig CaddyGlobalPortsConfig `json:"ports_config"`
Metrics bool `json:"metrics"`
LogConfig CaddyGlobalLogConfig `json:"log_config"`
TLSConfig CaddyGlobalTLSConfig `json:"tls_config"`
}
type CaddyGlobalPortsConfig struct {
AdminPort string `json:"admin_port"`
HTTPPort uint16 `json:"http_port"`
HTTPSPort uint16 `json:"https_port"`
}
type CaddyGlobalLogConfig struct {
Level string `json:"level"`
// 日志滚动配置
RotateSize string `json:"rotate_size"`
RotateKeep string `json:"rotate_keep"`
RotateKeepForTime string `json:"rotate_keep_for_time"`
}
// 维护一个日志等级列表
// Possible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL
var LogLevelList = map[string]struct{}{
"DEBUG": {},
"INFO": {},
"WARN": {},
"ERROR": {},
"PANIC": {},
"FATAL": {},
}
type CaddyGlobalTLSConfig struct {
Provider string `json:"provider"`
Token string `json:"token"`
}
// 维护一个提供商列表
var ProviderList = map[string]struct{}{
"cloudflare": {},
}

6
go.mod
View file

@ -7,13 +7,13 @@ require (
github.com/fenthope/record v0.0.3 github.com/fenthope/record v0.0.3
github.com/fenthope/sessions v0.0.1 github.com/fenthope/sessions v0.0.1
github.com/infinite-iroha/touka v0.2.2 github.com/infinite-iroha/touka v0.2.2
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.39.0
modernc.org/sqlite v1.38.0 modernc.org/sqlite v1.38.0
) )
require ( require (
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 // indirect github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 // indirect
github.com/WJQSERVER-STUDIO/httpc v0.7.0 // indirect github.com/WJQSERVER-STUDIO/httpc v0.7.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fenthope/reco v0.0.3 // indirect github.com/fenthope/reco v0.0.3 // indirect
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 // indirect github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 // indirect
@ -25,7 +25,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.33.0 // indirect
modernc.org/libc v1.65.10 // indirect modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect

12
go.sum
View file

@ -2,8 +2,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 h1:JLtFd00AdFg/TP+dtvIzLkdHwKUGPOAijN1sMtEYoFg= github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 h1:JLtFd00AdFg/TP+dtvIzLkdHwKUGPOAijN1sMtEYoFg=
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4/go.mod h1:FZ6XE+4TKy4MOfX1xWKe6Rwsg0ucYFCdNh1KLvyKTfc= github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4/go.mod h1:FZ6XE+4TKy4MOfX1xWKe6Rwsg0ucYFCdNh1KLvyKTfc=
github.com/WJQSERVER-STUDIO/httpc v0.7.0 h1:iHhqlxppJBjlmvsIjvLZKRbWXqSdbeSGGofjHGmqGJc= github.com/WJQSERVER-STUDIO/httpc v0.7.1 h1:D3NlfY52pwKIOSzkdRrLinUynyKELrcPZEO8QjlBq2M=
github.com/WJQSERVER-STUDIO/httpc v0.7.0/go.mod h1:M7KNUZjjhCkzzcg9lBPs9YfkImI+7vqjAyjdA19+joE= github.com/WJQSERVER-STUDIO/httpc v0.7.1/go.mod h1:M7KNUZjjhCkzzcg9lBPs9YfkImI+7vqjAyjdA19+joE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fenthope/reco v0.0.3 h1:RmnQ0D9a8PWtwOODawitTe4BztTnS9wYwrDbipISNq4= github.com/fenthope/reco v0.0.3 h1:RmnQ0D9a8PWtwOODawitTe4BztTnS9wYwrDbipISNq4=
@ -34,10 +34,10 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=

95
gtmpl/caddyfile Normal file
View file

@ -0,0 +1,95 @@
{
debug
admin :2019
http_port 80
https_port 443
metrics
order ja4h_header first
order webdav before file_server
order cache before rewrite
cache {
cache_name CaddyCache
}
log {
level INFO
output file ./log/caddy.log {
roll_size 10MB
roll_keep 10
}
}
}
(log) {
log {
format transform `{request>headers>X-Forwarded-For>[0]:request>remote_ip} - {user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}"` {
time_format "02/Jan/2006:15:04:05 -0700"
}
output file ./log/{args[0]}/access.log {
roll_size 10MB
roll_keep 10
roll_keep_for 24h
}
}
}
(error_page) {
handle_errors {
rewrite * /{err.status_code}.html
root * ./pages/errors
file_server
}
}
(encode) {
encode {
zstd
br
gzip
minimum_length 512
}
}
(cache) {
cache {
allowed_http_verbs GET
stale {args[0]}
ttl {args[1]}
}
}
(header_realip_cf) {
header_up X-Real-IP {remote_host}
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
header_up X-Forwarded-Proto {http.request.header.CF-Visitor}
}
(header_realip) {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
(tls) {
tls {
dns {args[0]} {args[1]}
}
}
(rate_limit) {
route /* {
rate_limit {remote.ip} {args[0]}r/m 10000 429
}
}
(route_nocache) {
route {args[0]} {
rate_limit {remote.ip} {args[1]}r/m 10000 429
cache {
stale 0s
ttl 0s
}
}
}
import ./config.d/*

View file

@ -1,42 +1,42 @@
{{- if .DomainConfig.MutiDomains -}} {{- if .DomainConfig.MutiDomains -}}
{{- range $i, $domain := .DomainConfig.Domains -}} {{- range $i, $domain := .DomainConfig.Domains -}}
{{- if $i}} {{" "}}{{- end -}} {{- if $i}} {{" "}}{{- end -}}
{{- . -}} {{- . -}}
{{- end -}} { {{- end -}} {
{{- else -}} {{- else -}}
{{- .DomainConfig.Domain}} { {{- .DomainConfig.Domain}} {
{{- end -}} {{- end -}}
{{- if .Upstream.EnableUpStream}} {{- if .Upstream.EnableUpStream}}
reverse_proxy { reverse_proxy {
to{{if .Upstream.MutiUpStreams}}{{range .Upstream.UpStreams}} {{.}}{{end}}{{else}} {{.Upstream.UpStream}}{{end}} to{{if .Upstream.MutiUpStreams}}{{range .Upstream.UpStreams}} {{.}}{{end}}{{else}} {{.Upstream.UpStream}}{{end}}
{{- range $key, $values := .Upstream.UpStreamHeaders}} {{- range $key, $values := .Upstream.UpStreamHeaders}}
{{- range $values}} {{- range $values}}
header_up {{$key}} "{{.}}" header_up {{$key}} "{{.}}"
{{- end}} {{- end}}
{{- end}} {{- end}}
} }
{{- else if .FileServer.EnableFileServer}} {{- else if .FileServer.EnableFileServer}}
root * {{.FileServer.FileDirPath}} root * {{.FileServer.FileDirPath}}
file_server{{if .FileServer.EnableBrowser}} browse{{end}} file_server{{if .FileServer.EnableBrowser}} browse{{end}}
{{- end}} {{- end}}
{{- range $key, $values := .Headers}} {{- range $key, $values := .Headers}}
{{- range $values}} {{- range $values}}
header {{$key}} "{{.}}" header {{$key}} "{{.}}"
{{- end}} {{- end}}
{{- end}} {{- end}}
{{- if .Log.EnableLog}} {{- if .Log.EnableLog}}
import log {{.Log.LogDomain}} import log {{.Log.LogDomain}}
{{- end}} {{- end}}
{{- if .ErrorPage.EnableErrorPage}} {{- if .ErrorPage.EnableErrorPage}}
import error_page import error_page
{{- end}} {{- end}}
{{- if .Encode.EnableEncode}} {{- if .Encode.EnableEncode}}
import encode import encode
{{- end}} {{- end}}
} }