Files
tiny-forge/internal/proxy/npm_provider.go
T
alexei.dolgolyov 7d6719da12 refactor: extract ProxyProvider interface with None and NPM implementations
Replace direct npm.Client usage throughout the codebase with the
proxy.Provider interface, enabling pluggable proxy backends. The
deployer, API layer, and proxy manager now use provider-agnostic
route management (ConfigureRoute/DeleteRoute) instead of NPM-specific
API calls. Adds ProxyRouteID (string) to Instance model and
ProxyProvider setting to Settings, with SQLite migrations for
backward compatibility.
2026-04-04 19:39:08 +03:00

119 lines
2.9 KiB
Go

package proxy
import (
"context"
"fmt"
"strconv"
"github.com/alexei/docker-watcher/internal/npm"
)
// NpmProvider wraps the NPM client behind the Provider interface.
// It handles authentication transparently before each operation.
type NpmProvider struct {
client *npm.Client
email string
password string
}
// NewNpmProvider creates an NPM-backed proxy provider.
// The email and password are the decrypted NPM credentials.
func NewNpmProvider(client *npm.Client, email, password string) *NpmProvider {
return &NpmProvider{
client: client,
email: email,
password: password,
}
}
// UpdateCredentials updates the stored NPM credentials (e.g., after settings change).
func (p *NpmProvider) UpdateCredentials(email, password string) {
p.email = email
p.password = password
}
func (p *NpmProvider) Name() string { return "npm" }
func (p *NpmProvider) ConfigureRoute(ctx context.Context, domain, targetHost string, targetPort int, opts RouteOptions) (string, error) {
if err := p.auth(ctx); err != nil {
return "", err
}
scheme := opts.ForwardScheme
if scheme == "" {
scheme = "http"
}
// Check if a proxy host already exists for this domain.
existing, found, err := p.client.FindProxyHostByDomain(ctx, domain)
if err != nil {
return "", fmt.Errorf("find existing proxy host: %w", err)
}
config := npm.ProxyHostConfig{
DomainNames: []string{domain},
ForwardScheme: scheme,
ForwardHost: targetHost,
ForwardPort: targetPort,
BlockExploits: true,
AllowWebsocket: true,
HTTP2Support: true,
Meta: npm.Meta{},
Locations: []any{},
}
if opts.SSLCertificateID > 0 {
config.CertificateID = opts.SSLCertificateID
config.SSLForced = true
config.HSTSEnabled = true
}
if found {
host, err := p.client.UpdateProxyHost(ctx, existing.ID, config)
if err != nil {
return "", fmt.Errorf("update proxy host: %w", err)
}
return strconv.Itoa(host.ID), nil
}
host, err := p.client.CreateProxyHost(ctx, config)
if err != nil {
return "", fmt.Errorf("create proxy host: %w", err)
}
return strconv.Itoa(host.ID), nil
}
func (p *NpmProvider) DeleteRoute(ctx context.Context, routeID string) error {
if routeID == "" {
return nil
}
id, err := strconv.Atoi(routeID)
if err != nil {
return fmt.Errorf("invalid npm proxy host id %q: %w", routeID, err)
}
if err := p.auth(ctx); err != nil {
return err
}
return p.client.DeleteProxyHost(ctx, id)
}
func (p *NpmProvider) ContainerLabels(_ string, _ int) map[string]string {
// NPM configures routing via its API, not Docker labels.
return nil
}
func (p *NpmProvider) Ping(ctx context.Context) error {
return p.client.Ping(ctx)
}
// auth authenticates to NPM if credentials are available.
func (p *NpmProvider) auth(ctx context.Context) error {
if p.email == "" {
return fmt.Errorf("NPM credentials not configured")
}
return p.client.Authenticate(ctx, p.email, p.password)
}