Files
tiny-forge/internal/proxy/npm_provider.go
T
alexei.dolgolyov 8d2c5a063b feat: static sites feature with Gitea/GitHub/GitLab support and Deno backend
Deploy static content from Git repository folders with optional server-side
API endpoints. Supports Gitea/Forgejo/Gogs, GitHub, and GitLab with provider
autodetection.

- New Sites entity with CRUD, encrypted secrets, and manual/push/tag sync triggers
- Pluggable GitProvider interface with three implementations
- Deno container mode: auto-generates router from API_{method}_{name} exports
- Static container mode: nginx serving files with optional markdown rendering
- Wizard UI with provider selector, repo picker, branch/folder tree pickers
- Deploy pipeline builds fresh image, starts container, configures NPM proxy
- Stop/Start buttons, force redeploy on manual trigger
- Periodic health checker detects crashed containers
- Proxy route existence check during auto-sync
2026-04-11 03:35:57 +03:00

131 lines
3.2 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,
AccessListID: opts.AccessListID,
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) RouteExists(ctx context.Context, domain string) (bool, error) {
if err := p.auth(ctx); err != nil {
return false, err
}
_, found, err := p.client.FindProxyHostByDomain(ctx, domain)
if err != nil {
return false, fmt.Errorf("find proxy host: %w", err)
}
return found, nil
}
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)
}