feat: persistent storage for Deno static sites
Build / build (push) Successful in 10m21s

- Add storage_enabled and storage_limit_mb columns to static_sites.
- Create/attach Docker volumes (tinyforge-site-{name}-data) for Deno
  sites with storage enabled, mounted at /app/data.
- Grant --allow-write=/app/data in Deno container CMD.
- Add storage usage API endpoint (GET /api/sites/{id}/storage).
- Show storage section in site detail page with usage bar.
- Add storage toggle and limit field to new site wizard.
- Use ConfirmDialog for secret deletion instead of inline delete.
This commit is contained in:
2026-04-13 00:12:51 +03:00
parent 9ec25a8d5a
commit b622384774
12 changed files with 327 additions and 15 deletions
+5 -1
View File
@@ -231,15 +231,19 @@ COPY public/ /app/public/
COPY api/ /app/api/
COPY router.ts /app/router.ts
# Create data directory for persistent storage (mounted as volume when enabled).
RUN mkdir -p /app/data
# Cache dependencies.
RUN deno install --entrypoint router.ts
EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-read", "--allow-env", "router.ts"]
CMD ["deno", "run", "--allow-net", "--allow-read", "--allow-write=/app/data", "--allow-env", "router.ts"]
`
}
// GenerateStaticDockerfile generates a minimal nginx Dockerfile for pure static sites.
func GenerateStaticDockerfile() string {
return `FROM nginx:alpine
+27
View File
@@ -10,6 +10,8 @@ import (
"strconv"
"time"
"github.com/moby/moby/api/types/mount"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/events"
@@ -198,6 +200,22 @@ func (m *Manager) Deploy(ctx context.Context, siteID string, force bool) error {
containerName := fmt.Sprintf("dw-site-%s", site.Name)
// Prepare volume mounts for persistent storage.
var mounts []mount.Mount
if site.StorageEnabled && mode == "deno" {
volName, volErr := m.docker.EnsureSiteVolume(ctx, site.Name)
if volErr != nil {
slog.Warn("static site: failed to ensure storage volume", "site", site.Name, "error", volErr)
} else {
mounts = append(mounts, mount.Mount{
Type: mount.TypeVolume,
Source: volName,
Target: "/app/data",
})
slog.Info("static site: storage volume attached", "site", site.Name, "volume", volName)
}
}
// Create and start new container.
containerID, err := m.docker.CreateContainer(ctx, docker.ContainerConfig{
Name: containerName,
@@ -206,6 +224,7 @@ func (m *Manager) Deploy(ctx context.Context, siteID string, force bool) error {
ExposedPorts: []string{containerPort + "/tcp"},
NetworkName: networkName,
NetworkID: networkID,
Mounts: mounts,
Labels: map[string]string{
"tinyforge.static-site": site.ID,
"tinyforge.static-site-name": site.Name,
@@ -229,6 +248,7 @@ func (m *Manager) Deploy(ctx context.Context, siteID string, force bool) error {
ExposedPorts: []string{containerPort + "/tcp"},
NetworkName: networkName,
NetworkID: networkID,
Mounts: mounts,
Labels: map[string]string{
"tinyforge.static-site": site.ID,
"tinyforge.static-site-name": site.Name,
@@ -346,6 +366,13 @@ func (m *Manager) Remove(ctx context.Context, siteID string) error {
}
}
// Remove storage volume if it was enabled (best effort).
if site.StorageEnabled {
if err := m.docker.RemoveSiteVolume(ctx, site.Name); err != nil {
slog.Warn("static site: failed to remove storage volume", "site", site.Name, "error", err)
}
}
return nil
}