90e6e59d9e
Build / build (push) Successful in 10m35s
- Health API now surfaces Docker /info + /version (version, platform, kernel, container/image counts, storage driver, memory, latency) and NPM aggregates (proxy host total, managed-by-Tinyforge count, access lists, certificates, endpoint URL). - Docker/NPM indicators moved out of the sidebar footer and into a compact mono-styled rail directly under the Tinyforge brand title, with pulse/fault animations and click-to-expand error hints. - New SystemDaemonsCard on the dashboard: two terminal-styled panels (Docker Engine + Proxy) with a running/paused/stopped stacked bar, key-value diagnostics, and a total-vs-managed proportion meter on the proxy-hosts tile. - Shared health store so the sidebar and dashboard share a single 30 s poll instead of duplicating traffic. - User-facing timezone preference with auto-detect fallback; all dates across projects, sites, stacks, settings, backup, event log and stale containers now render through \$fmt.date / \$fmt.datetime. - en/ru translations for both features.
139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/alexei/tinyforge/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)
|
|
}
|
|
|
|
// Authenticate is a public wrapper over the internal auth step. It is used by
|
|
// health checks that want to make authenticated list calls without going
|
|
// through the full ConfigureRoute path. Returns an error if credentials are
|
|
// not configured or the NPM API rejects them.
|
|
func (p *NpmProvider) Authenticate(ctx context.Context) error {
|
|
return p.auth(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)
|
|
}
|