Convex: per-user dotfiles + environment storage (encrypted)

- schema: userDotfiles (one encrypted row per file, HOME-relative path) and
  userEnvironment (home username + optional public dotfiles repo + setup command)
- userDotfiles.ts: list/remove/removeDirectory/rename + internal upsert/getRaw
- userDotfilesNode.ts ('use node'): putFile/importFiles/getFileContent (encrypt
  /decrypt via secretCrypto) + getEnvironmentForJob (worker-token, returns the
  owner's decrypted dotfiles + repo/setup config)
- userEnvironment.ts: getMine/updateMine + getRawEnvironmentForJobInternal
- model.ts: deriveHomeUsername + normalizeDotfilePath helpers
This commit is contained in:
Gabriel Brown
2026-06-24 09:38:43 -04:00
parent 32a71f00ca
commit 683fc62129
5 changed files with 402 additions and 0 deletions
+25
View File
@@ -35,3 +35,28 @@ export const optionalText = (value: string | undefined) => {
if (!trimmed) return undefined;
return trimmed;
};
// Linux username for the per-user container home (/home/<username>). Derived
// from the first token of the profile name, sanitized; falls back to "user".
export const deriveHomeUsername = (name?: string): string => {
const first = (name ?? '').trim().split(/\s+/)[0] ?? '';
const sanitized = first.toLowerCase().replace(/[^a-z0-9_-]/g, '');
return sanitized || 'user';
};
// Normalizes a dotfile path to a safe HOME-relative path (no leading slash, no
// "..", no empty segments). Throws on anything that would escape HOME.
export const normalizeDotfilePath = (rawPath: string): string => {
const cleaned = rawPath
.trim()
.replace(/^\.\/+/, '')
.replace(/^\/+/, '');
const segments = cleaned.split('/').filter((s) => s.length > 0);
if (segments.length === 0) {
throw new ConvexError('A dotfile path is required.');
}
if (segments.some((s) => s === '..' || s === '.')) {
throw new ConvexError(`Invalid dotfile path: ${rawPath}`);
}
return segments.join('/');
};