Compare commits

...

2 commits

Author SHA1 Message Date
ff1e3f0462 Make log location configurable 2024-05-04 15:39:15 +03:00
8d4cbdac90 Add debug flag 2024-05-04 14:29:49 +03:00
10 changed files with 36 additions and 12 deletions

View file

@ -40,7 +40,7 @@ config
### Config options: ### Config options:
Below are defaults of config.txt. The settings are defined in newline separated key=value pairs. Below are defaults of config.txt. The settings are defined in newline separated key=value pairs.
Please don't include the bash-style "comments" in your config, Please don't include the bash-style "comments" in your actual config,
they are provided purely for demonstration only and **will break the config if present**. they are provided purely for demonstration only and **will break the config if present**.
``` ```
username=admin # Your username username=admin # Your username
@ -48,7 +48,8 @@ password=admin # Your password
port=7101 # What port to run on (probably leave on 7101 if using docker) port=7101 # What port to run on (probably leave on 7101 if using docker)
timezone=Local # IANA Time zone database identifier ("UTC", Local", "Europe/Moscow" etc.), Defaults to Local if can't parse. timezone=Local # IANA Time zone database identifier ("UTC", Local", "Europe/Moscow" etc.), Defaults to Local if can't parse.
language=en # ISO-639 language code (currently supported - en, ru) language=en # ISO-639 language code (currently supported - en, ru)
log_to_file=false # Whether to write logs to a file (located in <config-dir>/log.txt) log_to_file=false # Whether to write logs to a file
log_file=config/log.txt # Where to store the log file if it is enabled
enable_scram=false # Whether the app should shut down if there are 3 or more failed login attempts within 100 seconds enable_scram=false # Whether the app should shut down if there are 3 or more failed login attempts within 100 seconds
# Not present by default, set only if you want to be notified of any failed login attempts over telegram # Not present by default, set only if you want to be notified of any failed login attempts over telegram
@ -73,4 +74,6 @@ If you for some reason decide to run plain executable instead of docker, it supp
override password override password
-port int -port int
override port override port
-debug
show debug log
``` ```

View file

@ -8,6 +8,6 @@ List of things to add to this project
* API revamp * API revamp
* Check export function for improvements * Check export function for improvements
* Customise log file * Customise log file
* More slog.Debug and a debug flag? * More slog.Debug
* Consider less clunky auth method * Consider less clunky auth method
* *Go* dependency-less? <-- this is a terrible idea * *Go* dependency-less? <-- this is a terrible idea

View file

@ -43,7 +43,7 @@ func NoteLoginFail(username string, password string, r *http.Request) {
// BasicAuth is a middleware that handles authentication & authorization for the app. // BasicAuth is a middleware that handles authentication & authorization for the app.
// It uses BasicAuth because I doubt there is a need for something sophisticated in a small hobby project // It uses BasicAuth because I doubt there is a need for something sophisticated in a small hobby project
// Originally taken from https://www.alexedwards.net/blog/basic-authentication-in-go (13.03.2024) // Originally taken from Alex Edwards's https://www.alexedwards.net/blog/basic-authentication-in-go, MIT Licensed. (13.03.2024)
func BasicAuth(next http.Handler) http.Handler { func BasicAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
@ -81,7 +81,7 @@ func Scram() {
// NotifyTelegram attempts to send a message to admin through telegram // NotifyTelegram attempts to send a message to admin through telegram
func NotifyTelegram(msg string) { func NotifyTelegram(msg string) {
if Cfg.TelegramChat == "" || Cfg.TelegramToken == "" { if Cfg.TelegramChat == "" || Cfg.TelegramToken == "" {
slog.Warn("ignoring telegram request due to lack of credentials") slog.Debug("ignoring telegram request due to lack of credentials")
return return
} }
client := &http.Client{} client := &http.Client{}

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"log/slog"
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
@ -21,6 +22,7 @@ type Config struct {
Timezone *time.Location `config:"timezone" type:"location"` Timezone *time.Location `config:"timezone" type:"location"`
Language string `config:"language" type:"string"` Language string `config:"language" type:"string"`
LogToFile bool `config:"log_to_file" type:"bool"` LogToFile bool `config:"log_to_file" type:"bool"`
LogFile string `config:"log_file" type:"string"`
Scram bool `config:"enable_scram" type:"bool"` Scram bool `config:"enable_scram" type:"bool"`
TelegramToken string `config:"tg_token" type:"string"` TelegramToken string `config:"tg_token" type:"string"`
@ -114,13 +116,22 @@ func (c *Config) Reload() error {
} else { } else {
c.Timezone = loc c.Timezone = loc
} }
slog.Debug("reloaded config", "config", c)
return LoadLanguage(c.Language) // Load selected language return LoadLanguage(c.Language) // Load selected language
} }
// ConfigInit loads config on startup // ConfigInit loads config on startup
// Some defaults are declared here
func ConfigInit() Config { func ConfigInit() Config {
cfg := Config{Port: 7101, Username: "admin", Password: "admin", Timezone: time.Local, Language: "en"} // Some defaults are declared here cfg := Config{
Port: 7101,
Username: "admin",
Password: "admin",
Timezone: time.Local,
Language: "en",
LogFile: "config/log.txt",
}
err := cfg.Reload() err := cfg.Reload()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View file

@ -4,4 +4,5 @@ port=7101
timezone=Local timezone=Local
language=en language=en
log_to_file=false log_to_file=false
log_file=config/log.txt
enable_scram=false enable_scram=false

View file

@ -11,6 +11,7 @@ func FlagInit() {
username := flag.String("user", "", "override username") username := flag.String("user", "", "override username")
password := flag.String("pass", "", "override password") password := flag.String("pass", "", "override password")
port := flag.Int("port", 0, "override port") port := flag.Int("port", 0, "override port")
debug := flag.Bool("debug", false, "debug logging")
flag.Parse() flag.Parse()
if *config != "" { if *config != "" {
@ -29,4 +30,5 @@ func FlagInit() {
if *port != 0 { if *port != 0 {
Cfg.Port = *port Cfg.Port = *port
} }
DebugMode = *debug
} }

View file

@ -9,6 +9,7 @@ import (
var Translations = map[string]string{} var Translations = map[string]string{}
// LoadLanguage loads a json file for selected language into the Translations map
func LoadLanguage(language string) error { func LoadLanguage(language string) error {
filename := "i18n/" + language + ".json" filename := "i18n/" + language + ".json"
@ -28,6 +29,7 @@ func LoadLanguage(language string) error {
return err return err
} }
// TranslatableText attempts to match an id to a string in current language
func TranslatableText(id string) string { func TranslatableText(id string) string {
if v, ok := Translations[id]; !ok { if v, ok := Translations[id]; !ok {
return id return id

View file

@ -8,15 +8,15 @@ import (
"os" "os"
) )
var LogFile = "config/log.txt" var DebugMode = false
// LogInit makes slog output to both stdout and a file if needed // LogInit makes slog output to both stdout and a file if needed, and enables debug mode if selected
func LogInit() { func LogInit() {
var w io.Writer var w io.Writer
if Cfg.LogToFile { if Cfg.LogToFile {
f, err := os.OpenFile(LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) f, err := os.OpenFile(Cfg.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil { if err != nil {
slog.Error("error opening log file, logging to stdout", "path", LogFile, "error", err) slog.Error("error opening log file, logging to stdout", "path", Cfg.LogFile, "error", err)
return return
} }
// No defer f.Close() because that breaks the MultiWriter // No defer f.Close() because that breaks the MultiWriter
@ -26,6 +26,10 @@ func LogInit() {
} }
// Make slog and chi use intended format // Make slog and chi use intended format
slog.SetDefault(slog.New(slog.NewTextHandler(w, nil))) var opts *slog.HandlerOptions
if DebugMode {
opts = &slog.HandlerOptions{Level: slog.LevelDebug}
}
slog.SetDefault(slog.New(slog.NewTextHandler(w, opts)))
middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: log.Default(), NoColor: true}) middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: log.Default(), NoColor: true})
} }

View file

@ -3,7 +3,7 @@ package main
var Cfg = ConfigInit() var Cfg = ConfigInit()
func main() { func main() {
LogInit()
FlagInit() FlagInit()
LogInit()
Serve() Serve()
} }

View file

@ -44,5 +44,6 @@ func Serve() {
r.Handle("/public/*", http.StripPrefix("/public/", fs)) r.Handle("/public/*", http.StripPrefix("/public/", fs))
slog.Info("🌺 Website working", "port", Cfg.Port) slog.Info("🌺 Website working", "port", Cfg.Port)
slog.Debug("Debug mode enabled")
log.Fatal(http.ListenAndServe(":"+strconv.Itoa(Cfg.Port), r)) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(Cfg.Port), r))
} }