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:
@@ -0,0 +1,95 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/client"
|
||||
)
|
||||
|
||||
// BuildImage builds a Docker image from a directory containing a Dockerfile.
|
||||
// The directory is packaged as a tar archive and sent to the Docker daemon.
|
||||
// The tag parameter is the image name:tag to apply (e.g., "dw-site-myapp:latest").
|
||||
func (c *Client) BuildImage(ctx context.Context, contextDir, tag string) error {
|
||||
// Create tar archive of the build context.
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
go func() {
|
||||
tw := tar.NewWriter(pw)
|
||||
err := filepath.Walk(contextDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(contextDir, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rel path: %w", err)
|
||||
}
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
|
||||
if relPath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("tar header for %s: %w", relPath, err)
|
||||
}
|
||||
header.Name = relPath
|
||||
|
||||
if err := tw.WriteHeader(header); err != nil {
|
||||
return fmt.Errorf("write tar header for %s: %w", relPath, err)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open %s: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(tw, file); err != nil {
|
||||
return fmt.Errorf("copy %s to tar: %w", relPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if closeErr := tw.Close(); closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
pw.CloseWithError(err)
|
||||
}()
|
||||
|
||||
resp, err := c.api.ImageBuild(ctx, pr, client.ImageBuildOptions{
|
||||
Dockerfile: "Dockerfile",
|
||||
Tags: []string{tag},
|
||||
Remove: true,
|
||||
ForceRemove: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("build image %s: %w", tag, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read the build output to completion (required for the build to finish).
|
||||
output, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read build output for %s: %w", tag, err)
|
||||
}
|
||||
|
||||
// Check for error in build output.
|
||||
if strings.Contains(string(output), `"error"`) {
|
||||
return fmt.Errorf("build image %s: build errors in output", tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user