testsAndMisc/linux_configuration/scripts/digital_wellbeing/seed_leechblock_storage.js
Krzysztof kuhy Rudnicki 720a291388 leechblock: switch defaults to gist JSON, seed LevelDB directly, block unsupported browsers
- Replace leechblock_defaults.js with LeechBlockOptions.json fetched from
  private gist (30 block sets, 1568 keys)
- Add seed_leechblock_storage.js: writes settings directly into Chrome's
  LevelDB via classic-level, bypassing content-verification entirely
  - Supports both .json and legacy .js defaults files
  - Chunked writes (individual db.put) to avoid batch size limits
  - Per-profile error isolation; Thorium LevelDB warning demoted to non-fatal
- Add package.json (type:module + classic-level dep) and .gitignore for
  node_modules
- install_leechblock.sh: drop background.js patching, call seeder instead;
  auto-installs classic-level if node_modules absent
- install_pacman_wrapper.sh: deploy .json defaults, seeder, and package.json
  to /usr/local/share/digital_wellbeing; run npm install there
- pacman_blocked_keywords.txt: add browsers not handled by install script
  (Microsoft Edge family, firefox-esr/nightly, opera-beta/dev,
  iridium, slimjet, chromium-dev, qutebrowser, midori)
2026-02-23 22:00:31 +01:00

126 lines
4.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* seed_leechblock_storage.js
*
* Writes LeechBlock NG default settings directly into Chrome's extension
* LevelDB storage. This bypasses Chrome's content-verification and service
* worker caching entirely.
*
* Usage:
* node seed_leechblock_storage.js <path/to/defaults.js> [--force]
*
* Must be run while Chrome is NOT open.
*
* Requires: classic-level (npm install classic-level)
*/
import { ClassicLevel } from "classic-level";
import { readFileSync, existsSync } from "fs";
import path from "path";
import os from "os";
// ── CLI args ─────────────────────────────────────────────────────────
const args = process.argv.slice(2);
const force = args.includes("--force");
const defaultsPath = args.find((a) => !a.startsWith("--"));
if (!defaultsPath) {
console.error("Usage: node seed_leechblock_storage.js <defaults.js> [--force]");
process.exit(1);
}
if (!existsSync(defaultsPath)) {
console.error(`defaults.js not found: ${defaultsPath}`);
process.exit(1);
}
// ── Load defaults from .json or .js file ────────────────────────────
const src = readFileSync(defaultsPath, "utf8");
let DEFAULTS;
try {
if (defaultsPath.endsWith(".json")) {
DEFAULTS = JSON.parse(src);
} else {
// Legacy .js format: const LEECHBLOCK_DEFAULTS = { ... }
// eslint-disable-next-line no-new-func
DEFAULTS = new Function(src + "\nreturn LEECHBLOCK_DEFAULTS;")();
}
} catch (e) {
console.error("Failed to load defaults file:", e.message);
process.exit(1);
}
console.log(`Loaded ${Object.keys(DEFAULTS).length} keys from ${defaultsPath}`);
// ── Known CWS extension ID for LeechBlock NG ─────────────────────────
const EXT_ID = "blaaajhemilngeeffpbfkdjjoefldkok";
// ── Find all Chrome/Chromium profile dirs with this extension ────────
const configDirs = [
path.join(os.homedir(), ".config/google-chrome"),
path.join(os.homedir(), ".config/chromium"),
path.join(os.homedir(), ".config/BraveSoftware/Brave-Browser"),
path.join(os.homedir(), ".config/vivaldi"),
path.join(os.homedir(), ".config/thorium"),
];
async function seedProfile(storageDir) {
const db = new ClassicLevel(storageDir, { createIfMissing: true });
try {
// Check for existing sites unless --force
if (!force) {
let hasAnySites = false;
const numSets = +(DEFAULTS.numSets ?? 6);
for (let s = 1; s <= numSets; s++) {
try {
const val = await db.get(`sites${s}`);
if (val && JSON.parse(val)) {
hasAnySites = true;
break;
}
} catch (_) {
/* key doesn't exist */
}
}
if (hasAnySites) {
console.log(` Skipping ${storageDir} — sites already configured (use --force to override).`);
return;
}
}
const entries = Object.entries(DEFAULTS);
for (const [key, value] of entries) {
await db.put(key, JSON.stringify(value));
}
console.log(` ✓ Seeded ${entries.length} settings into ${storageDir}`);
} finally {
await db.close();
}
}
let found = false;
const { readdirSync } = await import("fs");
for (const configDir of configDirs) {
if (!existsSync(configDir)) continue;
// Walk profiles: Default, Profile 1, Profile 2, ...
for (const profile of readdirSync(configDir)) {
const storageDir = path.join(
configDir, profile, "Local Extension Settings", EXT_ID
);
const extDir = path.join(configDir, profile, "Extensions", EXT_ID);
// Only seed profiles where LeechBlock is actually installed
if (existsSync(extDir) || existsSync(storageDir)) {
console.log(`Seeding ${configDir}/${profile}...`);
try {
await seedProfile(storageDir);
found = true;
} catch (e) {
console.warn(` ⚠ Failed to seed ${storageDir}: ${e.message}`);
}
}
}
}
if (!found) {
console.log("No LeechBlock NG installations found.");
process.exit(0);
}