389ed5aff8
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.
3.5 KiB
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 helpersinternal/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.go—ProxyHostConfig(create/update input),ProxyHost(API response),Meta, auth types, andboolIntcustom 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
Authenticateto enable transparent re-auth. - All HTTP errors include the response body text for debugging.
- Credentials are never included in error messages.
boolInttype handles NPM API's inconsistent 0/1 vs true/false for boolean fields.FindProxyHostByDomaindoes 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 toInstance.NpmProxyIDin the store for tracking.