Enhance environment variable documentation and configuration
Format / gofmt (push) Successful in 5s
CI / Build (push) Successful in 12s
CI / Go Tests (push) Successful in 22s
Release Artifacts / Validate release tag (push) Successful in 1s
Release Artifacts / Build and release executables (push) Successful in 33s
Release Artifacts / Build and release Docker image (push) Successful in 3m10s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-15 16:27:18 -05:00
parent ad4355df17
commit 90f1cf8bdf
11 changed files with 271 additions and 16 deletions
+30 -1
View File
@@ -172,6 +172,12 @@ func (s *Server) scratchPage(w http.ResponseWriter, r *http.Request) {
})
}
// multipartMaxMemory is how much of a multipart upload may live in RAM before
// the remainder spills to temporary files. It must NOT be tied to
// limits.max_upload_size: when that limit is multi-GiB, using it as maxMemory
// forces entire large files into process memory before storage compression.
const multipartMaxMemory = 32 << 20 // 32 MiB
func (s *Server) createScratch(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, s.cfg.Limits.MaxUploadSizeBytes)
@@ -180,7 +186,7 @@ func (s *Server) createScratch(w http.ResponseWriter, r *http.Request) {
contentType := strings.TrimSpace(r.Header.Get("Content-Type"))
if strings.HasPrefix(contentType, "multipart/form-data") {
if err := r.ParseMultipartForm(s.cfg.Limits.MaxUploadSizeBytes); err != nil {
if err := r.ParseMultipartForm(multipartMaxMemory); err != nil {
s.writeCreateError(w, err)
return
}
@@ -315,6 +321,29 @@ func (s *Server) rawScratch(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Disposition", disposition)
}
w.Header().Set("Content-Type", meta.ContentType)
// Scratches larger than the raw cache cannot be retained in memory. Stream
// them from storage instead of io.ReadAll so multi-GiB downloads do not OOM.
// Range requests are only supported via the cache path (ServeContent needs a Seeker).
if meta.Size > s.cfg.Limits.RawCacheMaxBytes {
file, _, openErr := s.store.Open(meta.ID)
if openErr != nil {
if errors.Is(openErr, storage.ErrNotFound) {
s.rawCache.Delete(id)
w.WriteHeader(http.StatusGone)
return
}
http.Error(w, "failed to read scratch", http.StatusInternalServerError)
return
}
defer file.Close()
w.Header().Set("Accept-Ranges", "none")
w.Header().Set("Content-Length", fmt.Sprintf("%d", meta.Size))
w.WriteHeader(http.StatusOK)
_, _ = io.Copy(w, file)
return
}
content, err, _ := s.rawCache.GetOrLoad(meta.ID, func() ([]byte, error) {
file, _, openErr := s.store.Open(meta.ID)
if openErr != nil {