Keep computers' browser sessions across restarts and recreates
Each computer's Chrome profile now lives on the data volume (DATA_DIR/computer-browsers/<id>, mounted at ~/.cobble-browser) and Chrome is pointed at it with --user-data-dir. The sandbox image resets its own profile's preferences to session-only cookies on every boot, which signed every site out whenever Chrome exited. New profiles are seeded with the image's preferences minus that rule, stale Singleton links are removed before each provision, and existing computers are recreated once (skipped while a job runs on them). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import { ComputerBrowserPreferences } from "@/src/data/computer-browser-preferences";
|
||||
import grabComputerBrowserDir from "./grab-computer-browser-dir";
|
||||
|
||||
type Params = {
|
||||
computer_id: number | string;
|
||||
/**
|
||||
* A new computer: a profile already at its path (left by a deleted
|
||||
* computer with the same id) is moved aside to `<id>-<timestamp>` and
|
||||
* kept, so it starts signed out.
|
||||
*/
|
||||
fresh?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Chrome's links saying which process holds a profile. One left by the
|
||||
* container being replaced would point at a process that's gone.
|
||||
*/
|
||||
const SingletonFiles = ["SingletonLock", "SingletonSocket", "SingletonCookie"];
|
||||
|
||||
/**
|
||||
* Creates a computer's browser profile directory (mounted at
|
||||
* `AppData.ComputerBrowserProfileDir`), owned by the sandbox's user, and
|
||||
* returns its path. A profile without preferences gets
|
||||
* `ComputerBrowserPreferences`; an existing one is left as Chrome saved it,
|
||||
* only rid of the links of the Chrome that ran before.
|
||||
*/
|
||||
export default async function prepareComputerBrowserDir({ computer_id, fresh }: Params) {
|
||||
const dir = grabComputerBrowserDir({ computer_id });
|
||||
const default_dir = path.join(dir, "Default");
|
||||
const preferences = path.join(default_dir, "Preferences");
|
||||
|
||||
if (fresh && (await fs.readdir(dir).catch(() => null))?.length) {
|
||||
await fs.rename(dir, `${dir}-${Date.now()}`);
|
||||
}
|
||||
|
||||
for (const folder of [dir, default_dir]) {
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
await chownToSandbox({ target: folder });
|
||||
}
|
||||
|
||||
for (const name of SingletonFiles) {
|
||||
await fs.rm(path.join(dir, name), { force: true });
|
||||
}
|
||||
|
||||
if (!(await fs.stat(preferences).catch(() => null))) {
|
||||
await fs.writeFile(preferences, JSON.stringify(ComputerBrowserPreferences));
|
||||
await chownToSandbox({ target: preferences });
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only root can hand a file to another user; a dev server running as that
|
||||
* user already owns it.
|
||||
*/
|
||||
async function chownToSandbox({ target }: { target: string }) {
|
||||
await fs.chown(target, AppData.ComputerUserUid, AppData.ComputerUserGid).catch(() => {});
|
||||
}
|
||||
Reference in New Issue
Block a user