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)
This commit is contained in:
Krzysztof kuhy Rudnicki 2026-02-23 22:00:31 +01:00
parent 21c9180b00
commit 720a291388
10 changed files with 2049 additions and 214 deletions

View File

@ -0,0 +1 @@
node_modules/

View File

@ -199,70 +199,53 @@ fi
EXT_PATH="$CURRENT_LINK" # stable path used by wrappers
# ── Inject default blocking configuration ─────────────────────────────
# Copy leechblock_defaults.js alongside the extension and patch
# background.js to import it and seed storage on first run.
# Write default blocking rules directly into Chrome's LevelDB extension
# storage via Node.js (classic-level). This is content-verification-proof:
# we never touch any extension JS file, so Chrome cannot detect tampering.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULTS_SRC="$SCRIPT_DIR/leechblock_defaults.js"
DEFAULTS_SRC="$SCRIPT_DIR/leechblock_defaults.json"
if [[ -f $DEFAULTS_SRC ]]; then
cp "$DEFAULTS_SRC" "$VERSION_DIR/defaults.js"
info "Copied default blocking configuration into extension"
BG_JS="$VERSION_DIR/background.js"
if [[ -f $BG_JS ]]; then
# 1) Add importScripts("defaults.js") right after importScripts("common.js")
if ! grep -q 'importScripts("defaults.js")' "$BG_JS"; then
sed -i 's|importScripts("common.js");|importScripts("common.js");\nimportScripts("defaults.js");|' "$BG_JS"
info "Patched background.js to import defaults.js"
fi
# Ensure classic-level is available next to this script.
if [[ ! -d "$SCRIPT_DIR/node_modules/classic-level" ]]; then
info "Installing classic-level npm package into $SCRIPT_DIR ..."
npm install --prefix "$SCRIPT_DIR" 2>&1 | grep -v '^npm warn' || true
fi
# 2) Inject first-run seeding logic after cleanTimeData(gOptions)
if ! grep -q 'LEECHBLOCK_DEFAULTS' "$BG_JS"; then
sed -i '/cleanTimeData(gOptions);/a\
\
\t\t// ── Seed default blocking rules on first run ──\
\t\tif (typeof LEECHBLOCK_DEFAULTS !== "undefined") {\
\t\t\tlet hasAnySites = false;\
\t\t\tfor (let s = 1; s <= +gOptions["numSets"]; s++) {\
\t\t\t\tif (gOptions["sites" + s]) { hasAnySites = true; break; }\
\t\t\t}\
\t\t\tif (!hasAnySites) {\
\t\t\t\tfor (let key in LEECHBLOCK_DEFAULTS) {\
\t\t\t\t\tgOptions[key] = LEECHBLOCK_DEFAULTS[key];\
\t\t\t\t}\
\t\t\t\tcleanOptions(gOptions);\
\t\t\t\tcleanTimeData(gOptions);\
\t\t\t\tgNumSets = +gOptions["numSets"];\
\t\t\t\tgStorage.set(gOptions).catch(\
\t\t\t\t\tfunction (e) { warn("Cannot seed defaults: " + e); }\
\t\t\t\t);\
\t\t\t\tlog("Seeded default blocking configuration");\
\t\t\t}\
\t\t}' "$BG_JS"
info "Patched background.js with first-run seeding logic"
fi
# Chrome locks its LevelDB files while running — close all Chromium browsers
# so the write succeeds.
pkill -f 'google-chrome|chromium|brave-browser|vivaldi|thorium' 2>/dev/null || true
sleep 1
# Seed defaults into every Chrome/Chromium profile found on this machine.
if node "$SCRIPT_DIR/seed_leechblock_storage.js" "$DEFAULTS_SRC"; then
info "Seeded default LeechBlock settings into browser storage"
else
warn "Could not seed LeechBlock defaults — run manually after install:"
warn " node $SCRIPT_DIR/seed_leechblock_storage.js $DEFAULTS_SRC"
fi
else
warn "leechblock_defaults.js not found at $DEFAULTS_SRC — skipping default config"
warn "leechblock_defaults.json not found at $DEFAULTS_SRC — skipping default config"
fi
# Detect browsers
declare -A BROWSERS
BROWSERS=(
[chromium]="Chromium"
[google - chrome - stable]="Google Chrome"
[google - chrome]="Google Chrome"
[brave - browser]="Brave"
[vivaldi - stable]="Vivaldi"
[google-chrome-stable]="Google Chrome"
[google-chrome]="Google Chrome"
[brave-browser]="Brave"
[vivaldi-stable]="Vivaldi"
[vivaldi]="Vivaldi"
[opera]="Opera"
[thorium - browser]="Thorium"
[thorium-browser]="Thorium"
)
declare -A FIREFOXES
FIREFOXES=(
[firefox]="Firefox"
[firefox - developer - edition]="Firefox Developer Edition"
[firefox-developer-edition]="Firefox Developer Edition"
[librewolf]="LibreWolf"
)
@ -272,32 +255,69 @@ found_any=0
user_apps_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
mkdir -p "$user_apps_dir"
# Replace the system browser launcher in-place so every launch includes LeechBlock.
# The original script/binary is backed up as <path>.orig.
# Requires sudo for system paths (/usr/bin).
# Inject --load-extension into a browser launcher so every launch includes LeechBlock.
# Handles two cases:
# 1) The binary is a shell script with an "exec" line — patch it in-place.
# 2) The binary is a compiled ELF — wrap it with a shell script.
# Follows symlinks only one level to avoid breaking shared wrapper scripts
# (e.g. browser-preexec-wrapper used by multiple browser symlinks).
# Requires sudo for system paths.
replace_browser_in_place() {
local bin="$1"
shift
local pretty="$1"
shift
local bin_path
bin_path=$(command -v "$bin" || true)
[[ -z $bin_path ]] && return
# Resolve symlinks to find the actual file to patch.
# Use readlink -f to get the canonical path.
local real_bin
real_bin=$(command -v "$bin" || true)
[[ -z $real_bin ]] && return
real_bin=$(readlink -f "$bin_path")
# Resolve to absolute path (handles symlinks etc.)
real_bin=$(readlink -f "$real_bin")
local load_ext_flag="--load-extension=\"$EXT_PATH\""
local orig_backup="${real_bin}.orig"
# If already wrapped, skip (idempotent)
if grep -q '__LEECHBLOCK_WRAPPER__' "$real_bin" 2>/dev/null; then
info "$pretty ($bin) already wrapped — skipping"
# If already patched, skip (idempotent)
if grep -q -- "$load_ext_flag" "$real_bin" 2>/dev/null; then
info "$pretty ($bin) already has LeechBlock --load-extension — skipping"
found_any=1
return
fi
# Kill running instances so the new wrapper takes effect
# Case 1: Shell script with an exec line — patch the exec line directly.
# This preserves the original script's logic (e.g. basename routing,
# hosts enforcement) and avoids breaking shared wrapper scripts.
if file "$real_bin" 2>/dev/null | grep -qi 'text\|script'; then
if grep -qE '^exec ' "$real_bin"; then
info "Patching exec line in $real_bin to add LeechBlock…"
# Kill running instances so the patched script takes effect
pkill -f "$real_bin" 2>/dev/null || true
sleep 1
# Back up before patching (only once)
local orig_backup="${real_bin}.orig"
if [[ ! -f $orig_backup ]]; then
info "Backing up $real_bin$orig_backup"
sudo cp -a "$real_bin" "$orig_backup"
fi
# Insert --load-extension right after "exec <command>" on the exec line.
# Matches: exec "$real_bin" "$@" or exec /path/to/bin $FLAGS "$@"
sudo sed -i "s|^exec \(.*\) \"\\\$@\"|exec \1 $load_ext_flag \"\\\$@\"|" "$real_bin"
info "$pretty exec line patched with LeechBlock"
found_any=1
return
fi
fi
# Case 2: Binary or script without a recognisable exec line — wrap it.
local orig_backup="${real_bin}.orig"
# Kill running instances
info "Killing running $pretty instances…"
pkill -f "$real_bin" 2>/dev/null || true
pkill -f "$(basename "$real_bin")" 2>/dev/null || true

View File

@ -1,151 +0,0 @@
/* LeechBlock NG default blocking configuration.
*
* Loaded by background.js via importScripts().
* On first run (no sites configured), these defaults are seeded into
* chrome.storage.local so the extension starts pre-configured.
*
* Mirrors the domains blocked in linux_configuration/hosts/install.sh.
* With matchSubdomains=true, listing "youtube.com" automatically covers
* www.youtube.com, m.youtube.com, etc.
*
* Maintained by install_leechblock.sh edit THIS file then re-run the
* installer to push changes into the extension.
*/
// eslint-disable-next-line no-unused-vars
const LEECHBLOCK_DEFAULTS = {
// ── General options ────────────────────────────────────────────────
numSets: "6",
matchSubdomains: true,
// ── Set 1 — YouTube & alternative front-ends ───────────────────────
setName1: "YouTube",
sites1: [
// Core YouTube
"youtube.com",
"youtu.be",
"youtube-nocookie.com",
"youtubei.googleapis.com",
"youtube.googleapis.com",
"yt3.ggpht.com",
"ytimg.com",
"googlevideo.com",
// Invidious instances
"invidious.io",
"invidio.us",
"vid.puffyan.us",
"yewtu.be",
"invidious.kavin.rocks",
"inv.riverside.rocks",
"invidious.namazso.eu",
"invidious.nerdvpn.de",
"invidious.projectsegfau.lt",
"invidious.slipfox.xyz",
"invidious.privacydev.net",
"invidious.perennialte.ch",
"invidious.protokoll-11.de",
"invidious.einfachzocken.eu",
"invidious.fdn.fr",
"inv.in.projectsegfau.lt",
"invidious.tiekoetter.com",
"invidious.lunar.icu",
"iv.ggtyler.dev",
"iv.melmac.space",
"invidious.incogniweb.net",
"invidious.drgns.space",
"invidious.io.lol",
"inv.n8pjl.ca",
"inv.zzls.xyz",
"inv.tux.pizza",
// Piped instances
"piped.video",
"piped.kavin.rocks",
"piped.mha.fi",
"piped.mint.lgbt",
"piped.projectsegfau.lt",
"piped.privacydev.net",
"piped.smnz.de",
"piped.adminforge.de",
"watch.whatever.social",
"piped.lunar.icu",
// Other alternative clients / front-ends
"viewtube.io",
"freetube.io",
"tubo.media",
"materialious.nadeko.net",
"clipious.org",
"newpipe.net",
"newpipe.schabi.org",
"grayjay.app",
"libretube.dev",
"hyperion.deishelon.com",
].join(" "),
times1: "0000-2400",
days1: [true, true, true, true, true, true, true],
// ── Set 2 — Food delivery services ─────────────────────────────────
setName2: "Food Delivery",
sites2: [
// Polish services
"pyszne.pl",
"glovo.com",
"glovoapp.com",
"bolt.eu",
"woltwojta.pl",
"wolt.com",
"jush.pl",
"delio.pl",
"delio.com",
"delio.com.pl",
"lisek.app",
"stava.app",
"biedronka.pl",
"barbora.pl",
"frisco.pl",
"swiatkwiatow.pl",
"szama.pl",
"auchandirect.pl",
// International services
"ubereats.com",
"uber.com",
"deliveroo.com",
"deliveroo.co.uk",
"foodpanda.com",
"grubhub.com",
"doordash.com",
"justeat.com",
"justeat.co.uk",
"postmates.com",
"seamless.com",
"menulog.com.au",
"delivery.com",
"getir.com",
"flink.com",
"gorillas.io",
"gopuff.com",
"instacart.com",
"takeaway.com",
].join(" "),
times2: "0000-2400",
days2: [true, true, true, true, true, true, true],
// ── Set 3 — Fast food chain websites ───────────────────────────────
setName3: "Fast Food",
sites3: [
"mcdonalds.com",
"mcdonalds.pl",
"kfc.com",
"kfc.pl",
"burgerking.com",
"burgerking.pl",
"pizzahut.com",
"pizzahut.pl",
"dominos.com",
"dominos.pl",
"subway.com",
"subway.pl",
].join(" "),
times3: "0000-2400",
days3: [true, true, true, true, true, true, true],
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,214 @@
{
"name": "digital-wellbeing-tools",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "digital-wellbeing-tools",
"version": "1.0.0",
"dependencies": {
"classic-level": "^1.4.1"
}
},
"node_modules/abstract-level": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz",
"integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"catering": "^2.1.0",
"is-buffer": "^2.0.5",
"level-supports": "^4.0.0",
"level-transcoder": "^1.0.1",
"module-error": "^1.0.1",
"queue-microtask": "^1.2.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/catering": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz",
"integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/classic-level": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz",
"integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"abstract-level": "^1.0.2",
"catering": "^2.1.0",
"module-error": "^1.0.1",
"napi-macros": "^2.2.2",
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/is-buffer": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/level-supports": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz",
"integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/level-transcoder": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz",
"integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"module-error": "^1.0.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/module-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz",
"integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/napi-macros": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz",
"integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==",
"license": "MIT"
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
}
}
}

View File

@ -0,0 +1,10 @@
{
"name": "digital-wellbeing-tools",
"version": "1.0.0",
"type": "module",
"description": "Node.js helpers for the digital-wellbeing scripts (e.g. LeechBlock storage seeder).",
"scripts": {},
"dependencies": {
"classic-level": "^1.4.1"
}
}

View File

@ -30,6 +30,14 @@ WHITELIST_DEST="${INSTALL_DIR}/pacman_whitelist.txt"
GREYLIST_DEST="${INSTALL_DIR}/pacman_greylist.txt"
INTEGRITY_DIR="/var/lib/pacman-wrapper"
INTEGRITY_FILE="${INTEGRITY_DIR}/policy.sha256"
LEECHBLOCK_INSTALLER_SOURCE="$(dirname "$0")/../install_leechblock.sh"
LEECHBLOCK_DEFAULTS_SOURCE="$(dirname "$0")/../leechblock_defaults.json"
LEECHBLOCK_SEEDER_SOURCE="$(dirname "$0")/../seed_leechblock_storage.js"
LEECHBLOCK_PKG_SOURCE="$(dirname "$0")/../package.json"
LEECHBLOCK_INSTALL_DIR="/usr/local/share/digital_wellbeing"
LEECHBLOCK_INSTALLER_DEST="${LEECHBLOCK_INSTALL_DIR}/install_leechblock.sh"
LEECHBLOCK_DEFAULTS_DEST="${LEECHBLOCK_INSTALL_DIR}/leechblock_defaults.json"
LEECHBLOCK_SEEDER_DEST="${LEECHBLOCK_INSTALL_DIR}/seed_leechblock_storage.js"
VBOX_ENFORCE_SOURCE="$(dirname "$0")/../virtualbox/enforce_vbox_hosts.sh"
VBOX_INSTALL_DIR="/usr/local/share/digital_wellbeing/virtualbox"
VBOX_ENFORCE_DEST="${VBOX_INSTALL_DIR}/enforce_vbox_hosts.sh"
@ -133,6 +141,30 @@ else
echo -e "${YELLOW}Warning: chattr not available, policy files will not be immutable${NC}"
fi
# Install LeechBlock installer and defaults if available
mkdir -p "$LEECHBLOCK_INSTALL_DIR"
if [ -f "$LEECHBLOCK_INSTALLER_SOURCE" ]; then
echo -e "${BLUE}Installing LeechBlock installer to ${LEECHBLOCK_INSTALLER_DEST}...${NC}"
cp "$LEECHBLOCK_INSTALLER_SOURCE" "$LEECHBLOCK_INSTALLER_DEST"
chmod +x "$LEECHBLOCK_INSTALLER_DEST"
echo -e "${GREEN}LeechBlock installer deployed to ${LEECHBLOCK_INSTALLER_DEST}${NC}"
else
echo -e "${YELLOW}LeechBlock installer not found at ${LEECHBLOCK_INSTALLER_SOURCE}, skipping...${NC}"
fi
if [ -f "$LEECHBLOCK_DEFAULTS_SOURCE" ]; then
cp "$LEECHBLOCK_DEFAULTS_SOURCE" "$LEECHBLOCK_DEFAULTS_DEST"
echo -e "${GREEN}LeechBlock defaults deployed to ${LEECHBLOCK_DEFAULTS_DEST}${NC}"
fi
if [ -f "$LEECHBLOCK_SEEDER_SOURCE" ]; then
cp "$LEECHBLOCK_SEEDER_SOURCE" "$LEECHBLOCK_SEEDER_DEST"
echo -e "${GREEN}LeechBlock seeder deployed to ${LEECHBLOCK_SEEDER_DEST}${NC}"
fi
if [ -f "$LEECHBLOCK_PKG_SOURCE" ]; then
cp "$LEECHBLOCK_PKG_SOURCE" "${LEECHBLOCK_INSTALL_DIR}/package.json"
echo -e "${BLUE}Installing Node.js deps in ${LEECHBLOCK_INSTALL_DIR}...${NC}"
npm install --prefix "$LEECHBLOCK_INSTALL_DIR" 2>&1 | grep -v '^npm warn' || true
fi
# Install VirtualBox enforcement script if available
if [ -f "$VBOX_ENFORCE_SOURCE" ]; then
echo -e "${BLUE}Installing VirtualBox hosts enforcement script...${NC}"

View File

@ -53,10 +53,26 @@ netsurf
amfora
tartube
youtube
# Chrome/Chromium variants
# Chrome/Chromium variants not handled by install_leechblock.sh
google-chrome
chromium
ungoogled-chromium
microsoft-edge
microsoft-edge-stable
microsoft-edge-beta
microsoft-edge-dev
iridium
slimjet
chromium-dev
# Firefox variants not handled by install_leechblock.sh
firefox-esr
firefox-nightly
# Opera variants not in BROWSERS array
opera-beta
opera-developer
# Qt/WebKit/other GUI browsers not handled by install_leechblock.sh
qutebrowser
midori
# VirtualBox (can bypass /etc/hosts restrictions)
virtualbox
vbox

View File

@ -738,12 +738,10 @@ auto_install_leechblock() {
script_dir="$(dirname "$(readlink -f "$0")")"
local leechblock_installer=""
if [[ -f "$script_dir/../install_leechblock.sh" ]]; then
leechblock_installer="$script_dir/../install_leechblock.sh"
elif [[ -f "$HOME/linux-configuration/scripts/digital_wellbeing/install_leechblock.sh" ]]; then
leechblock_installer="$HOME/linux-configuration/scripts/digital_wellbeing/install_leechblock.sh"
elif [[ -f "/usr/local/share/digital_wellbeing/install_leechblock.sh" ]]; then
if [[ -f "/usr/local/share/digital_wellbeing/install_leechblock.sh" ]]; then
leechblock_installer="/usr/local/share/digital_wellbeing/install_leechblock.sh"
elif [[ -f "$script_dir/../install_leechblock.sh" ]]; then
leechblock_installer="$script_dir/../install_leechblock.sh"
fi
if [[ -z $leechblock_installer ]]; then

View File

@ -0,0 +1,125 @@
#!/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);
}