mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-02 15:06:23 -07:00
consolidate path validation, remove platform-specific files from gochan-installer (since that is now handled by the config package)
This commit is contained in:
parent
564d659e02
commit
062cafc4f2
6 changed files with 310 additions and 332 deletions
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
@ -22,35 +23,34 @@ type pathsForm struct {
|
|||
WebRoot string `form:"webroot,required" method:"POST"`
|
||||
}
|
||||
|
||||
func (pf *pathsForm) validatePath(targetPath *string, desc string, expectDir bool) error {
|
||||
p := *targetPath
|
||||
if p == "" {
|
||||
return fmt.Errorf("%s is required", desc)
|
||||
}
|
||||
p = path.Clean(p)
|
||||
*targetPath = p
|
||||
|
||||
fi, err := os.Stat(p)
|
||||
func (pf *pathsForm) validateDirectory(dir string, createIfNotExist bool) error {
|
||||
fi, err := os.Stat(dir)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("%s %s does not exist", desc, p)
|
||||
if createIfNotExist {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("directory %s does not exist", dir)
|
||||
}
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
return fmt.Errorf("permission denied to %s", p)
|
||||
return fmt.Errorf("permission denied to access directory %s", dir)
|
||||
}
|
||||
if expectDir && !fi.Mode().IsDir() {
|
||||
return fmt.Errorf("%s exists at %s but is not a directory", desc, p)
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("%s exists at %s but is not a directory", dir, dir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pf *pathsForm) validate(warnEv, errEv *zerolog.Event) (err error) {
|
||||
func (pf *pathsForm) validate(warnEv, _ *zerolog.Event) (err error) {
|
||||
pf.TemplateDir = path.Clean(pf.TemplateDir)
|
||||
pf.DocumentRoot = path.Clean(pf.DocumentRoot)
|
||||
pf.LogDir = path.Clean(pf.LogDir)
|
||||
|
||||
if pf.ConfigPath == "" {
|
||||
warnEv.Msg("Required config output directory not set")
|
||||
return errors.New("config output directory is required")
|
||||
if pf.WebRoot == "" {
|
||||
pf.WebRoot = "/"
|
||||
}
|
||||
pf.WebRoot = path.Clean(pf.WebRoot)
|
||||
pf.ConfigPath = path.Clean(pf.ConfigPath)
|
||||
|
||||
validConfigPaths := cfgPaths
|
||||
|
@ -66,41 +66,36 @@ func (pf *pathsForm) validate(warnEv, errEv *zerolog.Event) (err error) {
|
|||
return fmt.Errorf("config output path %s is not allowed. Valid values are %s", strings.Join(cfgPaths, ", "), pf.ConfigPath)
|
||||
}
|
||||
|
||||
configDir := path.Dir(pf.ConfigPath)
|
||||
|
||||
if err = pf.validatePath(&configDir, "config output directory", true); err != nil {
|
||||
if err = pf.validateDirectory(path.Dir(pf.ConfigPath), true); err != nil {
|
||||
warnEv.Err(err).
|
||||
Msg("Invalid config output directory")
|
||||
Msg("Invalid config output path")
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pf.validatePath(&pf.TemplateDir, "template directory", true); err != nil {
|
||||
if err = pf.validateDirectory(pf.TemplateDir, true); err != nil {
|
||||
warnEv.Err(err).Str("templateDir", pf.TemplateDir).
|
||||
Msg("Invalid template directory")
|
||||
return err
|
||||
}
|
||||
if err = pf.validatePath(&pf.DocumentRoot, "document root", true); err != nil {
|
||||
if err = pf.validateDirectory(pf.DocumentRoot, true); err != nil {
|
||||
warnEv.Err(err).Str("documentRoot", pf.DocumentRoot).
|
||||
Msg("Invalid document root")
|
||||
return err
|
||||
}
|
||||
if err = pf.validatePath(&pf.LogDir, "log directory", true); err != nil {
|
||||
if err = pf.validateDirectory(pf.LogDir, true); err != nil {
|
||||
warnEv.Err(err).Str("logDir", pf.LogDir).
|
||||
Msg("Invalid log directory")
|
||||
return err
|
||||
}
|
||||
|
||||
if pf.WebRoot == "" {
|
||||
pf.WebRoot = "/"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dbForm struct {
|
||||
DBtype string `form:"dbtype,required,notempty" method:"POST"`
|
||||
DBhost string `form:"dbhost,required,notempty" method:"POST"`
|
||||
DBhost string `form:"dbhost,notempty,default=localhost" method:"POST"`
|
||||
DBname string `form:"dbname,required,notempty" method:"POST"`
|
||||
DBuser string `form:"dbuser,required,notempty" method:"POST"`
|
||||
DBuser string `form:"dbuser" method:"POST"`
|
||||
DBpass string `form:"dbpass" method:"POST"`
|
||||
DBprefix string `form:"dbprefix" method:"POST"`
|
||||
|
||||
|
@ -114,6 +109,10 @@ func (dbf *dbForm) validate() (status dbStatus, err error) {
|
|||
if dbf.DBprefix == "" {
|
||||
return dbStatusNoPrefix, nil
|
||||
}
|
||||
supportedDrivers := sql.Drivers()
|
||||
if !slices.Contains(supportedDrivers, dbf.DBtype) {
|
||||
return dbStatusUnknown, fmt.Errorf("unsupported database type %s, supported types are %s", dbf.DBtype, strings.Join(supportedDrivers, ", "))
|
||||
}
|
||||
if dbf.TimeoutSeconds <= 0 {
|
||||
return dbStatusUnknown, errors.New("request timeout must be greater than 0")
|
||||
}
|
||||
|
|
277
cmd/gochan-installer/handler.go
Normal file
277
cmd/gochan-installer/handler.go
Normal file
|
@ -0,0 +1,277 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/Eggbertx/go-forms"
|
||||
"github.com/gochan-org/gochan/pkg/building"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
"github.com/gochan-org/gochan/pkg/server"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
"github.com/uptrace/bunrouter"
|
||||
)
|
||||
|
||||
const (
|
||||
dbStatusUnknown dbStatus = iota
|
||||
dbStatusClean
|
||||
dbStatusNoPrefix
|
||||
dbStatusTablesExist
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed license.txt
|
||||
licenseTxt string
|
||||
|
||||
configPath string
|
||||
currentDBStatus = dbStatusUnknown
|
||||
adminUser *gcsql.Staff
|
||||
cfgPaths = slices.Clone(config.StandardConfigSearchPaths)
|
||||
)
|
||||
|
||||
type dbStatus int
|
||||
|
||||
func (dbs dbStatus) String() string {
|
||||
switch dbs {
|
||||
case dbStatusClean:
|
||||
return "The database does not appear to contain any Gochan tables. It will be provisioned in the next step."
|
||||
case dbStatusNoPrefix:
|
||||
return "Since no prefix was specified, the installer will attempt to provision the database in the next step."
|
||||
case dbStatusTablesExist:
|
||||
return fmt.Sprintf("The database appears to contain Gochan tables with the prefix %s. The next step (database provisioning) may return errors", config.GetSystemCriticalConfig().DBprefix)
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func installHandler(writer http.ResponseWriter, req bunrouter.Request) (err error) {
|
||||
infoEv, warnEv, errEv := gcutil.LogRequest(req.Request)
|
||||
var buf bytes.Buffer
|
||||
httpStatus := http.StatusOK
|
||||
page := req.Param("page")
|
||||
|
||||
defer func() {
|
||||
gcutil.LogDiscard(infoEv, warnEv, errEv)
|
||||
writer.WriteHeader(httpStatus)
|
||||
if err == nil {
|
||||
writer.Write(buf.Bytes())
|
||||
} else {
|
||||
server.ServeError(writer, err, false, nil)
|
||||
}
|
||||
if page == "save" {
|
||||
installServerStopper <- 1
|
||||
}
|
||||
}()
|
||||
var pageTitle string
|
||||
data := map[string]any{
|
||||
"page": page,
|
||||
"config": cfg,
|
||||
"nextButton": "Next",
|
||||
}
|
||||
|
||||
refererResult, err := serverutil.CheckReferer(req.Request)
|
||||
if err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
warnEv.Err(err).Caller().
|
||||
Str("referer", req.Referer()).
|
||||
Msg("Failed to check referer")
|
||||
return
|
||||
}
|
||||
|
||||
if refererResult == serverutil.NoReferer && req.Method == http.MethodPost {
|
||||
httpStatus = http.StatusBadRequest
|
||||
warnEv.Caller().Msg("No referer present for POST request")
|
||||
return
|
||||
} else if refererResult == serverutil.ExternalReferer {
|
||||
httpStatus = http.StatusForbidden
|
||||
warnEv.Caller().
|
||||
Str("referer", req.Referer()).
|
||||
Msg("Request came from an external referer (not allowed during installation)")
|
||||
return errors.New("your post looks like spam")
|
||||
}
|
||||
|
||||
switch page {
|
||||
case "":
|
||||
pageTitle = "Gochan Installation"
|
||||
data["nextPage"] = "license"
|
||||
case "license":
|
||||
pageTitle = "License"
|
||||
data["license"] = licenseTxt
|
||||
data["nextPage"] = "paths"
|
||||
case "paths":
|
||||
pageTitle = "Paths"
|
||||
data["cfgPaths"] = cfgPaths
|
||||
data["nextPage"] = "database"
|
||||
case "database":
|
||||
var pathFormData pathsForm
|
||||
if err = forms.FillStructFromForm(req.Request, &pathFormData); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Failed to fill form data")
|
||||
return
|
||||
}
|
||||
if err = pathFormData.validate(warnEv, errEv); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
configPath = pathFormData.ConfigPath
|
||||
cfg.DocumentRoot = pathFormData.DocumentRoot
|
||||
cfg.LogDir = pathFormData.LogDir
|
||||
cfg.TemplateDir = pathFormData.TemplateDir
|
||||
cfg.WebRoot = pathFormData.WebRoot
|
||||
config.SetSystemCriticalConfig(&cfg.SystemCriticalConfig)
|
||||
|
||||
pageTitle = "Database Setup"
|
||||
data["nextPage"] = "dbtest"
|
||||
data["nextButton"] = "Test Connection"
|
||||
case "dbtest":
|
||||
pageTitle = "Database Test"
|
||||
var dbFormData dbForm
|
||||
if err = forms.FillStructFromForm(req.Request, &dbFormData); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Failed to fill form data")
|
||||
return
|
||||
}
|
||||
if currentDBStatus, err = dbFormData.validate(); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Database test failed")
|
||||
return err
|
||||
}
|
||||
|
||||
data["testResult"] = currentDBStatus.String()
|
||||
cfg.DBtype = dbFormData.DBtype
|
||||
cfg.DBhost = dbFormData.DBhost
|
||||
cfg.DBname = dbFormData.DBname
|
||||
cfg.DBusername = dbFormData.DBuser
|
||||
cfg.DBpassword = dbFormData.DBpass
|
||||
cfg.DBprefix = dbFormData.DBprefix
|
||||
config.SetSystemCriticalConfig(&cfg.SystemCriticalConfig)
|
||||
|
||||
data["nextPage"] = "staff"
|
||||
case "staff":
|
||||
pageTitle = "Create Administrator Account"
|
||||
|
||||
if adminUser != nil {
|
||||
data["nextButton"] = "Next"
|
||||
data["alreadyCreated"] = true
|
||||
break
|
||||
}
|
||||
|
||||
// staff not created yet, show new admin form
|
||||
if currentDBStatus == dbStatusUnknown {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Msg("Database status is unknown, cannot proceed with provisioning")
|
||||
return errors.New("database status is unknown, cannot proceed with provisioning")
|
||||
}
|
||||
|
||||
err := gcsql.CheckAndInitializeDatabase(cfg.DBtype, false)
|
||||
if err != nil {
|
||||
errEv.Err(err).Msg("Failed to initialize database")
|
||||
httpStatus = http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gcsql.ResetViews(); err != nil {
|
||||
errEv.Err(err).Msg("Failed to reset database views")
|
||||
httpStatus = http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
|
||||
data["nextPage"] = "pre-save"
|
||||
case "pre-save":
|
||||
pageTitle = "Configuration Confirmation"
|
||||
|
||||
var staffFormData staffForm
|
||||
if err = forms.FillStructFromForm(req.Request, &staffFormData); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Failed to fill form data")
|
||||
return
|
||||
}
|
||||
if err = staffFormData.validate(); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
warnEv.Err(err).Msg("Invalid staff form data")
|
||||
return
|
||||
}
|
||||
|
||||
adminUser, err = gcsql.NewStaff(staffFormData.Username, staffFormData.Password, 3)
|
||||
if err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to create administrator account")
|
||||
return err
|
||||
}
|
||||
|
||||
if configPath == "" {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Msg("Configuration path is not set")
|
||||
return errors.New("configuration path is not set")
|
||||
}
|
||||
|
||||
var jsonBuf bytes.Buffer
|
||||
encoder := json.NewEncoder(&jsonBuf)
|
||||
encoder.SetIndent("", " ")
|
||||
if err = encoder.Encode(cfg); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to encode configuration to JSON")
|
||||
return err
|
||||
}
|
||||
data["configJSON"] = jsonBuf.String()
|
||||
data["configPath"] = configPath
|
||||
data["nextButton"] = "Save"
|
||||
data["nextPage"] = "save"
|
||||
case "save":
|
||||
pageTitle = "Save Configuration"
|
||||
if configPath == "" {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Msg("Configuration path is not set")
|
||||
return errors.New("configuration path is not set")
|
||||
}
|
||||
|
||||
if err = config.WriteConfig(configPath); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to write configuration")
|
||||
return err
|
||||
}
|
||||
|
||||
if err = building.BuildFrontPage(); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build front page")
|
||||
return err
|
||||
}
|
||||
|
||||
if err = building.BuildBoards(true); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build boards")
|
||||
return err
|
||||
}
|
||||
|
||||
infoEv.Str("configPath", configPath).Msg("Configuration written successfully")
|
||||
data["nextPage"] = ""
|
||||
default:
|
||||
httpStatus = http.StatusNotFound
|
||||
pageTitle = "Page Not Found"
|
||||
}
|
||||
|
||||
if err = building.BuildPageHeader(&buf, pageTitle, "", data); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build page header")
|
||||
return
|
||||
}
|
||||
if err = serverutil.MinifyTemplate(installTemplate, data, &buf, "text/html"); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to minify template")
|
||||
return
|
||||
}
|
||||
if err = building.BuildPageFooter(&buf); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build page footer")
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,75 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/Eggbertx/go-forms"
|
||||
"github.com/gochan-org/gochan/pkg/building"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
_ "github.com/gochan-org/gochan/pkg/gcsql/initsql"
|
||||
"github.com/gochan-org/gochan/pkg/gctemplates"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
_ "github.com/gochan-org/gochan/pkg/posting/uploads/inituploads"
|
||||
"github.com/gochan-org/gochan/pkg/server"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
"github.com/uptrace/bunrouter"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed license.txt
|
||||
licenseTxt string
|
||||
|
||||
installTemplate *template.Template
|
||||
installServerStopper chan int
|
||||
|
||||
configPath string
|
||||
|
||||
currentDBStatus = dbStatusUnknown
|
||||
adminUser *gcsql.Staff
|
||||
cfg *config.GochanConfig = config.GetDefaultConfig()
|
||||
)
|
||||
|
||||
const (
|
||||
dbStatusUnknown dbStatus = iota
|
||||
dbStatusClean
|
||||
dbStatusNoPrefix
|
||||
dbStatusTablesExist
|
||||
)
|
||||
|
||||
type dbStatus int
|
||||
|
||||
func (dbs dbStatus) String() string {
|
||||
switch dbs {
|
||||
case dbStatusClean:
|
||||
return "The database does not appear to contain any Gochan tables. It will be provisioned in the next step."
|
||||
case dbStatusNoPrefix:
|
||||
return "Since no prefix was specified, the installer will attempt to provision the database in the next step."
|
||||
case dbStatusTablesExist:
|
||||
return fmt.Sprintf("The database appears to contain Gochan tables with the prefix %s. The next step (database provisioning) may return errors", config.GetSystemCriticalConfig().DBprefix)
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
slices.Reverse(cfgPaths)
|
||||
|
||||
fatalEv := gcutil.LogFatal()
|
||||
infoEv := gcutil.LogInfo()
|
||||
defer gcutil.LogDiscard(infoEv, fatalEv)
|
||||
|
@ -176,228 +139,3 @@ func initTemplates() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installHandler(writer http.ResponseWriter, req bunrouter.Request) (err error) {
|
||||
infoEv, warnEv, errEv := gcutil.LogRequest(req.Request)
|
||||
var buf bytes.Buffer
|
||||
httpStatus := http.StatusOK
|
||||
page := req.Param("page")
|
||||
|
||||
defer func() {
|
||||
gcutil.LogDiscard(infoEv, warnEv, errEv)
|
||||
writer.WriteHeader(httpStatus)
|
||||
if err == nil {
|
||||
writer.Write(buf.Bytes())
|
||||
} else {
|
||||
server.ServeError(writer, err, false, nil)
|
||||
}
|
||||
if page == "save" {
|
||||
installServerStopper <- 1
|
||||
}
|
||||
}()
|
||||
var pageTitle string
|
||||
data := map[string]any{
|
||||
"page": page,
|
||||
"config": cfg,
|
||||
"nextButton": "Next",
|
||||
}
|
||||
|
||||
refererResult, err := serverutil.CheckReferer(req.Request)
|
||||
if err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
warnEv.Err(err).Caller().
|
||||
Str("referer", req.Referer()).
|
||||
Msg("Failed to check referer")
|
||||
return
|
||||
}
|
||||
|
||||
if refererResult == serverutil.NoReferer && req.Method == http.MethodPost {
|
||||
httpStatus = http.StatusBadRequest
|
||||
warnEv.Caller().Msg("No referer present for POST request")
|
||||
return
|
||||
} else if refererResult == serverutil.ExternalReferer {
|
||||
httpStatus = http.StatusForbidden
|
||||
warnEv.Caller().
|
||||
Str("referer", req.Referer()).
|
||||
Msg("Request came from an external referer (not allowed during installation)")
|
||||
return errors.New("your post looks like spam")
|
||||
}
|
||||
|
||||
switch page {
|
||||
case "":
|
||||
pageTitle = "Gochan Installation"
|
||||
data["nextPage"] = "license"
|
||||
case "license":
|
||||
pageTitle = "License"
|
||||
data["license"] = licenseTxt
|
||||
data["nextPage"] = "paths"
|
||||
case "paths":
|
||||
pageTitle = "Paths"
|
||||
data["cfgPaths"] = cfgPaths
|
||||
data["nextPage"] = "database"
|
||||
case "database":
|
||||
var pathFormData pathsForm
|
||||
if err = forms.FillStructFromForm(req.Request, &pathFormData); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Failed to fill form data")
|
||||
return
|
||||
}
|
||||
if err = pathFormData.validate(warnEv, errEv); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
configPath = pathFormData.ConfigPath
|
||||
cfg.DocumentRoot = pathFormData.DocumentRoot
|
||||
cfg.LogDir = pathFormData.LogDir
|
||||
cfg.TemplateDir = pathFormData.TemplateDir
|
||||
cfg.WebRoot = pathFormData.WebRoot
|
||||
config.SetSystemCriticalConfig(&cfg.SystemCriticalConfig)
|
||||
|
||||
pageTitle = "Database Setup"
|
||||
data["nextPage"] = "dbtest"
|
||||
data["nextButton"] = "Test Connection"
|
||||
case "dbtest":
|
||||
pageTitle = "Database Test"
|
||||
var dbFormData dbForm
|
||||
if err = forms.FillStructFromForm(req.Request, &dbFormData); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Failed to fill form data")
|
||||
return
|
||||
}
|
||||
if currentDBStatus, err = dbFormData.validate(); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Database test failed")
|
||||
return err
|
||||
}
|
||||
|
||||
data["testResult"] = currentDBStatus.String()
|
||||
cfg.DBtype = dbFormData.DBtype
|
||||
cfg.DBhost = dbFormData.DBhost
|
||||
cfg.DBname = dbFormData.DBname
|
||||
cfg.DBusername = dbFormData.DBuser
|
||||
cfg.DBpassword = dbFormData.DBpass
|
||||
cfg.DBprefix = dbFormData.DBprefix
|
||||
config.SetSystemCriticalConfig(&cfg.SystemCriticalConfig)
|
||||
|
||||
data["nextPage"] = "staff"
|
||||
case "staff":
|
||||
pageTitle = "Create Administrator Account"
|
||||
|
||||
if adminUser != nil {
|
||||
data["nextButton"] = "Next"
|
||||
data["alreadyCreated"] = true
|
||||
break
|
||||
}
|
||||
|
||||
// staff not created yet, show new admin form
|
||||
if currentDBStatus == dbStatusUnknown {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Msg("Database status is unknown, cannot proceed with provisioning")
|
||||
return errors.New("database status is unknown, cannot proceed with provisioning")
|
||||
}
|
||||
|
||||
err := gcsql.CheckAndInitializeDatabase(cfg.DBtype, false)
|
||||
if err != nil {
|
||||
errEv.Err(err).Msg("Failed to initialize database")
|
||||
httpStatus = http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gcsql.ResetViews(); err != nil {
|
||||
errEv.Err(err).Msg("Failed to reset database views")
|
||||
httpStatus = http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
|
||||
data["nextPage"] = "pre-save"
|
||||
case "pre-save":
|
||||
pageTitle = "Configuration Confirmation"
|
||||
|
||||
var staffFormData staffForm
|
||||
if err = forms.FillStructFromForm(req.Request, &staffFormData); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Err(err).Msg("Failed to fill form data")
|
||||
return
|
||||
}
|
||||
if err = staffFormData.validate(); err != nil {
|
||||
httpStatus = http.StatusBadRequest
|
||||
warnEv.Err(err).Msg("Invalid staff form data")
|
||||
return
|
||||
}
|
||||
|
||||
adminUser, err = gcsql.NewStaff(staffFormData.Username, staffFormData.Password, 3)
|
||||
if err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to create administrator account")
|
||||
return err
|
||||
}
|
||||
|
||||
if configPath == "" {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Msg("Configuration path is not set")
|
||||
return errors.New("configuration path is not set")
|
||||
}
|
||||
|
||||
var jsonBuf bytes.Buffer
|
||||
encoder := json.NewEncoder(&jsonBuf)
|
||||
encoder.SetIndent("", " ")
|
||||
if err = encoder.Encode(cfg); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to encode configuration to JSON")
|
||||
return err
|
||||
}
|
||||
data["configJSON"] = jsonBuf.String()
|
||||
data["configPath"] = configPath
|
||||
data["nextButton"] = "Save"
|
||||
data["nextPage"] = "save"
|
||||
case "save":
|
||||
pageTitle = "Save Configuration"
|
||||
if configPath == "" {
|
||||
httpStatus = http.StatusBadRequest
|
||||
errEv.Msg("Configuration path is not set")
|
||||
return errors.New("configuration path is not set")
|
||||
}
|
||||
|
||||
if err = config.WriteConfig(configPath); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to write configuration")
|
||||
return err
|
||||
}
|
||||
|
||||
if err = building.BuildFrontPage(); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build front page")
|
||||
return err
|
||||
}
|
||||
|
||||
if err = building.BuildBoards(true); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build boards")
|
||||
return err
|
||||
}
|
||||
|
||||
infoEv.Str("configPath", configPath).Msg("Configuration written successfully")
|
||||
data["nextPage"] = ""
|
||||
default:
|
||||
httpStatus = http.StatusNotFound
|
||||
pageTitle = "Page Not Found"
|
||||
}
|
||||
|
||||
if err = building.BuildPageHeader(&buf, pageTitle, "", data); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build page header")
|
||||
return
|
||||
}
|
||||
if err = serverutil.MinifyTemplate(installTemplate, data, &buf, "text/html"); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to minify template")
|
||||
return
|
||||
}
|
||||
if err = building.BuildPageFooter(&buf); err != nil {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
errEv.Err(err).Msg("Failed to build page footer")
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
//go:build darwin
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/gochan-org/gochan/pkg/config"
|
||||
|
||||
var (
|
||||
cfgPaths = config.StandardConfigSearchPaths
|
||||
)
|
|
@ -1,20 +0,0 @@
|
|||
//go:build !darwin && !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgPaths = slices.DeleteFunc(config.StandardConfigSearchPaths, func(s string) bool {
|
||||
return s == "/opt/homebrew/etc/gochan/gochan.json"
|
||||
}) // Exclude Homebrew path on non-macOS systems
|
||||
|
||||
)
|
||||
|
||||
func init() {
|
||||
slices.Reverse(cfgPaths) // /etc/gochan/gochan.json should be first on *nix systems
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
var (
|
||||
cfgPaths []string = nil
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue