webserver up and running for http/https, logger timestamp format change

This commit is contained in:
Maximilian Wagner
2025-12-25 23:31:48 +01:00
parent a54c4cf9ff
commit aa76d9c721
12 changed files with 190 additions and 62 deletions

6
.gitignore vendored
View File

@@ -6,7 +6,6 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
servtex
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
@@ -14,3 +13,8 @@ servtex
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Custom Additions
servtex
servtex.log
output

View File

@@ -1,13 +1,15 @@
package backend package backend
import ( import (
"encoding/json"
"errors"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"encoding/json"
"errors"
"git.noctra.dev/noctra/servtex/globals"
"time" "time"
"git.noctra.dev/noctra/servtex/globals"
) )
// Returns the current time in the timezone specified in the config file // Returns the current time in the timezone specified in the config file
@@ -21,13 +23,19 @@ func GetLocalTimeRFC() string {
return GetLocalTime().Format(time.RFC3339) return GetLocalTime().Format(time.RFC3339)
} }
func GetLocalTimeLog() string {
return GetLocalTime().Format("2006/01/02 15:04:05")
}
// Returns the current localtime in custom pretty print format // Returns the current localtime in custom pretty print format
func GetLocalTimePretty() string { func GetLocalTimePretty() string {
return GetLocalTime().Format("02.01.2006 15:04") return GetLocalTime().Format("02.01.2006 15:04")
} }
// Writes to log and prints to screen
func LogLine(message string) { func LogLine(message string) {
logline := "\n" + GetLocalTimeRFC() + " - " + message logline := GetLocalTimeLog() + " ServTeX: " + message + "\n"
fmt.Print(logline)
globals.LogFile.WriteString(logline) globals.LogFile.WriteString(logline)
} }
@@ -37,8 +45,7 @@ func LogLine(message string) {
func configReaderParse(filePath string, configOptionStorage *globals.Config) error { func configReaderParse(filePath string, configOptionStorage *globals.Config) error {
jsonData, err := os.ReadFile(filePath) jsonData, err := os.ReadFile(filePath)
if err == nil { if err == nil {
err = json.Unmarshal(jsonData, &configOptionStorage) if err = json.Unmarshal(jsonData, &configOptionStorage); err != nil {
if err != nil {
return errors.New("Config file could not be read") return errors.New("Config file could not be read")
} }
} else { } else {
@@ -89,10 +96,13 @@ func ChangeWatch(path string, callOnChange func()) {
// Intended to be run as goroutine // Intended to be run as goroutine
func LatexCompile(config globals.Config, execution *globals.LatexExecution) error { func LatexCompile(config globals.Config, execution *globals.LatexExecution) error {
if !execution.ExecutionLock.TryLock() { if execution.ExecutionLock.TryLock() {
defer execution.ExecutionLock.Unlock()
} else {
LogLine("LaTeX execution already underway") LogLine("LaTeX execution already underway")
return errors.New("Execution already in progress") return errors.New("Execution already in progress")
} }
LogLine("LaTeX execution started") LogLine("LaTeX execution started")
execution.ExecutionState = "Started" execution.ExecutionState = "Started"
@@ -109,7 +119,6 @@ func LatexCompile(config globals.Config, execution *globals.LatexExecution) erro
execution.ExecutionState = "Unknown Latex Engine" execution.ExecutionState = "Unknown Latex Engine"
execution.Output = []byte{} execution.Output = []byte{}
execution.Timestamp = GetLocalTimePretty() execution.Timestamp = GetLocalTimePretty()
execution.ExecutionLock.Unlock()
return errors.New("Unknown Latex Engine") return errors.New("Unknown Latex Engine")
} }
@@ -123,7 +132,6 @@ func LatexCompile(config globals.Config, execution *globals.LatexExecution) erro
execution.Output = stdout execution.Output = stdout
execution.Timestamp = GetLocalTimePretty() execution.Timestamp = GetLocalTimePretty()
execution.ExecutionState = "Done" execution.ExecutionState = "Done"
execution.ExecutionLock.Unlock()
return nil return nil
} }

View File

@@ -6,9 +6,11 @@
"latexSourceFilePath": "", "latexSourceFilePath": "",
"latexOutputPath": "./output", "latexOutputPath": "./output",
"webserverDomain": "localhost:9876", "webserverDomain": "localhost",
"webserverSecure": false, "webserverPort": "8080",
"certificatePath": "", "webserverSecure": true,
"certificateKeyPath": "", "webserverPortSecure": "8443",
"certificatePath": "./testing.crt",
"certificateKeyPath": "./testing.key",
"timezone": "Europe/Berlin" "timezone": "Europe/Berlin"
} }

View File

@@ -1,27 +0,0 @@
{{ define "body" }}
<body>
<div class="container-fluid h-100 d-flex flex-column">
<div class="row output flex-grow-1">
<div class="col-3 left-sidebar" hx-sse="connect:/sse on:status" hx-swap="innerHTML">
Status <br>
Last Compilation <br>
Such Info <br>
Much wow
</div>
<div class="col-9 p-0">
<iframe class="pdf-frame" src="servetex.pdf?ts=0" hx-sse="connect:/sse on:pdf" hx-swap="outerHTML"></iframe>
</div>
</div>
<div class="command-out" hx-sse="connect:/sse on:output" hx-swap="innerHTML">
compile start
compile file-x
compile file-y
example output
warning but who cares
finished compilation
output file created
</div>
</div>
</div>
</body>
{{ end }}

View File

@@ -1,14 +0,0 @@
{{ define "head" }}
<!DOCTYPE html>
<html lang="en">
<head>
<title>ServTeX</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/jscss/bootstrap.min.css">
<link rel="stylesheet" href="/jscss/custom-view.css">
<script src="/jscss/htmx.min.js"></script>
</head>
{{ end }}

View File

@@ -1 +1,41 @@
{{ template "header" . }} <!DOCTYPE html>
<html lang="en">
<head>
<title>ServTeX</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/jscss/bootstrap.min.css">
<link rel="stylesheet" href="/jscss/custom-view.css">
<script src="/jscss/htmx.min.js"></script>
<script src="/jscss/htmx-ext-sse.js"></script>
</head>
<body>
<div class="container-fluid h-100 d-flex flex-column">
<div class="row output flex-grow-1">
<div class="col-3 left-sidebar" hx-sse="connect:/sse on:status" hx-swap="innerHTML">
Status <br>
Last Compilation <br>
Such Info <br>
Much wow
</div>
<div class="col-9 p-0">
<iframe class="pdf-frame" src="/pdf?ts=0" hx-sse="connect:/sse on:pdf" hx-swap="outerHTML"></iframe>
</div>
</div>
<div class="command-out" hx-sse="connect:/sse on:output" hx-swap="innerHTML">
compile start<br>
compile file-x<br>
compile file-y<br>
example output<br>
warning but who cares<br>
finished compilation<br>
output file created<br>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1 +0,0 @@
<iframe class="pdf-frame" src="servetex.pdf?ts={{ .timestamp }}" hx-sse="connect:/sse/pdf" hx-swap="outerHTML"></iframe>

View File

@@ -3,6 +3,7 @@ package frontend
import ( import (
"fmt" "fmt"
"time" "time"
"embed"
"strings" "strings"
"net/http" "net/http"
"net/url" "net/url"
@@ -10,6 +11,13 @@ import (
"git.noctra.dev/noctra/servtex/globals" "git.noctra.dev/noctra/servtex/globals"
) )
//go:embed jscss/bootstrap.min.css
//go:embed jscss/custom-view.css
//go:embed jscss/htmx.min.js
//go:embed jscss/htmx-ext-sse.js
//go:embed templates/main.html
var WebFiles embed.FS
// Adds necessary Headers for SSE // Adds necessary Headers for SSE
func sseHeadersAdd(writer *http.ResponseWriter) { func sseHeadersAdd(writer *http.ResponseWriter) {
(*writer).Header().Set("Content-Type", "text/event-stream") (*writer).Header().Set("Content-Type", "text/event-stream")
@@ -74,7 +82,7 @@ func sseOutputSend(writer *http.ResponseWriter) {
// Server Side Event Handler // Server Side Event Handler
// //
// Sends a Ping instead of actual data when no new data available to save bandwidth // Sends a Ping instead of actual data when no new data available to save bandwidth
func SSEEventSender(writer http.ResponseWriter, request *http.Request) { func SSEventHandler(writer http.ResponseWriter, request *http.Request) {
lastExecution := "" lastExecution := ""
for range time.Tick(time.Second) { for range time.Tick(time.Second) {
if lastExecution == globals.LatexExec.Timestamp { if lastExecution == globals.LatexExec.Timestamp {
@@ -89,3 +97,15 @@ func SSEEventSender(writer http.ResponseWriter, request *http.Request) {
} }
} }
func PDFHandler(writer http.ResponseWriter, request *http.Request) {
http.NotFound(writer, request)
}
// Serves the main page of ServTeX
func MainHandler(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "text/html")
main, _ := WebFiles.ReadFile("templates/main.html")
writer.Write(main)
}

View File

@@ -17,7 +17,9 @@ type Config struct {
// webserver // webserver
WebserverDomain string `json:"webserverDomain"` WebserverDomain string `json:"webserverDomain"`
WebserverPort string `json:"webserverPort"`
WebserverSecure bool `json:"webserverSecure"` WebserverSecure bool `json:"webserverSecure"`
WebserverPortSecure string `json:"webserverPortSecure"`
CertificatePath string `json:"certificatePath"` CertificatePath string `json:"certificatePath"`
CertificateKeyPath string `json:"certificateKeyPath"` CertificateKeyPath string `json:"certificateKeyPath"`
Timezone string `json:"timezone"` Timezone string `json:"timezone"`
@@ -35,3 +37,4 @@ var LatexExec LatexExecution
var LogFile *os.File var LogFile *os.File

54
main.go
View File

@@ -2,20 +2,66 @@ package main
import ( import (
"os" "os"
"git.noctra.dev/noctra/servtex/globals" "os/signal"
"syscall"
"io/fs"
"context"
"fmt"
"time"
"net/http"
"git.noctra.dev/noctra/servtex/backend" "git.noctra.dev/noctra/servtex/backend"
//"net/http" "git.noctra.dev/noctra/servtex/frontend"
"git.noctra.dev/noctra/servtex/globals"
) )
// Exit Codes: // Exit Codes:
// 0 - ok // 0 - ok
// 1 - config file could not be read // 1 - config file could not be read
// 2 - log file could not be accessed
func main() { func main() {
// application init
err := backend.ConfigReader("config.json", &globals.AppConfig) err := backend.ConfigReader("config.json", &globals.AppConfig)
if err != nil { os.Exit(1) } if err != nil { os.Exit(1) }
globals.LogFile, err = os.Open(globals.AppConfig.LogFilePath) globals.LogFile, err = os.OpenFile(globals.AppConfig.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { os.Exit(2) } if err != nil { os.Exit(2) }
globals.LatexExec.Timestamp = "mytimestamp"
// webserver init
server := &http.Server{Addr: globals.AppConfig.WebserverDomain + ":" + globals.AppConfig.WebserverPort}
serverSecure := &http.Server{Addr: globals.AppConfig.WebserverDomain + ":" + globals.AppConfig.WebserverPortSecure}
http.HandleFunc("/", frontend.MainHandler)
http.HandleFunc("/sse", frontend.SSEventHandler)
http.HandleFunc("/pdf", frontend.PDFHandler)
jscss, _ := fs.Sub(frontend.WebFiles, "jscss")
http.Handle("/jscss/", http.StripPrefix("/jscss/", http.FileServer(http.FS(jscss))))
go server.ListenAndServe()
if globals.AppConfig.WebserverSecure {
go serverSecure.ListenAndServeTLS(globals.AppConfig.CertificatePath, globals.AppConfig.CertificateKeyPath)
}
backend.LogLine("Started")
// shutdown logic
fmt.Println("Press CTRL-C to Exit ServTeX")
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
context, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
fmt.Print("\r")
if err = server.Shutdown(context); err != nil {
backend.LogLine("Graceful Shutdown failed")
}
if err = serverSecure.Shutdown(context); err != nil {
backend.LogLine("Graceful Shutdown failed")
}
backend.LogLine("Stopped")
} }

19
testing.crt Normal file
View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUfDzMzEHwX7jXT3lKp9+o3MreMNUwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MTIyNTIxNTgzMFoXDTI2MTIy
NTIxNTgzMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAyORa1+z/hdvaFX9IRRiadw51xmuVNWenMslb+hu2+PWz
yAwYLNm03JAomFV/Qxm3RCUiZ9kPPw+CWjert40BX908k7rb5IeV4pHAsJkvR65T
ZFCnCpR+e7nOIhy7zDmlilcATq77/AWhyfodR0lUuSTMSO8dYwZRs2rTAkItAo3j
RnMDom742lBjTn7b4F/FZOstbPXX9j1povIyYQ5k92HqRwVeTG+h7w3njnoMOYeW
YOWUUB1LyPHdzPJ50ooqvsNdBcYkkAR8C/R5czUaF/KUXHYM0zoz+QPk+KqTRGeJ
XNVB99viI6QlNVniY0YtJBWl/TEzSQVbt1lEMf3JmwIDAQABo1MwUTAdBgNVHQ4E
FgQUscO27AhUsPrR0NaYDuWgHvgFMrIwHwYDVR0jBBgwFoAUscO27AhUsPrR0NaY
DuWgHvgFMrIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANDUA
YrkofOVQ4Y33YGYTpPSf8CKIOMgJuKFihre7fcBevwRqBkhu+QBty3hvfzJSzUEx
bT9twiO94F8gke1KAWjZQpd05aAtoa3OHf3JPTvRlPqNMpP+g+gCAj2Fm3NT8yBW
nQyIRFg1DVydc61HxvR49DqiigzV+GgjcSS/WAtEDu6l/WhmvGCTHwYxY6w3LT+R
3E/JEp/Mp9o55IFR9tz5wL3R5w4R5JwQZOKdbzQPkYjoxXjI3ldfXC9U/h86t5Md
QmdWYyIfQxsqGoA0LAsUll/zwv1ywudeK1lxNtywXIKIur90a3D81MiTkzbhytVE
DKD9klpayWzekOXrlA==
-----END CERTIFICATE-----

28
testing.key Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDI5FrX7P+F29oV
f0hFGJp3DnXGa5U1Z6cyyVv6G7b49bPIDBgs2bTckCiYVX9DGbdEJSJn2Q8/D4Ja
N6u3jQFf3TyTutvkh5XikcCwmS9HrlNkUKcKlH57uc4iHLvMOaWKVwBOrvv8BaHJ
+h1HSVS5JMxI7x1jBlGzatMCQi0CjeNGcwOibvjaUGNOftvgX8Vk6y1s9df2PWmi
8jJhDmT3YepHBV5Mb6HvDeeOegw5h5Zg5ZRQHUvI8d3M8nnSiiq+w10FxiSQBHwL
9HlzNRoX8pRcdgzTOjP5A+T4qpNEZ4lc1UH32+IjpCU1WeJjRi0kFaX9MTNJBVu3
WUQx/cmbAgMBAAECggEAY3JboBlT0rVnS44DPiU0TeyME+ns5o+FvsfcLr8qgCrN
USHfk9A/zpHUbrigM5zW0raZRhQ3Dm4EhtmZOVdlj0mrM6xkL0iJQ6wIWcOzGoZr
BLVCQ6QHywLLTqqvsqT01DtGTS0lU3iMQzp75O6hsLdNI2uvPfaCWlFu3Gba9jBb
ZdT+qd9cca3FQSL0fut3msLNr2STtKLgVTEB7IHz2lEbsioBTcd7IlrsXQKTsR9N
UOyTbOzDybt5kWmDYvtuQ7cfMfEuv4fWCBtFib1JljswJI2TaE1Gwpb7u8Hcq2w9
FCAPtxltYnNRO4Xhru+zkxnG2kVNf/3Z6jzsnfIFHQKBgQDqOl4cwiev8oLwyXbR
UVL7dfXjTGv6PphRAvXHwkOAH8xzndQT6ro7g3ftODDnndN/+/orblB3K4iBMQeU
GVkmKQb1J2sXhRyf5e60WX3ZrhmW7+n7Fmx+znF98NZNSF6QhoYQmfy2ffXlwped
FFyZIkD+MrpNn0hpDa+yS/uA9QKBgQDbkLtxZMo1NKBhC8hKLRO4gzNzTt4n3cbm
psT8bo56fJYjWcr6RGlUJXF+Hol+2cn7MUtYJDpfwT72L6+BF0uv6R6s8eL2R5gE
/gBkIhvx0fMrP5hR+GPd5Gd7PLudbNL5hVH1uOBOkbscy/XhIon32iD+AZzTkybP
DOxO5uZGTwKBgGxkRlkYoDUUDPRQxuNmtvgXRorBOta7UNFshUDD7WjFTl/SkeoF
ndkcpcrpTfhhWRbJDKQ8kJAVXT4r6k3mzRKTudyJOU3RE8YLKcPcBhlOMBlhPO4t
Glg0QOD/Kqzo6JoJJtFX8VKiR8DjpDXUzmUvLNR1tTFmnKPA6aWg8+phAoGADDww
dc0sB3L7TO0fKCMC6lFFWLOYZZhSMSAx8e8nOWQf6bBjQzb0t5+uh1ykRNFWFA1X
KX47UoKuQ4G8wfDOYusWroR8JUUwD3coBmxwKjWM22gb0NWKmx7TNWbY/ZjG2Oi4
/Hxk43vzdVNYTEdkcM9S71SfrJqSmw8ZS/xJ8LkCgYEA3DaHLR4KNitppL4Tx+5j
dpIKUlCVCtxp4cOtpyuDSg8ssfHB3ZsX7j3+KFX0hF4imORAfUlUkzayuAAw6spF
luPjDo+9g8mmVi3FArkbmef67L98A8C947EpoaqAeZWmFpG2w5tOB0vZcn/gFzOp
hA4tRHe4o26/PRBUb4GFlq8=
-----END PRIVATE KEY-----