Files
tiny-forge/internal/proxy/traefik_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

100 lines
3.3 KiB
Go

package proxy
import (
"context"
"fmt"
"net/http"
"strings"
"time"
)
// TraefikProvider manages proxy routes via Docker labels.
// Traefik auto-discovers containers with the appropriate labels.
type TraefikProvider struct {
entrypoint string
certResolver string
network string // Docker network for traefik.docker.network label
apiURL string // Traefik API URL for health checks (optional)
httpClient *http.Client
}
// NewTraefikProvider creates a Traefik-backed proxy provider.
func NewTraefikProvider(entrypoint, certResolver, network, apiURL string) *TraefikProvider {
if entrypoint == "" {
entrypoint = "websecure"
}
return &TraefikProvider{
entrypoint: entrypoint,
certResolver: certResolver,
network: network,
apiURL: strings.TrimRight(apiURL, "/"),
httpClient: &http.Client{Timeout: 5 * time.Second},
}
}
func (t *TraefikProvider) Name() string { return "traefik" }
// ConfigureRoute for Traefik is a no-op for deploy-managed containers.
// Labels are set at container creation time via ContainerLabels().
// Returns a route ID for tracking.
func (t *TraefikProvider) ConfigureRoute(_ context.Context, domain, _ string, _ int, _ RouteOptions) (string, error) {
routerName := sanitizeDomain(domain)
return routerName, nil
}
// DeleteRoute for Traefik is a no-op — removing the container removes the labels,
// and Traefik automatically de-registers the route.
func (t *TraefikProvider) DeleteRoute(_ context.Context, _ string) error {
return nil
}
// RouteExists for Traefik is a no-op (always returns true) since routes are
// managed via container labels and don't need separate tracking.
func (t *TraefikProvider) RouteExists(_ context.Context, _ string) (bool, error) {
return true, nil
}
// ContainerLabels returns Docker labels for Traefik auto-discovery.
func (t *TraefikProvider) ContainerLabels(domain string, port int) map[string]string {
name := sanitizeDomain(domain)
labels := map[string]string{
"traefik.enable": "true",
fmt.Sprintf("traefik.http.routers.%s.rule", name): fmt.Sprintf("Host(`%s`)", domain),
fmt.Sprintf("traefik.http.routers.%s.entrypoints", name): t.entrypoint,
fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port", name): fmt.Sprintf("%d", port),
}
if t.certResolver != "" {
labels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", name)] = t.certResolver
}
if t.network != "" {
labels["traefik.docker.network"] = t.network
}
return labels
}
// Ping checks Traefik API connectivity if a URL is configured.
func (t *TraefikProvider) Ping(ctx context.Context) error {
if t.apiURL == "" {
return nil // No API URL configured, skip health check.
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, t.apiURL+"/api/overview", nil)
if err != nil {
return fmt.Errorf("create traefik ping request: %w", err)
}
resp, err := t.httpClient.Do(req)
if err != nil {
return fmt.Errorf("traefik ping: %w", err)
}
resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("traefik api returned status %d", resp.StatusCode)
}
return nil
}
// sanitizeDomain converts a domain to a safe Traefik router name.
func sanitizeDomain(domain string) string {
r := strings.NewReplacer(".", "-", ":", "-", "*", "wildcard")
return r.Replace(strings.ToLower(domain))
}