Install Cookie-Editor in every computer's browser

A managed Chrome policy, written as root into each running computer's
container on provision and every sync-computers run, installs the
default extensions (normal_installed: can be turned off, not removed).
Existing computers get it without being recreated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-27 14:18:36 +01:00
co-authored by Claude Opus 5.5
parent dd6b48fcf5
commit ac1048c8f3
9 changed files with 181 additions and 3 deletions
@@ -7,6 +7,7 @@ import { logError } from "@/src/functions/backend/logger";
import prepareComputerBrowserDir from "./prepare-computer-browser-dir";
import prepareComputerDataDir from "./prepare-computer-data-dir";
import pushComputerUpdate from "./push-computer-update";
import syncComputerBrowserPolicy from "./sync-computer-browser-policy";
type Params = {
computer_id: number;
@@ -22,7 +23,8 @@ type Params = {
* (`cpus`, `memory_mb`, else the `AppData` defaults), then records it (`containers`:
* docker id, state, and its IP address on the network). A failure is
* recorded as `failed` with its reason, which the computer lists show. The
* owner's tabs hear about it either way. The metadata's `layout` says the
* owner's tabs hear about it either way. Its browser gets its policy
* (`syncComputerBrowserPolicy`). The metadata's `layout` says the
* container mounts only the workspace and the browser profile (`syncComputers` recreates one that
* doesn't). Never throws.
*/
@@ -83,6 +85,8 @@ export default async function provisionComputer({ computer_id, fresh }: Params)
row.id,
],
});
await syncComputerBrowserPolicy({ computer_id, docker_id: created.docker_id });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,45 @@
import { AppData } from "@/src/data/app-data";
import execDockerContainer from "@/src/functions/backend/docker/exec-docker-container";
import { logError } from "@/src/functions/backend/logger";
import computerBrowserPolicy from "@/src/utils/computer-browser-policy";
type Params = {
computer_id: number | string;
/**
* The computer's running container.
*/
docker_id: string;
};
/**
* Writes the browser policy (`computerBrowserPolicy`: the extensions every
* computer gets) into a running computer's container at
* `AppData.ComputerBrowserPolicyFile`, as root, only when it differs.
* Chrome watches that folder, so a running browser installs them without
* a restart. The file lives in the container, not on a mount, so a running
* computer gets it without being recreated; `provisionComputer` and the
* `sync-computers` cron write it again. Never throws.
*/
export default async function syncComputerBrowserPolicy({ computer_id, docker_id }: Params) {
const file = AppData.ComputerBrowserPolicyFile;
try {
const { exit_code } = await execDockerContainer({
container: docker_id,
cmd: [
"sh",
"-c",
`[ "$(cat "$POLICY_FILE" 2>/dev/null)" = "$(printf '%s' "$POLICY")" ] && exit 0
mkdir -p "$(dirname "$POLICY_FILE")" && printf '%s' "$POLICY" > "$POLICY_FILE.tmp" && chmod 644 "$POLICY_FILE.tmp" && mv "$POLICY_FILE.tmp" "$POLICY_FILE"`,
],
env: [`POLICY_FILE=${file}`, `POLICY=${computerBrowserPolicy()}`],
});
if (exit_code !== 0) throw new Error(`Writing ${file} exited with ${exit_code}.`);
return true;
} catch (error) {
await logError({ title: `Computer #${computer_id} couldn't get its browser policy`, error }).catch(() => {});
return false;
}
}
@@ -0,0 +1,55 @@
import dockerRequest from "./docker-request";
type Params = {
/**
* The container's id or name.
*/
container: string;
cmd: string[];
/**
* Who runs it, `root` by default.
*/
user?: string;
env?: string[];
timeout_ms?: number;
};
/**
* Runs a command in a running container (the API's `docker exec`) and
* waits for it. Resolves with its exit code, or throws when docker
* couldn't run it.
*/
export default async function execDockerContainer({
container,
cmd,
user = "root",
env,
timeout_ms = 30 * 1000,
}: Params) {
const created = await dockerRequest<{ Id: string }>({
method: "POST",
path: `/containers/${container}/exec`,
body: { Cmd: cmd, User: user, Env: env, AttachStdout: true, AttachStderr: true },
});
if (!created.ok || !created.data?.Id) {
throw new Error(`Couldn't run a command in the container: ${created.message}`);
}
const started = await dockerRequest({
method: "POST",
path: `/exec/${created.data.Id}/start`,
body: { Detach: false, Tty: false },
timeout_ms,
});
if (!started.ok) {
throw new Error(`Couldn't run a command in the container: ${started.message}`);
}
const inspected = await dockerRequest<{ ExitCode: number | null }>({
path: `/exec/${created.data.Id}/json`,
});
return { exit_code: inspected.data?.ExitCode ?? null };
}