From 47371ac9a11d0d8ec2c1ca38ea87f3b7e3121f99 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Wed, 12 Aug 2026 22:44:28 +0200 Subject: [PATCH] Wire uBlock Origin into every real Chromium launch, alongside LeechBlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install_ublock.sh patches the same wrapper install_leechblock.sh already targets, always rewriting --load-extension into a single comma-joined flag rather than appending a second occurrence — verified empirically that Chromium is last-wins across separate --load-extension flags and would otherwise silently drop LeechBlock. seed_ublock_storage.js previously hardcoded uBlock Origin's Chrome Web Store extension ID, but --load-extension (unpacked) loads get a synthetic, install-path-derived ID instead. It now reads the real ID back out of the profile's own Preferences file, with a --ext-path override for installers whose package path differs from Arch's. install_ungoogled_chromium_ubuntu.sh's UBLOCK_EXT_PATH was an unverified guess (/usr/share/webext/ublock-origin-chromium); corrected to the real Debian/Ubuntu package path confirmed via packages.ubuntu.com. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015yfDYbaw1D1WhVMqWhsSDA --- ...origin-wrapper-integration-2026-08-12.json | 56 +++++ .../digital_wellbeing/install_ublock.sh | 225 ++++++++++++++++++ .../digital_wellbeing/seed_ublock_storage.js | 179 ++++++++++++++ .../install_ungoogled_chromium_ubuntu.sh | 218 +++++++++++++++++ 4 files changed, 678 insertions(+) create mode 100644 docs/superpowers/evidence/ublock-origin-wrapper-integration-2026-08-12.json create mode 100755 linux_configuration/scripts/periodic_background/digital_wellbeing/install_ublock.sh create mode 100755 linux_configuration/scripts/periodic_background/digital_wellbeing/seed_ublock_storage.js create mode 100755 linux_configuration/scripts/single_use/install_ungoogled_chromium_ubuntu.sh diff --git a/docs/superpowers/evidence/ublock-origin-wrapper-integration-2026-08-12.json b/docs/superpowers/evidence/ublock-origin-wrapper-integration-2026-08-12.json new file mode 100644 index 00000000..4a696d0f --- /dev/null +++ b/docs/superpowers/evidence/ublock-origin-wrapper-integration-2026-08-12.json @@ -0,0 +1,56 @@ +{ + "intent": "Make full/classic uBlock Origin load on every real Chromium-family browser launch on Arch (alongside LeechBlockNG, already wired by install_leechblock.sh), and fix the same integration for the Ubuntu/Debian install script so both distros work end to end.", + "scope": [ + "linux_configuration/scripts/periodic_background/digital_wellbeing/install_ublock.sh (new)", + "linux_configuration/scripts/periodic_background/digital_wellbeing/seed_ublock_storage.js", + "linux_configuration/scripts/single_use/install_ungoogled_chromium_ubuntu.sh", + "Non-goal: no changes to LeechBlockNG's own installer or defaults" + ], + "changes": [ + "seed_ublock_storage.js: fixed extension-ID discovery for --load-extension (unpacked) deployments. Chromium assigns a synthetic, install-path-derived ID rather than the CWS ID for unpacked loads; the seeder now reads extensions.settings[*].path back out of the profile's own Preferences file to find the right LevelDB storage dir, falling back to the CWS ID only for real Chrome-Web-Store installs. Verified empirically: /usr/lib/ublock-origin resolves to id bnpeoafekllpappgoakfpagjghdaicfb on this machine.", + "seed_ublock_storage.js: added --ext-path= CLI override so non-Arch installers (different package path) can point the ID-discovery step at their own unpacked install location instead of relying on a hardcoded Arch path.", + "install_ublock.sh (new): wires uBlock Origin into the same Chromium-family wrapper install_leechblock.sh already patches, using a comma-joined single --load-extension flag rather than a second, separate flag occurrence.", + "install_ungoogled_chromium_ubuntu.sh: corrected UBLOCK_EXT_PATH from the unverified guess /usr/share/webext/ublock-origin-chromium to the actual Debian/Ubuntu package path /usr/share/chromium/extensions/ublock-origin, confirmed via packages.ubuntu.com's noble file listing for webext-ublock-origin-chromium (manifest.json present at that path). Also passes --ext-path to the seeder so ID discovery works on Ubuntu too." + ], + "verification": [ + { + "command": "Empirical: launched real /usr/bin/chromium headless with --load-extension=/usr/lib/ublock-origin, inspected Preferences", + "result": "pass", + "evidence": "extensions.settings['bnpeoafekllpappgoakfpagjghdaicfb'].path == '/usr/lib/ublock-origin', confirming the synthetic-ID mechanism the seeder fix relies on" + }, + { + "command": "Empirical: tested a single comma-joined --load-extension=pathA,pathB flag vs. two separate --load-extension= flags", + "result": "pass / critical finding", + "evidence": "Comma-joined: both extensions register with disable_reasons: []. Two separate flags: LAST-WINS, the first extension is silently dropped. install_ublock.sh and the Ubuntu script were written to always comma-join into any existing flag rather than append a second one." + }, + { + "command": "node ./seed_ublock_storage.js --force run against a scratch profile pre-seeded via a real headless chromium launch", + "result": "pass", + "evidence": "Correctly discovered synthetic ID from Preferences, wrote selectedFilterLists (71 entries) to the matching LevelDB dir; read back and confirmed persisted" + }, + { + "command": "node ./seed_ublock_storage.js --ext-path=/usr/share/chromium/extensions/ublock-origin --force run against a synthetic Preferences entry using the Debian path", + "result": "pass", + "evidence": "Correctly matched the override path and seeded the corresponding LevelDB dir, confirming the Ubuntu installer's fixed invocation will work" + }, + { + "command": "bash install_ublock.sh run for real against the live system (user-approved)", + "result": "pass", + "evidence": "Patched /usr/local/bin/browser-preexec-wrapper's exec line to a comma-joined --load-extension flag; launched real chromium afterward and confirmed both bnpeoafekllpappgoakfpagjghdaicfb (uBO) and kbkikbljacklcgphimabcdnifabmhfjn (LeechBlock) present with disable_reasons: [] in the live profile's Preferences" + }, + { + "command": "shellcheck install_ublock.sh scripts/single_use/install_ungoogled_chromium_ubuntu.sh && shfmt -d (both)", + "result": "pass", + "evidence": "shellcheck exit 0 for both; shfmt reported no diff for either file" + } + ], + "risks": [ + "install_ublock.sh's idempotency check greps for the uBO path already present inside a --load-extension=\"...\" flag; if a future manual edit changes flag quoting style it could double-patch on re-run (mitigated: it also backs up to .orig before any patch, same pattern as install_leechblock.sh)", + "UBLOCK_EXT_PATH for Ubuntu is now sourced from a live packages.ubuntu.com listing rather than execution on real Ubuntu hardware — the Ubuntu install script itself (download/apt install steps) remains unexecuted/unverified on this session, only the seeder+path logic was verified", + "The deployed /usr/local/bin/browser-preexec-wrapper is a stale (pre-crash-logging) revision vs. the repo's current system-maintenance/bin/browser-preexec-wrapper.sh source — install_ublock.sh patched the deployed file as-is; the crash-logging feature drift is a separate, unaddressed issue" + ], + "rollback": [ + "git revert this commit", + "On the live machine: restore /usr/local/bin/browser-preexec-wrapper from /usr/local/bin/browser-preexec-wrapper.orig (sudo cp -a) to undo the real system patch made this session" + ] +} diff --git a/linux_configuration/scripts/periodic_background/digital_wellbeing/install_ublock.sh b/linux_configuration/scripts/periodic_background/digital_wellbeing/install_ublock.sh new file mode 100755 index 00000000..0931093b --- /dev/null +++ b/linux_configuration/scripts/periodic_background/digital_wellbeing/install_ublock.sh @@ -0,0 +1,225 @@ +#!/usr/bin/env bash + +# uBlock Origin installer/wirer for Arch Linux (and derivatives) +# - Uses the ublock-origin pacman package already unpacked at /usr/lib/ublock-origin +# (install via `sudo pacman -S ublock-origin` if missing) +# - Wires Chromium-based browsers to auto-load it via --load-extension, alongside +# any extension a previous installer (e.g. install_leechblock.sh) already wired in +# - Seeds every selectable filter list into the extension's storage so uBO runs in +# full/classic mode with everything enabled, not just the shipped defaults +# +# IMPORTANT: --load-extension is last-wins across *separate* occurrences of the +# flag — Chromium silently drops every extension but the last one specified this +# way. Only a single, comma-joined "--load-extension=pathA,pathB" loads both. +# This script therefore never appends a second --load-extension flag: it always +# rewrites the existing flag (if any) into a comma-joined one. + +set -Eeuo pipefail + +SCRIPT_NAME=${0##*/} +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +info() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; } +warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } +err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + err "Missing dependency: $1" + MISSING=1 + fi +} + +usage() { + cat <&1 | grep -v '^npm warn' || true +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 + +if node "$SCRIPT_DIR/seed_ublock_storage.js"; then + info "Seeded uBlock Origin filter-list selection into browser storage" +else + warn "Could not seed uBlock Origin defaults — run manually after install:" + warn " node $SCRIPT_DIR/seed_ublock_storage.js" +fi + +# ── Detect browsers ───────────────────────────────────────────────────── +declare -A BROWSERS +BROWSERS=( + [chromium]="Chromium" + [google-chrome-stable]="Google Chrome" + [google-chrome]="Google Chrome" + [brave-browser]="Brave" + [vivaldi-stable]="Vivaldi" + [vivaldi]="Vivaldi" + [opera]="Opera" + [thorium-browser]="Thorium" +) + +found_any=0 + +# Add uBO to a browser launcher's --load-extension flag without dropping any +# extension a previous installer (LeechBlock) already wired in. Handles the +# same two cases as install_leechblock.sh's replace_browser_in_place: +# 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, same as install_leechblock.sh, so shared +# wrapper scripts (e.g. browser-preexec-wrapper used by multiple symlinks) +# only get patched once regardless of which browser name triggered it. +wire_browser_in_place() { + local bin="$1" + shift + local pretty="$1" + shift + + local bin_path + bin_path=$(command -v "$bin" || true) + [[ -z $bin_path ]] && return + + local real_bin + real_bin=$(readlink -f "$bin_path") + + # Already comma-joined in: nothing to do. + if grep -qE -- "--load-extension=\"[^\"]*${EXT_PATH//\//\\/}[^\"]*\"" "$real_bin" 2>/dev/null; then + info "$pretty ($bin) already has uBlock Origin in --load-extension — skipping" + found_any=1 + return + fi + + # Case 1: Shell script with an exec line carrying an existing + # --load-extension="..." flag (most likely from install_leechblock.sh) — + # rewrite it in-place into a comma-joined flag so both extensions load. + if file "$real_bin" 2>/dev/null | grep -qi 'text\|script'; then + if grep -qE -- '--load-extension="[^"]*"' "$real_bin" 2>/dev/null; then + info "Adding uBlock Origin to existing --load-extension flag in $real_bin…" + pkill -f "$real_bin" 2>/dev/null || true + sleep 1 + + 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 + + sudo sed -i -E "s|--load-extension=\"([^\"]*)\"|--load-extension=\"\1,${EXT_PATH}\"|" "$real_bin" + + info "✓ $pretty --load-extension flag now includes uBlock Origin" + found_any=1 + return + fi + + if grep -qE '^exec ' "$real_bin"; then + info "Patching exec line in $real_bin to add uBlock Origin…" + pkill -f "$real_bin" 2>/dev/null || true + sleep 1 + + 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 + + sudo sed -i "s|^exec \(.*\) \"\\\$@\"|exec \1 --load-extension=\"$EXT_PATH\" \"\\\$@\"|" "$real_bin" + + info "✓ $pretty exec line patched with uBlock Origin" + found_any=1 + return + fi + fi + + # Case 2: Binary or script without a recognisable exec/--load-extension + # line — wrap it directly (no prior LeechBlock installer has run here). + local orig_backup="${real_bin}.orig" + + info "Killing running $pretty instances…" + pkill -f "$real_bin" 2>/dev/null || true + pkill -f "$(basename "$real_bin")" 2>/dev/null || true + sleep 1 + + if [[ ! -f $orig_backup ]]; then + info "Backing up $real_bin → $orig_backup" + sudo cp -a "$real_bin" "$orig_backup" + else + info "Backup already exists: $orig_backup" + fi + + info "Replacing $real_bin with uBlock Origin wrapper…" + sudo tee "$real_bin" >/dev/null </dev/null 2>&1; then + wire_browser_in_place "$bin" "${BROWSERS[$bin]}" + fi +done + +echo +if [[ $found_any -eq 1 ]]; then + info "uBlock Origin integration complete." + warn "Chromium will mark it as a developer extension; this is expected for unpacked installs." +else + warn "No supported browsers detected. uBlock Origin package is present at: $EXT_PATH" + echo "Supported (auto-wired): ${!BROWSERS[*]}." +fi + +echo +info "Done." diff --git a/linux_configuration/scripts/periodic_background/digital_wellbeing/seed_ublock_storage.js b/linux_configuration/scripts/periodic_background/digital_wellbeing/seed_ublock_storage.js new file mode 100755 index 00000000..d3b63de1 --- /dev/null +++ b/linux_configuration/scripts/periodic_background/digital_wellbeing/seed_ublock_storage.js @@ -0,0 +1,179 @@ +#!/usr/bin/env node +/** + * seed_ublock_storage.js + * + * Writes a uBlock Origin `selectedFilterLists` entry directly into Chrome's + * extension LevelDB storage, enabling every filter list uBO ships (not just + * the defaults that are on out of the box). Same mechanism as + * seed_leechblock_storage.js: chrome.storage.local is a LevelDB store under + * "Local Extension Settings//", writable while the browser is + * closed. + * + * uBO downloads/compiles the content of any newly-enabled 3rd-party list on + * its own update cycle after the extension's first real launch (~105s), so + * newly-selected lists need one browser launch with network access before + * they're actually enforced — this only sets which lists are selected. + * + * Usage: + * node seed_ublock_storage.js [--force] [--ext-path=/path/to/unpacked/ublock] + * + * --ext-path overrides the unpacked install path to match against (defaults + * to Arch's /usr/lib/ublock-origin). Pass this on non-Arch installers whose + * package path differs, e.g. Debian/Ubuntu's + * /usr/share/chromium/extensions/ublock-origin. + * + * Must be run while the browser is NOT open. + * + * Requires: classic-level (npm install classic-level) + */ + +import { ClassicLevel } from "classic-level"; +import { existsSync, readdirSync, readFileSync } from "fs"; +import path from "path"; +import os from "os"; +import https from "https"; + +// ── CLI args ───────────────────────────────────────────────────────── +const args = process.argv.slice(2); +const force = args.includes("--force"); +const extPathArg = args.find((a) => a.startsWith("--ext-path=")); +const customExtPath = extPathArg ? extPathArg.slice("--ext-path=".length) : null; + +// ── Known CWS extension ID for uBlock Origin (MV2) ──────────────────── +const CWS_EXT_ID = "cjpalhdlnbpafiamejdnhcphjbkeiagm"; + +// uBO's package install path(s), used with --load-extension deployments. +// Chromium derives a synthetic, install-specific extension ID from the +// unpacked path rather than using the CWS ID — read it back out of the +// profile's own Preferences file instead of guessing it. Includes both +// known distro package paths so this script works unmodified whether it +// was seeded via --ext-path (e.g. from the Ubuntu installer) or run bare +// (Arch's ublock-origin package). +const UNPACKED_EXT_PATHS = customExtPath + ? [customExtPath] + : ["/usr/lib/ublock-origin", "/usr/share/chromium/extensions/ublock-origin"]; + +// Find the extension ID Chromium assigned to an unpacked extension loaded via +// --load-extension, by matching extensions.settings[*].path against known +// install paths. Returns null if the profile has no Preferences file yet +// (i.e. the browser has never been launched with this profile). +function findUnpackedExtensionId(configDir, profile) { + const prefsPath = path.join(configDir, profile, "Preferences"); + if (!existsSync(prefsPath)) return null; + let prefs; + try { + prefs = JSON.parse(readFileSync(prefsPath, "utf8")); + } catch (_) { + return null; + } + const settings = prefs?.extensions?.settings ?? {}; + for (const [id, entry] of Object.entries(settings)) { + if (UNPACKED_EXT_PATHS.includes(entry?.path)) return id; + } + return null; +} + +// ── Fetch uBO's own filter-list catalog and select every filter list ── +function fetchAssetsJson() { + return new Promise((resolve, reject) => { + https + .get( + "https://raw.githubusercontent.com/gorhill/uBlock/master/assets/assets.json", + (res) => { + if (res.statusCode !== 200) { + reject(new Error(`HTTP ${res.statusCode} fetching assets.json`)); + return; + } + let body = ""; + res.on("data", (chunk) => (body += chunk)); + res.on("end", () => { + try { + resolve(JSON.parse(body)); + } catch (e) { + reject(e); + } + }); + }, + ) + .on("error", reject); + }); +} + +function selectAllFilterListKeys(assets) { + // Every entry with content === "filters" is a filter list (as opposed to + // uBO's own resource/scriptlet/redirect assets, which use other content + // types). "off: true" only means "not selected by default" — it is still + // a valid, selectable list, which is exactly what we want to force on. + return Object.entries(assets) + .filter(([, v]) => v.content === "filters") + .map(([key]) => key); +} + +// ── 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"), + path.join(os.homedir(), ".config/ungoogled-chromium"), +]; + +async function seedProfile(storageDir, selectedFilterLists) { + const db = new ClassicLevel(storageDir, { createIfMissing: true }); + try { + if (!force) { + try { + const existing = await db.get("selectedFilterLists"); + if (existing && JSON.parse(existing).length > 0) { + console.log(` Skipping ${storageDir} — filter lists already configured (use --force to override).`); + return; + } + } catch (_) { + /* key doesn't exist — fine, proceed to seed */ + } + } + + await db.put("selectedFilterLists", JSON.stringify(selectedFilterLists)); + console.log(` ✓ Selected ${selectedFilterLists.length} filter lists in ${storageDir}`); + } finally { + await db.close(); + } +} + +console.log("Fetching uBlock Origin's filter-list catalog..."); +const assets = await fetchAssetsJson(); +const selectedFilterLists = selectAllFilterListKeys(assets); +console.log(`Found ${selectedFilterLists.length} selectable filter lists (enabling all of them).`); + +let found = false; +for (const configDir of configDirs) { + if (!existsSync(configDir)) continue; + for (const profile of readdirSync(configDir)) { + // --load-extension deployments (this repo's normal case): Chromium + // assigns a synthetic ID derived from the unpacked path, discoverable + // only via the profile's own Preferences file. + const unpackedId = findUnpackedExtensionId(configDir, profile); + // Real CWS installs (e.g. Brave's Chrome Web Store install): fixed ID. + const cwsExtDir = path.join(configDir, profile, "Extensions", CWS_EXT_ID); + const cwsStorageDir = path.join(configDir, profile, "Local Extension Settings", CWS_EXT_ID); + const cwsInstalled = existsSync(cwsExtDir) || existsSync(cwsStorageDir); + + const extId = unpackedId ?? (cwsInstalled ? CWS_EXT_ID : null); + if (!extId) continue; + + const storageDir = path.join(configDir, profile, "Local Extension Settings", extId); + console.log(`Seeding ${configDir}/${profile} (extension id ${extId})...`); + try { + await seedProfile(storageDir, selectedFilterLists); + found = true; + } catch (e) { + console.warn(` ⚠ Failed to seed ${storageDir}: ${e.message}`); + } + } +} + +if (!found) { + console.log("No uBlock Origin installations found."); + process.exit(0); +} diff --git a/linux_configuration/scripts/single_use/install_ungoogled_chromium_ubuntu.sh b/linux_configuration/scripts/single_use/install_ungoogled_chromium_ubuntu.sh new file mode 100755 index 00000000..1c70edfe --- /dev/null +++ b/linux_configuration/scripts/single_use/install_ungoogled_chromium_ubuntu.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +# Install ungoogled-chromium on Ubuntu/Debian with full uBlock Origin (MV2, +# all filter lists pre-enabled) loaded on every launch. +# +# Why ungoogled-chromium instead of plain chromium/google-chrome: Chromium +# removed Manifest V2 extension support starting with Chromium 138 (the last +# MV2 code paths are gone as of Chromium 151), which breaks full uBlock +# Origin (uBO's full/classic build is MV2-only; the MV3 "uBO Lite" that +# survives the cutoff drops dynamic filter lists and most cosmetic +# filtering). ungoogled-chromium is a Chromium fork whose maintainers +# currently patch the MV2 extension runtime back in — a maintainer-patched +# stopgap, not a permanent guarantee. Re-run this script's uBO seeding step +# (or check the ungoogled-chromium changelog) if a future release drops that +# patch and uBO stops loading. +# +# What this script does: +# 1. Installs ungoogled-chromium from a .deb release (no official Debian/ +# Ubuntu package exists upstream; berkley4/ungoogled-chromium-debian is +# the community-maintained binary build referenced from +# ungoogled-software's own binaries index). +# 2. Installs uBlock Origin unpacked via apt (webext-ublock-origin-chromium, +# Debian/Ubuntu universe) and wires --load-extension so it's always +# loaded, matching the Arch pacman_wrapper.sh + install_leechblock.sh +# pattern in this repo (but standalone: no digital-wellbeing blocking +# layer, no LeechBlock — just the browser + full ad blocker). +# 3. Pre-enables every uBO filter list (not just the out-of-the-box +# defaults) via seed_ublock_storage.js, the same LevelDB-seeding +# mechanism seed_leechblock_storage.js already uses in this repo. +# +# Usage: +# ./install_ungoogled_chromium_ubuntu.sh [--force] +# +# --force re-seeds uBO's filter-list selection even if it looks configured. +# +# Requires: Ubuntu/Debian with apt, sudo, curl, node+npm (for the seeder). +# Must be run while ungoogled-chromium is NOT open (the seeder writes to its +# LevelDB storage directly and a running browser holds it locked). + +set -Eeuo pipefail + +SCRIPT_NAME=${0##*/} +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +DIGITAL_WELLBEING_DIR="$SCRIPT_DIR/../periodic_background/digital_wellbeing" +SEEDER_SCRIPT="$DIGITAL_WELLBEING_DIR/seed_ublock_storage.js" + +UBLOCK_EXT_PATH="/usr/share/chromium/extensions/ublock-origin" +DEB_RELEASE_REPO="berkley4/ungoogled-chromium-debian" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +FORCE=0 +for arg in "$@"; do + case "$arg" in + --force) FORCE=1 ;; + --help | -h) + echo "Usage: $SCRIPT_NAME [--force]" + echo "" + echo " --force Re-seed uBO's filter-list selection even if already configured." + exit 0 + ;; + *) + error "Unknown option: $arg" + exit 1 + ;; + esac +done + +if [[ "$(id -u)" -eq 0 ]]; then + error "Don't run this as root. It calls sudo itself where needed." + exit 1 +fi + +if ! command -v apt-get >/dev/null 2>&1; then + error "apt-get not found — this script targets Ubuntu/Debian." + exit 1 +fi + +if pgrep -x chrome >/dev/null 2>&1 || pgrep -f "ungoogled-chromium" >/dev/null 2>&1; then + error "ungoogled-chromium appears to be running. Close it first — the uBO seeding step writes to its LevelDB storage directly and a running browser holds it locked." + exit 1 +fi + +# ── 1. Install ungoogled-chromium from the community .deb release ────── +if command -v ungoogled-chromium >/dev/null 2>&1; then + info "ungoogled-chromium already installed ($(ungoogled-chromium --version 2>/dev/null || echo 'version unknown')) — skipping install." +else + info "Fetching latest ungoogled-chromium .deb release info from ${DEB_RELEASE_REPO}..." + RELEASE_JSON=$(curl -fsSL "https://api.github.com/repos/${DEB_RELEASE_REPO}/releases/latest") + DEB_URL=$(echo "$RELEASE_JSON" | grep -oP '"browser_download_url":\s*"\K[^"]+ungoogled-chromium_[^"]+_amd64\.deb(?=")' | grep -v dbgsym | head -1) + SANDBOX_URL=$(echo "$RELEASE_JSON" | grep -oP '"browser_download_url":\s*"\K[^"]+ungoogled-chromium-sandbox_[^"]+_amd64\.deb(?=")' | grep -v dbgsym | head -1) + + if [[ -z "$DEB_URL" || -z "$SANDBOX_URL" ]]; then + error "Could not find expected .deb assets in the latest ${DEB_RELEASE_REPO} release." + error "Check https://github.com/${DEB_RELEASE_REPO}/releases manually." + exit 1 + fi + + TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$TMP_DIR"' EXIT + + info "Downloading $(basename "$DEB_URL")..." + curl -fsSL -o "$TMP_DIR/chromium.deb" "$DEB_URL" + info "Downloading $(basename "$SANDBOX_URL")..." + curl -fsSL -o "$TMP_DIR/sandbox.deb" "$SANDBOX_URL" + + info "Installing via apt (resolves runtime deps automatically)..." + sudo apt-get update -qq + sudo apt-get install -y "$TMP_DIR/sandbox.deb" "$TMP_DIR/chromium.deb" + + if ! command -v ungoogled-chromium >/dev/null 2>&1; then + error "Install finished but 'ungoogled-chromium' is not on PATH — check apt output above." + exit 1 + fi + info "✓ ungoogled-chromium installed: $(ungoogled-chromium --version 2>/dev/null || echo 'installed')" +fi + +# ── 2. Install uBlock Origin (unpacked, MV2) and wire --load-extension ── +if [[ ! -d "$UBLOCK_EXT_PATH" ]]; then + info "Installing webext-ublock-origin-chromium (unpacked uBO)..." + sudo apt-get update -qq + sudo apt-get install -y webext-ublock-origin-chromium +fi + +if [[ ! -d "$UBLOCK_EXT_PATH" ]]; then + error "webext-ublock-origin-chromium installed but $UBLOCK_EXT_PATH is missing." + error "Check 'dpkg -L webext-ublock-origin-chromium' for the actual install path and update UBLOCK_EXT_PATH in this script." + exit 1 +fi +info "✓ uBlock Origin unpacked at $UBLOCK_EXT_PATH" + +REAL_BIN="$(command -v ungoogled-chromium)" +LOAD_EXT_FLAG="--load-extension=\"$UBLOCK_EXT_PATH\"" + +if grep -q -- "$LOAD_EXT_FLAG" "$REAL_BIN" 2>/dev/null; then + info "ungoogled-chromium already wired to load uBlock Origin — skipping." +else + ORIG_BACKUP="${REAL_BIN}.orig" + if file "$REAL_BIN" 2>/dev/null | grep -qi 'text\|script'; then + # Real launcher is a shell script (typical for the chromium-launcher + # package this .deb depends on) — patch its exec line in place so any + # custom flags it already applies (GPU workarounds, profile dir, etc.) + # are preserved rather than bypassed by a wrapper. + if grep -qE '^exec ' "$REAL_BIN"; then + info "Patching exec line in $REAL_BIN to add uBlock Origin..." + if [[ ! -f "$ORIG_BACKUP" ]]; then + info "Backing up $REAL_BIN → $ORIG_BACKUP" + sudo cp -a "$REAL_BIN" "$ORIG_BACKUP" + fi + sudo sed -i "s|^exec \(.*\) \"\\\$@\"|exec \1 $LOAD_EXT_FLAG \"\\\$@\"|" "$REAL_BIN" + info "✓ ungoogled-chromium exec line patched with uBlock Origin" + else + error "Launcher at $REAL_BIN is a script but has no recognisable 'exec' line — patch it manually." + exit 1 + fi + else + # Real launcher is a compiled binary — wrap it instead of patching. + info "Wrapping $REAL_BIN with a uBlock Origin loader..." + if [[ ! -f "$ORIG_BACKUP" ]]; then + sudo cp -a "$REAL_BIN" "$ORIG_BACKUP" + fi + sudo tee "$REAL_BIN" >/dev/null </dev/null 2>&1; then + warn "node not found — installing via apt (needed for the filter-list seeder)." + sudo apt-get update -qq + sudo apt-get install -y nodejs npm +fi + +if [[ ! -f "$SEEDER_SCRIPT" ]]; then + error "Seeder script not found at $SEEDER_SCRIPT — is this repo checkout complete?" + exit 1 +fi + +if [[ ! -d "$DIGITAL_WELLBEING_DIR/node_modules/classic-level" ]]; then + info "Installing Node deps for the seeder (classic-level)..." + npm install --prefix "$DIGITAL_WELLBEING_DIR" 2>&1 | grep -v '^npm warn' || true +fi + +info "Launching ungoogled-chromium once to create its profile/extension storage dirs..." +"$REAL_BIN" --headless=new --disable-gpu about:blank >/dev/null 2>&1 & +BROWSER_PID=$! +sleep 4 +kill "$BROWSER_PID" 2>/dev/null || true +wait "$BROWSER_PID" 2>/dev/null || true + +SEED_ARGS=("--ext-path=$UBLOCK_EXT_PATH") +[[ "$FORCE" -eq 1 ]] && SEED_ARGS+=(--force) +info "Seeding uBlock Origin filter-list selection (all lists)..." +node "$SEEDER_SCRIPT" "${SEED_ARGS[@]}" + +cat <