|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
package backend
|
|
|
|
package backend
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
|
|
|
|
"embed"
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
|
@@ -13,7 +14,6 @@ import (
|
|
|
|
"slices"
|
|
|
|
"slices"
|
|
|
|
"strings"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
"embed"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"git.noctra.dev/noctra/servtex/globals"
|
|
|
|
"git.noctra.dev/noctra/servtex/globals"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
|
|
@@ -22,9 +22,15 @@ import (
|
|
|
|
//go:embed default_config.json
|
|
|
|
//go:embed default_config.json
|
|
|
|
var defaultConfig embed.FS
|
|
|
|
var defaultConfig embed.FS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// There are no errors if I can't see them
|
|
|
|
|
|
|
|
func Must[T any](object T, err error) T {
|
|
|
|
|
|
|
|
return object
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Returns the current time in the timezone specified in the config file
|
|
|
|
// Returns the current time in the timezone specified in the config file
|
|
|
|
func GetLocalTime() time.Time {
|
|
|
|
func GetLocalTime() time.Time {
|
|
|
|
timezone, _ := time.LoadLocation(globals.AppConfig.Timezone)
|
|
|
|
timezone := Must(time.LoadLocation(globals.AppConfig.Timezone))
|
|
|
|
return time.Now().In(timezone)
|
|
|
|
return time.Now().In(timezone)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -57,7 +63,7 @@ func LogLine(message string, level int) {
|
|
|
|
globals.LogFile.WriteString(logline)
|
|
|
|
globals.LogFile.WriteString(logline)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Logs client info. Configured log level sets the verbosity.
|
|
|
|
// Logs client info. Configured log level sets the verbosity. Logs only up to level INFO
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Bad Request if err != nil
|
|
|
|
// Bad Request if err != nil
|
|
|
|
func VerifyLogRequest(request *http.Request) (err error) {
|
|
|
|
func VerifyLogRequest(request *http.Request) (err error) {
|
|
|
|
@@ -66,16 +72,16 @@ func VerifyLogRequest(request *http.Request) (err error) {
|
|
|
|
|
|
|
|
|
|
|
|
if client.Proxied && globals.AppConfig.LogLevelNumeric > 1 {
|
|
|
|
if client.Proxied && globals.AppConfig.LogLevelNumeric > 1 {
|
|
|
|
LogLine(fmt.Sprintf(
|
|
|
|
LogLine(fmt.Sprintf(
|
|
|
|
"Request: %s via %s - %s %s",
|
|
|
|
"httpR: %s via %s - %s %s",
|
|
|
|
client.ClientIP,
|
|
|
|
client.ClientIP,
|
|
|
|
client.Proxy,
|
|
|
|
client.Proxy,
|
|
|
|
client.RequestType,
|
|
|
|
client.RequestType,
|
|
|
|
client.RequestPath,
|
|
|
|
client.RequestPath,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
4,
|
|
|
|
2,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
LogLine(fmt.Sprintf("Request: %s - %s %s", client.ClientIP, client.RequestType, client.RequestPath), 4)
|
|
|
|
LogLine(fmt.Sprintf("httpR: %s - %s %s", client.ClientIP, client.RequestType, client.RequestPath), 2)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
@@ -84,7 +90,7 @@ func VerifyLogRequest(request *http.Request) (err error) {
|
|
|
|
|
|
|
|
|
|
|
|
// Writes a configuration file populated with defaults
|
|
|
|
// Writes a configuration file populated with defaults
|
|
|
|
func createConfigWithDefaults(path string) error {
|
|
|
|
func createConfigWithDefaults(path string) error {
|
|
|
|
config, _ := defaultConfig.ReadFile("default_config.json")
|
|
|
|
config := Must(defaultConfig.ReadFile("default_config.json"))
|
|
|
|
err := os.WriteFile(path, config, 0644)
|
|
|
|
err := os.WriteFile(path, config, 0644)
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -122,7 +128,7 @@ func configReaderParse(filePath string, configOptionStorage *globals.Config) err
|
|
|
|
|
|
|
|
|
|
|
|
// populates the appconfig with default values
|
|
|
|
// populates the appconfig with default values
|
|
|
|
func configPopulator(configOptionStorage *globals.Config) {
|
|
|
|
func configPopulator(configOptionStorage *globals.Config) {
|
|
|
|
jsonData, _ := defaultConfig.ReadFile("default_config.json")
|
|
|
|
jsonData := Must(defaultConfig.ReadFile("default_config.json"))
|
|
|
|
json.Unmarshal(jsonData, &configOptionStorage)
|
|
|
|
json.Unmarshal(jsonData, &configOptionStorage)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -175,7 +181,6 @@ func watchLoop(watcher *fsnotify.Watcher, callOnChange func() error) {
|
|
|
|
defer watcher.Close()
|
|
|
|
defer watcher.Close()
|
|
|
|
for {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
|
// Errors
|
|
|
|
|
|
|
|
case err, ok := <-watcher.Errors:
|
|
|
|
case err, ok := <-watcher.Errors:
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
LogLine(fmt.Sprintf("Watcher crashed on error, manual compile only: %s", err), 4)
|
|
|
|
LogLine(fmt.Sprintf("Watcher crashed on error, manual compile only: %s", err), 4)
|
|
|
|
@@ -183,7 +188,6 @@ func watchLoop(watcher *fsnotify.Watcher, callOnChange func() error) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LogLine(fmt.Sprintf("Watcher Error: %s", err), 3)
|
|
|
|
LogLine(fmt.Sprintf("Watcher Error: %s", err), 3)
|
|
|
|
|
|
|
|
|
|
|
|
// Events
|
|
|
|
|
|
|
|
case event, ok := <-watcher.Events:
|
|
|
|
case event, ok := <-watcher.Events:
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
LogLine(fmt.Sprintf("Watcher crashed on event, manual compile only: %s", event), 4)
|
|
|
|
LogLine(fmt.Sprintf("Watcher crashed on event, manual compile only: %s", event), 4)
|
|
|
|
@@ -204,6 +208,7 @@ func watchLoop(watcher *fsnotify.Watcher, callOnChange func() error) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Returns a list of all .tex files recursively found in the passed directory
|
|
|
|
func texFinder(path string) []string {
|
|
|
|
func texFinder(path string) []string {
|
|
|
|
var texFiles []string
|
|
|
|
var texFiles []string
|
|
|
|
err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
|
|
err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
|
|
@@ -216,7 +221,7 @@ func texFinder(path string) []string {
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
LogLine("Some paths could not be traversed while search for .tex files", 3)
|
|
|
|
LogLine(fmt.Sprintf("Some paths could not be traversed while search for .tex files: %s", err), 3)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return texFiles
|
|
|
|
return texFiles
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -249,19 +254,40 @@ func updateExecutionTimestamp(execution *globals.LatexExecution) {
|
|
|
|
execution.TimestampRFC = GetLocalTimeRFC()
|
|
|
|
execution.TimestampRFC = GetLocalTimeRFC()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Returns the necessary time to sleep for rate limiting.
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Requires an RFC3339 timestamp. A dog will die if anything else is passed.
|
|
|
|
|
|
|
|
func rateLimitTime(timestampRFC string) (sleepTime time.Duration) {
|
|
|
|
|
|
|
|
lastExeTime := Must(time.Parse(time.RFC3339, timestampRFC))
|
|
|
|
|
|
|
|
rateLimitInterval := Must(time.ParseDuration(fmt.Sprintf("%ds", globals.AppConfig.RateLimitSeconds)))
|
|
|
|
|
|
|
|
return time.Until(lastExeTime.Add(rateLimitInterval)).Round(time.Second)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Intended to be run as goroutine
|
|
|
|
// Intended to be run as goroutine
|
|
|
|
func LatexCompile() error {
|
|
|
|
func LatexCompile() error {
|
|
|
|
execution := &globals.LatexExec
|
|
|
|
execution := &globals.LatexExec
|
|
|
|
config := globals.AppConfig
|
|
|
|
config := globals.AppConfig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check for already running compilation
|
|
|
|
if execution.ExecutionLock.TryLock() {
|
|
|
|
if execution.ExecutionLock.TryLock() {
|
|
|
|
defer execution.ExecutionLock.Unlock()
|
|
|
|
defer execution.ExecutionLock.Unlock()
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
LogLine("LaTeX execution already underway", 2)
|
|
|
|
LogLine("LaTeX: Already running", 2)
|
|
|
|
return errors.New("Execution already in progress")
|
|
|
|
return errors.New("Execution already in progress")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogLine("LaTeX execution started", 2)
|
|
|
|
// rate limit or start right away
|
|
|
|
|
|
|
|
rateLimit := rateLimitTime(execution.TimestampRFC)
|
|
|
|
|
|
|
|
if rateLimit.Seconds() > 0 {
|
|
|
|
|
|
|
|
LogLine(fmt.Sprintf("LaTeX: Rate limit: %d seconds", int(rateLimit.Seconds())), 3)
|
|
|
|
|
|
|
|
execution.ExecutionState = globals.LatexExecutionStateLimit
|
|
|
|
|
|
|
|
time.Sleep(rateLimit)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
LogLine("LaTeX: Started", 3)
|
|
|
|
|
|
|
|
execution.ExecutionState = globals.LatexExecutionStateRunning
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// build command
|
|
|
|
var latexCommand *exec.Cmd
|
|
|
|
var latexCommand *exec.Cmd
|
|
|
|
switch config.LatexEngine {
|
|
|
|
switch config.LatexEngine {
|
|
|
|
case "lualatex":
|
|
|
|
case "lualatex":
|
|
|
|
@@ -277,10 +303,11 @@ func LatexCompile() error {
|
|
|
|
execution.Output = []byte{}
|
|
|
|
execution.Output = []byte{}
|
|
|
|
updateExecutionTimestamp(execution)
|
|
|
|
updateExecutionTimestamp(execution)
|
|
|
|
|
|
|
|
|
|
|
|
LogLine("Unsupported LaTeX Engine", 4)
|
|
|
|
LogLine("LaTeX: Unsupported engine", 4)
|
|
|
|
return errors.New("Unsupported LaTeX Engine")
|
|
|
|
return errors.New("Unsupported LaTeX Engine")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// execute command
|
|
|
|
stdout, err := latexCommand.Output()
|
|
|
|
stdout, err := latexCommand.Output()
|
|
|
|
execution.Output = stdout
|
|
|
|
execution.Output = stdout
|
|
|
|
if err == nil {
|
|
|
|
if err == nil {
|
|
|
|
@@ -290,7 +317,7 @@ func LatexCompile() error {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateExecutionTimestamp(execution)
|
|
|
|
updateExecutionTimestamp(execution)
|
|
|
|
|
|
|
|
|
|
|
|
LogLine("LaTeX execution finished", 2)
|
|
|
|
LogLine("LaTeX: Finished", 3)
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -317,7 +344,7 @@ func isTrustedProxy(proxy string, trustedProxies []string) (trusted bool, usedPr
|
|
|
|
// resolve trustedProxies in case a DNS name was configured
|
|
|
|
// resolve trustedProxies in case a DNS name was configured
|
|
|
|
for _, trustedProxyDNS := range trustedProxies {
|
|
|
|
for _, trustedProxyDNS := range trustedProxies {
|
|
|
|
// get IPs of this dns name
|
|
|
|
// get IPs of this dns name
|
|
|
|
trustedProxyIPs, err := net.LookupAddr(trustedProxyDNS)
|
|
|
|
trustedProxyIPs, err := net.LookupHost(trustedProxyDNS)
|
|
|
|
if err != nil { continue }
|
|
|
|
if err != nil { continue }
|
|
|
|
|
|
|
|
|
|
|
|
// set usedProxy to DNS name if proxy is trusted
|
|
|
|
// set usedProxy to DNS name if proxy is trusted
|
|
|
|
|