Project proposal: gonzb-nzb-forwarder¶
Status: proposed and intentionally deferred. No forwarder binary, watcher script, container image, or supported deployment currently ships with GoNZB.
gonzb-nzb-forwarder is a proposed producer-neutral sidecar for delivering
completed NZBs from a posting host to a separate GoNZB server. This document is
a project note, not an implementation plan or operator runbook.
Problem statement¶
Postie and similar tools can produce a completed NZB on a host that should
remain isolated behind a VPN. Postie's current post_upload_script can submit
that NZB to GoNZB immediately, but at the tested upstream revision its durable
script retry worker is not started by the service lifecycle. A longer GoNZB or
network outage can therefore outlast the hook's bounded inline retries.
Loon's offline mode is the stronger motivating case: it produces a completed output directory but does not currently provide the generic HTTP post-upload hook used by Postie and pesto. Without this proposed sidecar, separate Loon and GoNZB servers need a shared filesystem mount for GoNZB's read-only inbox.
A shared NFS/SMB mount would let GoNZB use its existing read-only inbox, but it adds infrastructure and exposes the posting host's output across machines. A small outbound-only delivery sidecar could provide durable handoff without an inbound route to the posting server.
Proposed boundary¶
The forwarder would be a separate companion service, not part of Postie or the GoNZB server process:
Postie container ──writes──> completed-NZB volume <──read-only── forwarder
|
outbound HTTPS only
|
v
GoNZB uploader API
The project should be generic. Postie, pesto, Loon, or any other producer could use it by exposing a directory containing finalized NZBs. It should not know how the source material was acquired or posted.
Repository recommendation¶
If implemented, this should live in a separate repository and publish its own versioned container image. The deployment lifecycle, security surface, and release cadence are independent from GoNZB. Keeping it separate also prevents the GoNZB server image from acquiring filesystem-watcher or sidecar runtime concerns.
Suggested ownership split:
- GoNZB repository: uploader API contract, bounded validation, authentication, exact-content deduplication, and cross-project conformance fixtures.
gonzb-nzb-forwarderrepository: forwarder binary, container image, durable delivery state, health checks, metrics, release automation, SBOMs, and image signing.- Producer projects: only generate completed NZBs and expose their output volume; they do not gain GoNZB-specific core dependencies.
The preferred implementation would be a small static Go binary rather than a shell-script image. That avoids Bash/curl/coreutils runtime dependencies and provides a cleaner path for cancellation, health checks, structured logs, secret-file support, and deterministic multi-architecture images.
Proposed runtime contract¶
The sidecar would receive only:
| Resource | Access | Purpose |
|---|---|---|
| Completed-NZB volume | read-only | Discover finalized .nzb files recursively |
| Forwarder state volume | read-write | Durable delivery receipts and retry deadlines |
| GoNZB URL | configuration | Locate the HTTPS uploader endpoint |
| GoNZB token | secret | Authenticate with only uploader.submissions.create |
| CA trust | read-only | Verify the GoNZB TLS certificate |
The state volume must survive container recreation. The input volume must never be modified, renamed, or cleaned by the forwarder.
Proposed behavior¶
The first implementation should:
- recursively inspect only regular
.nzbfiles without following symlinks; - require a configurable settle period and verify the file remains unchanged;
- enforce a finite input-size limit before submission;
- deliver through
POST /api/v1/uploader/submissionsusing a dedicated token; - persist success by exact NZB SHA-256 so restarts do not resend old files;
- persist bounded exponential retry state for network and server failures;
- distinguish retryable failures from permanent validation/authentication failures and expose both operationally;
- use bounded concurrency and avoid repeated scans generating network chatter;
- provide a container health check and concise structured status metrics;
- avoid logging tokens, NZB bodies, passwords, or response metadata that may contain sensitive release information.
GoNZB would retain all authority. Every newly delivered NZB would still enter
pending_review; the forwarder could not approve, publish, or administer a
GoNZBNet pool.
Network and VPN model¶
The forwarder would not need NNTP or general Internet access. It should not automatically share Postie's provider-VPN network namespace. Instead, it should mount the completed output volume and receive the narrow route needed to reach GoNZB over HTTPS through an internal container network, LAN, or private overlay network.
This preserves the intended isolation:
- Postie's NNTP traffic remains constrained by its VPN policy;
- GoNZB receives an outbound authenticated request and never connects to Postie;
- the forwarder cannot access Postie's source input or NNTP credentials;
- GoNZB's API token grants submission only, not review or publication.
Proposed container properties¶
A future production image should:
- run as a fixed non-root user;
- use a read-only root filesystem;
- drop all Linux capabilities and enable
no-new-privileges; - support Docker/Kubernetes secret files instead of requiring the token in Compose environment text;
- provide signed multi-architecture images pinned by immutable digest;
- include an SBOM and automated vulnerability scanning;
- expose health without opening an externally reachable management service.
Relationship to Postie's hook¶
Until this project exists, the supported separate-server integration remains
Postie's post_upload_script calling GoNZB's one-shot submission helper. It
provides short bounded retries but is not presented as durable across a long
outage.
Pesto has the same viable HTTP-hook boundary. Loon does not: its current offline-output integration remains local/shared-filesystem only until a forwarder or equivalent operator-owned transport exists.
If the sidecar is implemented later, operators could choose one delivery path:
- the Postie hook for immediate best-effort delivery; or
- the sidecar for durable directory-backed delivery.
Using both should not be the recommended default because it creates an avoidable duplicate request, even though GoNZB safely deduplicates identical NZB bytes.
Acceptance criteria before implementation is considered complete¶
- A clean repository and independently versioned container image exist.
- Unit tests cover settle detection, mutation races, symlink rejection, content receipts, retry classification, restart recovery, and secret redaction.
- The existing synthetic Postie conformance proves posting, sidecar delivery, pending review, approval, pool publication, remote search, and remote grab.
- A multi-hour outage/restart soak proves no lost or duplicate-visible submissions and bounded request volume.
- Documentation covers Compose and Kubernetes deployment without requiring an inbound connection or cross-server filesystem mount.
No implementation work is currently scheduled by this document.