Phase 3: Go hygiene pass, runtime PUID/PGID, fork README
continuous-integration/drone/push Build encountered an error

Go:
- staticcheck 399 -> 0 with staticcheck.conf (style checks ST1000/1003/
  1005/1016/1020/1021/1022 excluded; error strings are shown in the UI).
- io/ioutil and rand.Seed removed; CloseNotifier kept with a lint-ignore
  until the Phase 2 context rewrite.
- Dead code deleted: Auto handler, getStreamByChannelID, updateXEPG,
  indexOfInt, jsonToMapInt64, removeOldSystemData, randomTime, and the
  commented-out blocks in struct-buffer.go and internal/authentication.
- Duplicates folded: cacheImagesInBackground(), one addErrorToStream().
- Bugs found by SA4006/SA5001: os.Create handle leaked per ffmpeg segment
  (buffer.go), http.NewRequest error unchecked (buffer.go), xepg.json
  migration wrote null on read error (migrate.go), WriteUserData errors
  silently dropped (authentication.go), defer Close before error check
  (buffer.go, toolchain.go). checkFilePermission results were discarded;
  an unwritable config or temp dir is now fatal at start-up.
- gofmt applied repo-wide; Drone runs gofmt check and staticcheck.

Docker:
- Entrypoint starts as root, applies PUID/PGID (falls back to XTEVE_UID/
  XTEVE_GID, then image defaults), fixes config ownership only when it
  differs, then drops to xteve via su-exec. --user starts skip all of it.
- /xteve removed from LEGACY_CONFIG_DIRS (it is the parent of the default).
- mwader/static-ffmpeg pinned to 7.1.1; VOLUME /xteve/config.
- Compose files pull registry.coadcorp.com/nathan/xteve:latest, use
  PUID/PGID/TZ, and explain that SSDP needs host networking.
- .dockerignore excludes the npm toolchain (bundle stays in html/js).

Docs: README rewritten for the fork (about, registry, compose, env vars,
security notes); README-DEV gains a container section.
This commit is contained in:
2026-09-26 12:57:43 +10:00
parent ddc70e170a
commit 504ea3f9f4
38 changed files with 1034 additions and 1382 deletions
+41 -140
View File
@@ -5,7 +5,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"runtime"
"sort"
@@ -71,28 +71,9 @@ func buildXEPG(background bool) {
createM3UFile()
queuePlexGuideRefresh("xepg rebuild")
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
showInfo("XEPG:" + "Ready to use")
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
go func() {
System.ImageCachingInProgress = 1
showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
Data.Cache.Images.Image.Caching()
Data.Cache.Images.Image.Remove()
showInfo("Image Caching:Done")
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg image cache refresh")
System.ImageCachingInProgress = 0
}()
}
cacheImagesInBackground()
System.ScanInProgress = 0
@@ -118,28 +99,9 @@ func buildXEPG(background bool) {
createM3UFile()
queuePlexGuideRefresh("xepg rebuild")
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
cacheImagesInBackground()
go func() {
System.ImageCachingInProgress = 1
showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
Data.Cache.Images.Image.Caching()
Data.Cache.Images.Image.Remove()
showInfo("Image Caching:Done")
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg image cache refresh")
System.ImageCachingInProgress = 0
}()
}
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
showInfo("XEPG:" + "Ready to use")
System.ScanInProgress = 0
@@ -161,52 +123,30 @@ func buildXEPG(background bool) {
}
// XEPG Daten aktualisieren
func updateXEPG(background bool) {
// cacheImagesInBackground : Bilder im Hintergrund cachen, danach XMLTV- und M3U-Datei neu erstellen
func cacheImagesInBackground() {
if System.ScanInProgress == 1 {
if !Settings.CacheImages || System.ImageCachingInProgress != 0 {
return
}
System.ScanInProgress = 1
go func() {
if Settings.EpgSource == "XEPG" {
System.ImageCachingInProgress = 1
showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
switch background {
Data.Cache.Images.Image.Caching()
Data.Cache.Images.Image.Remove()
showInfo("Image Caching:Done")
case false:
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg image cache refresh")
createXEPGDatabase()
mapping()
cleanupXEPG()
System.ImageCachingInProgress = 0
go func() {
}()
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg update")
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
System.ScanInProgress = 0
}()
case true:
System.ScanInProgress = 0
}
} else {
System.ScanInProgress = 0
}
// Cache löschen
//Data.Cache.XMLTV = nil //make(map[string]XMLTV)
//Data.Cache.XMLTV = make(map[string]XMLTV)
return
}
// Mapping Menü für die XMLTV Dateien erstellen
@@ -219,7 +159,6 @@ func createXEPGMapping() {
var friendlyDisplayName = func(channel Channel) (displayName string) {
var dn = channel.DisplayName
displayName = dn[0].Value
switch len(dn) {
case 1:
@@ -277,11 +216,10 @@ func createXEPGMapping() {
}
Data.XMLTV.Mapping = tmpMap
tmpMap = make(map[string]any)
} else {
if System.ConfigurationWizard == false {
if !System.ConfigurationWizard {
showWarning(1007)
}
@@ -304,7 +242,6 @@ func createXEPGMapping() {
Data.XMLTV.Mapping["xTeVe Dummy"] = dummy
return
}
// XEPG Datenbank erstellen / aktualisieren
@@ -339,7 +276,7 @@ func createXEPGDatabase() (err error) {
sort.Float64s(allChannelNumbers)
var firstFreeNumber float64 = Settings.MappingFirstChannel
var firstFreeNumber = Settings.MappingFirstChannel
for {
@@ -469,14 +406,14 @@ func createXEPGDatabase() (err error) {
xepgChannel.Name = m3uChannel.Name
// Kanalname aktualisieren, nur mit Kanal ID's möglich
if channelHasUUID == true {
if xepgChannel.XUpdateChannelName == true {
if channelHasUUID {
if xepgChannel.XUpdateChannelName {
xepgChannel.XName = m3uChannel.Name
}
}
// Kanallogo aktualisieren. Wird bei vorhandenem Logo in der XMLTV Datei wieder überschrieben
if xepgChannel.XUpdateChannelIcon == true {
if xepgChannel.XUpdateChannelIcon {
xepgChannel.TvgLogo = m3uChannel.TvgLogo
}
@@ -602,7 +539,7 @@ func findXEPGReplacementChannel(xmltvChannels map[string]any, xepgChannel XEPGCh
for id, data := range xmltvChannels {
xmltvChannel, castOK := data.(map[string]any)
if castOK == false {
if !castOK {
continue
}
@@ -615,7 +552,7 @@ func findXEPGReplacementChannel(xmltvChannels map[string]any, xepgChannel XEPGCh
}
displayName, hasDisplayName := xmltvChannel["display-name"].(string)
if hasDisplayName == false {
if !hasDisplayName {
continue
}
@@ -660,7 +597,7 @@ func mapping() (err error) {
}
// Automatische Mapping für neue Kanäle. Wird nur ausgeführt, wenn der Kanal deaktiviert ist und keine XMLTV Datei und kein XMLTV Kanal zugeordnet ist.
if xepgChannel.XActive == false {
if !xepgChannel.XActive {
// Werte kann "-" sein, deswegen len < 1
if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XMapping) < 1 {
@@ -704,7 +641,7 @@ func mapping() (err error) {
}
// Überprüfen, ob die zugeordneten XMLTV Dateien und Kanäle noch existieren.
if xepgChannel.XActive == true {
if xepgChannel.XActive {
var mapping = xepgChannel.XMapping
var file = xepgChannel.XmltvFile
@@ -718,7 +655,7 @@ func mapping() (err error) {
// Kanallogo aktualisieren
if logo, ok := channel["icon"].(string); ok {
if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
if xepgChannel.XUpdateChannelIcon && len(logo) > 0 {
xepgChannel.TvgLogo = logo
}
@@ -729,7 +666,7 @@ func mapping() (err error) {
xepgChannel.XMapping = channelID
if logo, ok := replacementChannel["icon"].(string); ok {
if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
if xepgChannel.XUpdateChannelIcon && len(logo) > 0 {
xepgChannel.TvgLogo = logo
}
}
@@ -752,7 +689,7 @@ func mapping() (err error) {
name = xepg
}
if strictMissingEPGMode == true {
if strictMissingEPGMode {
missingEPGCount++
appendXEPGIssueSample(missingEPGSamples, name)
xepgChannel.XActive = false
@@ -772,7 +709,7 @@ func mapping() (err error) {
providerName = file
}
if strictMissingEPGMode == true {
if strictMissingEPGMode {
missingXMLTVCount++
appendXEPGIssueSample(missingXMLTVSamples, providerName)
xepgChannel.XActive = false
@@ -849,7 +786,7 @@ func createXMLTVFile() (err error) {
Data.Cache.ImagesURLS = []string{}
Data.Cache.ImagesCache = []string{}
files, err := ioutil.ReadDir(System.Folder.ImagesCache)
files, err := os.ReadDir(System.Folder.ImagesCache)
if err == nil {
for _, file := range files {
@@ -887,7 +824,7 @@ func createXMLTVFile() (err error) {
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
if err == nil {
if xepgChannel.XActive == true {
if xepgChannel.XActive {
// Kanäle
var channel Channel
@@ -902,9 +839,7 @@ func createXMLTVFile() (err error) {
*tmpProgram, err = getProgramData(xepgChannel)
if err == nil {
for _, program := range tmpProgram.Program {
xepgXML.Program = append(xepgXML.Program, program)
}
xepgXML.Program = append(xepgXML.Program, tmpProgram.Program...)
}
@@ -947,7 +882,7 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
err = getLocalXMLTV(xmltvFile, &xmltv)
if err != nil {
if relaxedMissingEPGMode == true {
if relaxedMissingEPGMode {
fallbackToDummy()
err = nil
}
@@ -1024,7 +959,7 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
}
if len(xepgXML.Program) == 0 && relaxedMissingEPGMode == true && xmltvFile != System.Folder.Data+"xTeVe Dummy" {
if len(xepgXML.Program) == 0 && relaxedMissingEPGMode && xmltvFile != System.Folder.Data+"xTeVe Dummy" {
fallbackToDummy()
}
@@ -1072,7 +1007,7 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
epg.Desc = append(epg.Desc, &Desc{Value: xepgChannel.XDescription, Lang: "en"})
}
if Settings.XepgReplaceMissingImages == true {
if Settings.XepgReplaceMissingImages {
poster.Src = imgc.Image.GetURL(xepgChannel.TvgLogo)
epg.Poster = append(epg.Poster, poster)
}
@@ -1114,7 +1049,6 @@ func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChanne
}
return
}
// Programm Poster Cover aus der XMLTV Datei laden
@@ -1127,7 +1061,7 @@ func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelS
program.Poster = append(program.Poster, poster)
}
if Settings.XepgReplaceMissingImages == true {
if Settings.XepgReplaceMissingImages {
if len(xmltvProgram.Poster) == 0 {
var poster Poster
@@ -1161,7 +1095,6 @@ func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChan
}
return
}
// Videoparameter erstellen (createXMLTVFile)
@@ -1187,7 +1120,6 @@ func getVideo(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelSt
program.Video = video
return
}
// Lokale Provider XMLTV Datei laden
@@ -1236,7 +1168,6 @@ func createM3UFile() {
saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS)
return
}
// XEPG Datenbank bereinigen
@@ -1254,7 +1185,7 @@ func cleanupXEPG() {
sourceIDs = append(sourceIDs, source)
}
showInfo("XEPG:" + fmt.Sprintf("Cleanup database"))
showInfo("XEPG:" + "Cleanup database")
Data.XEPG.XEPGCount = 0
for id, dxc := range Data.XEPG.Channels {
@@ -1266,7 +1197,7 @@ func cleanupXEPG() {
if indexOfString(xepgChannel.Name+xepgChannel.FileM3UID, Data.Cache.Streams.Active) == -1 {
delete(Data.XEPG.Channels, id)
} else {
if xepgChannel.XActive == true {
if xepgChannel.XActive {
Data.XEPG.XEPGCount++
}
}
@@ -1291,34 +1222,4 @@ func cleanupXEPG() {
showWarning(2005)
}
return
}
// Streaming URL für die Channels App generieren
func getStreamByChannelID(channelID string) (playlistID, streamURL string, err error) {
err = errors.New("Channel not found")
for _, dxc := range Data.XEPG.Channels {
var xepgChannel XEPGChannelStruct
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
fmt.Println(xepgChannel.XChannelID)
if err == nil {
if channelID == xepgChannel.XChannelID {
playlistID = xepgChannel.FileM3UID
streamURL = xepgChannel.URL
return playlistID, streamURL, nil
}
}
}
return
}