Documentation
¶
Overview ¶
Package npm implements a minimal, dependency-free API client for Nginx Proxy Manager and NPMplus.
The two flavours authenticate differently:
NPM POST /api/tokens -> {"token":"<jwt>"} -> Authorization: Bearer <jwt>
NPMplus POST /api/tokens -> Set-Cookie: npm_token=...; HttpOnly
The client detects the mode from the login response and transparently re-authenticates when the session expires or the API answers 401.
Index ¶
- Constants
- Variables
- func Conform(r Resource, dialect Dialect)
- func Diff(current, desired Resource) string
- func Downgraded(r Resource, d Dialect) []string
- func Inert(r Resource) []string
- func IsManaged(m Meta) bool
- func IsNotFound(err error) bool
- func IsRedth(m Meta) bool
- func NormalizeDomains(domains []string) []string
- func OwnedByInstance(m Meta, instance string) bool
- func Owner(m Meta) string
- func StripNPMplus(r Resource)
- func Unavailable(kind Kind, dialect Dialect) []string
- func UnsupportedByNPM(r Resource) []string
- func XFrameOptionsValue(value string) string
- type APIError
- type AccessList
- type AuthMode
- type Certificate
- type CertificateID
- type Client
- func (c *Client) AuthMode() AuthMode
- func (c *Client) BaseURL() string
- func (c *Client) Create(ctx context.Context, resource Resource) (Resource, error)
- func (c *Client) CreateDeadHost(ctx context.Context, host *DeadHost) (*DeadHost, error)
- func (c *Client) CreateProxyHost(ctx context.Context, host *ProxyHost) (*ProxyHost, error)
- func (c *Client) CreateRedirectionHost(ctx context.Context, host *RedirectionHost) (*RedirectionHost, error)
- func (c *Client) CreateStream(ctx context.Context, stream *Stream) (*Stream, error)
- func (c *Client) Delete(ctx context.Context, kind Kind, id int) error
- func (c *Client) DeleteDeadHost(ctx context.Context, id int) error
- func (c *Client) DeleteProxyHost(ctx context.Context, id int) error
- func (c *Client) DeleteRedirectionHost(ctx context.Context, id int) error
- func (c *Client) DeleteStream(ctx context.Context, id int) error
- func (c *Client) DetectFlavour(ctx context.Context) (Flavour, error)
- func (c *Client) Dialect() Dialect
- func (c *Client) Flavour() Flavour
- func (c *Client) List(ctx context.Context, kind Kind) ([]Resource, error)
- func (c *Client) ListAccessLists(ctx context.Context) ([]AccessList, error)
- func (c *Client) ListCertificates(ctx context.Context) ([]Certificate, error)
- func (c *Client) ListDeadHosts(ctx context.Context) ([]DeadHost, error)
- func (c *Client) ListProxyHosts(ctx context.Context) ([]ProxyHost, error)
- func (c *Client) ListRedirectionHosts(ctx context.Context) ([]RedirectionHost, error)
- func (c *Client) ListStreams(ctx context.Context) ([]Stream, error)
- func (c *Client) Login(ctx context.Context) error
- func (c *Client) Ping(ctx context.Context) error
- func (c *Client) Recheck(ctx context.Context) error
- func (c *Client) ServerVersion() string
- func (c *Client) SetEnabled(ctx context.Context, kind Kind, id int, enabled bool) error
- func (c *Client) Update(ctx context.Context, id int, resource Resource) (Resource, error)
- func (c *Client) UpdateDeadHost(ctx context.Context, id int, host *DeadHost) (*DeadHost, error)
- func (c *Client) UpdateOwnCredentials(ctx context.Context, email, currentSecret, newSecret string) error
- func (c *Client) UpdateProxyHost(ctx context.Context, id int, host *ProxyHost) (*ProxyHost, error)
- func (c *Client) UpdateRedirectionHost(ctx context.Context, id int, host *RedirectionHost) (*RedirectionHost, error)
- func (c *Client) UpdateStream(ctx context.Context, id int, stream *Stream) (*Stream, error)
- type DeadHost
- func (h *DeadHost) AdoptServerState(current Resource)
- func (h *DeadHost) Describe() string
- func (h *DeadHost) Domains() []string
- func (h *DeadHost) Fingerprint() string
- func (h *DeadHost) IsEnabled() bool
- func (h *DeadHost) Kind() Kind
- func (h *DeadHost) OwnerMarked() bool
- func (h *DeadHost) Payload(dialect Dialect) (any, error)
- func (h *DeadHost) ResourceCertificate() CertificateID
- func (h *DeadHost) ResourceID() int
- func (h *DeadHost) ResourceKey() string
- func (h *DeadHost) ResourceMeta() Meta
- func (h *DeadHost) SetEnabled(enabled bool)
- func (h *DeadHost) SetResourceID(id int)
- func (h *DeadHost) UnmarshalJSON(data []byte) error
- type Dialect
- func (d Dialect) Accepts(kind Kind, field string) bool
- func (d Dialect) AcceptsValue(kind Kind, field, value string) bool
- func (d Dialect) CarriesMarker(kind Kind) bool
- func (d Dialect) KeepsOwnership(kind Kind) bool
- func (d Dialect) SupportsStreamCertificate() bool
- func (d Dialect) SupportsTrustForwardedProto() bool
- func (d Dialect) TypeOf(kind Kind, field string) string
- type Flag
- type Flavour
- type Kind
- type Location
- type Meta
- type Option
- func WithClock(now func() time.Time) Option
- func WithFlavour(f Flavour) Option
- func WithHTTPClient(hc *http.Client) Option
- func WithInsecureSkipVerify(skip bool) Option
- func WithLogger(l *slog.Logger) Option
- func WithPayloadLogging(enabled bool) Option
- func WithTimeout(d time.Duration) Option
- func WithUserAgent(ua string) Option
- type Port
- type Property
- type ProxyHost
- func (h *ProxyHost) AdoptServerState(current Resource)
- func (h *ProxyHost) Describe() string
- func (h *ProxyHost) Domains() []string
- func (h *ProxyHost) Fingerprint() string
- func (h *ProxyHost) IsEnabled() bool
- func (h *ProxyHost) Kind() Kind
- func (h *ProxyHost) OwnerMarked() bool
- func (h *ProxyHost) Payload(dialect Dialect) (any, error)
- func (h *ProxyHost) ResourceCertificate() CertificateID
- func (h *ProxyHost) ResourceID() int
- func (h *ProxyHost) ResourceKey() string
- func (h *ProxyHost) ResourceMeta() Meta
- func (h *ProxyHost) SetEnabled(enabled bool)
- func (h *ProxyHost) SetResourceID(id int)
- func (h *ProxyHost) UnmarshalJSON(data []byte) error
- type ProxyProtocolLevel
- type RedirectionHost
- func (h *RedirectionHost) AdoptServerState(current Resource)
- func (h *RedirectionHost) Describe() string
- func (h *RedirectionHost) Domains() []string
- func (h *RedirectionHost) Fingerprint() string
- func (h *RedirectionHost) IsEnabled() bool
- func (h *RedirectionHost) Kind() Kind
- func (h *RedirectionHost) OwnerMarked() bool
- func (h *RedirectionHost) Payload(dialect Dialect) (any, error)
- func (h *RedirectionHost) ResourceCertificate() CertificateID
- func (h *RedirectionHost) ResourceID() int
- func (h *RedirectionHost) ResourceKey() string
- func (h *RedirectionHost) ResourceMeta() Meta
- func (h *RedirectionHost) SetEnabled(enabled bool)
- func (h *RedirectionHost) SetResourceID(id int)
- func (h *RedirectionHost) UnmarshalJSON(data []byte) error
- type Resource
- type Stream
- func (s *Stream) AdoptServerState(current Resource)
- func (s *Stream) Describe() string
- func (s *Stream) Fingerprint() string
- func (s *Stream) IsEnabled() bool
- func (s *Stream) Kind() Kind
- func (s *Stream) OwnerMarked() bool
- func (s *Stream) Payload(dialect Dialect) (any, error)
- func (s *Stream) ResourceCertificate() CertificateID
- func (s *Stream) ResourceID() int
- func (s *Stream) ResourceKey() string
- func (s *Stream) ResourceMeta() Meta
- func (s *Stream) SetEnabled(enabled bool)
- func (s *Stream) SetResourceID(id int)
- func (s *Stream) UnmarshalJSON(data []byte) error
- type Version
- type WriteSchema
Constants ¶
const ( SchemeAuto = "auto" SchemeKeep = "$scheme" )
Redirection schemes that keep the scheme of the request.
const ( MetaManagedBy = "managed_by" MetaContainer = "managed_container" MetaIndex = "managed_index" // MetaContainerID pins the resource to a container id as well as a name, // so a renamed container is still recognised as the owner. MetaContainerID = "managed_container_id" // MetaInstance names the sync instance that created the resource. Several // instances can drive one NPM without treating each other's hosts as // orphans. MetaInstance = "managed_instance" MetaPrefix = "managed_prefix" // MetaDirectory is the UI group of a resource. NPMplus keeps it in the // meta object rather than in a column of its own, which is why grouping // works through the same field this tool already writes its ownership // markers into. Upstream nginx-proxy-manager stores the key too, but has // no grouping in its UI, so it is treated as an NPMplus-only setting. MetaDirectory = "directory" ManagedByValue = "npmplus-docker-sync" CertificateNew = "new" )
Meta keys written into every resource this tool owns.
const ( AccessListPublic = "public" AccessListCustom = "custom" AccessListGlobal = "global" )
Access list types. NPMplus replaced NPM's single `access_list_id` with a list of ids plus an explicit type; "public" means no access list at all and "global" (locations only) means "inherit from the proxy host".
const ( LocationPrefix = "" LocationExact = "= " LocationRegex = "~ " LocationIRegex = "~* " LocationPrefer = "^~ " LocationNamed = "@" )
Location modifiers. NPMplus stores the nginx prefix of a custom location verbatim, including the trailing space it needs in the generated config.
const MaxDescriptionLength = 255
MaxDescriptionLength is the limit NPMplus puts on `npmplus_description`.
const MaxDirectoryLength = 255
MaxDirectoryLength is the limit NPMplus puts on a group name: it truncates anything longer when it moves the value out of the meta object into a column, so a longer name would not survive an upgrade intact.
const RedthManagedByValue = "npm-docker-sync"
RedthManagedByValue is the ownership marker of Redth/npm-docker-sync, the project whose label syntax this tool adopted.
It is deliberately *not* treated as our own: both tools can run against the same NPM instance, and silently taking over the other's hosts would have them delete each other's work. Migration is explicit (MIGRATE_FROM_REDTH).
Variables ¶
var Kinds = []Kind{KindProxy, KindRedirect, KindStream, KindDead}
Kinds is the canonical processing order. Proxy hosts come first because they are by far the most common resource.
var LegacyManagedByValues = []string{}
LegacyManagedByValues are ownership markers written by earlier releases of *this* tool. They are still recognised so an upgrade adopts (and re-stamps) existing resources instead of orphaning them.
Functions ¶
func Conform ¶
Conform brings a desired resource in line with what the server can store and return: NPMplus-only settings go against upstream NPM, retired switches go against an NPMplus that no longer has them, and the resource records whether its ownership marker can be written. Call it once the dialect is known, before fingerprinting - a field the server drops would otherwise differ from the live resource on every single run.
func Diff ¶
Diff renders the configuration fields in which two resources differ, as "field: live → desired".
It exists so a dry-run says what would actually change, and so an edit made in the NPM UI is named in the log line that overwrites it - "would update" alone leaves the operator guessing.
func Downgraded ¶
Downgraded returns the labels whose value the server can only store in a weaker form: a proxy protocol v2 on an NPMplus release that only knows "on".
func Inert ¶
Inert returns the labels of the proxy host switches that do nothing for its scheme or in their combination and are therefore left out of the request (Location.cleanForwardOptions). The caller reports those a label set explicitly.
func IsManaged ¶
IsManaged reports whether the meta object carries the current ownership marker or one of its predecessors.
func IsNotFound ¶
IsNotFound reports whether err is a 404 from the API.
func NormalizeDomains ¶
NormalizeDomains lowercases, trims and sorts domain names so two resources can be compared deterministically.
func OwnedByInstance ¶
OwnedByInstance reports whether a resource of ours belongs to this sync instance. A resource without an instance stamp was written by a release that did not have them yet and is adopted once; a resource carrying another instance's id belongs to that instance and is never touched.
func Owner ¶
Owner returns the tool named in the ownership marker, "" for a resource created by hand.
func StripNPMplus ¶
func StripNPMplus(r Resource)
StripNPMplus removes every NPMplus-only setting from a resource, so the desired state matches what upstream nginx-proxy-manager can store and return. Call it once the flavour is known, before fingerprinting.
func Unavailable ¶
Unavailable returns the labels of the switches this server does not offer for the kind: NPMplus retired them (websockets and HTTP/2 are always on there), upstream NPM before 2.12 has no trust_forwarded_proto. The caller reports those a label set explicitly.
func UnsupportedByNPM ¶
UnsupportedByNPM returns the NPMplus-only fields the resource uses. Against upstream nginx-proxy-manager they are silently left out of the request, and the reconcile loop reports them once instead of failing the write.
func XFrameOptionsValue ¶
XFrameOptionsValue returns the spelling NPMplus expects for a lower case x-frame-options value.
Types ¶
type APIError ¶
type APIError struct {
Status int
Method string
Path string
Message string
// Body is the raw response body (truncated), kept for diagnostics when
// the API returns something other than its usual error envelope.
Body string
}
APIError is returned for non-2xx API responses.
type AccessList ¶
AccessList mirrors `/api/nginx/access-lists`. Only the identity is needed: labels reference a list by name and the sync resolves it into an id.
type Certificate ¶
type Certificate struct {
ID int `json:"id"`
Provider string `json:"provider"`
NiceName string `json:"nice_name"`
DomainNames []string `json:"domain_names"`
ExpiresOn string `json:"expires_on"`
// IsDeleted is NPM's soft-delete marker, serialised as 0/1.
IsDeleted Flag `json:"is_deleted"`
}
Certificate mirrors the `/api/nginx/certificates` resource.
NPMplus stores client CAs in the same collection (provider "mtls"); they are not server certificates, which is why the provider is decoded too.
type CertificateID ¶
CertificateID models NPM's polymorphic certificate_id field: responses return a number, while create/update payloads may carry the string "new" to request a fresh Let's Encrypt certificate.
func CertificateRef ¶
func CertificateRef(id int) CertificateID
CertificateRef returns a CertificateID pointing at an existing certificate.
func NewCertificate ¶
func NewCertificate() CertificateID
NewCertificate returns a CertificateID requesting a new LE certificate.
func (CertificateID) IsZero ¶
func (c CertificateID) IsZero() bool
IsZero reports whether no certificate is attached at all.
func (CertificateID) MarshalJSON ¶
func (c CertificateID) MarshalJSON() ([]byte, error)
MarshalJSON emits either a number or the literal string "new".
func (CertificateID) String ¶
func (c CertificateID) String() string
String implements fmt.Stringer.
func (*CertificateID) UnmarshalJSON ¶
func (c *CertificateID) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts numbers, numeric strings, "new", null and "".
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the NPM/NPMplus REST API. It is safe for concurrent use.
func New ¶
New creates a client for the given base URL (e.g. http://npm:81).
func (*Client) Create ¶
Create adds a resource and returns the version NPM stored.
Only the flavour specific request payload is sent, never the response model: both APIs reject unknown properties outright.
func (*Client) CreateDeadHost ¶
CreateDeadHost creates a new 404 host.
func (*Client) CreateProxyHost ¶
CreateProxyHost creates a new proxy host.
func (*Client) CreateRedirectionHost ¶
func (c *Client) CreateRedirectionHost(ctx context.Context, host *RedirectionHost) (*RedirectionHost, error)
CreateRedirectionHost creates a new redirection host.
func (*Client) CreateStream ¶
CreateStream creates a new stream.
func (*Client) DeleteDeadHost ¶
DeleteDeadHost removes a 404 host.
func (*Client) DeleteProxyHost ¶
DeleteProxyHost removes a proxy host.
func (*Client) DeleteRedirectionHost ¶
DeleteRedirectionHost removes a redirection host.
func (*Client) DeleteStream ¶
DeleteStream removes a stream.
func (*Client) DetectFlavour ¶
DetectFlavour probes the server and remembers the result. A pinned flavour is returned unchanged without any request.
Two signals are used, strongest first:
- An existing proxy host: NPMplus serialises `npmplus_access_list_ids`, upstream NPM serialises `access_list_id`. This is authoritative because it is the very field the write payloads disagree about.
- GET /api/: both report a `version`, but in different shapes - upstream NPM an object ({major, minor, revision}), NPMplus a string ("2026-07-24-r1-a30a954-2.15.1"). Older NPMplus versions reported none at all, which stays inconclusive and lands on the default below.
When neither is conclusive the detection falls back to NPMplus, which is what this tool is named after, and says so in the log.
func (*Client) Dialect ¶
Dialect is what the payload builders write for: the detected flavour plus the server version.
func (*Client) Flavour ¶
Flavour reports the dialect in use. It is FlavourAuto until either WithFlavour pinned one or DetectFlavour resolved one.
func (*Client) ListAccessLists ¶
func (c *Client) ListAccessLists(ctx context.Context) ([]AccessList, error)
ListAccessLists returns the access lists stored in NPM, so labels can name them instead of pinning an id that differs between instances.
func (*Client) ListCertificates ¶
func (c *Client) ListCertificates(ctx context.Context) ([]Certificate, error)
ListCertificates returns the certificates stored in NPM.
func (*Client) ListDeadHosts ¶
ListDeadHosts returns every 404 host known to NPM.
func (*Client) ListProxyHosts ¶
ListProxyHosts returns every proxy host known to NPM.
func (*Client) ListRedirectionHosts ¶
func (c *Client) ListRedirectionHosts(ctx context.Context) ([]RedirectionHost, error)
ListRedirectionHosts returns every redirection host known to NPM.
func (*Client) ListStreams ¶
ListStreams returns every TCP/UDP stream known to NPM.
func (*Client) Login ¶
Login authenticates against POST /api/tokens and detects whether the server uses cookies (NPMplus) or a bearer token (NPM).
func (*Client) Recheck ¶
Recheck notices a server upgrade: when GET /api/ reports a different version than before, the dialect is probed again. It is cheap enough to run on every periodic resync.
func (*Client) ServerVersion ¶
ServerVersion returns the version last reported by the API, "" when the server does not report one (NPMplus).
func (*Client) SetEnabled ¶
SetEnabled switches a resource on or off. Both flavours refuse `enabled` in a create/update body and expose dedicated endpoints instead, which answer 400 "Host is already enabled" when the state already matches - that is treated as success.
func (*Client) UpdateDeadHost ¶
UpdateDeadHost updates an existing 404 host.
func (*Client) UpdateOwnCredentials ¶
func (c *Client) UpdateOwnCredentials(ctx context.Context, email, currentSecret, newSecret string) error
UpdateOwnCredentials changes the e-mail address and the password of the logged-in user.
Both projects ship a default account that has to be rotated before the API is usable; this is what the integration harness uses to do that on images which do not seed the first user from the environment.
func (*Client) UpdateProxyHost ¶
UpdateProxyHost updates an existing proxy host.
func (*Client) UpdateRedirectionHost ¶
func (c *Client) UpdateRedirectionHost(ctx context.Context, id int, host *RedirectionHost) (*RedirectionHost, error)
UpdateRedirectionHost updates an existing redirection host.
type DeadHost ¶
type DeadHost struct {
ID int `json:"id,omitempty"`
CreatedOn string `json:"created_on,omitempty"`
ModifiedOn string `json:"modified_on,omitempty"`
OwnerUserID int `json:"owner_user_id,omitempty"`
DomainNames []string `json:"domain_names"`
CertificateID CertificateID `json:"certificate_id"`
SSLForced Flag `json:"ssl_forced"`
HSTSEnabled Flag `json:"hsts_enabled"`
HSTSSubdomains Flag `json:"hsts_subdomains"`
HTTP2Support Flag `json:"http2_support"`
HTTP3Support Flag `json:"npmplus_http3_support"`
AdvancedConfig string `json:"advanced_config"`
Enabled Flag `json:"enabled"`
Meta Meta `json:"meta"`
// Directory is NPMplus' `npmplus_directory` column. It is moved into the
// meta object when the resource is decoded and never kept here.
Directory string `json:"npmplus_directory,omitempty"`
// OwnerMarker records the ownership marker in the advanced configuration;
// see marker.go. Not an API field.
OwnerMarker Flag `json:"ownership_marker,omitempty"`
}
DeadHost mirrors `/api/nginx/dead-hosts`, the "404 host" in the NPM UI.
func (*DeadHost) AdoptServerState ¶
AdoptServerState implements Resource.
func (*DeadHost) Fingerprint ¶
Fingerprint implements Resource.
func (*DeadHost) OwnerMarked ¶
OwnerMarked implements Resource.
func (*DeadHost) ResourceCertificate ¶
func (h *DeadHost) ResourceCertificate() CertificateID
ResourceCertificate implements Resource.
func (*DeadHost) ResourceKey ¶
ResourceKey implements Resource.
func (*DeadHost) ResourceMeta ¶
ResourceMeta implements Resource.
func (*DeadHost) SetEnabled ¶
SetEnabled implements Resource.
func (*DeadHost) SetResourceID ¶
SetResourceID implements Resource.
func (*DeadHost) UnmarshalJSON ¶
UnmarshalJSON decodes the resource and lifts the ownership marker and the group column into the meta object.
type Dialect ¶
type Dialect struct {
Flavour Flavour
// Version is upstream NPM's release; NPMplus reports none we can compare.
Version Version
// Schema is what the server says it accepts (GET /api/schema). nil when
// it serves none, in which case Accepts falls back to built-in knowledge.
Schema WriteSchema
// MetaRetired records an NPMplus release that moved the meta object into
// columns. Only consulted without a schema.
MetaRetired bool
}
Dialect is everything a request body has to be written for. Both servers validate with `additionalProperties: false` and have gained and lost fields over their releases: a field that NPM 2.12 expects makes 2.11 reject the whole payload, and a `meta` object that every NPMplus release accepted makes the ones after its meta-to-columns migration reject it.
func (Dialect) Accepts ¶
Accepts reports whether a create/update body of the kind may carry the field. The schema read from the server decides when there is one; without it the answer comes from what is known about the flavour and its releases.
func (Dialect) AcceptsValue ¶
AcceptsValue reports whether the server allows the value for a field that is restricted to a list of values.
func (Dialect) CarriesMarker ¶
CarriesMarker reports whether resources of the kind can hold the ownership marker on this server. Every kind can, except upstream NPM's streams, which have no advanced configuration; those keep their owner in the meta object.
func (Dialect) KeepsOwnership ¶
KeepsOwnership reports whether the server can store who owns a resource of the kind at all. Without it nothing could ever be recognised as ours again.
func (Dialect) SupportsStreamCertificate ¶
SupportsStreamCertificate reports whether streams accept `certificate_id`. Upstream NPM added stream certificates in 2.12; NPMplus has always had them.
func (Dialect) SupportsTrustForwardedProto ¶
SupportsTrustForwardedProto reports whether proxy hosts accept `trust_forwarded_proto`. Upstream NPM added it in 2.12; NPMplus retired it together with the meta object.
type Flag ¶
type Flag bool
Flag models NPM's "enabled" field which is a number in some versions and a boolean in others.
func (Flag) MarshalJSON ¶
MarshalJSON always writes a boolean, which every NPM version accepts.
func (*Flag) UnmarshalJSON ¶
UnmarshalJSON accepts true/false, 1/0 and "1"/"0".
type Flavour ¶
type Flavour string
Flavour is the API dialect of the server this client talks to.
NPM and NPMplus share the same endpoints but validate request bodies against JSON schemas with `additionalProperties: false`, and those schemas have drifted apart: NPMplus dropped `enabled` and `access_list_id` from every write payload, replaced the latter with `npmplus_access_list_ids` plus `npmplus_access_list_type`, and types the stream ports as strings. Sending the wrong dialect fails with
400 data must NOT have additional properties
so the dialect has to be known before the first write.
const ( FlavourAuto Flavour = "auto" FlavourNPMplus Flavour = "npmplus" FlavourNPM Flavour = "npm" )
The supported dialects. FlavourAuto asks the client to probe the server.
func ParseFlavour ¶
ParseFlavour resolves the spellings accepted in configuration.
type Kind ¶
type Kind string
Kind enumerates the NPM resource types this tool manages.
const ( KindProxy Kind = "proxy" KindRedirect Kind = "redirect" KindStream Kind = "stream" KindDead Kind = "dead" )
The four routing resources of Nginx Proxy Manager / NPMplus.
type Location ¶
type Location struct {
Path string `json:"path"`
LocationType string `json:"location_type"`
AdvancedConfig string `json:"advanced_config"`
LocationConfig string `json:"npmplus_location_config,omitempty"`
ForwardScheme string `json:"forward_scheme"`
ForwardHost string `json:"forward_host"`
ForwardPort int `json:"forward_port"`
AccessListIDs []int `json:"npmplus_access_list_ids,omitempty"`
AccessListType string `json:"npmplus_access_list_type,omitempty"`
// AccessListNames are names that still have to be resolved into ids. They
// never reach the API.
AccessListNames []string `json:"-"`
// AccessLists is the expanded relation; see ProxyHost.AccessLists.
AccessLists []accessListRef `json:"access_lists,omitempty"`
// Enabled is nil when the server did not report it; a location without
// the flag is enabled.
Enabled *bool `json:"npmplus_enabled,omitempty"`
NoIndex Flag `json:"npmplus_noindex"`
DisableCrowdsecAppsec Flag `json:"npmplus_crowdsec_appsec"`
DisableRequestBuffering Flag `json:"npmplus_proxy_request_buffering"`
DisableResponseBuffering Flag `json:"npmplus_proxy_response_buffering"`
UpstreamCompression Flag `json:"npmplus_upstream_compression"`
FancyIndex Flag `json:"npmplus_fancyindex"`
XFrameOptions string `json:"npmplus_x_frame_options,omitempty"`
AuthRequest string `json:"npmplus_auth_request,omitempty"`
AuthRequestUpstream string `json:"npmplus_auth_request_upstream,omitempty"`
}
Location is an optional custom location block of a proxy host.
The npmplus_* fields are unknown to upstream NPM; the per-flavour payload structs decide which of them are sent. Three of them are inverted in the API - `npmplus_crowdsec_appsec: true` *disables* the AppSec component - so the model spells them out as Disable* and the label parser negates them.
type Meta ¶
Meta is the free-form meta object attached to every NPM resource. NPM keeps its Let's Encrypt settings here; we add our own ownership markers.
func AdoptMeta ¶
AdoptMeta carries the foreign bookkeeping of a resource we are taking over into the meta we are about to write, so a migration is not a data loss.
func (Meta) ClearDirectory ¶
func (m Meta) ClearDirectory()
ClearDirectory removes the group from the meta object, leaving the resource as if it had never been grouped by this tool.
func (Meta) ContainerID ¶
ContainerID returns the container id recorded in the marker, if any.
func (Meta) Directory ¶
Directory returns the UI group of the resource, "" for an ungrouped one. NPMplus trims the stored value before it groups by it, so this does too.
func (Meta) Instance ¶
Instance returns the sync instance that created the resource. An empty string means the resource predates instance stamping.
func (Meta) SetDirectory ¶
SetDirectory records the UI group. An empty name is kept as an empty value rather than dropped: "explicitly ungrouped" has to be distinguishable from "no group configured", which is what makes clearing one possible at all.
type Option ¶
type Option func(*Client)
Option customises the client.
func WithFlavour ¶
WithFlavour pins the dialect instead of probing for it. Useful when the API sits behind a proxy that rewrites responses, or to keep a known-good configuration stable across upgrades.
func WithHTTPClient ¶
WithHTTPClient replaces the underlying HTTP client. The client must have a cookie jar for NPMplus support; New installs one if it is missing.
func WithInsecureSkipVerify ¶
WithInsecureSkipVerify disables TLS verification. Only use it for NPM instances behind a self-signed certificate on a trusted network.
func WithPayloadLogging ¶
WithPayloadLogging mirrors complete request bodies into the debug log. Values of credential-looking meta keys are redacted, but the bodies still contain host names and advanced nginx config, so this stays opt-in.
func WithTimeout ¶
WithTimeout sets the per-request timeout.
func WithUserAgent ¶
WithUserAgent overrides the User-Agent header.
type Port ¶
type Port struct {
// contains filtered or unexported fields
}
Port models the polymorphic port fields of the stream resource. Upstream NPM types `incoming_port` and `forwarding_port` as integers, NPMplus types them as strings and additionally accepts a range ("8080-8090") for the incoming port and "$server_port" for the forwarded one.
The raw text is preserved so a foreign stream that uses one of the NPMplus forms still round-trips and keeps a stable identity.
func (Port) Int ¶
Int returns the numeric value, or 0 for an unset or non-numeric port such as a range or "$server_port".
func (Port) MarshalJSON ¶
MarshalJSON emits a number for numeric ports and a string otherwise, which matches what both flavours return. Request bodies do not use this: the per-flavour payload structs pick the type the server's schema demands.
func (*Port) UnmarshalJSON ¶
UnmarshalJSON accepts both the integer (NPM) and the string (NPMplus) form.
type Property ¶
type Property struct {
// Type is the JSON type, "" when the schema allows several.
Type string
// Enum lists the allowed values, nil when any value of the type is.
// Strings are kept as they are, other values as their JSON text.
Enum []string
}
Property is what a schema says about one request property.
type ProxyHost ¶
type ProxyHost struct {
ID int `json:"id,omitempty"`
CreatedOn string `json:"created_on,omitempty"`
ModifiedOn string `json:"modified_on,omitempty"`
OwnerUserID int `json:"owner_user_id,omitempty"`
DomainNames []string `json:"domain_names"`
ForwardHost string `json:"forward_host"`
ForwardPort int `json:"forward_port"`
ForwardScheme string `json:"forward_scheme"`
CertificateID CertificateID `json:"certificate_id"`
// Every boolean the API hands back is read through Flag: NPM up to 2.11
// returns the switches as the SQLite integers they are stored as
// (`"ssl_forced": 1`), while 2.12 and NPMplus return real booleans.
SSLForced Flag `json:"ssl_forced"`
HSTSEnabled Flag `json:"hsts_enabled"`
HSTSSubdomains Flag `json:"hsts_subdomains"`
TrustForwardedProto Flag `json:"trust_forwarded_proto"`
HTTP2Support Flag `json:"http2_support"`
BlockExploits Flag `json:"block_exploits"`
CachingEnabled Flag `json:"caching_enabled"`
AllowWebsocketUpgrade Flag `json:"allow_websocket_upgrade"`
AdvancedConfig string `json:"advanced_config"`
Enabled Flag `json:"enabled"`
Locations []Location `json:"locations"`
Meta Meta `json:"meta"`
// Directory is NPMplus' `npmplus_directory` column. It is moved into the
// meta object when the resource is decoded and never kept here.
Directory string `json:"npmplus_directory,omitempty"`
// OwnerMarker records the ownership marker in the advanced configuration;
// see marker.go. Not an API field.
OwnerMarker Flag `json:"ownership_marker,omitempty"`
// HTTP3Support is an NPMplus extension (`npmplus_http3_support`).
HTTP3Support Flag `json:"npmplus_http3_support"`
// NPMplus extensions. The three Disable* switches are inverted in the
// API (`npmplus_crowdsec_appsec: true` turns the AppSec component off),
// which is why the model spells them out that way.
NoIndex Flag `json:"npmplus_noindex"`
DisableCrowdsecAppsec Flag `json:"npmplus_crowdsec_appsec"`
DisableRequestBuffering Flag `json:"npmplus_proxy_request_buffering"`
DisableResponseBuffering Flag `json:"npmplus_proxy_response_buffering"`
UpstreamCompression Flag `json:"npmplus_upstream_compression"`
FancyIndex Flag `json:"npmplus_fancyindex"`
XFrameOptions string `json:"npmplus_x_frame_options"`
AuthRequest string `json:"npmplus_auth_request"`
AuthRequestUpstream string `json:"npmplus_auth_request_upstream"`
LocationConfig string `json:"npmplus_location_config"`
// Access lists: upstream NPM has a single `access_list_id`, NPMplus a
// list plus an explicit type. Both spellings are decoded so a host can be
// compared no matter which server returned it; accessLists() normalises.
AccessListID int `json:"access_list_id"`
AccessListIDs []int `json:"npmplus_access_list_ids"`
AccessListType string `json:"npmplus_access_list_type"`
// AccessListNames are names that still have to be resolved into ids.
// They never reach the API.
AccessListNames []string `json:"-"`
// AccessLists is the expanded relation NPMplus returns instead of
// npmplus_access_list_ids since it moved access lists into a table of
// their own. Read into AccessListIDs on decoding, never kept.
AccessLists []accessListRef `json:"access_lists,omitempty"`
}
ProxyHost mirrors `/api/nginx/proxy-hosts` as the API *returns* it. Request bodies are built by Payload; see payloads.go.
func (*ProxyHost) AdoptServerState ¶
AdoptServerState implements Resource.
func (*ProxyHost) Fingerprint ¶
Fingerprint implements Resource.
func (*ProxyHost) OwnerMarked ¶
OwnerMarked implements Resource.
func (*ProxyHost) ResourceCertificate ¶
func (h *ProxyHost) ResourceCertificate() CertificateID
ResourceCertificate implements Resource.
func (*ProxyHost) ResourceKey ¶
ResourceKey implements Resource.
func (*ProxyHost) ResourceMeta ¶
ResourceMeta implements Resource.
func (*ProxyHost) SetEnabled ¶
SetEnabled implements Resource.
func (*ProxyHost) SetResourceID ¶
SetResourceID implements Resource.
func (*ProxyHost) UnmarshalJSON ¶
UnmarshalJSON decodes the resource and lifts the ownership marker and the group column into the meta object.
type ProxyProtocolLevel ¶
type ProxyProtocolLevel int
ProxyProtocolLevel is NPMplus' `npmplus_proxy_protocol_forwarding`. The two generations of NPMplus disagree about its type: older ones declare an integer with the enum [0, 1, 2] (off, v1, v2), while 2.15.1 declares a boolean - it still coerces 0 and 1 on the way in, rejects 2 with "must be boolean", and reports the stored value back as true/false.
Decoding therefore has to accept both shapes; encoding stays numeric, because that is what both generations take.
func (ProxyProtocolLevel) MarshalJSON ¶
func (p ProxyProtocolLevel) MarshalJSON() ([]byte, error)
MarshalJSON writes the numeric form.
func (*ProxyProtocolLevel) UnmarshalJSON ¶
func (p *ProxyProtocolLevel) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts 0/1/2, true/false and their string spellings.
type RedirectionHost ¶
type RedirectionHost struct {
ID int `json:"id,omitempty"`
CreatedOn string `json:"created_on,omitempty"`
ModifiedOn string `json:"modified_on,omitempty"`
OwnerUserID int `json:"owner_user_id,omitempty"`
DomainNames []string `json:"domain_names"`
ForwardScheme string `json:"forward_scheme"`
ForwardDomainName string `json:"forward_domain_name"`
ForwardHTTPCode int `json:"forward_http_code"`
PreservePath Flag `json:"preserve_path"`
CertificateID CertificateID `json:"certificate_id"`
SSLForced Flag `json:"ssl_forced"`
HSTSEnabled Flag `json:"hsts_enabled"`
HSTSSubdomains Flag `json:"hsts_subdomains"`
HTTP2Support Flag `json:"http2_support"`
HTTP3Support Flag `json:"npmplus_http3_support"`
BlockExploits Flag `json:"block_exploits"`
AdvancedConfig string `json:"advanced_config"`
Enabled Flag `json:"enabled"`
Meta Meta `json:"meta"`
// Directory is NPMplus' `npmplus_directory` column. It is moved into the
// meta object when the resource is decoded and never kept here.
Directory string `json:"npmplus_directory,omitempty"`
// OwnerMarker records the ownership marker in the advanced configuration;
// see marker.go. Not an API field.
OwnerMarker Flag `json:"ownership_marker,omitempty"`
}
RedirectionHost mirrors `/api/nginx/redirection-hosts`.
func (*RedirectionHost) AdoptServerState ¶
func (h *RedirectionHost) AdoptServerState(current Resource)
AdoptServerState implements Resource.
func (*RedirectionHost) Describe ¶
func (h *RedirectionHost) Describe() string
Describe implements Resource.
func (*RedirectionHost) Domains ¶
func (h *RedirectionHost) Domains() []string
Domains returns the normalised domain names of the host.
func (*RedirectionHost) Fingerprint ¶
func (h *RedirectionHost) Fingerprint() string
Fingerprint implements Resource.
func (*RedirectionHost) IsEnabled ¶
func (h *RedirectionHost) IsEnabled() bool
IsEnabled implements Resource.
func (*RedirectionHost) OwnerMarked ¶
func (h *RedirectionHost) OwnerMarked() bool
OwnerMarked implements Resource.
func (*RedirectionHost) Payload ¶
func (h *RedirectionHost) Payload(dialect Dialect) (any, error)
Payload implements Resource.
func (*RedirectionHost) ResourceCertificate ¶
func (h *RedirectionHost) ResourceCertificate() CertificateID
ResourceCertificate implements Resource.
func (*RedirectionHost) ResourceID ¶
func (h *RedirectionHost) ResourceID() int
ResourceID implements Resource.
func (*RedirectionHost) ResourceKey ¶
func (h *RedirectionHost) ResourceKey() string
ResourceKey implements Resource.
func (*RedirectionHost) ResourceMeta ¶
func (h *RedirectionHost) ResourceMeta() Meta
ResourceMeta implements Resource.
func (*RedirectionHost) SetEnabled ¶
func (h *RedirectionHost) SetEnabled(enabled bool)
SetEnabled implements Resource.
func (*RedirectionHost) SetResourceID ¶
func (h *RedirectionHost) SetResourceID(id int)
SetResourceID implements Resource.
func (*RedirectionHost) UnmarshalJSON ¶
func (h *RedirectionHost) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the resource and lifts the ownership marker and the group column into the meta object.
type Resource ¶
type Resource interface {
// Kind reports which collection the resource belongs to.
Kind() Kind
// ResourceID is the NPM id (0 for a resource that does not exist yet).
ResourceID() int
// SetResourceID stores the id assigned by NPM.
SetResourceID(id int)
// ResourceKey is the stable identity within its kind: the first domain
// name, or the incoming port for streams.
ResourceKey() string
// ResourceMeta exposes the ownership of the resource. For a resource read
// from the API it combines the meta object with the marker in the
// advanced configuration; see liftOwnership.
ResourceMeta() Meta
// OwnerMarked reports whether the resource carries a trusted ownership
// marker - for a desired resource, whether it is going to.
OwnerMarked() bool
// ResourceCertificate reports the attached certificate, which the
// automatic selection uses to stay on a certificate that still fits.
ResourceCertificate() CertificateID
// Fingerprint hashes the configuration-relevant fields. It deliberately
// excludes the enabled flag, which is not part of the write payload and
// is reconciled through the /enable and /disable endpoints instead.
Fingerprint() string
// Payload returns the create/update request body for the given API
// flavour. It fails when the desired configuration uses a feature the
// flavour does not have.
Payload(dialect Dialect) (any, error)
// IsEnabled reports the desired (or, for a resource read back from the
// API, the live) enabled state.
IsEnabled() bool
// SetEnabled records the enabled state.
SetEnabled(enabled bool)
// AdoptServerState copies server-assigned values (such as an issued
// certificate id) from the live resource into the desired payload, so a
// converged resource does not produce an endless update loop.
AdoptServerState(current Resource)
// Describe returns a short, log friendly description.
Describe() string
}
Resource is the behaviour every managed NPM resource shares. It lets the reconcile worker treat proxy hosts, redirections, streams and 404 hosts uniformly while each type keeps its own API payload.
type Stream ¶
type Stream struct {
ID int `json:"id,omitempty"`
CreatedOn string `json:"created_on,omitempty"`
ModifiedOn string `json:"modified_on,omitempty"`
OwnerUserID int `json:"owner_user_id,omitempty"`
IncomingPort Port `json:"incoming_port"`
ForwardingHost string `json:"forwarding_host"`
ForwardingPort Port `json:"forwarding_port"`
TCPForwarding Flag `json:"tcp_forwarding"`
UDPForwarding Flag `json:"udp_forwarding"`
CertificateID CertificateID `json:"certificate_id"`
Enabled Flag `json:"enabled"`
Meta Meta `json:"meta"`
// NPMplus extensions.
ProxyProtocol ProxyProtocolLevel `json:"npmplus_proxy_protocol_forwarding"`
ProxyTLS Flag `json:"npmplus_proxy_tls"`
AdvancedConfig string `json:"npmplus_advanced_config"`
Description string `json:"npmplus_description"`
// Directory is NPMplus' `npmplus_directory` column. It is moved into the
// meta object when the resource is decoded and never kept here.
Directory string `json:"npmplus_directory,omitempty"`
// OwnerMarker records the ownership marker in the advanced configuration;
// see marker.go. Not an API field.
OwnerMarker Flag `json:"ownership_marker,omitempty"`
}
Stream mirrors `/api/nginx/streams`. Streams have no domain names: their identity is the incoming port.
func (*Stream) AdoptServerState ¶
AdoptServerState implements Resource.
func (*Stream) ResourceCertificate ¶
func (s *Stream) ResourceCertificate() CertificateID
ResourceCertificate implements Resource.
func (*Stream) ResourceKey ¶
ResourceKey implements Resource: NPM allows exactly one stream per incoming port, which makes the port the natural identity.
func (*Stream) ResourceMeta ¶
ResourceMeta implements Resource.
func (*Stream) SetEnabled ¶
SetEnabled implements Resource.
func (*Stream) SetResourceID ¶
SetResourceID implements Resource.
func (*Stream) UnmarshalJSON ¶
UnmarshalJSON decodes the resource and lifts the ownership marker and the group column into the meta object.
type Version ¶
type Version struct {
Major int `json:"major"`
Minor int `json:"minor"`
Revision int `json:"revision"`
}
Version is what upstream NPM reports under `version` in GET /api/: {"major":2,"minor":11,"revision":3}. NPMplus reports a string there, which parses to the zero Version - "no version I can compare against".
func (Version) AtLeast ¶
AtLeast reports whether the version is at least major.minor.
An unknown version counts as new enough. A server that reports none is either NPMplus or something this tool has not seen, and assuming "old" there would silently drop settings the user asked for; assuming "new" at worst produces the server's own error message, which names the field.
type WriteSchema ¶
WriteSchema lists, per resource kind, the properties a create or update body may carry. It is read from GET /api/schema, which serves the very document both servers validate request bodies against, so it is the authoritative answer to "may this payload contain field X" - no version table needed.
Properties of a proxy host's custom locations are listed as "locations/<name>".