65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import { execFileSync } from 'child_process';
|
|
|
|
export const ZFS_METADATA_PREFIX = 'org.clawdie';
|
|
|
|
interface ZfsMetadataDeps {
|
|
execFileSync: typeof execFileSync;
|
|
}
|
|
|
|
const DEFAULT_DEPS: ZfsMetadataDeps = {
|
|
execFileSync,
|
|
};
|
|
|
|
function propertyName(key: string): string {
|
|
const normalized = key.trim().toLowerCase();
|
|
if (!/^[a-z0-9][a-z0-9-]*$/u.test(normalized)) {
|
|
throw new Error(`invalid ZFS metadata key: ${key}`);
|
|
}
|
|
return `${ZFS_METADATA_PREFIX}:${normalized}`;
|
|
}
|
|
|
|
function normalizeValue(value: string | number | boolean): string {
|
|
return String(value).trim();
|
|
}
|
|
|
|
export function setZfsMetadata(
|
|
dataset: string,
|
|
props: Record<string, string | number | boolean | null | undefined>,
|
|
deps: Partial<ZfsMetadataDeps> = {},
|
|
): void {
|
|
const resolvedDeps = { ...DEFAULT_DEPS, ...deps };
|
|
for (const [key, raw] of Object.entries(props)) {
|
|
if (raw === null || raw === undefined) continue;
|
|
const value = normalizeValue(raw);
|
|
if (!value) continue;
|
|
resolvedDeps.execFileSync(
|
|
'zfs',
|
|
['set', `${propertyName(key)}=${value}`, dataset],
|
|
{ stdio: ['ignore', 'ignore', 'pipe'] },
|
|
);
|
|
}
|
|
}
|
|
|
|
export function getZfsMetadata(
|
|
dataset: string,
|
|
keys: string[],
|
|
deps: Partial<ZfsMetadataDeps> = {},
|
|
): Record<string, string | null> {
|
|
const resolvedDeps = { ...DEFAULT_DEPS, ...deps };
|
|
const result: Record<string, string | null> = {};
|
|
for (const key of keys) {
|
|
const prop = propertyName(key);
|
|
try {
|
|
const out = resolvedDeps.execFileSync(
|
|
'zfs',
|
|
['get', '-H', '-o', 'value', prop, dataset],
|
|
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
);
|
|
const value = out.trim();
|
|
result[key] = !value || value === '-' ? null : value;
|
|
} catch {
|
|
result[key] = null;
|
|
}
|
|
}
|
|
return result;
|
|
}
|