/* Copyright 2021 Arsen Musayelyan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "errors" "fmt" "github.com/Masterminds/sprig" "github.com/gorilla/mux" "github.com/pelletier/go-toml" "github.com/rs/zerolog" "github.com/rs/zerolog/log" flag "github.com/spf13/pflag" "github.com/wader/gormstore/v2" "golang.org/x/crypto/bcrypt" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "html/template" "net" "net/http" "os" "path/filepath" "strings" "time" ) // Set global logger to ConsoleWriter var Log = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) type App struct { Route *mux.Router Templates map[string]*template.Template Session *gormstore.Store Config Conf } // Create new empty map to store templates var templates = map[string]*template.Template{} func main() { // Create command-line flags addr := flag.IPP("addr", "a", net.ParseIP("0.0.0.0"), "Bind address for HTTP server") port := flag.IntP("port", "p", 8080, "Bind port for HTTP server") config := flag.StringP("config", "c", "simpledash.toml", "TOML config file") hash := flag.String("hash", "", "Generate new bcrypt password hash") flag.ErrHelp = errors.New("simpledash: help requested") // Parse flags flag.Parse() if *hash != "" { hash, err := bcrypt.GenerateFromPassword([]byte(*hash), bcrypt.DefaultCost) if err != nil { Log.Fatal().Err(err).Msg("Error creating bcrypt hash") } fmt.Println(string(hash)) os.Exit(0) } // Create new router router := mux.NewRouter().StrictSlash(true) // Create OS-specific glob for all templates path := filepath.Join("resources", "templates", "*.html") // Create OS-specific glob for all card templates cardGlob := filepath.Join("resources", "templates", "cards", "*.html") // Get all template paths tmplMatches, _ := filepath.Glob(path) cardMatches, _ := filepath.Glob(cardGlob) matches := append(tmplMatches, cardMatches...) // For each template path for _, match := range matches { // Get name of file without path or extension fileName := strings.TrimSuffix(filepath.Base(match), filepath.Ext(match)) // If file is called base if fileName == "base" { // Skip continue } var err error // Parse detected template and base template, add to templates map templates[fileName], err = template.New( filepath.Base(match)).Funcs( sprig.FuncMap()).Funcs( getFuncMap()).ParseFiles( "resources/templates/base.html", match) if err != nil { Log.Fatal().Str("template", fileName).Err(err).Msg("Error parsing template") } } // Open sqlite database called sessions.db for storing sessions sessionDB, _ := gorm.Open(sqlite.Open("sessions.db"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) // Create session store from database sessionStore := gormstore.New(sessionDB, []byte("")) // Create channel to stop periodic cleanup quitCleanup := make(chan struct{}) // Clean up expired sessions every hour go sessionStore.PeriodicCleanup(1*time.Hour, quitCleanup) // Open config file configFile, err := os.Open(filepath.Clean(*config)) if err != nil { Log.Fatal().Err(err).Msg("Error opening config file") } // Create new TOML decoder dec := toml.NewDecoder(configFile) // Create new nil variable to store decoded config var decodedConf Conf // Decode config into variable err = dec.Decode(&decodedConf) if err != nil { Log.Fatal().Err(err).Msg("Error decoding config file") } // Register HTTP routes registerRoutes(App{ Route: router, Templates: templates, Session: sessionStore, Config: decodedConf, }) // Create string address from flag values strAddr := fmt.Sprint(*addr, ":", *port) // Create listener on IPv4 using address created above ln, err := net.Listen("tcp4", strAddr) if err != nil { Log.Fatal().Err(err).Msg("Error creating listener") } // Log HTTP server start Log.Info().Str("addr", strAddr).Msg("Starting HTTP server") // Start HTTP server using previously-created router and listener err = http.Serve(ln, router) if err != nil { Log.Fatal().Err(err).Msg("Error while serving") } }