#!/usr/bin/env bash # LeechBlockNG installer for Arch Linux (and derivatives) # - Downloads the latest release from GitHub # - Extracts it under ~/.local/share/leechblockng/ # - Wires Chromium-based browsers to auto-load the extension via --load-extension # - For Firefox-based browsers, prints safe next steps (stable Firefox requires signed XPI) set -Eeuo pipefail SCRIPT_NAME=${0##*/} 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 </dev/null 2>&1; then warn "jq not found — will fall back to a simpler tag detection method." fi [[ $MISSING -eq 1 ]] && { err "Please install missing tools and re-run."; exit 1; } REPO_OWNER="proginosko" REPO_NAME="LeechBlockNG" get_latest_tag() { local tag if command -v jq >/dev/null 2>&1; then tag=$(curl -fsSL "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | jq -r '.tag_name // empty' || true) if [[ -n "$tag" && "$tag" != "null" ]]; then echo "$tag"; return 0 fi fi # Fallback: follow redirect for /releases/latest to extract tag tag=$(curl -fsSLI "https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/latest" | awk -F'/tag/' '/^location:/I {print $2}' | tr -d '\r\n' || true) if [[ -n "$tag" ]]; then echo "$tag"; return 0 fi return 1 } if [[ -z "$VERSION" ]]; then info "Resolving latest release tag from GitHub…" if ! VERSION=$(get_latest_tag); then err "Failed to determine latest version tag"; exit 1 fi fi if [[ ! "$VERSION" =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway." fi VERSION=${VERSION#v} # strip leading v for folder names TAG="v${VERSION}" XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"} INSTALL_ROOT="$XDG_DATA_HOME/leechblockng" VERSION_DIR="$INSTALL_ROOT/$VERSION" CURRENT_LINK="$INSTALL_ROOT/current" if [[ -d "$VERSION_DIR" && $FORCE -ne 1 ]]; then info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)." else info "Downloading LeechBlockNG $TAG source from GitHub…" tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT ARCHIVE_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${TAG}.tar.gz" ARCHIVE_FILE="$tmpdir/${REPO_NAME}-${TAG}.tar.gz" curl -fL --retry 3 -o "$ARCHIVE_FILE" "$ARCHIVE_URL" info "Extracting…" mkdir -p "$tmpdir/extract" tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract" # The archive usually extracts to REPO_NAME-TAG/ … src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true) [[ -z "$src_root" ]] && { err "Could not locate extracted source root"; exit 1; } # Find the extension manifest (support a couple of common layouts) manifest_path=$(find "$src_root" -maxdepth 5 -type f -name manifest.json | head -n1 || true) if [[ -z "$manifest_path" ]]; then err "manifest.json not found in the extracted archive. The project layout may have changed." exit 1 fi ext_dir=$(dirname "$manifest_path") mkdir -p "$INSTALL_ROOT" rm -rf "$VERSION_DIR" info "Installing to $VERSION_DIR…" mkdir -p "$VERSION_DIR" # Copy the extension directory as-is (avoid bringing tests or build scripts) rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2>/dev/null || cp -a "$ext_dir/." "$VERSION_DIR/" ln -sfn "$VERSION_DIR" "$CURRENT_LINK" fi EXT_PATH="$CURRENT_LINK" # stable path used by wrappers # 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" ) declare -A FIREFOXES FIREFOXES=( [firefox]="Firefox" [firefox-developer-edition]="Firefox Developer Edition" [librewolf]="LibreWolf" ) found_any=0 wrap_bin_dir="$HOME/.local/bin" mkdir -p "$wrap_bin_dir" # Create a user desktop entry user_apps_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications" mkdir -p "$user_apps_dir" create_wrapper_and_desktop() { local bin="$1"; shift local pretty="$1"; shift local wrapper="$wrap_bin_dir/${bin}-with-leechblock" local real_bin real_bin=$(command -v "$bin" || true) [[ -z "$real_bin" ]] && return cat >"$wrapper" </dev/null | head -n1 || true) if [[ -n "$sys_desktop" ]]; then existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true) existing_name=$(awk -F= '/^Name=/{print $2; exit}' "$sys_desktop" || true) categories=$(awk -F= '/^Categories=/{print $2; exit}' "$sys_desktop" || true) fi [[ -z "$existing_icon" ]] && existing_icon="$bin" [[ -z "$existing_name" ]] && existing_name="$pretty" [[ -z "$categories" ]] && categories="Network;WebBrowser;" local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop" cat >"$desktop_file" </dev/null 2>&1; then create_wrapper_and_desktop "$bin" "${BROWSERS[$bin]}" fi done ff_found=0 for bin in "${!FIREFOXES[@]}"; do if command -v "$bin" >/dev/null 2>&1; then ff_found=1 fi done echo if [[ $found_any -eq 1 ]]; then info "Chromium-based integration complete. Launch the browser via its '(LeechBlock)' launcher." warn "Chromium will mark it as a developer extension; this is expected for unpacked installs." fi if [[ $ff_found -eq 1 ]]; then echo warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing." cat </dev/null 2>&1; then POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution") fi if command -v firefox-developer-edition >/dev/null 2>&1; then POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution") fi if command -v librewolf >/dev/null 2>&1; then POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution") fi # Generic mozilla path as fallback POLICY_DIRS+=("/usr/lib/mozilla/distribution") updated_any=0 for pol_target in "${POLICY_DIRS[@]}"; do tmp_pol=$(mktemp) existing="${pol_target}/policies.json" if sudo test -f "$existing"; then info "Merging into existing policies.json at $existing" sudo cp "$existing" "$tmp_pol" if command -v jq >/dev/null 2>&1; then merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" ' .policies |= (. // {}) | .policies.ExtensionSettings |= (. // {}) | .policies.ExtensionSettings."*" |= (. // {"installation_mode":"allowed"}) | .policies.ExtensionSettings[$id] |= (. // {}) | .policies.ExtensionSettings[$id].installation_mode = "force_installed" | .policies.ExtensionSettings[$id].install_url = $url ' "$tmp_pol") || merged="" if [[ -n "$merged" ]]; then printf '%s\n' "$merged" > "$tmp_pol" else warn "jq merge failed; skipping $pol_target" rm -f "$tmp_pol"; continue fi else warn "jq not available; creating minimal policies.json (existing file will be backed up)." sudo cp "$existing" "${existing}.bak.$(date +%s)" cat > "$tmp_pol" < "$tmp_pol" <