import { chmodSync, lstatSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";import { join } from "node:path"; const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";const UPLOAD_FILE = new RegExp(`^${UUID}\\.(?:input|wav)$`, "i");const OUTPUT_FILE = new RegExp(`^${UUID}\\.wav$`, "i"); export type ChatterboxStoragePaths = { readonly speechDirectory: string; readonly cacheDirectory: string; readonly voiceDirectory: string; readonly outputDirectory: string; readonly uploadDirectory: string;}; export const secureSpeechDirectory = (path: string): void => { mkdirSync(path, { recursive: true, mode: 0o700 }); if (!lstatSync(path).isDirectory()) throw new Error("Speech storage path is not a directory"); chmodSync(path, 0o700);}; const removeOwnedFiles = (directory: string, pattern: RegExp): void => { for (const entry of readdirSync(directory, { withFileTypes: true })) { if (!pattern.test(entry.name) || (!entry.isFile() && !entry.isSymbolicLink())) continue; unlinkSync(join(directory, entry.name)); }}; export const prepareChatterboxStorage = (paths: ChatterboxStoragePaths): void => { [ paths.speechDirectory, paths.cacheDirectory, paths.voiceDirectory, paths.outputDirectory, paths.uploadDirectory, ].forEach(secureSpeechDirectory); removeOwnedFiles(paths.uploadDirectory, UPLOAD_FILE); removeOwnedFiles(paths.outputDirectory, OUTPUT_FILE);}; export const prepareVoicePlaintextStorage = (directory: string): void => { secureSpeechDirectory(directory); removeOwnedFiles(directory, OUTPUT_FILE);};
import { chmodSync, lstatSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
import { join } from "node:path";
const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
const UPLOAD_FILE = new RegExp(`^${UUID}\\.(?:input|wav)$`, "i");
const OUTPUT_FILE = new RegExp(`^${UUID}\\.wav$`, "i");
export type ChatterboxStoragePaths = {
readonly speechDirectory: string;
readonly cacheDirectory: string;
readonly voiceDirectory: string;
readonly outputDirectory: string;
readonly uploadDirectory: string;
};
export const secureSpeechDirectory = (path: string): void => {
mkdirSync(path, { recursive: true, mode: 0o700 });
if (!lstatSync(path).isDirectory()) throw new Error("Speech storage path is not a directory");
chmodSync(path, 0o700);
};
const removeOwnedFiles = (directory: string, pattern: RegExp): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (!pattern.test(entry.name) || (!entry.isFile() && !entry.isSymbolicLink())) continue;
unlinkSync(join(directory, entry.name));
}
};
export const prepareChatterboxStorage = (paths: ChatterboxStoragePaths): void => {
[
paths.speechDirectory,
paths.cacheDirectory,
paths.voiceDirectory,
paths.outputDirectory,
paths.uploadDirectory,
].forEach(secureSpeechDirectory);
removeOwnedFiles(paths.uploadDirectory, UPLOAD_FILE);
removeOwnedFiles(paths.outputDirectory, OUTPUT_FILE);
};
export const prepareVoicePlaintextStorage = (directory: string): void => {
secureSpeechDirectory(directory);
removeOwnedFiles(directory, OUTPUT_FILE);
};