Documentation
¶
Overview ¶
Package plextv is Ether's plex.tv client: PIN sign-in, server claim, connection publishing, resources, shared users and their server access tokens, and plex.direct certificates. It also holds the Registrar that runs these periodically.
Index ¶
- Constants
- Variables
- func Fingerprint(c *x509.Certificate) string
- func LatestPublicVersion(ctx context.Context, hc *http.Client, endpoint string) (string, error)
- func NewerPMSVersion(a, b string) bool
- func PMSVersion(c config.Config) string
- func ValidPMSVersion(v string) bool
- type APIError
- type Account
- type CertBundle
- type CertInfo
- type CertManager
- func (m *CertManager) Ensure(ctx context.Context, serverToken string, hosts []string, force bool) (CertInfo, error)
- func (m *CertManager) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
- func (m *CertManager) Info() CertInfo
- func (m *CertManager) Install(b *CertBundle) error
- func (m *CertManager) NeedsRenewal() bool
- func (m *CertManager) RunSharedSync(ctx context.Context, every time.Duration)
- func (m *CertManager) SetEventBus(bus EventBus)
- func (m *CertManager) SetRenewalAllowed(fn func() bool)
- func (m *CertManager) SyncShared(ctx context.Context) (bool, error)
- func (m *CertManager) TLSConfig() *tls.Config
- type CertManagerOptions
- type CertRequest
- type ClaimResult
- type Client
- func (c *Client) AccessTokens(ctx context.Context, serverToken string) ([]Friend, error)
- func (c *Client) AuthURL(pin *PIN, forwardURL string) string
- func (c *Client) BaseURL() string
- func (c *Client) CertificateSubject(ctx context.Context, serverToken string) (cn, hash string, err error)
- func (c *Client) CheckPIN(ctx context.Context, id int64, code string) (*PIN, error)
- func (c *Client) Claim(ctx context.Context, userToken string) (*ClaimResult, error)
- func (c *Client) ClaimToken(ctx context.Context, userToken string) (string, error)
- func (c *Client) CreatePIN(ctx context.Context) (*PIN, error)
- func (c *Client) ExchangeClaim(ctx context.Context, claimToken string) (string, error)
- func (c *Client) FindServer(ctx context.Context, token, machineID string) (*Resource, error)
- func (c *Client) Friends(ctx context.Context, serverToken string) ([]Friend, error)
- func (c *Client) HomeUserToken(ctx context.Context, ownerToken string, id int64, pin string) (string, error)
- func (c *Client) Identity() Identity
- func (c *Client) PublishConnections(ctx context.Context, serverToken string, uris []string) error
- func (c *Client) PublishConnectionsWith(ctx context.Context, serverToken string, uris []string, flags *PublishFlags) error
- func (c *Client) PubsubServers(ctx context.Context, serverToken string) ([]string, error)
- func (c *Client) RegisterServer(ctx context.Context, serverToken string, reg ServerRegistration) error
- func (c *Client) ReportConnectivity(ctx context.Context, serverToken string) (string, error)
- func (c *Client) RequestPlexDirectCertificate(ctx context.Context, serverToken string, req CertRequest) (*CertBundle, error)
- func (c *Client) Resources(ctx context.Context, token string) ([]Resource, error)
- func (c *Client) SharedServers(ctx context.Context, ownerToken string) ([]Friend, error)
- func (c *Client) User(ctx context.Context, token string) (*Account, error)
- func (c *Client) UserServices(ctx context.Context, serverToken string) ([]Service, error)
- func (c *Client) UserSubscriptions(ctx context.Context, serverToken string) ([]SharedUser, error)
- func (c *Client) Users(ctx context.Context, ownerToken string) ([]SharedUser, error)
- func (c *Client) WaitPIN(ctx context.Context, pin *PIN, interval time.Duration) (string, error)
- func (c *Client) WithIdentity(id Identity) *Client
- type ConfigStore
- type EventBus
- type Friend
- type Identity
- type Options
- type PIN
- type PortMapper
- type Presence
- type PresenceOptions
- type PresenceStatus
- type PublishFlags
- type PubsubMessage
- type Registrar
- func (r *Registrar) CheckReachability(ctx context.Context) remote.Reachability
- func (r *Registrar) Claim(ctx context.Context, userToken string) (*ClaimResult, error)
- func (r *Registrar) Client() *Client
- func (r *Registrar) NodeURIs(ctx context.Context) []string
- func (r *Registrar) PublishNow(ctx context.Context) error
- func (r *Registrar) RefreshOwner(ctx context.Context) error
- func (r *Registrar) RenewCertificate(ctx context.Context, force bool) error
- func (r *Registrar) RequestPublish()
- func (r *Registrar) RunPortMapping(ctx context.Context)
- func (r *Registrar) SignInClient() *Client
- func (r *Registrar) Start(ctx context.Context) error
- func (r *Registrar) Status() Status
- func (r *Registrar) Stop()
- func (r *Registrar) SyncFriendsNow(ctx context.Context) error
- func (r *Registrar) Trigger()
- func (r *Registrar) Unclaim(ctx context.Context) error
- type RegistrarOptions
- type Resource
- type ResourceConnection
- type ServerRegistration
- type Service
- type SharedCertStore
- type SharedUser
- type Status
- type Subscription
- type UserSync
- type WANDetector
Constants ¶
const ( ClaimExchange = "claim-exchange" ClaimOwnerToken = "owner-token" )
Claim methods reported in ClaimResult.
const ( SharedCertName = "plextv/plexdirect" // CertTopic is published after a node stores a new certificate. CertTopic = "certs:changed" // DefaultCertSyncInterval is RunSharedSync's default poll interval. DefaultCertSyncInterval = time.Minute )
const ( // DefaultBaseURL is plex.tv's API host. DefaultBaseURL = "https://plex.tv" // DefaultServersURL is the PMS account API host observed in current PMS traffic. DefaultServersURL = "https://servers.plex.tv" // DefaultAuthAppURL is the hosted sign-in page PIN codes are approved on. DefaultAuthAppURL = "https://app.plex.tv/auth" // DefaultServerVersion is the PMS version Ether reports towards plex.tv // and clients. Official apps gate features on server version, so it // tracks a recent PMS release rather than Ether's own version. DefaultServerVersion = "1.43.4.10903-e5521bd8c" // ServerProduct is the product name of a Plex Media Server device. ServerProduct = "Plex Media Server" // AppProduct is the product shown for the setup wizard's sign-in. AppProduct = "Ether" )
const PubsubTLSName = "pubsub.plex.tv"
PubsubTLSName is the name pubsub hosts' certificates are valid for.
Variables ¶
ErrCertUnavailable means plex.tv will not issue a plex.direct certificate for this device (no subject, or the endpoint is refused).
var ErrPINExpired = errors.New("plextv: sign-in PIN expired")
ErrPINExpired is returned by WaitPIN when the PIN expired unapproved.
var ErrRateLimited = errors.New("plex.tv rate limit exceeded")
ErrRateLimited matches plex.tv 429 responses (errors.Is).
var ErrRenewalDeferred = errors.New("plextv: certificate renewal is handled by the cluster leader")
ErrRenewalDeferred: this node may not request certificates (another cluster node is the leader).
Functions ¶
func Fingerprint ¶
func Fingerprint(c *x509.Certificate) string
Fingerprint is the hex SHA-256 of a certificate's DER.
func LatestPublicVersion ¶
LatestPublicVersion reads the public Plex release manifest. It only reads version metadata; it never downloads or executes a Plex server binary.
func NewerPMSVersion ¶
func PMSVersion ¶
PMSVersion is the version Ether reports: the owner override, else the latest public release when the owner opted into tracking, else the version the PMS conformance fixtures were recorded against.
func ValidPMSVersion ¶
Types ¶
type APIError ¶
type APIError struct {
Method string
Path string
Status int
Code int // plex.tv error code, e.g. 1001
Message string
RetryAfter time.Duration
}
APIError is a non-2xx plex.tv response.
type Account ¶
type Account struct {
ID int64 `json:"id"`
UUID string `json:"uuid"`
Username string `json:"username"`
Title string `json:"title"`
FriendlyName string `json:"friendlyName"`
Email string `json:"email"`
Thumb string `json:"thumb"`
AuthToken string `json:"authToken"`
Home bool `json:"home"`
HomeAdmin bool `json:"homeAdmin"`
Restricted bool `json:"restricted"`
Subscription Subscription `json:"subscription"`
}
Account is a plex.tv user (GET /api/v2/user).
func (*Account) DisplayName ¶
DisplayName prefers the friendly name, then title, then username.
type CertBundle ¶
type CertBundle struct {
CertPEM []byte // leaf first, then any chain
KeyPEM []byte // PKCS#8
Leaf *x509.Certificate
Hash string // plex.direct hash ("" for self-signed without one)
Subject string // e.g. "*.<hash>.plex.direct"
}
CertBundle is an issued (or self-signed) certificate and its key.
func GenerateSelfSigned ¶
GenerateSelfSigned makes a self-signed certificate for hosts (DNS names or IPs) valid for validity. It is Ether's fallback when no plex.direct certificate can be obtained. Official apps do not trust it: they fall back to plain http on the LAN (when secure connections are "preferred") and refuse it for https connections, so it mostly serves browsers and third-party clients that let users accept it.
type CertInfo ¶
type CertInfo struct {
Present bool `json:"present"`
SelfSigned bool `json:"selfSigned"`
Custom bool `json:"custom"` // owner-supplied files, never replaced
Hash string `json:"hash,omitempty"`
Subject string `json:"subject,omitempty"`
DNSNames []string `json:"dnsNames,omitempty"`
NotBefore time.Time `json:"notBefore,omitempty"`
NotAfter time.Time `json:"notAfter,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
CertFile string `json:"certFile,omitempty"`
KeyFile string `json:"keyFile,omitempty"`
LastAttempt time.Time `json:"lastAttempt,omitempty"` // last plex.tv request
NextAttempt time.Time `json:"nextAttempt,omitempty"`
RetryAfter time.Duration `json:"retryAfter,omitempty"`
LastError string `json:"lastError,omitempty"`
}
CertInfo describes the certificate Ether serves.
type CertManager ¶
type CertManager struct {
// contains filtered or unexported fields
}
CertManager obtains, stores and renews the server's TLS certificate: a plex.direct certificate when plex.tv issues one, otherwise a self-signed fallback. It serves it through GetCertificate so renewals apply without restarting listeners.
func NewCertManager ¶
func NewCertManager(o CertManagerOptions) (*CertManager, error)
NewCertManager returns a manager and loads any certificate on disk.
func (*CertManager) Ensure ¶
func (m *CertManager) Ensure(ctx context.Context, serverToken string, hosts []string, force bool) (CertInfo, error)
Ensure makes sure a usable certificate is installed. With a server token and a missing, self-signed or expiring certificate it requests a plex.direct certificate (at most once per RetryAfter after a failure, unless force). If none can be obtained and there is no valid certificate, it installs a self-signed one for hosts. It returns the resulting info; the error reports the plex.tv failure, if any, even when a fallback is in place.
func (*CertManager) GetCertificate ¶
func (m *CertManager) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate is a tls.Config.GetCertificate callback.
func (*CertManager) Info ¶
func (m *CertManager) Info() CertInfo
Info returns the current certificate state.
func (*CertManager) Install ¶
func (m *CertManager) Install(b *CertBundle) error
Install stores and activates a bundle (used by Ensure; exported so the spike can install a certificate it obtained by hand).
func (*CertManager) NeedsRenewal ¶
func (m *CertManager) NeedsRenewal() bool
NeedsRenewal reports whether the current certificate should be replaced.
func (*CertManager) RunSharedSync ¶
func (m *CertManager) RunSharedSync(ctx context.Context, every time.Duration)
RunSharedSync polls the shared certificate every interval (default DefaultCertSyncInterval) until ctx ends. It returns at once when the manager has no shared store.
func (*CertManager) SetEventBus ¶
func (m *CertManager) SetEventBus(bus EventBus)
SetEventBus subscribes to certificate change announcements from other nodes (and the bus's resync notice) and announces this node's writes. Nil disconnects.
func (*CertManager) SetRenewalAllowed ¶
func (m *CertManager) SetRenewalAllowed(fn func() bool)
SetRenewalAllowed installs the predicate deciding whether this node may request certificates from plex.tv (cluster wiring passes the leader check). Nil, the default, always allows (a standalone server).
func (*CertManager) SyncShared ¶
func (m *CertManager) SyncShared(ctx context.Context) (bool, error)
SyncShared installs the cluster's certificate when another node stored a newer one, and seeds the shared copy from this node's plex.direct certificate when the cluster has none. It reports whether the served certificate changed.
func (*CertManager) TLSConfig ¶
func (m *CertManager) TLSConfig() *tls.Config
TLSConfig returns a server TLS config backed by GetCertificate.
type CertManagerOptions ¶
type CertManagerOptions struct {
// Dir holds plexdirect.crt, plexdirect.key and cert-state.json.
Dir string
// Client must carry the server identity.
Client *Client
// CustomCertFile/CustomKeyFile, when set to files outside Dir, are an
// owner-supplied certificate: loaded and reported, never replaced.
CustomCertFile string
CustomKeyFile string
// RenewBefore triggers renewal this long before expiry (default: a third
// of the certificate's lifetime, at least 7 days).
RenewBefore time.Duration
// RetryAfter is the minimum gap between plex.tv certificate requests
// after a failure (default 24h). plex.tv rate-limits CSR uploads per
// device; PMS servers that retried daily ended up locked out for days.
RetryAfter time.Duration
// ClientErrorRetryAfter controls retries after non-429 4xx responses,
// which usually indicate a malformed request. Default: 5 minutes.
ClientErrorRetryAfter time.Duration
// SelfSignedValidity for the fallback certificate (default 1 year).
SelfSignedValidity time.Duration
// Request overrides the download timing (tests).
Request CertRequest
// Shared, on a cluster node, holds the certificate for every node
// (see certshared.go). Nil: a standalone server keeps it in Dir only.
Logger *slog.Logger
Now func() time.Time
}
CertManagerOptions configures a CertManager.
type CertRequest ¶
type CertRequest struct {
// Key is the private key to certify; a fresh RSA-2048 key when nil
// (RSA for compatibility with older TV clients).
Key crypto.Signer
// Reason is "missing" (no certificate yet) or "expiring", as PMS sends.
Reason string
// InvalidIn is seconds until the current certificate stops being valid
// (negative when already expired; 0 when missing), as PMS sends.
InvalidIn int
// PollInterval between download attempts (default 3s) and Timeout for
// the whole download phase (default 90s, PMS's window).
PollInterval time.Duration
Timeout time.Duration
}
CertRequest tunes RequestPlexDirectCertificate.
type ClaimResult ¶
ClaimResult is the outcome of claiming the server.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to plex.tv with one identity. It is safe for concurrent use.
func (*Client) AccessTokens ¶
AccessTokens fetches the access tokens of every account that can use this server: GET servers.plex.tv/api/v2/server/access_tokens, called with the server token and the server identity. This is the call PMS makes ("MyPlex: updating with N access tokens").
func (*Client) AuthURL ¶
AuthURL is the app.plex.tv page where the owner approves pin. forwardURL, when set, is where plex.tv sends the browser afterwards (the wizard).
func (*Client) CertificateSubject ¶
func (c *Client) CertificateSubject(ctx context.Context, serverToken string) (cn, hash string, err error)
CertificateSubject asks plex.tv for the certificate subject of this device: GET /api/v2/devices/{machineIdentifier}/certificate/subject. The subject carries the plex.direct hash ("/CN=*.<hash>.plex.direct"); the hash is assigned by plex.tv, not derived locally.
func (*Client) CheckPIN ¶
CheckPIN polls a PIN: GET /api/v2/pins/{id}?code=. AuthToken is empty until the user approves it. It must be called with the same client identifier that created the PIN.
func (*Client) Claim ¶
Claim claims this server for the user behind userToken, the way a fresh PMS install is claimed: fetch a claim token for the user, exchange it under the server identity for the server's own token, then confirm the owner. If the exchange is refused for a reason other than a bad user token, it falls back to using the owner's token directly (how PMS worked before claim tokens existed) and reports Method = ClaimOwnerToken.
c must carry the server identity.
func (*Client) ClaimToken ¶
ClaimToken fetches a short-lived claim token ("claim-xxxx") for the signed-in user: GET /api/claim/token.json. This is what plex.tv/claim shows and what Plex Web hands a fresh server during setup.
func (*Client) CreatePIN ¶
CreatePIN starts a sign-in: POST /api/v2/pins?strong=true. Strong PINs are long codes meant for the hosted auth page (AuthURL) rather than typing.
func (*Client) ExchangeClaim ¶
ExchangeClaim turns a claim token into the server's own plex.tv token: POST /api/claim/exchange?token=claim-xxxx, sent with the server identity (X-Plex-Provides: server, client identifier = machine identifier). This is exactly what PMS (and the official Docker image's first-run script) do; the response is a <user> document with an <authentication-token> element.
func (*Client) FindServer ¶
FindServer returns this server's (machineID's) entry in token's resources, or core.ErrNotFound.
func (*Client) Friends ¶
Friends returns everyone with access to this server and their tokens. It uses AccessTokens (what PMS calls) and falls back to SharedServers, then enriches entries with profile data from Users when available.
func (*Client) HomeUserToken ¶
func (c *Client) HomeUserToken(ctx context.Context, ownerToken string, id int64, pin string) (string, error)
HomeUserToken uses the owner's authorized Home switch operation. A PIN, when configured by Plex, is still required; this never resets or bypasses it.
func (*Client) PublishConnections ¶
PublishConnections publishes the server's connection URIs on its plex.tv device record: PUT /devices/{machineIdentifier}?Connection[][uri]=... (one Connection[][uri] per URI; an empty list publishes a single empty value, which clears them, as PMS does when it has no addresses).
c must carry the server identity: plex.tv associates the PUT with the device whose client identifier matches the path.
func (*Client) PublishConnectionsWith ¶
func (c *Client) PublishConnectionsWith(ctx context.Context, serverToken string, uris []string, flags *PublishFlags) error
PublishConnectionsWith publishes connection URIs plus capability flags; flags=nil sends only the URIs.
func (*Client) PubsubServers ¶
PubsubServers lists pubsub hosts, best region first as plex.tv orders them.
func (*Client) RegisterServer ¶
func (c *Client) RegisterServer(ctx context.Context, serverToken string, reg ServerRegistration) error
RegisterServer sends ServerRegistration as PMS does: POST {servers host}/servers.xml?auth_token=<server token> with Content-Type: application/xml and the MediaContainer/Server XML body of registrationXML. PMS sends its X-Plex-* identity headers but no X-Plex-Token header and no other query parameters. Failures remain non-fatal because clients discover connections through resources.
func (*Client) ReportConnectivity ¶
ReportConnectivity starts Plex's asynchronous reachability check. The PMS sends PUT /api/servers/{machineIdentifier}/connectivity with its server token and a fresh asyncIdentifier immediately after servers.xml registration.
func (*Client) RequestPlexDirectCertificate ¶
func (c *Client) RequestPlexDirectCertificate(ctx context.Context, serverToken string, req CertRequest) (*CertBundle, error)
RequestPlexDirectCertificate runs PMS's certificate flow end to end:
- GET /api/v2/devices/{id}/certificate/subject -> "*.<hash>.plex.direct"
- PUT /api/v2/devices/{id}/certificate/csr?reason=…&invalidIn=… multipart/form-data, one part "file" (no filename, Content-Type application/x-pem-file) holding the PEM CSR; 2xx
- GET /api/v2/devices/{id}/certificate/download (202 while pending, 200 with a PEM chain: leaf first, then the intermediate)
The request formats follow the PMS 1.43.4 executable; downloads are still parsed liberally (PEM, DER or JSON-wrapped PEM). plex.tv rate-limits step 2 (HTTP 429, code 1003) per device, so callers must not retry quickly: see CertManager.
It is deliberately self-contained so the spike can call it directly.
func (*Client) Resources ¶
Resources lists the devices visible to token, with plex.direct https connections, relays and IPv6 included, as official apps request it.
func (*Client) SharedServers ¶
SharedServers lists the owner's shares of this server with their access tokens: GET /api/servers/{machineIdentifier}/shared_servers (owner token). It is the documented fallback for AccessTokens.
func (*Client) User ¶
User returns the account a token belongs to. A revoked or unknown token yields an error matching core.ErrUnauthorized.
func (*Client) UserServices ¶
UserServices fetches the current server-scoped service snapshot.
func (*Client) UserSubscriptions ¶
UserSubscriptions reads the server-scoped subscription snapshot. Plex returns an XML users collection; the owner's subscription feature names are retained for the admin UI, while Active reflects Plex Pass entitlement.
func (*Client) WithIdentity ¶
WithIdentity returns a copy of c that sends id.
type ConfigStore ¶
ConfigStore is the part of *config.Store the registrar uses.
type EventBus ¶
type EventBus interface {
Publish(ctx context.Context, topic string, payload []byte) error
Subscribe(topic string, fn func([]byte)) (unsubscribe func())
}
EventBus is the cluster event bus as the certificate manager uses it.
type Friend ¶
type Friend struct {
PlexID int64 `json:"plexId"`
UUID string `json:"uuid,omitempty"`
Username string `json:"username"`
Title string `json:"title"`
Email string `json:"email,omitempty"`
Thumb string `json:"thumb,omitempty"`
Token string `json:"-"`
Owned bool `json:"owned"` // the owner's own access token
Home bool `json:"home"`
Restricted bool `json:"restricted"` // managed user
Pending bool `json:"pending"` // invite not accepted yet
AllLibs bool `json:"allLibraries"`
Sections []string `json:"sections,omitempty"` // shared section keys
}
Friend is an account with access to this server and the server access token plex.tv issued it. Official apps send that token (from their resources list's accessToken) as X-Plex-Token when they talk to the server, so the server can recognise friends without asking plex.tv.
func (Friend) DisplayName ¶
DisplayName prefers title over username.
type Identity ¶
type Identity struct {
Product string
Version string
ClientIdentifier string
DeviceName string
Device string
Platform string
PlatformVersion string
// Provides is "server" for the server identity, empty for the wizard.
Provides string
}
Identity is the set of X-Plex-* headers a client sends to plex.tv.
func AppIdentity ¶
AppIdentity is the setup wizard's identity for PIN sign-in. It uses config.Plex.ClientIdentifier so the owner's plex.tv "authorized devices" list shows an "Ether" entry distinct from the server device.
func ServerIdentity ¶
ServerIdentity is how Ether presents itself as a server: the same headers PMS sends, with the machine identifier as client identifier, because plex.tv keys a server's device record (and clients' resources entries) on it.
type Options ¶
type Options struct {
VersionSource func() string
BaseURL string // DefaultBaseURL when empty
AuthAppURL string // DefaultAuthAppURL when empty
HTTPClient *http.Client
Identity Identity
Logger *slog.Logger
// RetryBackoff is the initial delay between retries of idempotent
// requests (default 500ms).
RetryBackoff time.Duration
}
Options configures a Client.
type PIN ¶
type PIN struct {
ID int64 `json:"id"`
Code string `json:"code"`
ClientIdentifier string `json:"clientIdentifier"`
Trusted bool `json:"trusted"`
ExpiresIn int `json:"expiresIn"`
ExpiresAt time.Time
// AuthToken is set once the user approved the PIN.
AuthToken string `json:"authToken"`
}
PIN is a plex.tv sign-in PIN (POST /api/v2/pins).
type PortMapper ¶
type PortMapper interface {
Ensure(ctx context.Context, internal, external int) (remote.MappingStatus, error)
Status() remote.MappingStatus
Close(ctx context.Context)
}
PortMapper keeps the remote-access port forwarded (remote.PortMapper).
type Presence ¶
type Presence struct {
// contains filtered or unexported fields
}
Presence keeps the pubsub stream open, reconnecting with backoff and rotating hosts after repeated failures.
func NewPresence ¶
func NewPresence(o PresenceOptions) *Presence
func (*Presence) Status ¶
func (p *Presence) Status() PresenceStatus
type PresenceOptions ¶
type PresenceOptions struct {
Client *Client
// Credentials returns the owner's plex.tv id, this server's machine
// identifier and server token; ok=false while unclaimed.
Credentials func() (ownerID int64, machineID, token string, ok bool)
OnMessage func(PubsubMessage)
// StreamClient performs the SSE request; defaults to one verifying TLS
// against PubsubTLSName.
StreamClient *http.Client
// StreamURL builds the stream URL for a host (tests use http).
StreamURL func(host string, ownerID int64, machineID, token string) string
MinBackoff, MaxBackoff time.Duration
// IdleTimeout reconnects when the stream delivers no bytes for this long
// (default 5m). Pubsub sends ": -1" keep-alive comments, so silence
// means a half-open connection (NAT timeout, sleep/wake) that would
// otherwise block the reader forever and leave the server offline.
IdleTimeout time.Duration
}
PresenceOptions configure a Presence.
type PresenceStatus ¶
type PresenceStatus struct {
Connected bool `json:"connected"`
Host string `json:"host,omitempty"`
Since time.Time `json:"since,omitempty"`
LastError string `json:"lastError,omitempty"`
LastMessage string `json:"lastMessage,omitempty"`
}
PresenceStatus is reported in the registrar status.
type PublishFlags ¶
type PublishFlags struct {
HTTPSEnabled bool
HTTPSRequired bool
DNSRebindingProtection bool
NATLoopbackSupported bool
}
PublishFlags are the capability flags PMS appends to its device publish (after the Connection[][uri] list, in this order: httpsEnabled, httpsRequired, dnsRebindingProtection, natLoopbackSupported). plex.tv uses httpsEnabled to offer clients the https plex.direct form of each published http URI.
type PubsubMessage ¶
type PubsubMessage struct {
Command string `xml:"command,attr"`
AsyncIdentifier string `xml:"asyncIdentifier,attr"`
Connectivity string `xml:"connectivity,attr"`
Address string `xml:"address,attr"`
Port string `xml:"port,attr"`
Raw string `xml:"-"`
}
PubsubMessage is one event pushed to the server.
func (PubsubMessage) Reachable ¶
func (m PubsubMessage) Reachable() bool
Reachable reports a notifyConnectivity result.
type Registrar ¶
type Registrar struct {
// contains filtered or unexported fields
}
Registrar runs Ether's plex.tv presence: claim, connection publishing, friend sync, certificate renewal, port mapping and reachability.
func NewRegistrar ¶
func NewRegistrar(o RegistrarOptions) (*Registrar, error)
NewRegistrar validates options and returns a registrar; call Start.
func (*Registrar) CheckReachability ¶
func (r *Registrar) CheckReachability(ctx context.Context) remote.Reachability
CheckReachability probes the WAN connections and records the result.
func (*Registrar) Claim ¶
Claim claims the server for the account behind userToken (from PIN sign-in), persists the server token and owner, informs UserSync and triggers publishing.
func (*Registrar) NodeURIs ¶
NodeURIs returns the URIs this node would publish for itself, refreshing its LAN/WAN addresses first when they are older than AddressInterval (the loop keeps them fresh while it runs). A clustered node heartbeats these so the leader can publish the union.
func (*Registrar) PublishNow ¶
PublishNow publishes the current connections to plex.tv and verifies the server appears in the resources list.
func (*Registrar) RefreshOwner ¶
RefreshOwner re-reads the owner's account (Plex Pass state) and passes it to UserSync; call at startup.
func (*Registrar) RenewCertificate ¶
RenewCertificate runs the certificate manager; force bypasses the failure back-off (admin "retry now").
func (*Registrar) RequestPublish ¶
func (r *Registrar) RequestPublish()
RequestPublish asks the running loop to publish connections now without re-checking addresses (e.g. after cluster membership changed).
func (*Registrar) RunPortMapping ¶
RunPortMapping keeps this node's port mapping (UPnP IGD or NAT-PMP) alive until ctx ends, then removes it. Used with NodeMapping, on every cluster node whether or not it leads.
func (*Registrar) SignInClient ¶
SignInClient returns a client with the setup wizard's identity for PIN sign-in (CreatePIN, AuthURL, WaitPIN).
func (*Registrar) Stop ¶
func (r *Registrar) Stop()
Stop ends the loop and removes any port mapping.
func (*Registrar) SyncFriendsNow ¶
SyncFriendsNow refreshes friends and their access tokens.
type RegistrarOptions ¶
type RegistrarOptions struct {
Config ConfigStore
// Client carries the server identity; built from Config when nil.
Client *Client
BaseURL string // for the default client (tests)
Version string // PMS version reported to plex.tv
Users UserSync
WAN WANDetector
LAN func() ([]net.IP, error)
Mapper PortMapper // nil creates a remote.PortMapper
Reach *remote.ReachabilityChecker
Certs *CertManager // nil disables certificate management
// Intervals (defaults in parentheses).
Tick time.Duration // loop granularity (15s)
AddressInterval time.Duration // LAN/WAN re-check (30s)
PublishInterval time.Duration // unconditional re-publish (1h)
FriendSyncInterval time.Duration // access token sync (1m, observed)
MappingInterval time.Duration // UPnP/NAT-PMP renew check (5m)
CertInterval time.Duration // certificate expiry check (1w, observed when not near expiry)
ReachabilityInterval time.Duration // remote self-test (1h, observed)
// PeerURIs, when set (clustered servers), returns the other live
// nodes' connection URIs in publication order. PublishNow appends them,
// de-duplicated, after this node's own URIs so plex.tv lists the union.
// It is called on every publish and must not block on the network.
PeerURIs func() []string
// NodeMapping moves port mapping out of the publishing loop: the owner
// runs RunPortMapping on every node (each cluster node is reached
// through its own port forward, plex.publicPort being node-local), and
// Stop leaves the mapping in place. Off, the loop maps the port and
// Stop removes the mapping (a standalone server).
NodeMapping bool
// PublishPlexDirect also publishes the https plex.direct URIs. Off by
// default: PMS publishes plain http URIs and plex.tv synthesises the
// plex.direct https variants for clients asking with includeHttps=1.
PublishPlexDirect bool
Logger *slog.Logger
Now func() time.Time
}
RegistrarOptions configures a Registrar. Only Config is required; nil collaborators get production defaults or are disabled.
type Resource ¶
type Resource struct {
Name string `json:"name"`
Product string `json:"product"`
ProductVersion string `json:"productVersion"`
Platform string `json:"platform"`
PlatformVersion string `json:"platformVersion"`
Device string `json:"device"`
ClientIdentifier string `json:"clientIdentifier"`
CreatedAt flexTime `json:"createdAt"`
LastSeenAt flexTime `json:"lastSeenAt"`
Provides string `json:"provides"`
OwnerID *int64 `json:"ownerId"`
SourceTitle string `json:"sourceTitle"`
PublicAddress string `json:"publicAddress"`
AccessToken string `json:"accessToken"`
Owned bool `json:"owned"`
Home bool `json:"home"`
Synced bool `json:"synced"`
Relay bool `json:"relay"`
Presence bool `json:"presence"`
HTTPSRequired bool `json:"httpsRequired"`
PublicAddressMatches bool `json:"publicAddressMatches"`
DNSRebindingProtection bool `json:"dnsRebindingProtection"`
NatLoopbackSupported bool `json:"natLoopbackSupported"`
Connections []ResourceConnection `json:"connections"`
}
Resource is a device in the account's resources list (GET /api/v2/resources), which is how official apps find servers.
func (Resource) ProvidesServer ¶
ProvidesServer reports whether the resource is a media server.
func (*Resource) UnmarshalJSON ¶
UnmarshalJSON accepts Plex's boolean flags serialized as either JSON booleans or numeric 0/1 values.
type ResourceConnection ¶
type ResourceConnection struct {
Protocol string `json:"protocol"`
Address string `json:"address"`
Port int `json:"port"`
URI string `json:"uri"`
Local bool `json:"local"`
Relay bool `json:"relay"`
IPv6 bool `json:"IPv6"`
}
ResourceConnection is one published way to reach a resource.
func (*ResourceConnection) UnmarshalJSON ¶
func (c *ResourceConnection) UnmarshalJSON(b []byte) error
UnmarshalJSON handles the integer IPv6 flag emitted by current resources responses as well as boolean variants used by other Plex API versions.
type ServerRegistration ¶
type ServerRegistration struct {
Name string
Address string // public IP; empty lets plex.tv use the request's source address
Port int // public (mapped) port; 0 when not published
PrivateAddress string // primary LAN address (not part of the PMS body)
PrivatePort int // listener port (not part of the PMS body)
Version string
LocalAddresses []string // LAN IPs
// MappingState mirrors PMS's states: "mapped", "unmapped", "unknown".
// PMS does not send it in servers.xml; it is kept for callers.
MappingState string
// Now stamps the "date" attribute; zero means time.Now().
Now time.Time
}
ServerRegistration is the server info PMS sends to POST /servers.xml ("MyPlex: Sending Server Info to myPlex (user=…, ip=…, port=…)"), which is where plex.tv learns the public address/port.
type Service ¶
Service describes a Plex service entitlement attached to an account. Credentials are intentionally omitted from the public type.
type SharedCertStore ¶
type SharedCertStore interface {
}
SharedCertStore holds named, sealed blobs shared by the cluster (implemented by clusterconfig.Secrets). Version 0 means absent.
type SharedUser ¶
type SharedUser struct {
Servers []string
}
SharedUser is an entry of the owner's friends/home list (v2 server users).
type Status ¶
type Status struct {
Claimed bool `json:"claimed"`
OwnerUsername string `json:"ownerUsername,omitempty"`
MachineIdentifier string `json:"machineIdentifier"`
LANAddresses []string `json:"lanAddresses"`
PublicIP string `json:"publicIp,omitempty"`
PublicIPErr string `json:"publicIpError,omitempty"`
Connections []remote.Connection `json:"connections"`
PublishedURIs []string `json:"publishedUris"`
LastPublish time.Time `json:"lastPublish,omitempty"`
LastPublishError string `json:"lastPublishError,omitempty"`
RegisterError string `json:"registerError,omitempty"` // servers.xml (non-fatal)
// Listed reports whether plex.tv's resources list contains this server;
// ResourceConnections is what clients will be offered.
Listed bool `json:"listed"`
ResourceConnections []ResourceConnection `json:"resourceConnections,omitempty"`
PublicAddress string `json:"publicAddress,omitempty"` // as plex.tv sees us
PlexPass bool `json:"plexPass"` // owner subscription active
RemoteAccess bool `json:"remoteAccess"`
Reachability *remote.Reachability `json:"reachability,omitempty"`
UPnP remote.MappingStatus `json:"upnp"`
Cert CertInfo `json:"cert"`
CertError string `json:"certError,omitempty"`
// Presence is the plex.tv pubsub stream that marks the server online.
Presence PresenceStatus `json:"presence"`
// PlexReachable is plex.tv's last notifyConnectivity verdict for our
// published address (nil until one arrives).
PlexReachable *bool `json:"plexReachable,omitempty"`
Friends int `json:"friends"`
LastFriendSync time.Time `json:"lastFriendSync,omitempty"`
FriendSyncError string `json:"friendSyncError,omitempty"`
}
Status is the plex.tv / remote-access state for the admin dashboard.
type Subscription ¶
type Subscription struct {
Active bool `json:"active"`
Status string `json:"status"`
Plan string `json:"plan"`
Features []string `json:"features"`
}
Subscription is the account's Plex Pass state.