mirror of
https://github.com/1f349/violet.git
synced 2024-11-22 03:11:44 +00:00
Hardcode some file names
This commit is contained in:
parent
7e05271a79
commit
76e37f7af9
@ -1,15 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
type startUpConfig struct {
|
type startUpConfig struct {
|
||||||
Database string `json:"db"`
|
|
||||||
MjwtPubKey string `json:"mjwt_pub_key"`
|
|
||||||
CertPath string `json:"cert_path"`
|
|
||||||
KeyPath string `json:"key_path"`
|
|
||||||
SelfSigned bool `json:"self_signed"`
|
SelfSigned bool `json:"self_signed"`
|
||||||
ErrorPagePath string `json:"error_page_path"`
|
ErrorPagePath string `json:"error_page_path"`
|
||||||
Listen listenConfig `json:"listen"`
|
Listen listenConfig `json:"listen"`
|
||||||
InkscapeCmd string `json:"inkscape"`
|
InkscapeCmd string `json:"inkscape"`
|
||||||
RateLimit uint64 `json:"rate_limit"`
|
RateLimit uint64 `json:"rate_limit"`
|
||||||
|
SignerIssuer string `json:"signer_issuer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type listenConfig struct {
|
type listenConfig struct {
|
||||||
|
@ -18,9 +18,11 @@ import (
|
|||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -63,26 +65,24 @@ func (s *serveCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
|
|||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
normalLoad(conf)
|
// working directory is the parent of the config file
|
||||||
|
wd := filepath.Dir(s.configPath)
|
||||||
|
normalLoad(conf, wd)
|
||||||
return subcommands.ExitSuccess
|
return subcommands.ExitSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalLoad(conf startUpConfig) {
|
func normalLoad(conf startUpConfig, wd string) {
|
||||||
// the cert and key paths are useless in self-signed mode
|
// the cert and key paths are useless in self-signed mode
|
||||||
if !conf.SelfSigned {
|
if !conf.SelfSigned {
|
||||||
if conf.CertPath != "" {
|
// create path to cert dir
|
||||||
// create path to cert dir
|
err := os.MkdirAll(filepath.Join(wd, "certs"), os.ModePerm)
|
||||||
err := os.MkdirAll(conf.CertPath, os.ModePerm)
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatal("[Violet] Failed to create certificate path")
|
||||||
log.Fatalf("[Violet] Failed to create certificate path '%s'", conf.CertPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if conf.KeyPath != "" {
|
// create path to key dir
|
||||||
// create path to key dir
|
err = os.MkdirAll(filepath.Join(wd, "keys"), os.ModePerm)
|
||||||
err := os.MkdirAll(conf.KeyPath, os.ModePerm)
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatal("[Violet] Failed to create certificate key path")
|
||||||
log.Fatalf("[Violet] Failed to create certificate key path '%s'", conf.KeyPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,25 +96,28 @@ func normalLoad(conf startUpConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the MJWT RSA public key from a pem encoded file
|
// load the MJWT RSA private key from a pem encoded file
|
||||||
mjwtVerify, err := mjwt.NewMJwtVerifierFromFile(conf.MjwtPubKey)
|
mjwtSigner, err := mjwt.NewMJwtSignerFromFileOrCreate(conf.SignerIssuer, filepath.Join(wd, "violet.private.pem"), rand.New(rand.NewSource(time.Now().UnixNano())), 4096)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[Violet] Failed to load MJWT verifier public key from file: '%s'", conf.MjwtPubKey)
|
log.Fatal("[Violet] Failed to load MJWT verifier public key from file: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// open sqlite database
|
// open sqlite database
|
||||||
db, err := sql.Open("sqlite3", conf.Database)
|
db, err := sql.Open("sqlite3", "violet.db.sqlite")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[Violet] Failed to open database '%s'...", conf.Database)
|
log.Fatal("[Violet] Failed to open database")
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedDomains := domains.New(db) // load allowed domains
|
certDir := os.DirFS(filepath.Join(wd, "certs"))
|
||||||
acmeChallenges := utils.NewAcmeChallenge() // load acme challenge store
|
keyDir := os.DirFS(filepath.Join(wd, "keys"))
|
||||||
allowedCerts := certs.New(os.DirFS(conf.CertPath), os.DirFS(conf.KeyPath), conf.SelfSigned) // load certificate manager
|
|
||||||
hybridTransport := proxy.NewHybridTransport() // load reverse proxy
|
allowedDomains := domains.New(db) // load allowed domains
|
||||||
dynamicFavicons := favicons.New(db, conf.InkscapeCmd) // load dynamic favicon provider
|
acmeChallenges := utils.NewAcmeChallenge() // load acme challenge store
|
||||||
dynamicErrorPages := errorPages.New(errorPageDir) // load dynamic error page provider
|
allowedCerts := certs.New(certDir, keyDir, conf.SelfSigned) // load certificate manager
|
||||||
dynamicRouter := router.NewManager(db, hybridTransport) // load dynamic router manager
|
hybridTransport := proxy.NewHybridTransport() // load reverse proxy
|
||||||
|
dynamicFavicons := favicons.New(db, conf.InkscapeCmd) // load dynamic favicon provider
|
||||||
|
dynamicErrorPages := errorPages.New(errorPageDir) // load dynamic error page provider
|
||||||
|
dynamicRouter := router.NewManager(db, hybridTransport) // load dynamic router manager
|
||||||
|
|
||||||
// struct containing config for the http servers
|
// struct containing config for the http servers
|
||||||
srvConf := &servers.Conf{
|
srvConf := &servers.Conf{
|
||||||
@ -127,7 +130,7 @@ func normalLoad(conf startUpConfig) {
|
|||||||
Acme: acmeChallenges,
|
Acme: acmeChallenges,
|
||||||
Certs: allowedCerts,
|
Certs: allowedCerts,
|
||||||
Favicons: dynamicFavicons,
|
Favicons: dynamicFavicons,
|
||||||
Verify: mjwtVerify,
|
Signer: mjwtSigner,
|
||||||
ErrorPages: dynamicErrorPages,
|
ErrorPages: dynamicErrorPages,
|
||||||
Router: dynamicRouter,
|
Router: dynamicRouter,
|
||||||
}
|
}
|
||||||
|
@ -59,14 +59,15 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{})
|
|||||||
|
|
||||||
// store answers from questions
|
// store answers from questions
|
||||||
var answers struct {
|
var answers struct {
|
||||||
SelfSigned bool
|
SelfSigned bool
|
||||||
ErrorPages bool
|
ErrorPages bool
|
||||||
ApiListen string
|
ApiListen string
|
||||||
HttpListen string
|
HttpListen string
|
||||||
HttpsListen string
|
HttpsListen string
|
||||||
RateLimit uint64
|
RateLimit uint64
|
||||||
FirstDomain string
|
FirstDomain string
|
||||||
ApiUrl string
|
ApiUrl string
|
||||||
|
SignerIssuer string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ask main questions
|
// ask main questions
|
||||||
@ -103,6 +104,10 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{})
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "SignerIssuer",
|
||||||
|
Prompt: &survey.Input{Message: "Issuer name to sign API tokens with", Default: "Violet"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "FirstDomain",
|
Name: "FirstDomain",
|
||||||
Prompt: &survey.Input{Message: "First domain", Default: "example.com", Help: "Setup the first domain or it will be more difficult to setup later"},
|
Prompt: &survey.Input{Message: "First domain", Default: "example.com", Help: "Setup the first domain or it will be more difficult to setup later"},
|
||||||
@ -130,10 +135,6 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{})
|
|||||||
confEncode := json.NewEncoder(createConf)
|
confEncode := json.NewEncoder(createConf)
|
||||||
confEncode.SetIndent("", " ")
|
confEncode.SetIndent("", " ")
|
||||||
err = confEncode.Encode(startUpConfig{
|
err = confEncode.Encode(startUpConfig{
|
||||||
Database: filepath.Join(wdAbs, "violet.sqlite"),
|
|
||||||
MjwtPubKey: filepath.Join(wdAbs, "mjwt.public.key"),
|
|
||||||
CertPath: filepath.Join(wdAbs, "certs"),
|
|
||||||
KeyPath: filepath.Join(wdAbs, "keys"),
|
|
||||||
SelfSigned: answers.SelfSigned,
|
SelfSigned: answers.SelfSigned,
|
||||||
ErrorPagePath: errorPagePath,
|
ErrorPagePath: errorPagePath,
|
||||||
Listen: listenConfig{
|
Listen: listenConfig{
|
||||||
@ -141,8 +142,9 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{})
|
|||||||
Http: answers.HttpListen,
|
Http: answers.HttpListen,
|
||||||
Https: answers.HttpsListen,
|
Https: answers.HttpsListen,
|
||||||
},
|
},
|
||||||
InkscapeCmd: "inkscape",
|
InkscapeCmd: "inkscape",
|
||||||
RateLimit: answers.RateLimit,
|
RateLimit: answers.RateLimit,
|
||||||
|
SignerIssuer: answers.SignerIssuer,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Violet] Failed to write config file: ", err)
|
fmt.Println("[Violet] Failed to write config file: ", err)
|
||||||
|
2
go.mod
2
go.mod
@ -5,7 +5,7 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/MrMelon54/certgen v0.0.1
|
github.com/MrMelon54/certgen v0.0.1
|
||||||
github.com/MrMelon54/mjwt v0.0.2
|
github.com/MrMelon54/mjwt v0.1.1
|
||||||
github.com/MrMelon54/png2ico v1.0.1
|
github.com/MrMelon54/png2ico v1.0.1
|
||||||
github.com/MrMelon54/rescheduler v0.0.1
|
github.com/MrMelon54/rescheduler v0.0.1
|
||||||
github.com/MrMelon54/trie v0.0.2
|
github.com/MrMelon54/trie v0.0.2
|
||||||
|
2
go.sum
2
go.sum
@ -4,6 +4,8 @@ github.com/MrMelon54/certgen v0.0.1 h1:ycWdZ2RlxQ5qSuejeBVv4aXjGo5hdqqL4j4EjrXnF
|
|||||||
github.com/MrMelon54/certgen v0.0.1/go.mod h1:GHflVlSbtFLJZLpN1oWyUvDBRrR8qCWiwZLXCCnS2Gc=
|
github.com/MrMelon54/certgen v0.0.1/go.mod h1:GHflVlSbtFLJZLpN1oWyUvDBRrR8qCWiwZLXCCnS2Gc=
|
||||||
github.com/MrMelon54/mjwt v0.0.2 h1:jDqyPnFloh80XdSmZ6jt9qhUj/ULcoQ4QSHXPdkAIE4=
|
github.com/MrMelon54/mjwt v0.0.2 h1:jDqyPnFloh80XdSmZ6jt9qhUj/ULcoQ4QSHXPdkAIE4=
|
||||||
github.com/MrMelon54/mjwt v0.0.2/go.mod h1:HzY8P6Je+ovS/fwK5sILRMq5mnZT4+WuFRc98LBy7z4=
|
github.com/MrMelon54/mjwt v0.0.2/go.mod h1:HzY8P6Je+ovS/fwK5sILRMq5mnZT4+WuFRc98LBy7z4=
|
||||||
|
github.com/MrMelon54/mjwt v0.1.1 h1:m+aTpxbhQCrOPKHN170DQMFR5r938LkviU38unob5Jw=
|
||||||
|
github.com/MrMelon54/mjwt v0.1.1/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
||||||
github.com/MrMelon54/png2ico v1.0.1 h1:zJoSSl4OkvSIMWGyGPvb8fWNa0KrUvMIjgNGLNLJhVQ=
|
github.com/MrMelon54/png2ico v1.0.1 h1:zJoSSl4OkvSIMWGyGPvb8fWNa0KrUvMIjgNGLNLJhVQ=
|
||||||
github.com/MrMelon54/png2ico v1.0.1/go.mod h1:NOv3tO4497mInG+3tcFkIohmxCywUwMLU8WNxJZLVmU=
|
github.com/MrMelon54/png2ico v1.0.1/go.mod h1:NOv3tO4497mInG+3tcFkIohmxCywUwMLU8WNxJZLVmU=
|
||||||
github.com/MrMelon54/rescheduler v0.0.1 h1:gzNvL8X81M00uYN0i9clFVrXCkG1UuLNYxDcvjKyBqo=
|
github.com/MrMelon54/rescheduler v0.0.1 h1:gzNvL8X81M00uYN0i9clFVrXCkG1UuLNYxDcvjKyBqo=
|
||||||
|
@ -18,7 +18,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server
|
|||||||
|
|
||||||
// Endpoint for compile action
|
// Endpoint for compile action
|
||||||
r.POST("/compile", func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
|
r.POST("/compile", func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
|
||||||
if !hasPerms(conf.Verify, req, "violet:compile") {
|
if !hasPerms(conf.Signer, req, "violet:compile") {
|
||||||
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server
|
|||||||
|
|
||||||
// Endpoint for domains
|
// Endpoint for domains
|
||||||
r.PUT("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.PUT("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
if !hasPerms(conf.Verify, req, "violet:domains") {
|
if !hasPerms(conf.Signer, req, "violet:domains") {
|
||||||
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server
|
|||||||
conf.Domains.Compile()
|
conf.Domains.Compile()
|
||||||
})
|
})
|
||||||
r.DELETE("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.DELETE("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
if !hasPerms(conf.Verify, req, "violet:domains") {
|
if !hasPerms(conf.Signer, req, "violet:domains") {
|
||||||
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server
|
|||||||
|
|
||||||
// Endpoint for acme-challenge
|
// Endpoint for acme-challenge
|
||||||
r.PUT("/acme-challenge/:domain/:key/:value", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.PUT("/acme-challenge/:domain/:key/:value", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
if !hasPerms(conf.Verify, req, "violet:acme-challenge") {
|
if !hasPerms(conf.Signer, req, "violet:acme-challenge") {
|
||||||
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server
|
|||||||
rw.WriteHeader(http.StatusAccepted)
|
rw.WriteHeader(http.StatusAccepted)
|
||||||
})
|
})
|
||||||
r.DELETE("/acme-challenge/:domain/:key", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.DELETE("/acme-challenge/:domain/:key", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
if !hasPerms(conf.Verify, req, "violet:acme-challenge") {
|
if !hasPerms(conf.Signer, req, "violet:acme-challenge") {
|
||||||
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
utils.RespondHttpStatus(rw, http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,7 @@ func genSnakeOilProv() mjwt.Signer {
|
|||||||
func genSnakeOilKey(perm string) string {
|
func genSnakeOilKey(perm string) string {
|
||||||
p := claims.NewPermStorage()
|
p := claims.NewPermStorage()
|
||||||
p.Set(perm)
|
p.Set(perm)
|
||||||
val, err := snakeOilProv.GenerateJwt("abc", "abc", 5*time.Minute, auth.AccessTokenClaims{
|
val, err := snakeOilProv.GenerateJwt("abc", "abc", nil, 5*time.Minute, auth.AccessTokenClaims{Perms: p})
|
||||||
UserId: 1,
|
|
||||||
Perms: p,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -54,7 +51,7 @@ func TestNewApiServer_Compile(t *testing.T) {
|
|||||||
apiConf := &Conf{
|
apiConf := &Conf{
|
||||||
Domains: &fakeDomains{},
|
Domains: &fakeDomains{},
|
||||||
Acme: utils.NewAcmeChallenge(),
|
Acme: utils.NewAcmeChallenge(),
|
||||||
Verify: snakeOilProv,
|
Signer: snakeOilProv,
|
||||||
}
|
}
|
||||||
f := &fakeCompilable{}
|
f := &fakeCompilable{}
|
||||||
srv := NewApiServer(apiConf, utils.MultiCompilable{f})
|
srv := NewApiServer(apiConf, utils.MultiCompilable{f})
|
||||||
@ -81,7 +78,7 @@ func TestNewApiServer_AcmeChallenge_Put(t *testing.T) {
|
|||||||
apiConf := &Conf{
|
apiConf := &Conf{
|
||||||
Domains: &fakeDomains{},
|
Domains: &fakeDomains{},
|
||||||
Acme: utils.NewAcmeChallenge(),
|
Acme: utils.NewAcmeChallenge(),
|
||||||
Verify: snakeOilProv,
|
Signer: snakeOilProv,
|
||||||
}
|
}
|
||||||
srv := NewApiServer(apiConf, utils.MultiCompilable{})
|
srv := NewApiServer(apiConf, utils.MultiCompilable{})
|
||||||
acmeKey := genSnakeOilKey("violet:acme-challenge")
|
acmeKey := genSnakeOilKey("violet:acme-challenge")
|
||||||
@ -125,7 +122,7 @@ func TestNewApiServer_AcmeChallenge_Delete(t *testing.T) {
|
|||||||
apiConf := &Conf{
|
apiConf := &Conf{
|
||||||
Domains: &fakeDomains{},
|
Domains: &fakeDomains{},
|
||||||
Acme: utils.NewAcmeChallenge(),
|
Acme: utils.NewAcmeChallenge(),
|
||||||
Verify: snakeOilProv,
|
Signer: snakeOilProv,
|
||||||
}
|
}
|
||||||
srv := NewApiServer(apiConf, utils.MultiCompilable{})
|
srv := NewApiServer(apiConf, utils.MultiCompilable{})
|
||||||
acmeKey := genSnakeOilKey("violet:acme-challenge")
|
acmeKey := genSnakeOilKey("violet:acme-challenge")
|
||||||
|
@ -20,7 +20,7 @@ type Conf struct {
|
|||||||
Acme AcmeChallengeProvider
|
Acme AcmeChallengeProvider
|
||||||
Certs CertProvider
|
Certs CertProvider
|
||||||
Favicons *favicons.Favicons
|
Favicons *favicons.Favicons
|
||||||
Verify mjwt.Verifier
|
Signer mjwt.Verifier
|
||||||
ErrorPages *errorPages.ErrorPages
|
ErrorPages *errorPages.ErrorPages
|
||||||
Router *router.Manager
|
Router *router.Manager
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ func TestNewHttpServer_AcmeChallenge(t *testing.T) {
|
|||||||
httpConf := &Conf{
|
httpConf := &Conf{
|
||||||
Domains: &fakeDomains{},
|
Domains: &fakeDomains{},
|
||||||
Acme: utils.NewAcmeChallenge(),
|
Acme: utils.NewAcmeChallenge(),
|
||||||
Verify: snakeOilProv,
|
Signer: snakeOilProv,
|
||||||
}
|
}
|
||||||
srv := NewHttpServer(httpConf)
|
srv := NewHttpServer(httpConf)
|
||||||
httpConf.Acme.Put("example.com", "456", "456def")
|
httpConf.Acme.Put("example.com", "456", "456def")
|
||||||
|
@ -30,7 +30,7 @@ func TestNewHttpsServer_RateLimit(t *testing.T) {
|
|||||||
RateLimit: 5,
|
RateLimit: 5,
|
||||||
Domains: &fakeDomains{},
|
Domains: &fakeDomains{},
|
||||||
Certs: certs.New(nil, nil, true),
|
Certs: certs.New(nil, nil, true),
|
||||||
Verify: snakeOilProv,
|
Signer: snakeOilProv,
|
||||||
Router: router.NewManager(db, proxy.NewHybridTransportWithCalls(ft, ft)),
|
Router: router.NewManager(db, proxy.NewHybridTransportWithCalls(ft, ft)),
|
||||||
}
|
}
|
||||||
srv := NewHttpsServer(httpsConf)
|
srv := NewHttpsServer(httpsConf)
|
||||||
|
Loading…
Reference in New Issue
Block a user