Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
1535f23
fix: downgrade godirwalk to v1.16.2 and update tests for compatibility
bmaeofu Jul 13, 2026
fa58508
version update to 2.0.1
bmaeofu Jul 31, 2026
a83cad5
docs: add dual-os build plan
bmaeofu Jul 31, 2026
134d02b
docs: add AGENTS.md with project conventions
bmaeofu Jul 31, 2026
74d1eb9
docs: add language policy and docs_agent skill to AGENTS.md
bmaeofu Jul 31, 2026
a371c29
docs: translate dual-os plan to English
bmaeofu Jul 31, 2026
acf4559
docs: remove completed task 00, renumber remaining tasks
bmaeofu Jul 31, 2026
d3a1cfb
refactor: extract OS-specific ffmpeg priority into build-tag files
bmaeofu Jul 31, 2026
5bdb2f2
feat: dual-OS build infrastructure
bmaeofu Jul 31, 2026
f237480
docs: add Docker deployment config and README
bmaeofu Jul 31, 2026
c897d1d
fix: switch Docker runtime to linuxserver/ffmpeg for Intel ARC QSV su…
bmaeofu Jul 31, 2026
597f1e7
port to Dual OS (windows + Linux)
bmaeofu Jul 31, 2026
29fad21
docs: add subagent routing rules to AGENTS.md
bmaeofu Aug 1, 2026
418ddb0
docs: move subagent routing to user-level AGENTS.md (~/.omp/agent/AGE…
bmaeofu Aug 1, 2026
6624dcc
docs: restore subagent routing in project AGENTS.md, overrides global
bmaeofu Aug 1, 2026
2c0dfc8
fix: remove image tag from compose, force rebuild via Komodo extra_args
bmaeofu Aug 1, 2026
ba69eae
fix: uppercase hostname on client insert to prevent duplicate registr…
bmaeofu Aug 2, 2026
d3c117a
fix: set hostname for production instance so it registers as AVIOR-GO…
bmaeofu Aug 2, 2026
14aaaba
feat: config-driven UNC path translation for Docker instances
bmaeofu Aug 2, 2026
cc96517
feat: add PathMappings to QSV example config, mount recording shares …
bmaeofu Aug 2, 2026
0a11b2e
fix: entrypoint rm before cp to avoid 'Text file busy' crash loop
bmaeofu Aug 2, 2026
fb7b92f
feat: configurable file ownership via PUID/PGID
bmaeofu Aug 2, 2026
ed6cd61
fix: default PUID/PGID to mm:users (1002:100)
bmaeofu Aug 2, 2026
5ce5293
fix: use setpriv (util-linux) instead of su-exec for PUID/PGID
bmaeofu Aug 2, 2026
cc8ba45
chore: update module config in QSV example to match code structs
bmaeofu Aug 2, 2026
b59f033
chore: update module enablement (Audio, DuplicateLengthCheck, ErrorSk…
bmaeofu Aug 2, 2026
7c3f5cf
fix: configurable ClientName to prevent phantom client re-registration
bmaeofu Aug 2, 2026
06f37a9
chore: set ClientName AVIOR-GO in QSV example config
bmaeofu Aug 2, 2026
d41a95d
fix: add UMS hostname variants to PathMappings in QSV example config
bmaeofu Aug 3, 2026
4ba10a5
feat: add ARC test config (QSV encode, 2-min limit, re_encode focus)
bmaeofu Aug 3, 2026
7cf0df6
fix: use 0644 instead of os.ModeAppend for glg.FileWriter perm
bmaeofu Aug 3, 2026
8812a52
fix: create log dir explicitly with 0755 before glg.FileWriter
bmaeofu Aug 3, 2026
add87df
feat: add initial configuration and logging for ARC job processing
bmaeofu Aug 3, 2026
c19292f
feat: add additional log entries for coding test directory scans and …
bmaeofu Aug 3, 2026
5808997
fix: chmod log dir explicitly - MkdirAll does not fix existing 000 dir
bmaeofu Aug 3, 2026
e8d8387
fix: pin QSV to ARC GPU (/dev/dri/renderD129) in ARC test config
bmaeofu Aug 3, 2026
3593c10
fix: pin QSV to ARC GPU (/dev/dri/renderD129) in production config
bmaeofu Aug 3, 2026
260614e
fix: invalidate lib cache when MediaPaths config changes
bmaeofu Aug 3, 2026
e76dfbb
feat: configurable CacheLibScan - disable lib cache to always scan fresh
bmaeofu Aug 3, 2026
309a82c
feat: add UNRAID production config with ARC380 GPU
bmaeofu Aug 3, 2026
57a09bd
fix: update MinAge to 0 for immediate processing of files
bmaeofu Aug 3, 2026
5f37c15
feat: parameterize compose for multi-instance (INSTANCE_SUFFIX, HOST_…
bmaeofu Aug 3, 2026
43f5b11
feat: parameterize CONTAINER_PORT for Instance-based multi-instance n…
bmaeofu Aug 3, 2026
2cc3120
feat: set TZ=Europe/Berlin (configurable) for local time in logs
bmaeofu Aug 3, 2026
63cbed4
fix: persist CacheLibScan=false - omitempty dropped false values on S…
bmaeofu Aug 3, 2026
ba59d1a
feat: update ignored clients and add configuration documentation for …
bmaeofu Aug 3, 2026
7bf2a0e
feat: explicit container_name per instance (avior-go, avior-go-1)
bmaeofu Aug 3, 2026
cdffb90
feat: ensure CacheLibScan is consistently set to false in configurati…
bmaeofu Aug 3, 2026
a6a32c6
feat: add example configuration for soft encoding
bmaeofu Aug 4, 2026
6c8c93e
feat: update documentation for multi-instance setup, clarify INSTANCE…
bmaeofu Aug 5, 2026
73607af
feat: year-aware duplicate detection
bmaeofu Aug 8, 2026
c7b6010
feat: implement year-aware duplicate detection for media files
bmaeofu Aug 8, 2026
1704948
fix: use Info=/Title= release year only, ignore recording date in .txt
bmaeofu Aug 8, 2026
7848ad7
refactor: port full movie_nfo_lib year-extraction patterns instead of…
bmaeofu Aug 8, 2026
75f3ef9
feat: full port of movie_nfo_lib year-extraction cascade (genre+count…
bmaeofu Aug 8, 2026
462fadc
feat: transparent logging for year extraction and collision decisions
bmaeofu Aug 8, 2026
378b149
fix: use Infof instead of Logf for year-aware logging
bmaeofu Aug 8, 2026
790fa6e
feat: auto-disable Redis when CacheLibScan is false
bmaeofu Aug 8, 2026
15cbdcd
docs: specify duplicate name normalization
bmaeofu Aug 9, 2026
65fbbd9
docs: plan full duplicate name normalization
bmaeofu Aug 9, 2026
2e84606
feat: normalize duplicate names by alphanumeric key
bmaeofu Aug 9, 2026
3d049f0
fix: distinguish no-duplicate from unknown-year in year-aware log
bmaeofu Aug 25, 2026
fd2368d
perf: skip redundant second duplicate scan
bmaeofu Aug 25, 2026
f221ab8
feat: update .gitignore for media files and add new log files for "Di…
bmaeofu Aug 25, 2026
7696a91
feat: add Docker healthcheck on /alive/ endpoint
bmaeofu Aug 25, 2026
3ccc876
fix: stop logging unknown-year message when no duplicate exists
bmaeofu Aug 25, 2026
7d23454
fix: mount exists symlink target; surface MkdirAll errors
bmaeofu Aug 26, 2026
44d59a2
revert: drop symlink-target mount, relative exists symlink suffices
bmaeofu Aug 26, 2026
9c27a75
feat: add [5.1]/[5.0]/[7.1] to example MultiTags
bmaeofu Aug 26, 2026
f3ff72e
fix: panic in yearFromMetaCandidate when genre word after year
bmaeofu Aug 28, 2026
097a9b1
fix: make .INFO.log world-writable (0666) for Unraid users
bmaeofu Aug 28, 2026
5f8d855
feat: run as nobody:users by default (unraid standard)
bmaeofu Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .cortex/plans/dual-os-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Plan: Dual-OS Build for avior-go (Windows + Linux)

## Context

avior-go currently only compiles for Windows because `encoder/encoder.go` directly uses
the Windows API (`golang.org/x/sys/windows`: `OpenProcess`, `SetPriorityClass`,
`CloseHandle`) to set the process priority of the spawned `ffmpeg` process.
Goal: The same code should optionally produce a Windows executable (`avior-go.exe`) or a
Linux binary (`avior-go`) via Go build tags / `GOOS`, so that avior-go instances can run
under Linux in Docker in the future.

Facts from the code (verified in this session):
- Only Windows dependency in the entire repo: `encoder/encoder.go` lines 176–188
(`windows.OpenProcess` / `windows.SetPriorityClass` / `windows.CloseHandle`), plus the
import `"golang.org/x/sys/windows"` on line 23.
- All path operations already use `filepath.Join` (app.go, config/config.go,
api/api.go) — OS-agnostic. UNC paths exist only in config JSONs (user data, not a
code problem).
- All Go dependencies in `go.mod` are pure Go (gorilla, redis, mongo-driver, glg,
godirwalk, lumberjack …) → `CGO_ENABLED=0` cross-compile is possible, no C toolchain
needed.
- CI: `.github/workflows/go.yml` currently builds only `goos: windows`, `goarch: amd64`
via `wangyoucao577/go-release-action@v1.18`.
- Go version per go.mod: `go 1.25.0`.

End state: `GOOS=windows go build` and `GOOS=linux go build` both work; ffmpeg priority
is set per OS via build-tag files (Windows: PriorityClass, Linux: nice level); CI builds
both artifacts.
## Tasks (one MD file each)

1. `dual-os-task-01-priority-build-tags.md` — Extract OS-specific priority setting
from `encoder/encoder.go` into build-tag files.
2. `dual-os-task-02-build-switches.md` — Build switches (Makefile + scripts) for
Windows and Linux executables.
3. `dual-os-task-03-ci-matrix.md` — Extend GitHub Actions workflow to a
Windows+Linux matrix.
4. `dual-os-task-04-verification.md` — Verification: cross-compile both targets,
tests, smoke check.
5. `dual-os-task-05-docker-compose.md` — Dockerfile + compose.yaml for Komodo deployment
(mount `/mnt/user/media` → `/media`).

Dependencies: Task 01 first (otherwise Linux won't compile). Tasks 02 and 03 are
independent of each other, but both require Task 01. Task 05 requires Task 01 (uses the
same Linux build). Task 04 last.


124 changes: 124 additions & 0 deletions .cortex/plans/dual-os-task-01-priority-build-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Task 01: OS-specific ffmpeg Priority via Build Tags

## Goal

`encoder/encoder.go` won't compile under Linux (direct import of
`golang.org/x/sys/windows`). The priority setting is extracted into two build-tag files;
`encoder.go` only calls an OS-neutral helper function.

## Edits

### 1. New file `encoder/priority_windows.go`

```go
//go:build windows

package encoder

import (
"os/exec"

"github.com/Spiritreader/avior-go/config"
"github.com/kpango/glg"
"golang.org/x/sys/windows"
)

// setProcessPriority sets the Windows PriorityClass of the spawned ffmpeg process.
// Errors are only logged (as before), since the encoding itself doesn't depend on it.
func setProcessPriority(cmd *exec.Cmd, cfg *config.Data) {
hProcess, err := windows.OpenProcess(0x0400|0x0200, false, uint32(cmd.Process.Pid))
if err != nil {
_ = glg.Warnf("could not get ffmpeg handle using pid %d, err: %s", cmd.Process.Pid, err)
return
}
defer func() {
if err := windows.CloseHandle(hProcess); err != nil {
_ = glg.Errorf("could not close handle for pid %d, err: %s", cmd.Process.Pid, err)
}
}()
if err := windows.SetPriorityClass(hProcess, config.PriorityUint32(cfg.Local.EncoderPriority)); err != nil {
_ = glg.Warnf("could not set priority %s for ffmpeg handle using pid %d, err: %s",
cfg.Local.EncoderPriority, cmd.Process.Pid, err)
}
}
```

Behavior change over the original: `return` after `OpenProcess` error (avoids
`SetPriorityClass` on invalid handle) and `CloseHandle` via `defer`. This is the
correct version of the existing code — no functional change in the success case. Log
strings remain identical, except the `SetPriorityClass` log outputs
`cfg.Local.EncoderPriority` instead of `cfg.Local.EncoderConfig` (fix for a copy-paste
error in the original, line 183).

### 2. New file `encoder/priority_linux.go`

```go
//go:build linux

package encoder

import (
"os/exec"
"syscall"

"github.com/Spiritreader/avior-go/config"
"github.com/kpango/glg"
)

// niceLevel maps the configured Windows priority level to a Linux nice value.
// Mapping: HIGH/ABOVE_NORMAL -> -5 (higher priority, requires root/CAP_SYS_NICE),
// NORMAL -> 0, BELOW_NORMAL -> 10, IDLE -> 19.
// Unknown values fall back to 19 (idle) — analogous to the Windows fallback in
// config.PriorityUint32, which returns IDLE for unknown values.
func niceLevel(priority string) int {
switch priority {
case config.PRIORITY_HIGH.String(), config.PRIORITY_ABOVE_NORMAL.String():
return -5
case config.PRIORITY_NORMAL.String():
return 0
case config.PRIORITY_BELOW_NORMAL.String():
return 10
default:
return 19
}
}

// setProcessPriority sets the nice level of the spawned ffmpeg process.
// An error (e.g. missing permissions for negative nice values in a Docker container)
// is only logged as a warning; encoding proceeds normally.
func setProcessPriority(cmd *exec.Cmd, cfg *config.Data) {
if err := syscall.Setpriority(syscall.PRIO_PROCESS, cmd.Process.Pid, niceLevel(cfg.Local.EncoderPriority)); err != nil {
_ = glg.Warnf("could not set priority %s for ffmpeg process with pid %d, err: %s",
cfg.Local.EncoderPriority, cmd.Process.Pid, err)
}
}
```

Note: In Docker without `CAP_SYS_NICE`, negative nice values will fail — this is
acceptable (warning in log, encoding runs with default priority). The container docs
may later recommend `--cap-add SYS_NICE`, but that is not part of this plan.

### 3. Adapt `encoder/encoder.go`

- Remove import `"golang.org/x/sys/windows"` (line 23). `config` stays imported
(still used elsewhere in the file — verified: `cfg.Local.*` throughout).
- Replace the block on lines 176–188 (the three `windows.*` calls with error logs) with:

```go
setProcessPriority(cmd, cfg)
```

The call goes directly after the successful `cmd.Start()`, exactly where the old block
was.

## No further callsites

`grep` for `x/sys` and `windows\.` in the entire repo: only `encoder/encoder.go`.
`config.PriorityUint32` remains unchanged (still used by `priority_windows.go`).
Clean cutover: no compatibility code.

## Check

- `GOOS=windows go build ./...` and `GOOS=linux go build ./...` from the repo root
both compile without errors.
- `go vet ./encoder` without findings.
82 changes: 82 additions & 0 deletions .cortex/plans/dual-os-task-02-build-switches.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Task 02: Build Switches for Windows and Linux Executables

## Goal

One command per target platform produces the final binary. Go compiles cross-platform
natively via the `GOOS` switch; all dependencies are pure Go, therefore `CGO_ENABLED=0`
(static binary, ideal for slim Docker images).

## Edits

### 1. New `Makefile` in repo root

```make
BINARY := avior-go
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
LDFLAGS := -s -w -X main.buildVersion=$(VERSION)

.PHONY: build-windows build-linux build all

build-windows:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/$(BINARY)-windows-amd64.exe app.go

build-linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/$(BINARY)-linux-amd64 app.go

build: build-windows

all: build-windows build-linux
```

Notes:
- Entry point is `app.go` (package main in the repo root; verified via
`.vscode/launch.json`, which launches `app.go` as the program).
- `-ldflags "-s -w"` strips symbols (smaller binary for containers).
- `main.buildVersion` is only set if the variable exists — unverified whether
`buildVersion` exists in app.go. If `go build` fails with "no such variable":
reduce ldflags to `-s -w` (fallback, no code requirement).

### 2. Alternative scripts for systems without make

`tools/build.ps1` (Windows development, reusing existing `tools/` convention):

```powershell
param([ValidateSet("windows","linux","all")][string]$Target = "windows")
$env:CGO_ENABLED = "0"
switch ($Target) {
"windows" { $env:GOOS="windows"; $env:GOARCH="amd64"; go build -ldflags "-s -w" -o dist/avior-go-windows-amd64.exe app.go }
"linux" { $env:GOOS="linux"; $env:GOARCH="amd64"; go build -ldflags "-s -w" -o dist/avior-go-linux-amd64 app.go }
"all" { & $PSCommandPath -Target windows; & $PSCommandPath -Target linux }
}
```

`tools/build.sh` (Linux/macOS):

```sh
#!/bin/sh
set -e
target="${1:-linux}"
export CGO_ENABLED=0 GOARCH=amd64
case "$target" in
windows) GOOS=windows go build -ldflags "-s -w" -o dist/avior-go-windows-amd64.exe app.go ;;
linux) GOOS=linux go build -ldflags "-s -w" -o dist/avior-go-linux-amd64 app.go ;;
all) "$0" windows && "$0" linux ;;
*) echo "usage: $0 [windows|linux|all]" >&2; exit 1 ;;
esac
```

### 3. Update `.gitignore`

Add `dist/` (the `*.exe` line already exists, but doesn't cover `dist/` on Linux where
binaries have no `.exe` extension).

## Usage (the "switches")

- Windows executable: `make build-windows` or `./tools/build.ps1 -Target windows`
- Linux binary: `make build-linux` or `./tools/build.sh linux`

## Check

From repo root:
- `make all` produces `dist/avior-go-windows-amd64.exe` AND `dist/avior-go-linux-amd64`.
- `file dist/avior-go-linux-amd64` (under WSL/Git Bash) reports `ELF 64-bit ... statically linked`.
58 changes: 58 additions & 0 deletions .cortex/plans/dual-os-task-03-ci-matrix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Task 03: Extend CI Workflow to Windows+Linux

## Goal

`.github/workflows/go.yml` builds both binaries on release and attaches them to the
release. The existing `wangyoucao577/go-release-action@v1.18` step is parameterized
per OS (matrix) instead of introducing a second mechanism (preserving the existing
convention).

## Edit: Replace `.github/workflows/go.yml` entirely with

```yaml
name: Go

on:
release:
types: [created, edited]
branches: [ master ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: windows
goarch: amd64
binary_name: avior-go
- goos: linux
goarch: amd64
binary_name: avior-go
steps:
- uses: actions/checkout@v2
- uses: wangyoucao577/go-release-action@v1.18
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
binary_name: ${{ matrix.binary_name }}
ldflags: -s -w
```

Reason: matrix instead of two hard jobs: same configuration, artifacts both land on the
same release (`go-release-action` attaches one asset per `goos`; Windows asset is
named `avior-go_windows_amd64.exe.zip`, Linux `avior-go_linux_amd64.tar.gz` —
naming convention of the action, verify on first release).

## Check

- Validate workflow YAML locally: `python -c "import yaml,sys; yaml.safe_load(open('.github/workflows/go.yml'))"`.
- Real check only possible on next release tag — alternative verification:
simulate the matrix run locally with the same commands
(`CGO_ENABLED=0 GOOS=linux go build ./...`, see Task 04).

## Dependency

Task 01 must be merged, otherwise the Linux target will fail in CI.
62 changes: 62 additions & 0 deletions .cortex/plans/dual-os-task-04-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Task 04: Verification of the Dual-OS Build

## Goal

Prove that both targets compile from the same code and that the behavior of the
changed code paths (Task 01) is unchanged or correct.

Working directory for all commands: repo root `C:/repos/avior-go`. No env vars or
fixtures needed; ffmpeg/ffprobe are not required for the pure build checks.

## Checks (in this order)

1. **Both targets compile** (exercises Tasks 01 + 02):
```
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/avior-go-windows-amd64.exe app.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/avior-go-linux-amd64 app.go
```
Expected: both commands exit 0, both files exist. Before Task 01, the Linux command
failed with `build constraints exclude all Go files ... x/sys/windows` — exactly
this error must be gone.

2. **Linux binary is static** (Docker suitability):
```
file dist/avior-go-linux-amd64
```
Expected: `ELF 64-bit LSB executable, x86-64, ... statically linked`.
(`file` via Git Bash/WSL; if unavailable: skip — building with `CGO_ENABLED=0`
guarantees static linking.)

3. **Vet + existing tests** (regression):
```
go vet ./...
go test ./...
```
Expected: no new findings; existing tests (including the `config` package) pass.
Windows-specific paths are tested on the dev Windows machine; the Linux path
(`priority_linux.go`) has no unit tests — intentional, it only wraps a syscall
with logging.

4. **Windows smoke test (behavior unchanged)**:
Start `avior-go-windows-amd64.exe` with the existing `config_dev.json`, run an
encode job, and verify in the log (`log/main.log`) that NO new warning
`could not set priority ... for ffmpeg handle` appears — priority setting works
as before.

5. **Linux smoke test (new behavior)**:
Start the Linux binary in a container or via WSL (`./avior-go-linux-amd64` alongside
a `config.json` with Linux paths in `MediaPaths`/`OutDirectory` and reachable
MongoDB/Redis). Once an encode job runs: the log must not contain a
`could not set priority` warning (unless the container runs without permissions
for negative nice values at `HIGH`/`ABOVE_NORMAL` — then exactly one warning is
acceptable and documented; encoding continues).
Additionally during an encode: `ps -o pid,ni,cmd -C ffmpeg` — the `NI` field must
match the configured mapping (IDLE → 19, NORMAL → 0, BELOW_NORMAL → 10).

## Abort criteria / fallbacks

- Check 1 fails for Linux with import error on `x/sys/windows`: Task 01 incomplete
(import not removed or build tag missing/misspelled — tag must be exactly
`//go:build windows` or `//go:build linux` as the first line).
- Check 3 fails in `encoder` package: signature
`setProcessPriority(cmd *exec.Cmd, cfg *config.Data)` must be identical in both files.
Loading