Wire uBlock Origin into every real Chromium launch, alongside LeechBlock
Some checks failed
Pre-commit checks / pre-commit (push) Has been cancelled
Python tests / test (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yfDYbaw1D1WhVMqWhsSDA
This commit is contained in:
Krzysztof Rudnicki 2026-08-12 22:44:28 +02:00
parent 0c28f9988f
commit 47371ac9a1
4 changed files with 678 additions and 0 deletions

View File

@ -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=<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"
]
}

View File

@ -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 <<EOF
${SCRIPT_NAME} — Wire up uBlock Origin (pacman package) for Chromium-based browsers
Usage: ${SCRIPT_NAME}
Notes:
- Requires the 'ublock-origin' pacman package (sudo pacman -S ublock-origin).
- Chromium-based browsers are integrated via the same wrapper pattern used by
install_leechblock.sh; running both installers preserves both extensions in
a single comma-joined --load-extension flag.
- Firefox-based browsers are out of scope here — install uBlock Origin from
addons.mozilla.org instead, permanent installs work there unlike LeechBlock.
EOF
}
case "${1:-}" in
-h | --help)
usage
exit 0
;;
"") ;;
*)
err "Unrecognized option: $1"
usage
exit 2
;;
esac
MISSING=0
require_cmd sed
require_cmd find
[[ $MISSING -eq 1 ]] && {
err "Please install missing tools and re-run."
exit 1
}
EXT_PATH="/usr/lib/ublock-origin"
if [[ ! -f "$EXT_PATH/manifest.json" ]]; then
err "uBlock Origin not found at $EXT_PATH (manifest.json missing)."
err "Install it first: sudo pacman -S ublock-origin"
exit 1
fi
# ── Seed default filter-list selection (all lists enabled) ────────────
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
# 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 <<WRAP
#!/usr/bin/env bash
# __UBLOCK_WRAPPER__ — auto-generated by install_ublock.sh
# Original backed up at: $orig_backup
exec "$orig_backup" --load-extension="$EXT_PATH" "\$@"
WRAP
sudo chmod +x "$real_bin"
info "✓ $pretty now always launches with uBlock Origin"
found_any=1
}
info "Detecting installed browsers…"
for bin in "${!BROWSERS[@]}"; do
if command -v "$bin" >/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."

View File

@ -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/<extension-id>/", 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);
}

View File

@ -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 <<WRAP
#!/usr/bin/env bash
# __UBLOCK_WRAPPER__ — auto-generated by install_ungoogled_chromium_ubuntu.sh
# Original backed up at: $ORIG_BACKUP
exec "$ORIG_BACKUP" --load-extension="$UBLOCK_EXT_PATH" "\$@"
WRAP
sudo chmod +x "$REAL_BIN"
info "✓ ungoogled-chromium now always launches with uBlock Origin"
fi
fi
# ── 3. Pre-enable every uBO filter list ─────────────────────────────────
if ! command -v node >/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 <<EOF
${GREEN}Done.${NC} ungoogled-chromium is installed with uBlock Origin always loaded
and every filter list selected.
Note: newly-selected 3rd-party filter lists are only marked *selected* by
this seeding step — uBO downloads and compiles their actual content on its
own update cycle (~105s) after the next real launch with network access.
Launch ungoogled-chromium normally once and give it a couple of minutes
before expecting every list to be actively blocking.
EOF