v0.314.0: a restore puts the setup gate back as it was (R-1057); a test build moves with the floor (R-1053); Prowlarr links itself (R-1058); the box claims Plex by address (R-1060)
gates / gates (push) Successful in 47s
gates / gates (push) Successful in 47s
MinAgent 0.155.0 (unchanged). Tests red-proved by mutation (A: 10, B: 7, C: 1, C2: 2). Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
// Prowlarr : one application each for Sonarr and Radarr, „Full Sync" (once)
|
||||
// Plex : the drive's folders added to the EXISTING TV/film libraries, a scan, „scan when a change is
|
||||
// detected" ON — with the server's own PlexOnlineToken read from Preferences.xml AT USE (`09` §3
|
||||
// decision 248); never stored, never logged, sent only to http://plex:32400
|
||||
// decision 248); never stored, never logged, sent only to the plex container (by address, R-1060)
|
||||
// Jellyfin : the drive's folders added, a scan — with the API key named „felhom" read from Jellyfin's own
|
||||
// database AT USE. No such key → one household step, said on the app's page.
|
||||
//
|
||||
@@ -89,10 +89,26 @@ type Wirer struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// New is the production Wirer: apps by container name on the docker network.
|
||||
// New is the production Wirer: apps by container name on the docker network — Plex by its ADDRESS (R-1060, plexaddr.go).
|
||||
func New(box Box, logger *log.Logger) *Wirer {
|
||||
return &Wirer{Box: box, Logger: logger, HTTP: &http.Client{Timeout: 60 * time.Second},
|
||||
URL: func(c string, port int) string { return fmt.Sprintf("http://%s:%d", c, port) }}
|
||||
return &Wirer{Box: box, Logger: logger, HTTP: &http.Client{Timeout: 60 * time.Second}, URL: productionURL(logger)}
|
||||
}
|
||||
|
||||
// productionURL maps a container to its base URL: by name, except Plex by address. A Plex that cannot be resolved falls
|
||||
// back to the name (its calls then fail as before) with a WARN, never silently.
|
||||
func productionURL(logger *log.Logger) func(string, int) string {
|
||||
return func(c string, port int) string {
|
||||
if c == "plex" {
|
||||
u, err := PlexURL(port)
|
||||
if err == nil {
|
||||
return u
|
||||
}
|
||||
if logger != nil {
|
||||
logger.Printf("[WARN] [mediawire] plex: %v — calling it by name, which Plex refuses as non-local (R-1060)", err)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("http://%s:%d", c, port)
|
||||
}
|
||||
}
|
||||
|
||||
// Run wires every present drive into every installed media app. Single-flight: a second caller waits.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package mediawire
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// R-1060 (v0.314.0): every call the box makes to Plex goes to its ADDRESS, never to the name „plex".
|
||||
//
|
||||
// Plex refuses a request whose Host header is a name it does not know as non-local: „Request came in with unrecognized
|
||||
// domain / IP 'plex' in header Host; treating as non-local" (its own log, demo-hp 9201, 2026-10-15). The claim answered
|
||||
// 401 in 0 ms with two fresh codes; by address the same request is local (a fake code: 500 from plex.tv; measured,
|
||||
// felhom.eu audits/media-build-2026-10-15/G7/G5e-host-measure.txt). The K6 spike on Tester 1 called by address and passed;
|
||||
// the product called by name, and its tests replaced Plex with a seam, so nothing met the refusal before a household did.
|
||||
// Used by all four calls: the claim and the identity (internal/web), the library wiring and the sessions (this package).
|
||||
// Pinned by plexaddr_test.go and internal/web/r1060_plex_by_address_test.go.
|
||||
|
||||
// PlexLookup resolves the plex container on the docker network AT CALL TIME — never cached: a restarted container may
|
||||
// get another address. A seam for tests.
|
||||
var PlexLookup = func(ctx context.Context) ([]net.IPAddr, error) {
|
||||
return net.DefaultResolver.LookupIPAddr(ctx, "plex")
|
||||
}
|
||||
|
||||
// PlexURL is Plex's base URL by address: http://<ip>:<port> (IPv4 first).
|
||||
func PlexURL(port int) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
addrs, err := PlexLookup(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolving the plex container: %w", err)
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if ip4 := a.IP.To4(); ip4 != nil {
|
||||
return "http://" + net.JoinHostPort(ip4.String(), strconv.Itoa(port)), nil
|
||||
}
|
||||
}
|
||||
for _, a := range addrs {
|
||||
return "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(port)), nil
|
||||
}
|
||||
return "", fmt.Errorf("the plex container resolves to no address")
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package mediawire
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-1060 — the library wiring and the sessions call Plex by ADDRESS (productionURL), resolved at every call; every
|
||||
// other app stays by name. COMPANION RED-PROOF: drop the plex case in productionURL → „http://plex:32400" and this fails.
|
||||
func TestR1060_ProductionURLCallsPlexByAddress(t *testing.T) {
|
||||
old := PlexLookup
|
||||
t.Cleanup(func() { PlexLookup = old })
|
||||
calls := 0
|
||||
PlexLookup = func(context.Context) ([]net.IPAddr, error) {
|
||||
calls++
|
||||
return []net.IPAddr{{IP: net.ParseIP("fd00::5")}, {IP: net.ParseIP("172.18.0.15")}}, nil
|
||||
}
|
||||
url := productionURL(nil)
|
||||
if got := url("plex", 32400); got != "http://172.18.0.15:32400" {
|
||||
t.Fatalf("plex → %q, want its IPv4 address", got)
|
||||
}
|
||||
_ = url("plex", 32400)
|
||||
if calls != 2 {
|
||||
t.Fatalf("resolved %d times for 2 calls — the address must be resolved at each call, never cached", calls)
|
||||
}
|
||||
if got := url("sonarr", 8989); got != "http://sonarr:8989" {
|
||||
t.Fatalf("sonarr → %q, other apps stay by name", got)
|
||||
}
|
||||
PlexLookup = func(context.Context) ([]net.IPAddr, error) { return nil, errors.New("no such host") }
|
||||
if got := url("plex", 32400); got != "http://plex:32400" {
|
||||
t.Fatalf("an unresolvable plex falls back to the name (logged), got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user