7d6719da12
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.
119 lines
2.9 KiB
Go
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)
|
|
}
|