Files
tiny-forge/plans/docker-watcher-core/phase-4-npm-client.md
T
alexei.dolgolyov 389ed5aff8 feat(docker-watcher): phases 3+4 - Docker client & NPM client
Phase 3: Docker Engine API wrapper — pull/inspect images, container
lifecycle (create/start/stop/remove/restart), network management,
label-based container tracking, deterministic naming.

Phase 4: Nginx Proxy Manager API client — JWT auth with auto-refresh,
CRUD for proxy hosts, domain-based host lookup.
2026-03-27 21:08:57 +03:00

3.5 KiB

Phase 4: NPM Client

Status: Complete Parent plan: PLAN.md Domain: backend

Objective

Implement the Nginx Proxy Manager API client — JWT authentication, CRUD for proxy hosts, and host lookup.

Tasks

  • Task 1: Create NPM client struct with base URL, cached JWT token, and auto-refresh
  • Task 2: Implement Authenticate(ctx, email, password) — POST /api/tokens, store JWT
  • Task 3: Implement CreateProxyHost(ctx, config) — POST /api/nginx/proxy-hosts
  • Task 4: Implement UpdateProxyHost(ctx, id, config) — PUT /api/nginx/proxy-hosts/{id}
  • Task 5: Implement DeleteProxyHost(ctx, id) — DELETE /api/nginx/proxy-hosts/{id}
  • Task 6: Implement ListProxyHosts(ctx) — GET /api/nginx/proxy-hosts
  • Task 7: Implement FindProxyHostByDomain(ctx, domain) — search existing hosts by domain name
  • Task 8: Define proxy host config struct (domain, forward host/port, SSL settings, etc.)
  • Task 9: Handle JWT token expiry — re-authenticate automatically on 401

Files to Modify/Create

  • internal/npm/client.go — NPM API client, auth, HTTP helpers
  • internal/npm/types.go — request/response types for proxy hosts

Acceptance Criteria

  • Client authenticates and caches JWT
  • CRUD operations work for proxy hosts
  • Token refresh happens transparently on expiry
  • Proxy host config supports: domain, forward host, forward port, SSL (Let's Encrypt optional)
  • FindByDomain enables checking if a proxy already exists before creating

Notes

  • NPM API base: typically http://npm:81/api
  • Forward host for containers: use container name on the shared Docker network
  • Forward port: the container's internal port (from EXPOSE)
  • SSL: for staging, can be disabled; production may want Let's Encrypt
  • NPM credentials come from settings (encrypted in SQLite, decrypted at runtime)

Review Checklist

  • All tasks completed
  • JWT caching and refresh work correctly
  • HTTP errors are properly handled (not just status code, but response body)
  • No credentials logged or leaked in errors
  • Struct types match NPM API contract

Handoff to Next Phase

What was built

  • internal/npm/types.goProxyHostConfig (create/update input), ProxyHost (API response), Meta, auth types, and boolInt custom JSON type for NPM's 0/1 boolean fields.
  • internal/npm/client.go — Full NPM API client with JWT auth, auto-refresh, and CRUD.

Public API surface

npm.New(baseURL string) *Client
(*Client).Authenticate(ctx, email, password string) error
(*Client).CreateProxyHost(ctx, config ProxyHostConfig) (ProxyHost, error)
(*Client).UpdateProxyHost(ctx, id int, config ProxyHostConfig) (ProxyHost, error)
(*Client).DeleteProxyHost(ctx, id int) error
(*Client).ListProxyHosts(ctx) ([]ProxyHost, error)
(*Client).FindProxyHostByDomain(ctx, domain string) (ProxyHost, bool, error)

Key design decisions

  • JWT token is cached with expiry; auto-refreshed 5 minutes before expiry or on 401.
  • Credentials are stored in memory after Authenticate to enable transparent re-auth.
  • All HTTP errors include the response body text for debugging.
  • Credentials are never included in error messages.
  • boolInt type handles NPM API's inconsistent 0/1 vs true/false for boolean fields.
  • FindProxyHostByDomain does case-insensitive matching against all domain names.

Dependencies for next phase

  • Caller must provide decrypted NPM credentials (email + password from settings via crypto.Decrypt).
  • ProxyHost.ID (int) maps to Instance.NpmProxyID in the store for tracking.