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:
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user