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
This commit is contained in:
2026-04-11 03:35:57 +03:00
parent b0816502bf
commit 8d2c5a063b
31 changed files with 4967 additions and 5 deletions
+4
View File
@@ -17,6 +17,10 @@ func (n *NoneProvider) DeleteRoute(_ context.Context, _ string) error {
return nil
}
func (n *NoneProvider) RouteExists(_ context.Context, _ string) (bool, error) {
return true, nil
}
func (n *NoneProvider) ContainerLabels(_ string, _ int) map[string]string {
return nil
}
+11
View File
@@ -101,6 +101,17 @@ func (p *NpmProvider) DeleteRoute(ctx context.Context, routeID string) error {
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
+4
View File
@@ -27,6 +27,10 @@ type Provider interface {
// Does nothing if routeID is empty.
DeleteRoute(ctx context.Context, routeID string) error
// RouteExists returns true if a proxy route exists for the given domain.
// Used for health checks during auto-sync to detect externally removed routes.
RouteExists(ctx context.Context, domain string) (bool, error)
// ContainerLabels returns Docker labels to set on containers at creation time.
// Traefik uses labels for auto-discovery; NPM and None return nil.
ContainerLabels(domain string, port int) map[string]string
+6
View File
@@ -48,6 +48,12 @@ 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)