Skip to content

Feat/dual os - #14

Draft
Spiritreader wants to merge 72 commits into
masterfrom
feat/dual-os
Draft

Feat/dual os#14
Spiritreader wants to merge 72 commits into
masterfrom
feat/dual-os

Conversation

@Spiritreader

Copy link
Copy Markdown
Owner

No description provided.

bmaeofu added 20 commits July 13, 2026 22:38
- Add encoder/priority_windows.go (PriorityClass via x/sys/windows)
- Add encoder/priority_linux.go (nice levels via syscall.Setpriority)
- Remove windows import from encoder.go, replace with setProcessPriority() call
- Fix unmasked bug: err -> vErrify in verification log (was shadowed by windows err var)
- Add Makefile + build scripts for windows/linux targets
- Extend CI workflow to windows+linux matrix
- Add Dockerfile + compose.yaml for Komodo deployment
- Add .dockerignore
- docker/config.example.json: converted Windows UNC paths to /media/... paths
  for compose mount (/mnt/user/media -> /media)
- docker/README.md: setup instructions, hardware acceleration notes,
  software encoder fallback reference
- compose.yaml: add commented GPU devices passthrough section
…pport

- Use linuxserver/ffmpeg:latest as runtime base (ships QSV-enabled ffmpeg)
- Enable /dev/dri GPU passthrough in compose.yaml via devices section
- Update README: confirm QSV works out of box on Unraid with ARC GPU
- compose.yaml: drop image: avior-go:latest so docker compose up always
  builds when extra_args --build is passed (avoids stale cached images)
- docker/README.md: document Komodo stack config with branch and
  extra_args = "--build"
…ations

GetClientForMachine() looked up clients by strings.ToUpper(hostname)
but inserted with the raw hostname. On Linux the hostname is lowercase
(container ID), so the lookup never matched and every restart created a
new duplicate client entry. Now hostname is uppercased once before both
lookup and insert, consistent with the historical UPPERCASE registry
(VDR-U, PHOENIX, ...).
Add PathMappings (config.json map of UNC prefix -> local container path)
and translatePath() in the worker, applied once at job-processing time.
The Docker instance can now process DB jobs whose Path is a Windows UNC
path (e.g. \\192.168.178.75\recording_pool\... -> /recording_pool/...).
No mappings configured = identity, so Windows instances are unaffected.
@Spiritreader
Spiritreader marked this pull request as draft August 2, 2026 14:07
Comment thread db/client.go
state := globalstate.Instance()
state.HostName = hostname
var thisMachine *structs.Client
err := ds.Db().Collection("clients").FindOne(ctx, bson.M{"Name": strings.ToUpper(hostname)}).Decode(&thisMachine)

@Spiritreader Spiritreader Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does changing this cause issues with machine detection?
If machines names aren't normalized, we would would henceforth produce duplicate clients in the DB when signing in, or not find a client anymore that was previously uppercased.

This is generally fine, but either the DB needs to be adjusted one-time to be compliant with the new name format, or we perform normalization during name read and writes throughout the codebase (all to lowercase when checking etc).

bmaeofu added 8 commits August 2, 2026 16:21
/data/avior-go may be the currently running binary (previous start exec'd
from the same volume). cp onto a running executable fails with 'Text file
busy', so the entrypoint aborted and the container crash-looped on every
restart after the first deploy. rm -f first, then copy.
Run the app as the configured UID/GID (LinuxServer convention) so every
file it creates - logs in /data/log, .INFO.log next to media, config.json -
is owned by that user on the host, matching the mm:users ownership the
Windows instances produced. umask 002 keeps group/other rw. Default
without PUID/PGID: root (previous behavior).
su-exec is an Alpine package and not available in the Ubuntu-based
linuxserver/ffmpeg image; apt install failed with exit 100. setpriv is
preinstalled in util-linux. Keep su-exec as fallback if present.
Same module settings as the software example (Threshold, Mode, MaxSize,
MinResolution, Accuracy, Difference/SampleCount/Fraction) and desired
enablement/priority.
Port from test/DockerwoARC: new config field ClientName overrides the
container hostname for DB client registration, preventing phantom clients
(container ID) on every restart. Empty = previous os.Hostname() behavior.
bmaeofu added 30 commits August 3, 2026 14:58
…aming

ClientName stays identical across instances (all 'UNRAID'); the Instance
field in config.json separates them (UNRAID, UNRAID-1, ...) and sets the
listening port 10000+Instance. Compose now exposes CONTAINER_PORT so the
host mapping follows the instance's actual container port.
The app logs with time.Local which reads the TZ env var; unset defaults
to UTC in the container. Add TZ with Europe/Berlin default, overridable
per stack via environment.
…ave()

json omitempty on a bool omits false when marshaling. config.Save() runs
on every start (api.go), so CacheLibScan:false vanished from config.json
after each restart. Remove omitempty so false is written explicitly.
Without container_name Compose derives <project>-<service>-<replica>
(avior-go-avior-go-1), which is confusing. Set container_name from
INSTANCE_SUFFIX so instances are simply avior-go, avior-go-1, ...
When the exact output name already exists in the library but the release
year differs (e.g. 'Die Löwin' 2011 vs 2024), the new film is renamed to
'Die Löwin (2024)' before the duplicate modules decide — treating
same-title/different-year films as separate instead of duplicates.

- media/year.go: ExtractYear/ExtractYearFromFile (subtitle -> .txt -> .log
  fallback, ported from movie_nfo_lib), NormalizeName, HasYearSuffix
- config: YearAwareDupes flag (default true)
- worker: findDuplicateYear + suffix append before checkForDuplicates
- RE2 port notes: no lookahead (decade check manual), \w is ASCII
  (\p{L} for umlauts)
ExtractYearFromFile scanned ALL .txt lines and took the first year — the
Created=/Date= lines carry the RECORDING date (e.g. 03.08.2026), which is
not the release year, so 'Die Löwin' (2024 film, 2026 recording) got
suffixed as 'Die Löwin (2026)'. Restrict .txt source to Info= and Title=
lines. Regression test added.
… custom regexes

Replace the simplified custom patterns with faithful Go ports of the
library's proven logic (extract_txt_metadata / _extract_log_metadata):
COUNTRY_HINT_PATTERN, TXT_META_PATTERN, LOG_META_LINE(_STRICT)_PATTERN,
TIMER_NAME_META_PATTERN, TYPE2_* patterns, _slice_log_lines_for_metadata.
This fixes the multi-country cases (Deutschland/Estland/Lettland) that the
simplified version missed, and reuses the battle-tested extraction order
(subtitle -> .txt Info=/Description= -> .log Timer Name -> meta line).
RE2 port notes: no lookahead (decade check manual), \w is ASCII (\p{L}),
and a capturing country-list alternation wrapped in a repeat breaks RE2 —
country lists are non-capturing, year is group 1.
…ry context)

The year is only recognized in Genre+Country+Year context — the full
COUNTRY_HINT_PATTERN backbone distinguishes 4-digit years from other
numbers. Port the complete _extract_from_candidate cascade:
preprocessing (FSK/parens/subtitle-truncation/Min-strip/dupe-collapse),
start_match truncation, TXT_META -> TYPE2 -> loose -> short_genre ->
permissive stages, all gated by NARRATIVE_MARKER (excludes 'Jahr 2022',
'im 2024'). Add scored Description=-segment selection, episode
detection (IsProbablyEpisodeFilename), and real-case regression tests
(new film .log TimerName 2024 vs TimerStart 2026 -> 2024; old film
'Melodram Südafrika/2011' -> 2011).
The year-aware duplicate flow was silent on all negative branches, so a
film with an unreadable .log (e.g. 'Die Löwin .log' with a stray space)
was silently treated as an exact duplicate and replaced. Now every step
is visible in the log:
- year extraction: which source (subtitle/txt Info=/Description=/log)
  yielded the year, or why none was found
- collision decision: no year / duplicate year unknown / same year /
  different year -> suffix appended
Regression test for the real production case (subtitle wins over
recording year in .log) added.
glg.LOG has no file writer (AddLevelWriter(glg.LOG, log) is commented out
in app.go), so glg.Logf only reaches the console, not main.log. The new
year extraction / collision logs must use Infof to be visible in the log
file. Pre-existing Logf calls (input file, media struct, walker positions)
left untouched.
CacheLibScan=false disables the library cache (always fresh FS scan),
which makes the Redis job broadcast redundant. AutoManage now treats
Redis as disabled unless BOTH Redis.Enabled and CacheLibScan are true —
no Redis connection/session is held, and the worker's Running() checks
skip broadcast/cache paths. Re-enabling CacheLibScan (or Redis.Enabled)
restores Redis automatically; the coupling is logical, not a hard config
default, so Redis stays available if caching is ever needed again.
findDuplicateYear returned "" both when no duplicate matched and when a
duplicate's year could not be extracted, and the caller logged the same
misleading 'exact-name duplicate found but its year is unknown' message
for both. The log now names the exact duplicate path, or states that no
normalized-name duplicate exists.
The year-aware check already runs checkForDuplicates and returns the same
matches the later duplicate check would find — except when a year collision
renames the file. findDuplicateYear now returns the match list, ProcessJob
reuses it, and only a real rename (name changed, matches stale) triggers a
second scan. With CacheLibScan=false this halves the library walks per job
on Unraid, reducing disk spin-ups.
The year-aware switch still logged 'exact-name duplicate found but its
year is unknown' for every empty dupeYear, including the no-match case
that findDuplicateYear already reported as 'no normalized-name
duplicate'. With the returned matches the switch now only logs the
unknown-year message when a duplicate actually exists.
The exists/ symlinks inside the recording pool point to the host path
/mnt/user/recording_pool/recording/exists (central exists storage). That
path was unresolvable inside the container, so moving a duplicate source
file to <dir>/exists failed with 'no such file or directory'. Mount the
symlink target path into the container at the same absolute location.

Also replace the swallowed os.Mkdir in moveMediaFile/moveLogs/
copyLogsToEncOut with os.MkdirAll and error logging, so any future
directory creation failure is reported instead of surfacing as a
confusing rename/open error.
The absolute /mnt/user/recording_pool:/mnt/user/recording_pool mount
made the exists symlink resolvable in the container, but source and
target then sat on two different mounts, so rename failed with EXDEV and
MoppyFile fell back to copy+delete. A relative symlink (exists ->
../recording/exists) resolves within the same /recording_pool mount in
the container and on the host, so no extra mount is needed.
The Basic Instinct case showed that metadata-only channel tags like
'[5.1]' were missing from MultiTags, so a source whose tuner log lists
both AC3 Audio 5.1 and AC3 Audio Stereo (audio switch mid-film) resolved
to MULTI_MAYBE instead of MULTI_PROBABLY and the AudioModule could not
decide replacement. Add the bracket tags to all four docker example
configs so fresh installs get them from the start.
Slice bounds out of range when the first genre/country word appeared
AFTER the first 4-digit number (startIdx > mYearFirst[1]): the bounded
match window s[startIdx:mYearFirst[1]] was invalid. Every job whose
metadata/tuner line had that shape crashed the whole service on
startup, producing the restart loop and unhealthy status on avior-go
and avior-go-1. Fall back to the year-prefix window when the genre
start lies beyond the year end. Regression test added.
joblog.AppendTo writes via lumberjack, which creates files with mode
0600 (since the 2020 logrotate switch from os.OpenFile 0644). On Unraid
every user (nobody:users) must be able to read/write/delete the
.INFO.log files next to the media, so writeSkippedLog now chmods the
file to 0666 after writing. Regression test (unix-only).
Shares on Unraid are conventionally owned by nobody:users, so SMB and
other tools work without per-user PUID config. Default PUID to 99
(nobody) and PGID to 100 (users); a dedicated service user can still be
selected via PUID/PGID env override. entrypoint.sh already chowns /data
and drops privileges via setpriv, no change needed there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants