signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
270 lines • 10.3 kB
JavaScript
;
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* WASM Plugin Virtual Filesystem (VFS) Management
*
* Handles isolated storage for WASM plugins using WASI virtual filesystem.
* Each plugin gets its own VFS root for secure, sandboxed file access.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPluginStoragePaths = getPluginStoragePaths;
exports.initializePluginVfs = initializePluginVfs;
exports.readPluginConfig = readPluginConfig;
exports.writePluginConfig = writePluginConfig;
exports.migrateFromNodeJs = migrateFromNodeJs;
exports.cleanupVfsTmp = cleanupVfsTmp;
exports.getVfsDiskUsage = getVfsDiskUsage;
exports.deletePluginVfs = deletePluginVfs;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const debug_1 = __importDefault(require("debug"));
const pluginid_1 = require("../pluginid");
const atomicWrite_1 = require("../atomicWrite");
const debug = (0, debug_1.default)('signalk:wasm:storage');
/**
* Get storage paths for a WASM plugin
*
* @param configPath - Server config directory path
* @param pluginId - Plugin ID (e.g., "hello-assemblyscript") - used for config file to match regular plugins
* @param packageName - NPM package name (e.g., "@signalk/hello-assemblyscript") - used for VFS directory
*/
function getPluginStoragePaths(configPath, pluginId, packageName) {
// Config file goes directly in plugin-config-data/ like regular plugins
const configDataPath = path.join(configPath, 'plugin-config-data');
// Use plugin ID for config file (matches regular Node.js plugins)
const configFile = path.join(configDataPath, `${pluginId}.json`);
// Use sanitized package name for VFS directory (for isolation)
// Use same pattern as plugin ID: @ → _, / → _
// @signalk/hello-assemblyscript -> _signalk_hello-assemblyscript
const sanitizedPackageName = (0, pluginid_1.derivePluginId)(packageName);
const pluginDataRoot = path.join(configDataPath, sanitizedPackageName);
const vfsRoot = path.join(pluginDataRoot, 'vfs');
return {
pluginDataRoot,
configFile, // e.g., ~/.signalk/plugin-config-data/_signalk_example-hello-assemblyscript.json
vfsRoot, // e.g., ~/.signalk/plugin-config-data/_signalk_example-hello-assemblyscript/vfs/
vfsData: path.join(vfsRoot, 'data'),
vfsConfig: path.join(vfsRoot, 'config'),
vfsTmp: path.join(vfsRoot, 'tmp')
};
}
/**
* Initialize VFS structure for a WASM plugin
*/
function initializePluginVfs(paths) {
debug(`Initializing VFS for plugin at ${paths.vfsRoot}`);
try {
// Create VFS root and subdirectories
const dirsToCreate = [
paths.pluginDataRoot,
paths.vfsRoot,
paths.vfsData,
paths.vfsConfig,
paths.vfsTmp
];
for (const dir of dirsToCreate) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
debug(`Created directory: ${dir}`);
}
}
debug(`VFS initialized successfully at ${paths.vfsRoot}`);
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
debug(`Failed to initialize VFS: ${errorMsg}`);
throw new Error(`Failed to initialize plugin VFS: ${errorMsg}`);
}
}
/**
* Read plugin configuration from server-managed config file
*/
function readPluginConfig(configFile) {
try {
if (!fs.existsSync(configFile)) {
debug(`Config file not found: ${configFile}, returning default config`);
// Note: Do NOT include configuration key - UI shows "Configure" button when configuration is null/undefined
return {
enabled: false
};
}
const configData = fs.readFileSync(configFile, 'utf8');
return JSON.parse(configData);
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
debug(`Error reading plugin config: ${errorMsg}`);
// Note: Do NOT include configuration key - UI shows "Configure" button when configuration is null/undefined
return {
enabled: false
};
}
}
/**
* Write plugin configuration to server-managed config file
*/
function writePluginConfig(configFile, config) {
try {
const configDir = path.dirname(configFile);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
(0, atomicWrite_1.atomicWriteFileSync)(configFile, JSON.stringify(config, null, 2));
debug(`Wrote plugin config to ${configFile}`);
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
debug(`Error writing plugin config: ${errorMsg}`);
throw new Error(`Failed to write plugin config: ${errorMsg}`);
}
}
/**
* Migrate data from Node.js plugin format to WASM VFS format
*
* Copies files from legacy Node.js plugin data directory to VFS /data directory.
* Legacy files are preserved for rollback.
*/
function migrateFromNodeJs(legacyDataDir, vfsDataDir, filesToMigrate) {
debug(`Migrating data from ${legacyDataDir} to ${vfsDataDir}`);
if (!fs.existsSync(legacyDataDir)) {
debug('Legacy data directory does not exist, skipping migration');
return;
}
if (!fs.existsSync(vfsDataDir)) {
fs.mkdirSync(vfsDataDir, { recursive: true });
}
let migratedCount = 0;
for (const filename of filesToMigrate) {
const legacyPath = path.join(legacyDataDir, filename);
const vfsPath = path.join(vfsDataDir, filename);
if (fs.existsSync(legacyPath)) {
try {
// Copy file to VFS (preserve legacy file)
fs.copyFileSync(legacyPath, vfsPath);
debug(`Migrated: ${filename}`);
migratedCount++;
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
debug(`Failed to migrate ${filename}: ${errorMsg}`);
}
}
}
debug(`Migration complete: ${migratedCount}/${filesToMigrate.length} files migrated`);
}
/**
* Clean up VFS temporary files
*/
function cleanupVfsTmp(vfsTmpDir) {
try {
if (!fs.existsSync(vfsTmpDir)) {
return;
}
const files = fs.readdirSync(vfsTmpDir);
let deletedCount = 0;
for (const file of files) {
try {
const filePath = path.join(vfsTmpDir, file);
const stats = fs.statSync(filePath);
if (stats.isFile()) {
fs.unlinkSync(filePath);
deletedCount++;
}
}
catch (error) {
debug(`Failed to delete temp file ${file}:`, error);
}
}
debug(`Cleaned up ${deletedCount} temporary files from ${vfsTmpDir}`);
}
catch (error) {
debug(`Error cleaning up temp directory:`, error);
}
}
/**
* Get disk usage for a plugin's VFS
*/
function getVfsDiskUsage(vfsRoot) {
let totalBytes = 0;
let fileCount = 0;
function walkDirectory(dir) {
try {
if (!fs.existsSync(dir)) {
return;
}
const entries = fs.readdirSync(dir);
for (const entry of entries) {
const entryPath = path.join(dir, entry);
const stats = fs.statSync(entryPath);
if (stats.isFile()) {
totalBytes += stats.size;
fileCount++;
}
else if (stats.isDirectory()) {
walkDirectory(entryPath);
}
}
}
catch (error) {
debug(`Error reading directory ${dir}:`, error);
}
}
walkDirectory(vfsRoot);
return { totalBytes, fileCount };
}
/**
* Delete all VFS data for a plugin
*/
function deletePluginVfs(paths) {
debug(`Deleting VFS for plugin at ${paths.vfsRoot}`);
try {
if (fs.existsSync(paths.vfsRoot)) {
fs.rmSync(paths.vfsRoot, { recursive: true, force: true });
debug(`Deleted VFS directory: ${paths.vfsRoot}`);
}
// Note: We keep the server-managed config file for plugin settings
debug('VFS deletion complete');
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
debug(`Error deleting VFS: ${errorMsg}`);
throw new Error(`Failed to delete plugin VFS: ${errorMsg}`);
}
}
//# sourceMappingURL=wasm-storage.js.map