bugfixes
This commit is contained in:
parent
af60ee5319
commit
ab9f1f79b5
@ -3,6 +3,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@ -246,6 +247,7 @@ func localAppHostCandidates() []string {
|
||||
host = normalizeOriginHost(host)
|
||||
if host != "" && !strings.Contains(host, ".") {
|
||||
add(host + ".local")
|
||||
addHostWithDNSSuffixes(add, host)
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,6 +280,44 @@ func addInterfaceIPv4Hosts(add func(string)) {
|
||||
}
|
||||
|
||||
add(ip4.String())
|
||||
addReverseLookupHosts(add, ip4)
|
||||
}
|
||||
}
|
||||
|
||||
func addHostWithDNSSuffixes(add func(string), host string) {
|
||||
host = normalizeOriginHost(host)
|
||||
if host == "" || strings.Contains(host, ".") {
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range []string{
|
||||
"USERDNSDOMAIN",
|
||||
"DNSDOMAIN",
|
||||
"LOCALDOMAIN",
|
||||
} {
|
||||
suffix := normalizeOriginHost(os.Getenv(key))
|
||||
if suffix == "" || !strings.Contains(suffix, ".") {
|
||||
continue
|
||||
}
|
||||
add(host + "." + suffix)
|
||||
}
|
||||
}
|
||||
|
||||
func addReverseLookupHosts(add func(string), ip net.IP) {
|
||||
if ip == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
names, err := net.DefaultResolver.LookupAddr(ctx, ip.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
add(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp_amd64.deb
vendored
BIN
backend/dist/nsfwapp_amd64.deb
vendored
Binary file not shown.
@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
//go:embed ml/*.py ml/detection_labels.json ai_server.py recorder-cert.pem recorder-key.pem yolo26n-pose.pt
|
||||
//go:embed ml/*.py ml/detection_labels.json ai_server.py yolo26n-pose.pt
|
||||
var embeddedMLFiles embed.FS
|
||||
|
||||
func embeddedWriteFileIfNeeded(dstPath string, b []byte, perm os.FileMode) error {
|
||||
@ -126,42 +126,3 @@ func embeddedPoseModelPath() (string, error) {
|
||||
|
||||
return dstPath, nil
|
||||
}
|
||||
|
||||
func embeddedTLSCertBytes() ([]byte, error) {
|
||||
return embeddedMLFiles.ReadFile("recorder-cert.pem")
|
||||
}
|
||||
|
||||
func embeddedTLSKeyBytes() ([]byte, error) {
|
||||
return embeddedMLFiles.ReadFile("recorder-key.pem")
|
||||
}
|
||||
|
||||
func embeddedTLSFilesDir() (string, string, error) {
|
||||
dir := filepath.Join(os.TempDir(), "nsfwapp-tls")
|
||||
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
certBytes, err := embeddedTLSCertBytes()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
keyBytes, err := embeddedTLSKeyBytes()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
certPath := filepath.Join(dir, "recorder-cert.pem")
|
||||
keyPath := filepath.Join(dir, "recorder-key.pem")
|
||||
|
||||
if err := os.WriteFile(certPath, certBytes, 0600); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(keyPath, keyBytes, 0600); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return certPath, keyPath, nil
|
||||
}
|
||||
|
||||
@ -5,17 +5,22 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
appLogMu sync.Mutex
|
||||
appLogFile *os.File
|
||||
appLogMu sync.Mutex
|
||||
appLogFile *os.File
|
||||
appConsoleOut *os.File
|
||||
appStdStreamsRedirected bool
|
||||
appDiagnosticsConfigured bool
|
||||
)
|
||||
|
||||
func appLogPath() string {
|
||||
@ -59,6 +64,7 @@ func initAppLog() {
|
||||
}
|
||||
|
||||
appLogFile = f
|
||||
configureAppDiagnosticsLocked()
|
||||
|
||||
_, _ = fmt.Fprintf(
|
||||
appLogFile,
|
||||
@ -69,6 +75,26 @@ func initAppLog() {
|
||||
_ = appLogFile.Sync()
|
||||
}
|
||||
|
||||
func configureAppDiagnosticsLocked() {
|
||||
if appLogFile == nil || appDiagnosticsConfigured {
|
||||
return
|
||||
}
|
||||
|
||||
log.SetOutput(appLogFile)
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
|
||||
debug.SetTraceback("all")
|
||||
|
||||
appConsoleOut = os.Stdout
|
||||
|
||||
if err := redirectProcessStdStreamsToFile(appLogFile); err != nil {
|
||||
_, _ = fmt.Fprintf(appLogFile, "⚠️ stdout/stderr konnte nicht auf recorder.log umgeleitet werden: %v\n", err)
|
||||
} else {
|
||||
appStdStreamsRedirected = true
|
||||
}
|
||||
|
||||
appDiagnosticsConfigured = true
|
||||
}
|
||||
|
||||
func closeAppLog() {
|
||||
appLogMu.Lock()
|
||||
defer appLogMu.Unlock()
|
||||
@ -113,7 +139,13 @@ func appLogWriteLine(line string) {
|
||||
appLogMu.Lock()
|
||||
defer appLogMu.Unlock()
|
||||
|
||||
fmt.Print(out)
|
||||
if appStdStreamsRedirected {
|
||||
if appConsoleOut != nil && appConsoleOut != appLogFile {
|
||||
_, _ = appConsoleOut.WriteString(out)
|
||||
}
|
||||
} else {
|
||||
fmt.Print(out)
|
||||
}
|
||||
|
||||
if appLogFile != nil {
|
||||
_, _ = appLogFile.WriteString(out)
|
||||
@ -129,6 +161,44 @@ func appLogf(format string, args ...any) {
|
||||
appLogWriteLine(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func appLogMultiline(prefix string, text string) {
|
||||
text = strings.TrimRight(text, "\r\n")
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
appLogWriteLine(prefix + strings.TrimRight(line, "\r"))
|
||||
}
|
||||
}
|
||||
|
||||
func appLogPanic(scope string, recovered any) {
|
||||
scope = strings.TrimSpace(scope)
|
||||
if scope == "" {
|
||||
scope = "unbekannt"
|
||||
}
|
||||
|
||||
appLogln("❌ PANIC in "+scope+":", recovered)
|
||||
appLogln("Stacktrace:")
|
||||
appLogMultiline(" ", string(debug.Stack()))
|
||||
}
|
||||
|
||||
func appRecoverPanic(scope string) bool {
|
||||
if recovered := recover(); recovered != nil {
|
||||
appLogPanic(scope, recovered)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func appGo(scope string, fn func()) {
|
||||
go func() {
|
||||
defer appRecoverPanic(scope)
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
|
||||
// appErrorLogSuppressed unterdrückt das automatische ❌-Logging für bekannte,
|
||||
// erwartbare und sehr häufige Fehler (z.B. abgelaufene HLS-Edge-URLs während
|
||||
// eines Stream-Reconnects). Der Fehler wird weiterhin zurückgegeben, damit die
|
||||
|
||||
26
backend/log_redirect_unix.go
Normal file
26
backend/log_redirect_unix.go
Normal file
@ -0,0 +1,26 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func redirectProcessStdStreamsToFile(f *os.File) error {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd := int(f.Fd())
|
||||
if err := syscall.Dup2(fd, int(os.Stdout.Fd())); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syscall.Dup2(fd, int(os.Stderr.Fd())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.Stdout = f
|
||||
os.Stderr = f
|
||||
return nil
|
||||
}
|
||||
27
backend/log_redirect_windows.go
Normal file
27
backend/log_redirect_windows.go
Normal file
@ -0,0 +1,27 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func redirectProcessStdStreamsToFile(f *os.File) error {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
handle := windows.Handle(f.Fd())
|
||||
if err := windows.SetStdHandle(windows.STD_OUTPUT_HANDLE, handle); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := windows.SetStdHandle(windows.STD_ERROR_HANDLE, handle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.Stdout = f
|
||||
os.Stderr = f
|
||||
return nil
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEbjCCAtagAwIBAgIRAKGvq9b44yCGC32q8PwkueowDQYJKoZIhvcNAQELBQAw
|
||||
gYsxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEwMC4GA1UECwwnVEVH
|
||||
RFNTRFxSb3RoZXJATDE0UEJCSzk1MTAwMDA2IChSb3RoZXIpMTcwNQYDVQQDDC5t
|
||||
a2NlcnQgVEVHRFNTRFxSb3RoZXJATDE0UEJCSzk1MTAwMDA2IChSb3RoZXIpMB4X
|
||||
DTI2MDUwODEwMDQzN1oXDTI4MDgwODEwMDQzN1owWzEnMCUGA1UEChMebWtjZXJ0
|
||||
IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTAwLgYDVQQLDCdURUdEU1NEXFJvdGhl
|
||||
ckBMMTRQQkJLOTUxMDAwMDYgKFJvdGhlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDDghqU3igC3sNP2zAJTZbvZcT1xxjUgk3oA2j0WwNPO+xhCH4K
|
||||
mJ6enU5zrGWI4+T0IHfbo737rvgk6XfTDSvzB9Uags8egBUGxbr9eMBya0hmnYGs
|
||||
zIQGJeI4TIeQ7sa7krCpMB/e1YxR1qYFf1C8oSt43dJCdJP4Ev//BaR7rrFt3D3N
|
||||
CC2OgxVYSh/hR9XNIIEPMoioQIYhle41mQWjyPFt16Fjg9sWrrnHSz7M4cgVVSot
|
||||
+aBlRjbxgOarLyST3XdtjAfC6CqxnzprX1LfznuULjatoS+pFDBDoy2B13xwa9Br
|
||||
f+Xy3CswmSO+AzNomWfbeBkJnl6xb3Tb1urrAgMBAAGjfDB6MA4GA1UdDwEB/wQE
|
||||
AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBR0Zph7hFKq4GpE
|
||||
XO0y3U+T0iFoQDAyBgNVHREEKzApgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA
|
||||
AAAAAAAAAAGHBAoAARkwDQYJKoZIhvcNAQELBQADggGBAJShhgQS+9cwuVOJUIW+
|
||||
uy9t1ZAjR9qTd31kHdK1vu294GiavrG8RD+92BRujG4Fq1ambvqcu9sUaC8+0HAP
|
||||
cQvdmnbTxG0ZHkGP7VVj3Poee0jpsVAVtV3aVwKxyO/E7Mv/nEKTqNfnECbEuiqC
|
||||
tOJ8mI3E9tf2/ljzq2PqvfIPFBBtUT2lihNAFXM/06Nzoa1NPdfYd58N+VBFSOSq
|
||||
zEM4fmpcqMThZGKGBoWmHwTE5Mw6mZFLyxHJpOFu5wNzyPJGes5o/F0ot5n+ZGah
|
||||
GbYaGmhEedOxC3/WXRtexJmZ0jrB8Hkx+c/4h6+0HKU/PelrFJS+c2d6h8YGXyhv
|
||||
pUsfyyoQzx7rSgyLfJqXK+52ZtnWNc0yVVMyKJwjvhv4mcP9l/q3UGR/i7xkD5Pc
|
||||
2bLgM+wc8nKI7Yd/xejLpQDC0b/P1XibT1T3GRjiC/eu64G/uFOF+m3P5XvmF8JO
|
||||
0cj8fXwUCq+TVD+22rFmtT+2VYkf8RUsC48Ou+PHccmDyw==
|
||||
-----END CERTIFICATE-----
|
||||
@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDDghqU3igC3sNP
|
||||
2zAJTZbvZcT1xxjUgk3oA2j0WwNPO+xhCH4KmJ6enU5zrGWI4+T0IHfbo737rvgk
|
||||
6XfTDSvzB9Uags8egBUGxbr9eMBya0hmnYGszIQGJeI4TIeQ7sa7krCpMB/e1YxR
|
||||
1qYFf1C8oSt43dJCdJP4Ev//BaR7rrFt3D3NCC2OgxVYSh/hR9XNIIEPMoioQIYh
|
||||
le41mQWjyPFt16Fjg9sWrrnHSz7M4cgVVSot+aBlRjbxgOarLyST3XdtjAfC6Cqx
|
||||
nzprX1LfznuULjatoS+pFDBDoy2B13xwa9Brf+Xy3CswmSO+AzNomWfbeBkJnl6x
|
||||
b3Tb1urrAgMBAAECggEBAI31EBv72w2KdkKroot+vROCz6quMAdNvgezQif7VcHY
|
||||
fuBN7EcBXltJWUeAbBEjeIESejUPBcmT2DXlF841CC5lB4VCaeV5lsreE9IsNYBf
|
||||
CakIwLmZnltgcovydZT063QTJRcUDHAems5pjw76zMLKO+h9GEiMoUxFb3/atv3e
|
||||
K5HE+uUd6JcPU0q91S6TxqvS9gH30OOkwH6Kl4tzWauQAKg/sVzYafbyTAuEpiMO
|
||||
jLLLAn/sOmiL4knnRVu1FK4aZSH+t9BN9YuwfucXFn9jFgPtIJ0bF1GuqnwIeDI5
|
||||
BTNanHfaZRHD0+FbS0KbhARIFgDveoGrdoEtou0eDOECgYEA5dJqUvk96f7zCrxD
|
||||
eSuUl1MvOjydY3GacfjevZoTxJDqh4Wt2wgLYJc4XB7tI02ci7NncecG8qPfqWq7
|
||||
aFjQAGAYdR+XvE7R2m3OFK9WbRX4cH6b+l7mSq0eOvcAAajn4RuNr/il+7Opw7zX
|
||||
eKUB/pnW36B+ethdr5l+kDUQb8MCgYEA2ccanIT2QAxJb5j0F287NGhaTPuWfxcX
|
||||
2T53I2fIwMiell0YXjwsKwKpckubRB2f5X4b9QJifUTmtxHky/Tx4H9OVhCaxumw
|
||||
edol5YNzZaP/Tx8m2mqpyN2WPbezIFeA+GNcmDAxEo/NT2B64OVCHWKosndvb+Yk
|
||||
GFmVb+g9zbkCgYEA1YFJLZRHJJ+pgour02HdRUgOU/gD72KWrNMbeuEtBCvs9cIG
|
||||
5bjveOiDf3FrtKRhjpc4vuR12+zJ2EZDnIkFk5OypPyYpmRDKL1h+m15yRXkG/5D
|
||||
QbHwF+gEcZsN8nzMDqDeXGCPMuqSCDnjoz0IQVMB//bGCbIANyZOIgJqJqkCgYEA
|
||||
vHc+ZG383fjEJLvtoco1JmmYnD6uQ1Ys4WjZmd5bMdtswxvV1tekMaSgF7WurQgm
|
||||
NGkqsKJbsaVLNOtbYdac7He/x2OfTr02aH2Nhk54M2H1tPd0nFjqjlaVitvLPRX9
|
||||
GviCTYKHNVUVjLgmHzLIQL382FXcLq6wVhJQ7QPDWKECgYA6ZmdB8waN2SFOJRnj
|
||||
5zWTgdImicl4yCu8PaEyloO9whYSdYH8zU3K+FKaowXGNInp3BGCmP9TUwoG8Iwx
|
||||
Ug5d55P2FgwnhCi59Y6dMXih1p94BuQ0vcWPBqztofl7lZbBEnjwzKVycFvcyNFX
|
||||
QFb/UA4/CfQli9ciDtlqzZt8SA==
|
||||
-----END PRIVATE KEY-----
|
||||
@ -22,19 +22,6 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var embeddedTLSOnce sync.Once
|
||||
var embeddedTLSCertPath string
|
||||
var embeddedTLSKeyPath string
|
||||
var embeddedTLSErr error
|
||||
|
||||
func embeddedTLSPathsCached() (string, string, error) {
|
||||
embeddedTLSOnce.Do(func() {
|
||||
embeddedTLSCertPath, embeddedTLSKeyPath, embeddedTLSErr = embeddedTLSFilesDir()
|
||||
})
|
||||
|
||||
return embeddedTLSCertPath, embeddedTLSKeyPath, embeddedTLSErr
|
||||
}
|
||||
|
||||
func escapePowerShell(s string) string {
|
||||
return strings.ReplaceAll(s, "'", "''")
|
||||
}
|
||||
@ -654,7 +641,7 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
go func() {
|
||||
appGo("ai-server-wait", func() {
|
||||
err := cmd.Wait()
|
||||
|
||||
if err != nil && ctx.Err() == nil && !proc.isStopping() {
|
||||
@ -664,7 +651,7 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
}
|
||||
|
||||
close(proc.done)
|
||||
}()
|
||||
})
|
||||
|
||||
if err := waitForAIServer(ctx, aiServerURL(), 120*time.Second); err != nil {
|
||||
appLogln("⚠️ AI Server noch nicht bereit:", err)
|
||||
@ -820,7 +807,7 @@ func startTrainingBestModelWatcher(ctx context.Context, trainingRoot string) {
|
||||
|
||||
lastRestart = time.Now()
|
||||
appLogln("🔄 Training-Modell geändert:", strings.Join(changedPaths, " | "))
|
||||
go restartAIServer()
|
||||
appGo("ai-server-restart", restartAIServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -961,12 +948,15 @@ func tlsCertFile() string {
|
||||
return resolveMaybeRelativeToExe(raw)
|
||||
}
|
||||
|
||||
certPath, _, err := embeddedTLSPathsCached()
|
||||
certPath, _, err := localTLSPathsCached()
|
||||
if err == nil && certPath != "" {
|
||||
return certPath
|
||||
}
|
||||
if err != nil {
|
||||
appLogln("⚠️ dynamisches TLS-Zertifikat konnte nicht erstellt werden:", err)
|
||||
}
|
||||
|
||||
return resolveMaybeRelativeToExe("recorder-cert.pem")
|
||||
return ""
|
||||
}
|
||||
|
||||
func tlsKeyFile() string {
|
||||
@ -975,12 +965,16 @@ func tlsKeyFile() string {
|
||||
return resolveMaybeRelativeToExe(raw)
|
||||
}
|
||||
|
||||
_, keyPath, err := embeddedTLSPathsCached()
|
||||
_, keyPath, err := localTLSPathsCached()
|
||||
if err == nil && keyPath != "" {
|
||||
return keyPath
|
||||
}
|
||||
|
||||
return resolveMaybeRelativeToExe("recorder-key.pem")
|
||||
if err != nil {
|
||||
appLogln("⚠️ dynamischer TLS-Key konnte nicht erstellt werden:", err)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func publicAppURL() string {
|
||||
@ -1030,7 +1024,13 @@ func publicAppURLs() []string {
|
||||
func main() {
|
||||
clearAppLog()
|
||||
initAppLog()
|
||||
defer closeAppLog()
|
||||
defer func() {
|
||||
panicked := appRecoverPanic("main")
|
||||
closeAppLog()
|
||||
if panicked {
|
||||
os.Exit(2)
|
||||
}
|
||||
}()
|
||||
|
||||
loadAppEnv()
|
||||
|
||||
@ -1055,7 +1055,7 @@ func main() {
|
||||
"Die Anwendung wird bereits ausgeführt.",
|
||||
)
|
||||
appLogln("⚠️ Die App läuft bereits oder Port " + appPort() + " ist bereits belegt.")
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
defer appLn.Close()
|
||||
|
||||
@ -1071,7 +1071,7 @@ func main() {
|
||||
|
||||
startPostworkLeftoverScanOnStartup()
|
||||
|
||||
go startGeneratedGarbageCollector()
|
||||
appGo("generated-garbage-collector", startGeneratedGarbageCollector)
|
||||
|
||||
// Altes NSFW-ONNX nicht mehr hart initialisieren.
|
||||
// Das neue Training-/YOLO-Modell wird über trainingPredictFrame genutzt.
|
||||
@ -1089,7 +1089,7 @@ func main() {
|
||||
if err != nil {
|
||||
appLogln("❌ auth init:", err)
|
||||
closeNSFW()
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
store := registerRoutes(mux, auth)
|
||||
@ -1100,14 +1100,14 @@ func main() {
|
||||
}
|
||||
|
||||
// Hintergrund-Worker
|
||||
go startDiskSpaceGuard()
|
||||
go startChaturbateOnlinePoller(store)
|
||||
go startChaturbateAutoStartWorker(store)
|
||||
go startMyFreeCamsAutoStartWorker(store)
|
||||
appGo("disk-space-guard", startDiskSpaceGuard)
|
||||
appGo("chaturbate-online-poller", func() { startChaturbateOnlinePoller(store) })
|
||||
appGo("chaturbate-autostart-worker", func() { startChaturbateAutoStartWorker(store) })
|
||||
appGo("myfreecams-autostart-worker", func() { startMyFreeCamsAutoStartWorker(store) })
|
||||
|
||||
// optional: Bootstrap nur im Hintergrund
|
||||
if getSettings().UseChaturbateAPI {
|
||||
go func() {
|
||||
appGo("chaturbate-api-bootstrap", func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@ -1116,7 +1116,7 @@ func main() {
|
||||
} else {
|
||||
appLogln("✅ Chaturbate API geladen")
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
if _, err := ensureCoversDir(); err != nil {
|
||||
@ -1164,14 +1164,14 @@ func main() {
|
||||
stopSig := make(chan os.Signal, 1)
|
||||
signal.Notify(stopSig, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
appGo("shutdown-signal-listener", func() {
|
||||
<-stopSig
|
||||
shutdown()
|
||||
}()
|
||||
})
|
||||
|
||||
serverErrCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
appGo("http-server", func() {
|
||||
var err error
|
||||
|
||||
if httpsEnabled() {
|
||||
@ -1186,9 +1186,9 @@ func main() {
|
||||
}
|
||||
|
||||
serverErrCh <- nil
|
||||
}()
|
||||
})
|
||||
|
||||
go func() {
|
||||
appGo("ai-server-start", func() {
|
||||
aiProc, err := startAIServer(appCtx)
|
||||
if err != nil {
|
||||
appLogln("⚠️ AI Server konnte nicht gestartet werden:", err)
|
||||
@ -1205,23 +1205,25 @@ func main() {
|
||||
watchedTrainingRoot := aiServerRoot
|
||||
aiServerMu.Unlock()
|
||||
|
||||
go startTrainingBestModelWatcher(appCtx, watchedTrainingRoot)
|
||||
}()
|
||||
appGo("training-best-model-watcher", func() {
|
||||
startTrainingBestModelWatcher(appCtx, watchedTrainingRoot)
|
||||
})
|
||||
})
|
||||
|
||||
go func() {
|
||||
appGo("startup-notification", func() {
|
||||
_ = showAppNotification(
|
||||
"Recorder gestartet",
|
||||
buildStartupNotificationMessage(),
|
||||
)
|
||||
}()
|
||||
})
|
||||
|
||||
go func() {
|
||||
appGo("http-server-error-listener", func() {
|
||||
err := <-serverErrCh
|
||||
if err != nil {
|
||||
appLogln("❌ HTTP-Server Fehler:", err)
|
||||
shutdown()
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
runTray(appCtx, shutdown, buildTrayStats)
|
||||
shutdown()
|
||||
|
||||
237
backend/tls_dynamic.go
Normal file
237
backend/tls_dynamic.go
Normal file
@ -0,0 +1,237 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
cryptoRand "crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
localTLSOnce sync.Once
|
||||
localTLSCertPath string
|
||||
localTLSKeyPath string
|
||||
localTLSErr error
|
||||
)
|
||||
|
||||
func localTLSPathsCached() (string, string, error) {
|
||||
localTLSOnce.Do(func() {
|
||||
localTLSCertPath, localTLSKeyPath, localTLSErr = ensureLocalTLSCertificate()
|
||||
})
|
||||
|
||||
return localTLSCertPath, localTLSKeyPath, localTLSErr
|
||||
}
|
||||
|
||||
func localTLSDir() (string, error) {
|
||||
if dir, err := os.UserConfigDir(); err == nil && strings.TrimSpace(dir) != "" {
|
||||
return filepath.Join(dir, "nsfwapp", "tls"), nil
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil || strings.TrimSpace(home) == "" {
|
||||
return "", fmt.Errorf("user config dir konnte nicht ermittelt werden")
|
||||
}
|
||||
|
||||
return filepath.Join(home, ".config", "nsfwapp", "tls"), nil
|
||||
}
|
||||
|
||||
func localTLSHosts() []string {
|
||||
seen := map[string]bool{}
|
||||
out := []string{}
|
||||
|
||||
add := func(host string) {
|
||||
host = normalizeOriginHost(host)
|
||||
if host == "" || seen[host] {
|
||||
return
|
||||
}
|
||||
seen[host] = true
|
||||
out = append(out, host)
|
||||
}
|
||||
|
||||
for _, origin := range configuredPublicAppOrigins() {
|
||||
u, err := url.Parse(strings.TrimSpace(origin))
|
||||
if err == nil {
|
||||
add(u.Hostname())
|
||||
}
|
||||
}
|
||||
|
||||
for _, host := range localAppHostCandidates() {
|
||||
add(host)
|
||||
}
|
||||
|
||||
add("localhost")
|
||||
add("127.0.0.1")
|
||||
add("::1")
|
||||
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func tlsCertificateCoversHosts(certPath string, hosts []string) bool {
|
||||
cert, err := readLeafCertificate(certPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if now.Before(cert.NotBefore) || now.After(cert.NotAfter.Add(-30*24*time.Hour)) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
host = normalizeOriginHost(host)
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
if err := cert.VerifyHostname(host); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func readLeafCertificate(certPath string) (*x509.Certificate, error) {
|
||||
b, err := os.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(b)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("keine PEM-Zertifikatsdaten")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
func ensureLocalTLSCertificate() (string, string, error) {
|
||||
dir, err := localTLSDir()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
certPath := filepath.Join(dir, "recorder-cert.pem")
|
||||
keyPath := filepath.Join(dir, "recorder-key.pem")
|
||||
hosts := localTLSHosts()
|
||||
|
||||
if fileExistsNonEmpty(certPath) &&
|
||||
fileExistsNonEmpty(keyPath) &&
|
||||
tlsCertificateCoversHosts(certPath, hosts) {
|
||||
appLogln("🔐 TLS-Zertifikat aktiv:", certPath)
|
||||
return certPath, keyPath, nil
|
||||
}
|
||||
|
||||
if err := generateLocalTLSCertificate(certPath, keyPath, hosts); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
appLogln("🔐 TLS-Zertifikat neu erstellt:", certPath)
|
||||
appLogln("🔐 TLS-Namen:", strings.Join(hosts, ", "))
|
||||
return certPath, keyPath, nil
|
||||
}
|
||||
|
||||
func generateLocalTLSCertificate(certPath string, keyPath string, hosts []string) error {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), cryptoRand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serialLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serial, err := cryptoRand.Int(cryptoRand.Reader, serialLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notBefore := time.Now().Add(-1 * time.Hour)
|
||||
notAfter := notBefore.Add(397 * 24 * time.Hour)
|
||||
|
||||
tpl := &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "nsfwapp local",
|
||||
Organization: []string{"nsfwapp"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
seenDNS := map[string]bool{}
|
||||
seenIP := map[string]bool{}
|
||||
for _, host := range hosts {
|
||||
host = normalizeOriginHost(host)
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
key := ip.String()
|
||||
if !seenIP[key] {
|
||||
seenIP[key] = true
|
||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !seenDNS[host] {
|
||||
seenDNS[host] = true
|
||||
tpl.DNSNames = append(tpl.DNSNames, host)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tpl.DNSNames) == 0 && len(tpl.IPAddresses) == 0 {
|
||||
tpl.DNSNames = []string{"localhost"}
|
||||
}
|
||||
|
||||
certDER, err := x509.CreateCertificate(
|
||||
cryptoRand.Reader,
|
||||
tpl,
|
||||
tpl,
|
||||
&key.PublicKey,
|
||||
key,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyDER, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certDER,
|
||||
})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyDER,
|
||||
})
|
||||
|
||||
if err := os.WriteFile(certPath, certPEM, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Binary file not shown.
@ -1,26 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEbjCCAtagAwIBAgIRAKGvq9b44yCGC32q8PwkueowDQYJKoZIhvcNAQELBQAw
|
||||
gYsxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEwMC4GA1UECwwnVEVH
|
||||
RFNTRFxSb3RoZXJATDE0UEJCSzk1MTAwMDA2IChSb3RoZXIpMTcwNQYDVQQDDC5t
|
||||
a2NlcnQgVEVHRFNTRFxSb3RoZXJATDE0UEJCSzk1MTAwMDA2IChSb3RoZXIpMB4X
|
||||
DTI2MDUwODEwMDQzN1oXDTI4MDgwODEwMDQzN1owWzEnMCUGA1UEChMebWtjZXJ0
|
||||
IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTAwLgYDVQQLDCdURUdEU1NEXFJvdGhl
|
||||
ckBMMTRQQkJLOTUxMDAwMDYgKFJvdGhlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDDghqU3igC3sNP2zAJTZbvZcT1xxjUgk3oA2j0WwNPO+xhCH4K
|
||||
mJ6enU5zrGWI4+T0IHfbo737rvgk6XfTDSvzB9Uags8egBUGxbr9eMBya0hmnYGs
|
||||
zIQGJeI4TIeQ7sa7krCpMB/e1YxR1qYFf1C8oSt43dJCdJP4Ev//BaR7rrFt3D3N
|
||||
CC2OgxVYSh/hR9XNIIEPMoioQIYhle41mQWjyPFt16Fjg9sWrrnHSz7M4cgVVSot
|
||||
+aBlRjbxgOarLyST3XdtjAfC6CqxnzprX1LfznuULjatoS+pFDBDoy2B13xwa9Br
|
||||
f+Xy3CswmSO+AzNomWfbeBkJnl6xb3Tb1urrAgMBAAGjfDB6MA4GA1UdDwEB/wQE
|
||||
AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBR0Zph7hFKq4GpE
|
||||
XO0y3U+T0iFoQDAyBgNVHREEKzApgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA
|
||||
AAAAAAAAAAGHBAoAARkwDQYJKoZIhvcNAQELBQADggGBAJShhgQS+9cwuVOJUIW+
|
||||
uy9t1ZAjR9qTd31kHdK1vu294GiavrG8RD+92BRujG4Fq1ambvqcu9sUaC8+0HAP
|
||||
cQvdmnbTxG0ZHkGP7VVj3Poee0jpsVAVtV3aVwKxyO/E7Mv/nEKTqNfnECbEuiqC
|
||||
tOJ8mI3E9tf2/ljzq2PqvfIPFBBtUT2lihNAFXM/06Nzoa1NPdfYd58N+VBFSOSq
|
||||
zEM4fmpcqMThZGKGBoWmHwTE5Mw6mZFLyxHJpOFu5wNzyPJGes5o/F0ot5n+ZGah
|
||||
GbYaGmhEedOxC3/WXRtexJmZ0jrB8Hkx+c/4h6+0HKU/PelrFJS+c2d6h8YGXyhv
|
||||
pUsfyyoQzx7rSgyLfJqXK+52ZtnWNc0yVVMyKJwjvhv4mcP9l/q3UGR/i7xkD5Pc
|
||||
2bLgM+wc8nKI7Yd/xejLpQDC0b/P1XibT1T3GRjiC/eu64G/uFOF+m3P5XvmF8JO
|
||||
0cj8fXwUCq+TVD+22rFmtT+2VYkf8RUsC48Ou+PHccmDyw==
|
||||
-----END CERTIFICATE-----
|
||||
@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDDghqU3igC3sNP
|
||||
2zAJTZbvZcT1xxjUgk3oA2j0WwNPO+xhCH4KmJ6enU5zrGWI4+T0IHfbo737rvgk
|
||||
6XfTDSvzB9Uags8egBUGxbr9eMBya0hmnYGszIQGJeI4TIeQ7sa7krCpMB/e1YxR
|
||||
1qYFf1C8oSt43dJCdJP4Ev//BaR7rrFt3D3NCC2OgxVYSh/hR9XNIIEPMoioQIYh
|
||||
le41mQWjyPFt16Fjg9sWrrnHSz7M4cgVVSot+aBlRjbxgOarLyST3XdtjAfC6Cqx
|
||||
nzprX1LfznuULjatoS+pFDBDoy2B13xwa9Brf+Xy3CswmSO+AzNomWfbeBkJnl6x
|
||||
b3Tb1urrAgMBAAECggEBAI31EBv72w2KdkKroot+vROCz6quMAdNvgezQif7VcHY
|
||||
fuBN7EcBXltJWUeAbBEjeIESejUPBcmT2DXlF841CC5lB4VCaeV5lsreE9IsNYBf
|
||||
CakIwLmZnltgcovydZT063QTJRcUDHAems5pjw76zMLKO+h9GEiMoUxFb3/atv3e
|
||||
K5HE+uUd6JcPU0q91S6TxqvS9gH30OOkwH6Kl4tzWauQAKg/sVzYafbyTAuEpiMO
|
||||
jLLLAn/sOmiL4knnRVu1FK4aZSH+t9BN9YuwfucXFn9jFgPtIJ0bF1GuqnwIeDI5
|
||||
BTNanHfaZRHD0+FbS0KbhARIFgDveoGrdoEtou0eDOECgYEA5dJqUvk96f7zCrxD
|
||||
eSuUl1MvOjydY3GacfjevZoTxJDqh4Wt2wgLYJc4XB7tI02ci7NncecG8qPfqWq7
|
||||
aFjQAGAYdR+XvE7R2m3OFK9WbRX4cH6b+l7mSq0eOvcAAajn4RuNr/il+7Opw7zX
|
||||
eKUB/pnW36B+ethdr5l+kDUQb8MCgYEA2ccanIT2QAxJb5j0F287NGhaTPuWfxcX
|
||||
2T53I2fIwMiell0YXjwsKwKpckubRB2f5X4b9QJifUTmtxHky/Tx4H9OVhCaxumw
|
||||
edol5YNzZaP/Tx8m2mqpyN2WPbezIFeA+GNcmDAxEo/NT2B64OVCHWKosndvb+Yk
|
||||
GFmVb+g9zbkCgYEA1YFJLZRHJJ+pgour02HdRUgOU/gD72KWrNMbeuEtBCvs9cIG
|
||||
5bjveOiDf3FrtKRhjpc4vuR12+zJ2EZDnIkFk5OypPyYpmRDKL1h+m15yRXkG/5D
|
||||
QbHwF+gEcZsN8nzMDqDeXGCPMuqSCDnjoz0IQVMB//bGCbIANyZOIgJqJqkCgYEA
|
||||
vHc+ZG383fjEJLvtoco1JmmYnD6uQ1Ys4WjZmd5bMdtswxvV1tekMaSgF7WurQgm
|
||||
NGkqsKJbsaVLNOtbYdac7He/x2OfTr02aH2Nhk54M2H1tPd0nFjqjlaVitvLPRX9
|
||||
GviCTYKHNVUVjLgmHzLIQL382FXcLq6wVhJQ7QPDWKECgYA6ZmdB8waN2SFOJRnj
|
||||
5zWTgdImicl4yCu8PaEyloO9whYSdYH8zU3K+FKaowXGNInp3BGCmP9TUwoG8Iwx
|
||||
Ug5d55P2FgwnhCi59Y6dMXih1p94BuQ0vcWPBqztofl7lZbBEnjwzKVycFvcyNFX
|
||||
QFb/UA4/CfQli9ciDtlqzZt8SA==
|
||||
-----END PRIVATE KEY-----
|
||||
Loading…
x
Reference in New Issue
Block a user