1
0
forked from wrenn/wrenn

Add disk_size_mb, auto-expand base images, admin templates endpoint

Disk sizing:
- Add disk_size_mb column to sandboxes table (default 20480 = 20GB)
- Add disk_size_mb to CreateSandboxRequest proto, passed through the
  full chain: service → RPC → host agent → sandbox manager → devicemapper
- devicemapper.CreateSnapshot takes separate cowSizeBytes param so the
  sparse CoW file can be sized independently from the origin
- EnsureImageSizes() runs at host agent startup: expands any base image
  smaller than 20GB via truncate + resize2fs (sparse, no extra physical
  disk). Sandboxes then get the full 20GB via fast dm-snapshot path
- FlattenRootfs shrinks output images with resize2fs -M so stored
  templates are compact; EnsureImageSizes re-expands on next startup

Admin templates visibility:
- Add GET /v1/admin/templates endpoint listing all templates across teams
- Frontend admin templates page uses listAdminTemplates() instead of
  team-scoped listSnapshots()
- Platform templates (team_id = all-zeros UUID) now visible to all teams:
  GetTemplateByTeam, ListTemplatesByTeam, ListTemplatesByTeamAndType
  queries include platform team_id in WHERE clause
This commit is contained in:
2026-03-26 23:45:41 +06:00
parent 4ddd494160
commit c0d6381bbe
19 changed files with 241 additions and 42 deletions

View File

@ -0,0 +1,74 @@
package sandbox
import (
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
)
// DefaultDiskSizeMB is the standard disk size for base images. Images smaller
// than this are expanded at startup so that dm-snapshot sandboxes see the full
// size without per-sandbox copies. The expansion is sparse — only metadata
// changes; no physical disk is consumed beyond the original content.
const DefaultDiskSizeMB = 20480 // 20 GB
// EnsureImageSizes walks the images directory and expands any rootfs.ext4 that
// is smaller than the target size. This is idempotent: images already at or
// above the target size are left untouched. Should be called once at host agent
// startup before any sandboxes are created.
func EnsureImageSizes(imagesDir string, targetMB int) error {
if targetMB <= 0 {
targetMB = DefaultDiskSizeMB
}
targetBytes := int64(targetMB) * 1024 * 1024
entries, err := os.ReadDir(imagesDir)
if err != nil {
return fmt.Errorf("read images dir: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
rootfs := filepath.Join(imagesDir, entry.Name(), "rootfs.ext4")
info, err := os.Stat(rootfs)
if err != nil {
continue // not every template dir has a rootfs.ext4
}
if info.Size() >= targetBytes {
continue // already large enough
}
slog.Info("expanding base image",
"template", entry.Name(),
"from_mb", info.Size()/(1024*1024),
"to_mb", targetMB,
)
// Expand the file (sparse — instant, no physical disk used).
if err := os.Truncate(rootfs, targetBytes); err != nil {
return fmt.Errorf("truncate %s: %w", rootfs, err)
}
// Check filesystem before resize.
if out, err := exec.Command("e2fsck", "-fy", rootfs).CombinedOutput(); err != nil {
// e2fsck returns 1 if it fixed errors, which is fine.
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() > 1 {
return fmt.Errorf("e2fsck %s: %s: %w", rootfs, string(out), err)
}
}
// Grow the ext4 filesystem to fill the new file size.
if out, err := exec.Command("resize2fs", rootfs).CombinedOutput(); err != nil {
return fmt.Errorf("resize2fs %s: %s: %w", rootfs, string(out), err)
}
slog.Info("base image expanded", "template", entry.Name(), "size_mb", targetMB)
}
return nil
}