mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-02 15:06:23 -07:00
Start setting up gochan-installer for providing a web interface for setting up configuration
This commit is contained in:
parent
fbee82edee
commit
772bd265f9
10 changed files with 302 additions and 68 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@
|
|||
.vagrant/
|
||||
*.log
|
||||
/templates/override
|
||||
/cmd/*/license.txt
|
||||
|
||||
# Go output
|
||||
/gochan*
|
||||
|
|
71
build.py
71
build.py
|
@ -52,10 +52,12 @@ gochan_version = "unknown"
|
|||
gcos = ""
|
||||
gcos_name = "" # used for release, since macOS GOOS is "darwin"
|
||||
exe = ""
|
||||
gochan_bin = ""
|
||||
gochan_exe = ""
|
||||
migration_bin = ""
|
||||
migration_exe = ""
|
||||
gochan_bin = "gochan"
|
||||
gochan_exe = gochan_bin
|
||||
installer_bin = "gochan-installer"
|
||||
installer_exe = installer_bin
|
||||
migration_bin = "gochan-migration"
|
||||
migration_exe = migration_bin
|
||||
|
||||
def path_info(loc):
|
||||
i = PATH_UNKNOWN
|
||||
|
@ -186,9 +188,8 @@ def set_vars(goos=""):
|
|||
global gcos
|
||||
global gcos_name # used for release, since macOS GOOS is "darwin"
|
||||
global exe
|
||||
global gochan_bin
|
||||
global gochan_exe
|
||||
global migration_bin
|
||||
global installer_exe
|
||||
global migration_exe
|
||||
|
||||
if goos != "":
|
||||
|
@ -203,9 +204,8 @@ def set_vars(goos=""):
|
|||
if gcos_name == "darwin":
|
||||
gcos_name = "macos"
|
||||
|
||||
gochan_bin = "gochan"
|
||||
gochan_exe = gochan_bin + exe
|
||||
migration_bin = "gochan-migration"
|
||||
installer_exe = installer_bin + exe
|
||||
migration_exe = migration_bin + exe
|
||||
|
||||
|
||||
|
@ -255,6 +255,14 @@ def build(debugging=False, plugin_path="", static_templates=False):
|
|||
sys.exit(1)
|
||||
print("Built gochan successfully")
|
||||
|
||||
copy("LICENSE", "cmd/gochan-installer/license.txt")
|
||||
gochan_installer_build_cmd = build_cmd_base + ["-o", installer_exe, "./cmd/gochan-installer"]
|
||||
status = run_cmd(gochan_installer_build_cmd, realtime=True, print_command=True)[1]
|
||||
if status != 0:
|
||||
print("Failed building gochan-installer, see command output for details")
|
||||
sys.exit(1)
|
||||
print("Built gochan-installer successfully")
|
||||
|
||||
gochan_migrate_build_cmd = build_cmd_base + ["-o", migration_exe, "./cmd/gochan-migration"]
|
||||
status = run_cmd(gochan_migrate_build_cmd, realtime=True, print_command=True)[1]
|
||||
if status != 0:
|
||||
|
@ -265,10 +273,24 @@ def build(debugging=False, plugin_path="", static_templates=False):
|
|||
|
||||
def clean():
|
||||
print("Cleaning up")
|
||||
del_files = ("gochan", "gochan.exe", "gochan-migration", "gochan-migration.exe", "releases/")
|
||||
del_files = ("gochan", "gochan.exe", "gochan-installer", "gochan-installer.exe", "gochan-migration", "gochan-migration.exe", "releases/", "cmd/gochan/license.txt")
|
||||
for del_file in del_files:
|
||||
delete(del_file)
|
||||
|
||||
def install_executable(src_file, dest_dir, symlinks=False):
|
||||
if not path.exists(src_file):
|
||||
build()
|
||||
|
||||
dest_file = path.join(dest_dir, src_file)
|
||||
print(f"Installing {src_file}, to {dest_file}")
|
||||
try:
|
||||
if symlinks:
|
||||
symlink(src_file, dest_file)
|
||||
else:
|
||||
copy(src_file, dest_file)
|
||||
except shutil.SameFileError:
|
||||
print(f"{src_file} and {dest_file} are the same file, skipping")
|
||||
|
||||
|
||||
def install(prefix="/usr", document_root="/srv/gochan", symlinks=False, js_only=False, css_only=False, templates_only=False):
|
||||
if gcos == "windows":
|
||||
|
@ -339,40 +361,21 @@ def install(prefix="/usr", document_root="/srv/gochan", symlinks=False, js_only=
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
if path.exists(gochan_exe) is False:
|
||||
build()
|
||||
print("Installing", gochan_exe, "to", path.join(prefix, "bin", gochan_exe))
|
||||
try:
|
||||
if symlinks:
|
||||
symlink(gochan_exe, path.join(prefix, "bin", gochan_exe))
|
||||
else:
|
||||
copy(gochan_exe, path.join(prefix, "bin", gochan_exe))
|
||||
except shutil.SameFileError:
|
||||
print(gochan_exe, "and", path.join(prefix, "bin", gochan_exe), "are the same file, skipping")
|
||||
|
||||
if path.exists(migration_exe) is False:
|
||||
build()
|
||||
print("Installing ", migration_exe, "to", path.join(prefix, "bin", migration_exe))
|
||||
try:
|
||||
if symlinks:
|
||||
symlink(migration_exe, path.join(prefix, "bin", migration_exe))
|
||||
else:
|
||||
copy(migration_exe, path.join(prefix, "bin", migration_exe))
|
||||
except shutil.SameFileError:
|
||||
print(migration_exe, "and", path.join(prefix, "bin", migration_exe), "are the same file, skipping")
|
||||
bin_dest_dir = path.join(prefix, "bin")
|
||||
install_executable(gochan_exe, bin_dest_dir, symlinks)
|
||||
install_executable(installer_exe, bin_dest_dir, symlinks)
|
||||
install_executable(migration_exe, bin_dest_dir, symlinks)
|
||||
|
||||
print(
|
||||
"gochan was successfully installed. If you haven't already, you should copy\n",
|
||||
"examples/configs/gochan.example.json to /etc/gochan/gochan.json (modify as needed)\n",
|
||||
"You may also need to go to https://yourgochansite/manage/rebuildall to rebuild the javascript config")
|
||||
"examples/configs/gochan.example.json to /etc/gochan/gochan.json (modify as needed)\n")
|
||||
if gcos == "linux":
|
||||
print(
|
||||
"If your Linux distribution has systemd, you will also need to run the following commands:\n",
|
||||
"cp examples/configs/gochan-[mysql|postgresql|sqlite3].service /lib/systemd/system/gochan.service\n",
|
||||
"systemctl daemon-reload\n",
|
||||
"systemctl enable gochan.service\n",
|
||||
"systemctl start gochan.service")
|
||||
print("")
|
||||
"systemctl start gochan.service\n")
|
||||
|
||||
|
||||
def js(watch=False):
|
||||
|
|
193
cmd/gochan-installer/main.go
Normal file
193
cmd/gochan-installer/main.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/building"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
_ "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
|
||||
// workingConfig *config.GochanConfig = config.GetDefaultConfig()
|
||||
)
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
fatalEv := gcutil.LogFatal()
|
||||
infoEv := gcutil.LogInfo()
|
||||
defer gcutil.LogDiscard(infoEv, fatalEv)
|
||||
|
||||
workingConfig := config.GetDefaultConfig()
|
||||
|
||||
flag.StringVar(&workingConfig.SiteHost, "host", "127.0.0.1", "Host to listen on")
|
||||
flag.IntVar(&workingConfig.Port, "port", 8080, "Port to bind to")
|
||||
flag.BoolVar(&workingConfig.UseFastCGI, "fastcgi", false, "Use FastCGI instead of HTTP")
|
||||
flag.StringVar(&workingConfig.WebRoot, "webroot", "/", "Web root path")
|
||||
flag.StringVar(&workingConfig.TemplateDir, "template-dir", "", "Template directory")
|
||||
flag.StringVar(&workingConfig.DocumentRoot, "document-root", "", "Document root directory")
|
||||
flag.Parse()
|
||||
|
||||
if jsonPath := config.GetGochanJSONPath(); jsonPath != "" {
|
||||
infoEv.Str("jsonPath", jsonPath).
|
||||
Msg("Gochan already installed (found gochan.json)")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
config.SetSiteConfig(&workingConfig.SiteConfig)
|
||||
config.SetSystemCriticalConfig(&workingConfig.SystemCriticalConfig)
|
||||
|
||||
if err = initTemplates(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
listenAddr := net.JoinHostPort(workingConfig.SiteHost, strconv.Itoa(workingConfig.Port))
|
||||
|
||||
router := server.GetRouter()
|
||||
router.GET(path.Join(workingConfig.WebRoot, "/install"), installHandler)
|
||||
router.POST(path.Join(workingConfig.WebRoot, "/install/:page"), installHandler)
|
||||
// router.GET(path.Join(workingConfig.WebRoot, "/install/:page"), installHandler)
|
||||
|
||||
if workingConfig.DocumentRoot == "" {
|
||||
fatalEv.Msg("-document-root command line argument is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var listener net.Listener
|
||||
installServerStopper = make(chan int)
|
||||
go func() {
|
||||
<-installServerStopper
|
||||
if listener != nil {
|
||||
if err = listener.Close(); err != nil {
|
||||
fatalEv.Err(err).Caller().Msg("Failed to close listener")
|
||||
}
|
||||
}
|
||||
}()
|
||||
if workingConfig.UseFastCGI {
|
||||
listener, err = net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
fatalEv.Err(err).Caller().Msg("Failed listening on address/port")
|
||||
}
|
||||
if err = fcgi.Serve(listener, router); err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
fatalEv.Err(err).Caller().Msg("Failed to serve FastCGI")
|
||||
}
|
||||
} else {
|
||||
httpServer := &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: router,
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
if err = httpServer.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
fatalEv.Err(err).Caller().Msg("Failed to serve HTTP")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
fatalEv.Err(err).Caller().Msg("Error initializing server")
|
||||
}
|
||||
}
|
||||
|
||||
func initTemplates() error {
|
||||
var err error
|
||||
|
||||
fatalEv := gcutil.LogFatal()
|
||||
defer fatalEv.Discard()
|
||||
|
||||
systemCriticalConfig := config.GetSystemCriticalConfig()
|
||||
|
||||
if systemCriticalConfig.TemplateDir == "" {
|
||||
fatalEv.Msg("-template-dir command line argument is required")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = gctemplates.InitTemplates(); err != nil {
|
||||
fatalEv.Err(err).Caller().Msg("Failed to initialize templates")
|
||||
return err
|
||||
}
|
||||
|
||||
installTemplateBytes, err := os.ReadFile(path.Join(systemCriticalConfig.TemplateDir, "install.html"))
|
||||
if err != nil {
|
||||
fatalEv.Err(err).Caller().Msg("Failed to read install template")
|
||||
}
|
||||
if installTemplate, err = gctemplates.ParseTemplate("install.html", string(installTemplateBytes)); err != nil {
|
||||
fatalEv.Err(err).Caller().Msg("Failed to parse install template")
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
defer func() {
|
||||
gcutil.LogDiscard(infoEv, warnEv, errEv)
|
||||
writer.WriteHeader(httpStatus)
|
||||
if err == nil {
|
||||
writer.Write(buf.Bytes())
|
||||
} else {
|
||||
server.ServeError(writer, err, false, nil)
|
||||
}
|
||||
}()
|
||||
var pageTitle string
|
||||
page := req.Param("page")
|
||||
data := map[string]any{
|
||||
"page": page,
|
||||
}
|
||||
switch page {
|
||||
case "":
|
||||
pageTitle = "Gochan Installation"
|
||||
case "license":
|
||||
pageTitle = "License"
|
||||
data["license"] = licenseTxt
|
||||
case "database":
|
||||
pageTitle = "Database Setup"
|
||||
case "stop":
|
||||
writer.Write([]byte("Stopping server..."))
|
||||
installServerStopper <- 1 // Stop the server
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -24,6 +24,11 @@ func initServer() {
|
|||
var err error
|
||||
systemCritical := config.GetSystemCriticalConfig()
|
||||
listenAddr := net.JoinHostPort(systemCritical.ListenAddress, strconv.Itoa(systemCritical.Port))
|
||||
fatalEv := gcutil.LogFatal().
|
||||
Str("listenAddress", systemCritical.ListenAddress).
|
||||
Bool("useFastCGI", systemCritical.UseFastCGI).
|
||||
Int("port", systemCritical.Port)
|
||||
defer fatalEv.Discard()
|
||||
|
||||
router := server.GetRouter()
|
||||
router.GET(config.WebPath("/captcha"), bunrouter.HTTPHandlerFunc(posting.ServeCaptcha))
|
||||
|
@ -41,9 +46,7 @@ func initServer() {
|
|||
if systemCritical.UseFastCGI {
|
||||
listener, err = net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
gcutil.LogFatal().Err(err).Caller().
|
||||
Str("ListenAddress", systemCritical.ListenAddress).
|
||||
Int("Port", systemCritical.Port).Msg("Failed listening on address/port")
|
||||
fatalEv.Err(err).Caller().Msg("Failed listening on address/port")
|
||||
}
|
||||
err = fcgi.Serve(listener, router)
|
||||
} else {
|
||||
|
@ -56,8 +59,7 @@ func initServer() {
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
gcutil.LogFatal().Err(err).Caller().
|
||||
Msg("Error initializing server")
|
||||
fatalEv.Err(err).Caller().Msg("Error initializing server")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ var (
|
|||
ErrNoMatchingEmbedHandler = errors.New("no matching handler for the embed URL")
|
||||
)
|
||||
|
||||
type InitialSetupStatus int
|
||||
|
||||
type GochanConfig struct {
|
||||
SystemCriticalConfig
|
||||
SiteConfig
|
||||
|
@ -863,6 +865,14 @@ func (em *EmbedMatcher) HasThumbnail() bool {
|
|||
return em.ThumbnailURLTemplate != ""
|
||||
}
|
||||
|
||||
func GetInitialSetupStatus() InitialSetupStatus {
|
||||
return initialSetupStatus
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *GochanConfig {
|
||||
return defaultGochanConfig
|
||||
}
|
||||
|
||||
func WriteConfig() error {
|
||||
return cfg.Write()
|
||||
}
|
||||
|
|
|
@ -44,21 +44,6 @@ func SetRandomSeed(seed string) {
|
|||
cfg.RandomSeed = seed
|
||||
}
|
||||
|
||||
// SetSystemCriticalConfig sets system critical configuration values in testing. It will panic if it is not run in a
|
||||
// test environment
|
||||
func SetSystemCriticalConfig(systemCritical *SystemCriticalConfig) {
|
||||
testutil.PanicIfNotTest()
|
||||
setDefaultCfgIfNotSet()
|
||||
cfg.SystemCriticalConfig = *systemCritical
|
||||
}
|
||||
|
||||
// SetSiteConfig sets the site configuration values in testing. It will panic if it is not run in a test environment
|
||||
func SetSiteConfig(siteConfig *SiteConfig) {
|
||||
testutil.PanicIfNotTest()
|
||||
setDefaultCfgIfNotSet()
|
||||
cfg.SiteConfig = *siteConfig
|
||||
}
|
||||
|
||||
// SetBoardConfig applies the configuration to the given board. It will panic if it is not run in a test environment
|
||||
func SetBoardConfig(board string, boardCfg *BoardConfig) error {
|
||||
testutil.PanicIfNotTest()
|
||||
|
|
|
@ -17,13 +17,20 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
InitialSetupStatusUnknown InitialSetupStatus = iota
|
||||
InitialSetupNotStarted
|
||||
InitialSetupComplete
|
||||
|
||||
DirFileMode fs.FileMode = 0775
|
||||
NormalFileMode fs.FileMode = 0664
|
||||
)
|
||||
|
||||
var (
|
||||
uid int
|
||||
gid int
|
||||
uid int
|
||||
gid int
|
||||
standardConfigSearchPaths = []string{"gochan.json", "/usr/local/etc/gochan/gochan.json", "/etc/gochan/gochan.json"}
|
||||
|
||||
initialSetupStatus InitialSetupStatus = InitialSetupStatusUnknown
|
||||
)
|
||||
|
||||
// MissingField represents a field missing from the configuration file
|
||||
|
@ -48,6 +55,15 @@ func (iv *InvalidValueError) Error() string {
|
|||
return str
|
||||
}
|
||||
|
||||
// GetGochanJSONPath returns the location of gochan.json, searching in the working directory,
|
||||
// /usr/local/etc/gochan, and /etc/gochan in that order. If it is not found, it returns an empty string.
|
||||
func GetGochanJSONPath() string {
|
||||
if cfgPath != "" {
|
||||
return cfgPath
|
||||
}
|
||||
return gcutil.FindResource(standardConfigSearchPaths...)
|
||||
}
|
||||
|
||||
// GetUser returns the IDs of the user and group gochan should be acting as
|
||||
// when creating files. If they are 0, it is using the current user
|
||||
func GetUser() (int, int) {
|
||||
|
@ -72,7 +88,19 @@ func TakeOwnershipOfFile(f *os.File) error {
|
|||
return f.Chown(uid, gid)
|
||||
}
|
||||
|
||||
func loadConfig(searchPaths ...string) (err error) {
|
||||
// SetSystemCriticalConfig sets system critical configuration values
|
||||
func SetSystemCriticalConfig(systemCritical *SystemCriticalConfig) {
|
||||
setDefaultCfgIfNotSet()
|
||||
cfg.SystemCriticalConfig = *systemCritical
|
||||
}
|
||||
|
||||
// SetSiteConfig sets the site configuration values
|
||||
func SetSiteConfig(siteConfig *SiteConfig) {
|
||||
setDefaultCfgIfNotSet()
|
||||
cfg.SiteConfig = *siteConfig
|
||||
}
|
||||
|
||||
func loadConfig() (err error) {
|
||||
cfg = defaultGochanConfig
|
||||
if testing.Testing() {
|
||||
// create a dummy config for testing if we're using go test
|
||||
|
@ -97,7 +125,7 @@ func loadConfig(searchPaths ...string) (err error) {
|
|||
}
|
||||
return
|
||||
}
|
||||
cfgPath = gcutil.FindResource(searchPaths...)
|
||||
cfgPath = gcutil.FindResource(standardConfigSearchPaths...)
|
||||
if cfgPath == "" {
|
||||
return errors.New("gochan.json not found")
|
||||
}
|
||||
|
@ -121,11 +149,8 @@ func loadConfig(searchPaths ...string) (err error) {
|
|||
|
||||
// InitConfig loads and parses gochan.json on startup and verifies its contents
|
||||
func InitConfig() (err error) {
|
||||
var searchPaths []string
|
||||
if !testing.Testing() {
|
||||
searchPaths = []string{"gochan.json", "/usr/local/etc/gochan/gochan.json", "/etc/gochan/gochan.json"}
|
||||
}
|
||||
if err = loadConfig(searchPaths...); err != nil {
|
||||
initialSetupStatus = InitialSetupNotStarted
|
||||
if err = loadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -188,7 +213,7 @@ func InitConfig() (err error) {
|
|||
|
||||
_, zoneOffset := time.Now().Zone()
|
||||
cfg.TimeZone = zoneOffset / 60 / 60
|
||||
|
||||
initialSetupStatus = InitialSetupComplete
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -109,7 +109,11 @@ func sectionBoardsTmplFunc(sectionID int) []gcsql.Board {
|
|||
|
||||
func init() {
|
||||
events.RegisterEvent([]string{"reset-boards-sections"}, func(_ string, _ ...any) error {
|
||||
return gcsql.ResetBoardSectionArrays()
|
||||
if config.GetSQLConfig().DBhost != "" {
|
||||
// Only reset if SQL is configured
|
||||
return gcsql.ResetBoardSectionArrays()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
gctemplates.AddTemplateFuncs(template.FuncMap{
|
||||
"banMask": banMaskTmplFunc,
|
||||
|
|
|
@ -20,10 +20,13 @@ type templateRef interface {
|
|||
|
||||
// InitMinifier sets up the HTML/JS/JSON minifier if enabled in gochan.json
|
||||
func InitMinifier() {
|
||||
siteConfig := config.GetSiteConfig()
|
||||
if !siteConfig.MinifyHTML && !siteConfig.MinifyJS {
|
||||
return
|
||||
var siteConfig *config.SiteConfig
|
||||
if config.GetInitialSetupStatus() == config.InitialSetupComplete {
|
||||
siteConfig = config.GetSiteConfig()
|
||||
} else {
|
||||
siteConfig = &config.GetDefaultConfig().SiteConfig
|
||||
}
|
||||
|
||||
minifier = minify.New()
|
||||
if siteConfig.MinifyHTML {
|
||||
minifier.AddFunc("text/html", minifyHTML.Minify)
|
||||
|
@ -40,6 +43,9 @@ func canMinify(mediaType string) (minify bool) {
|
|||
InitMinifier()
|
||||
}
|
||||
}()
|
||||
if config.GetInitialSetupStatus() != config.InitialSetupComplete {
|
||||
return true
|
||||
}
|
||||
siteConfig := config.GetSiteConfig()
|
||||
if mediaType == "text/html" && siteConfig.MinifyHTML {
|
||||
return true
|
||||
|
|
5
templates/install.html
Normal file
5
templates/install.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{if eq "" .page}}
|
||||
Welcome to the Gochan installer! This installer will help you configure Gochan to run on your system, including setting the necessary directories and connecting to the SQL database.
|
||||
{{else if eq .page "license"}}
|
||||
{{else}}
|
||||
{{end}}
|
Loading…
Add table
Add a link
Reference in a new issue