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.
This commit is contained in:
@@ -38,12 +38,13 @@ func (s *Server) getHealth(w http.ResponseWriter, r *http.Request) {
|
||||
result["docker"] = map[string]any{"connected": true}
|
||||
}
|
||||
|
||||
// Check NPM connectivity if configured.
|
||||
if s.npm != nil {
|
||||
if err := s.npm.Ping(ctx); err != nil {
|
||||
result["npm"] = map[string]any{"connected": false, "error": "NPM unreachable"}
|
||||
// Check proxy provider connectivity.
|
||||
if s.proxyProvider != nil {
|
||||
providerName := s.proxyProvider.Name()
|
||||
if err := s.proxyProvider.Ping(ctx); err != nil {
|
||||
result["proxy"] = map[string]any{"provider": providerName, "connected": false, "error": providerName + " unreachable"}
|
||||
} else {
|
||||
result["npm"] = map[string]any{"connected": true}
|
||||
result["proxy"] = map[string]any{"provider": providerName, "connected": true}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/alexei/docker-watcher/internal/crypto"
|
||||
"github.com/alexei/docker-watcher/internal/store"
|
||||
)
|
||||
|
||||
@@ -116,18 +115,10 @@ func (s *Server) removeInstance(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete NPM proxy host if it has one.
|
||||
if inst.NpmProxyID > 0 {
|
||||
settings, err := s.store.GetSettings()
|
||||
if err == nil {
|
||||
npmPassword, err := crypto.Decrypt(s.encKey, settings.NpmPassword)
|
||||
if err == nil {
|
||||
if authErr := s.npm.Authenticate(r.Context(), settings.NpmEmail, npmPassword); authErr == nil {
|
||||
if delErr := s.npm.DeleteProxyHost(r.Context(), inst.NpmProxyID); delErr != nil {
|
||||
slog.Warn("delete proxy host on instance removal", "proxy_id", inst.NpmProxyID, "error", delErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete proxy route if it has one.
|
||||
if inst.ProxyRouteID != "" {
|
||||
if err := s.proxyProvider.DeleteRoute(r.Context(), inst.ProxyRouteID); err != nil {
|
||||
slog.Warn("delete proxy route on instance removal", "route_id", inst.ProxyRouteID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-12
@@ -26,10 +26,11 @@ type DNSProviderChangedFunc func(provider dns.Provider)
|
||||
|
||||
// Server holds all dependencies for the API layer.
|
||||
type Server struct {
|
||||
store *store.Store
|
||||
docker *docker.Client
|
||||
npm *npm.Client
|
||||
deployer DeployTriggerer
|
||||
store *store.Store
|
||||
docker *docker.Client
|
||||
npm *npm.Client // optional: only for NPM-specific endpoints (certificates)
|
||||
proxyProvider proxy.Provider
|
||||
deployer DeployTriggerer
|
||||
webhook *webhook.Handler
|
||||
eventBus *events.Bus
|
||||
encKey [32]byte
|
||||
@@ -53,6 +54,7 @@ func NewServer(
|
||||
st *store.Store,
|
||||
dockerClient *docker.Client,
|
||||
npmClient *npm.Client,
|
||||
proxyProvider proxy.Provider,
|
||||
deployer DeployTriggerer,
|
||||
webhookHandler *webhook.Handler,
|
||||
eventBus *events.Bus,
|
||||
@@ -61,14 +63,15 @@ func NewServer(
|
||||
localAuth := auth.NewLocalAuth(encKey)
|
||||
|
||||
s := &Server{
|
||||
store: st,
|
||||
docker: dockerClient,
|
||||
npm: npmClient,
|
||||
deployer: deployer,
|
||||
webhook: webhookHandler,
|
||||
eventBus: eventBus,
|
||||
encKey: encKey,
|
||||
localAuth: localAuth,
|
||||
store: st,
|
||||
docker: dockerClient,
|
||||
npm: npmClient,
|
||||
proxyProvider: proxyProvider,
|
||||
deployer: deployer,
|
||||
webhook: webhookHandler,
|
||||
eventBus: eventBus,
|
||||
encKey: encKey,
|
||||
localAuth: localAuth,
|
||||
}
|
||||
|
||||
// Try to initialize OIDC provider from stored settings.
|
||||
|
||||
@@ -34,6 +34,7 @@ type settingsRequest struct {
|
||||
DNSProvider *string `json:"dns_provider,omitempty"`
|
||||
CloudflareAPIToken string `json:"cloudflare_api_token"`
|
||||
CloudflareZoneID *string `json:"cloudflare_zone_id,omitempty"`
|
||||
ProxyProvider *string `json:"proxy_provider,omitempty"`
|
||||
BackupEnabled *bool `json:"backup_enabled,omitempty"`
|
||||
BackupIntervalHours *int `json:"backup_interval_hours,omitempty"`
|
||||
BackupRetentionCount *int `json:"backup_retention_count,omitempty"`
|
||||
@@ -65,6 +66,7 @@ func (s *Server) getSettings(w http.ResponseWriter, r *http.Request) {
|
||||
"dns_provider": settings.DNSProvider,
|
||||
"has_cloudflare_api_token": settings.CloudflareAPIToken != "",
|
||||
"cloudflare_zone_id": settings.CloudflareZoneID,
|
||||
"proxy_provider": settings.ProxyProvider,
|
||||
"backup_enabled": settings.BackupEnabled,
|
||||
"backup_interval_hours": settings.BackupIntervalHours,
|
||||
"backup_retention_count": settings.BackupRetentionCount,
|
||||
@@ -166,6 +168,16 @@ func (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
updated.CloudflareZoneID = *req.CloudflareZoneID
|
||||
}
|
||||
|
||||
// Proxy provider setting.
|
||||
if req.ProxyProvider != nil {
|
||||
prov := *req.ProxyProvider
|
||||
if prov != "" && prov != "none" && prov != "npm" {
|
||||
respondError(w, http.StatusBadRequest, "proxy_provider must be 'none' or 'npm'")
|
||||
return
|
||||
}
|
||||
updated.ProxyProvider = prov
|
||||
}
|
||||
|
||||
// Backup settings.
|
||||
if req.BackupEnabled != nil {
|
||||
updated.BackupEnabled = *req.BackupEnabled
|
||||
|
||||
+4
-13
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/alexei/docker-watcher/internal/crypto"
|
||||
"github.com/alexei/docker-watcher/internal/events"
|
||||
"github.com/alexei/docker-watcher/internal/stale"
|
||||
"github.com/alexei/docker-watcher/internal/store"
|
||||
@@ -121,18 +120,10 @@ func (s *Server) cleanupInstance(r *http.Request, inst store.Instance) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete NPM proxy host if present.
|
||||
if inst.NpmProxyID > 0 {
|
||||
settings, err := s.store.GetSettings()
|
||||
if err == nil {
|
||||
npmPassword, err := crypto.Decrypt(s.encKey, settings.NpmPassword)
|
||||
if err == nil {
|
||||
if authErr := s.npm.Authenticate(ctx, settings.NpmEmail, npmPassword); authErr == nil {
|
||||
if delErr := s.npm.DeleteProxyHost(ctx, inst.NpmProxyID); delErr != nil {
|
||||
slog.Warn("stale cleanup: delete proxy host", "proxy_id", inst.NpmProxyID, "error", delErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete proxy route if present.
|
||||
if inst.ProxyRouteID != "" {
|
||||
if err := s.proxyProvider.DeleteRoute(ctx, inst.ProxyRouteID); err != nil {
|
||||
slog.Warn("stale cleanup: delete proxy route", "route_id", inst.ProxyRouteID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user