Skip to main content

File Storage

The storage module (lib/storage.ts) provides a safe file I/O layer for all file operations in Open Genie. All paths are sandboxed within the configured storage root.

Configuration

VariableDefaultDescription
GENIE_STORAGE_PATH./data/filesRoot directory for all file storage

Directory Structure

data/files/
├── uploads/ # User-uploaded files
├── camera-captures/ # Camera snapshot images
├── .thumbnails/ # Generated thumbnails (hidden)
└── .cache/ # Temporary cache files (hidden)

API

saveFile(subPath, data)

Write a buffer or readable stream to storage.

import { saveFile } from "@/lib/storage";

await saveFile("uploads/photo.jpg", buffer);
await saveFile("camera-captures/cam1/frame.jpg", readableStream);

getFilePath(subPath)

Resolve a sub-path to an absolute path and verify it exists.

const absPath = await getFilePath("uploads/photo.jpg");
// Returns absolute path or null if not found

deleteFile(subPath)

Remove a file from storage.

await deleteFile("uploads/old-photo.jpg");

listFiles(directory, options?)

List files in a storage directory.

const files = await listFiles("uploads", {
recursive: true,
extensions: [".jpg", ".png"],
});
// Returns array of { name, path, size, mtime }

getStorageStats()

Get aggregate statistics about storage usage.

const stats = await getStorageStats();
// { totalSize, fileCount, byCategory: { uploads: {...}, captures: {...} } }

createFileReadStream(path, options?)

Create a Node.js readable stream for serving files.

const stream = createFileReadStream("uploads/video.mp4", {
start: 0,
end: 1024 * 1024, // First 1MB
});

ensureDirectory(subPath)

Create a directory (and parents) if it doesn't exist.

await ensureDirectory("camera-captures/cam1");

Security

All path operations go through resolveSafe(), which:

  1. Resolves the path to an absolute path
  2. Verifies it stays within GENIE_STORAGE_PATH
  3. Rejects any path traversal attempts (e.g., ../../etc/passwd)
// These will throw:
await saveFile("../../etc/passwd", data); // Path traversal
await getFilePath("/absolute/path/file.txt"); // Outside storage root