Restrict Studio 2 host cleanup to exact preview paths
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createUpscaleHost } from './upscale-host.mjs'
|
||||
import { purgeStudio2Files } from './studio2-purge.mjs'
|
||||
import { stableMemoryArgs } from './comfy-memory-policy.mjs'
|
||||
import { createGpuReservation } from './gpu-reservation.mjs'
|
||||
import { createGpuProxy } from './gpu-proxy.mjs'
|
||||
@@ -989,6 +990,12 @@ async function handleControl(req, res) {
|
||||
markWork()
|
||||
return streamFile(res, path)
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/studio2/purge') {
|
||||
if (!gpuReservation.isOwner(String(req.headers['x-aigen-gpu-lease'] || ''))) return json(res, 409, { ok: false, error: 'GPU lease required' })
|
||||
const body = await readJson(req)
|
||||
try { return json(res, 200, purgeStudio2Files(body, rootsForType)) }
|
||||
catch (error) { return json(res, 400, { ok: false, error: String(error.message || error) }) }
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/purge') {
|
||||
const body = await readJson(req)
|
||||
return json(res, 200, purgeDesktopFiles(body))
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { resolve, relative, isAbsolute } from 'node:path'
|
||||
import { existsSync, realpathSync, statSync, unlinkSync } from 'node:fs'
|
||||
import { scopedFile } from '../shared/studio2/contracts.mjs'
|
||||
|
||||
/** Exact Studio 2 files only: no folder fallback, sibling deletion, or sweeping. */
|
||||
export function purgeStudio2Files({prefix, files}, rootsForType) {
|
||||
if (!Array.isArray(files) || !files.length || files.length>100 || !prefix) throw new Error('Invalid Studio 2 purge manifest')
|
||||
const targets=[]
|
||||
for (const file of files) {
|
||||
if (!['input','output'].includes(file.type) || !scopedFile(file,prefix) || !String(file.subfolder).replaceAll('\\','/').startsWith(String(prefix).replace(/\/$/,'')+'/studio2/')) throw new Error('Purge file is outside the Studio 2 prefix')
|
||||
const roots=rootsForType(file.type)
|
||||
if(!roots.length)throw new Error('No media root is configured for cleanup')
|
||||
for (const root of roots) {
|
||||
const target=resolve(root,file.subfolder,file.filename),rel=relative(resolve(root),target)
|
||||
if (!rel || rel.startsWith('..') || isAbsolute(rel)) throw new Error('Purge path escapes its media root')
|
||||
if (!existsSync(target)) continue
|
||||
const realRoot=realpathSync(root),realTarget=realpathSync(target),realRel=relative(realRoot,realTarget)
|
||||
if (!realRel || realRel.startsWith('..') || isAbsolute(realRel) || !statSync(realTarget).isFile()) throw new Error('Purge target is not a media file inside its root')
|
||||
const prefixRel=relative(resolve(realRoot,prefix),realTarget)
|
||||
if(!prefixRel || prefixRel.startsWith('..') || isAbsolute(prefixRel))throw new Error('Purge target resolves outside the instance prefix')
|
||||
targets.push(target)
|
||||
}
|
||||
}
|
||||
for(const target of new Set(targets))unlinkSync(target)
|
||||
return {ok:true,cleared:files.length,deleted:[...new Set(targets)]}
|
||||
}
|
||||
Reference in New Issue
Block a user