#!/bin/bash # ============================================================================= # Linux-BF: Bangla Font Installer for Linux # Author: @abusayed0206 # Website: https://banglafont.pages.dev # GitHub: https://github.com/abusayed0206/linux-bf # Description: Universal installer for Bangla fonts on all major Linux distros # ============================================================================= set -euo pipefail # Exit on error, undefined vars, pipe failures # Colors and formatting RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' # No Color BOLD='\033[1m' # Configuration SCRIPT_NAME="Linux-BF Installer" VERSION="2.0.0" FONTS_JSON_URL="https://banglafont.pages.dev/fonts.json" USER_FONTS_DIR="$HOME/.local/share/fonts/bangla-fonts" SYSTEM_FONTS_DIR="/usr/share/fonts/bangla-fonts" TEMP_DIR="/tmp/linux-bf-installer-$$" LOG_FILE="/tmp/linux-bf-install.log" # Default options INSTALL_MODE="user" # user or system FORCE_INSTALL=false DRY_RUN=false SELECTED_FONTS="" VERBOSE=false # ============================================================================= # Utility Functions # ============================================================================= log() { echo "$(date '+%Y-%m-%d %H:%M:%S'): $*" >> "$LOG_FILE" } print_header() { clear echo -e "${CYAN}${BOLD}" echo "┌─────────────────────────────────────────────────────────────┐" echo "│ 🇧🇩 Linux-BF v$VERSION │" echo "│ Bangla Font Installer for Linux │" echo "│ │" echo "│ Author: @abusayed0206 │" echo "│ Website: https://banglafont.pages.dev │" echo "└─────────────────────────────────────────────────────────────┘" echo -e "${NC}" } print_success() { echo -e "${GREEN}✓${NC} $1" log "SUCCESS: $1" } print_error() { echo -e "${RED}✗${NC} $1" >&2 log "ERROR: $1" } print_warning() { echo -e "${YELLOW}⚠${NC} $1" log "WARNING: $1" } print_info() { echo -e "${BLUE}ℹ${NC} $1" log "INFO: $1" } print_step() { echo -e "${MAGENTA}▶${NC} ${BOLD}$1${NC}" log "STEP: $1" } show_progress() { local current=$1 local total=$2 local width=50 local percentage=$((current * 100 / total)) local filled=$((current * width / total)) printf "\r${CYAN}[" printf "%${filled}s" | tr ' ' '=' printf "%$((width - filled))s" | tr ' ' '-' printf "] %d%% (%d/%d)${NC}" "$percentage" "$current" "$total" } # ============================================================================= # System Detection and Dependencies # ============================================================================= detect_distro() { if [[ -f /etc/os-release ]]; then . /etc/os-release DISTRO="$ID" DISTRO_FAMILY="$ID_LIKE" elif command -v lsb_release >/dev/null 2>&1; then DISTRO=$(lsb_release -si | tr '[:upper:]' '[:lower:]') else DISTRO="unknown" fi case "$DISTRO" in ubuntu|debian|mint|elementary|pop) PACKAGE_MANAGER="apt" ;; fedora|rhel|centos|rocky|almalinux) PACKAGE_MANAGER="dnf" ;; opensuse*|sles) PACKAGE_MANAGER="zypper" ;; arch|manjaro|endeavouros) PACKAGE_MANAGER="pacman" ;; *) PACKAGE_MANAGER="unknown" ;; esac print_info "Detected distribution: $DISTRO" [[ $VERBOSE == true ]] && print_info "Package manager: $PACKAGE_MANAGER" } check_dependencies() { local missing_deps=() local optional_deps=() # Check for required tools for cmd in curl fc-cache; do if ! command -v "$cmd" >/dev/null 2>&1; then missing_deps+=("$cmd") fi done # Check for optional but recommended tools if ! command -v jq >/dev/null 2>&1; then optional_deps+=("jq") print_warning "jq not found - using basic JSON parsing (slower but functional)" fi if [[ ${#missing_deps[@]} -gt 0 ]]; then print_error "Missing required dependencies: ${missing_deps[*]}" print_info "Please install missing dependencies:" case "$PACKAGE_MANAGER" in apt) echo -e "${YELLOW}sudo apt update && sudo apt install -y curl fontconfig${NC}" [[ ${#optional_deps[@]} -gt 0 ]] && echo -e "${CYAN}Optional: sudo apt install -y jq${NC}" ;; dnf) echo -e "${YELLOW}sudo dnf install -y curl fontconfig${NC}" [[ ${#optional_deps[@]} -gt 0 ]] && echo -e "${CYAN}Optional: sudo dnf install -y jq${NC}" ;; zypper) echo -e "${YELLOW}sudo zypper install -y curl fontconfig${NC}" [[ ${#optional_deps[@]} -gt 0 ]] && echo -e "${CYAN}Optional: sudo zypper install -y jq${NC}" ;; pacman) echo -e "${YELLOW}sudo pacman -S curl fontconfig${NC}" [[ ${#optional_deps[@]} -gt 0 ]] && echo -e "${CYAN}Optional: sudo pacman -S jq${NC}" ;; *) echo -e "${YELLOW}Please install: curl, fontconfig using your package manager${NC}" [[ ${#optional_deps[@]} -gt 0 ]] && echo -e "${CYAN}Optional: jq${NC}" ;; esac exit 1 fi print_success "All required dependencies are available" [[ ${#optional_deps[@]} -gt 0 ]] && print_info "Consider installing optional dependencies for better performance: ${optional_deps[*]}" } check_permissions() { if [[ "$INSTALL_MODE" == "system" ]]; then if [[ $EUID -ne 0 ]]; then if command -v sudo >/dev/null 2>&1; then print_info "System installation requires sudo privileges" if ! sudo -n true 2>/dev/null; then print_warning "You will be prompted for your password" fi else print_error "System installation requires root privileges, but sudo is not available" exit 1 fi fi fi } # ============================================================================= # Font Management Functions # ============================================================================= fetch_fonts_data() { print_step "Fetching font metadata from server..." if ! curl -s --fail --connect-timeout 10 --max-time 30 "$FONTS_JSON_URL" -o "$TEMP_DIR/fonts.json"; then print_error "Failed to fetch font metadata from $FONTS_JSON_URL" print_info "Please check your internet connection and try again" exit 1 fi # Validate JSON if ! validate_json "$TEMP_DIR/fonts.json"; then print_error "Invalid JSON data received from server" exit 1 fi TOTAL_FONTS=$(parse_json_array_length "$TEMP_DIR/fonts.json") print_success "Successfully fetched metadata for $TOTAL_FONTS fonts" } display_fonts() { local fonts_data="$TEMP_DIR/fonts.json" echo -e "\n${BOLD}${CYAN}Available Bangla Fonts:${NC}\n" printf "%-4s %-25s %-10s %-15s %s\n" "No." "Font Name" "Size" "Category" "Description" printf "%-4s %-25s %-10s %-15s %s\n" "----" "-------------------------" "----------" "---------------" "-------------------" for i in $(seq 0 $((TOTAL_FONTS - 1))); do local name=$(parse_json_field "$fonts_data" "$i" "name") local size=$(parse_json_field "$fonts_data" "$i" "size") local category=$(parse_json_field "$fonts_data" "$i" "category") local description=$(parse_json_field "$fonts_data" "$i" "description") # Truncate long descriptions if [[ ${#description} -gt 40 ]]; then description="${description:0:37}..." fi printf "%-4d %-25s %-10s %-15s %s\n" "$((i + 1))" "$name" "$size" "$category" "$description" done echo "" } get_user_selection() { if [[ -n "$SELECTED_FONTS" ]]; then return # Selection already provided via command line fi echo -e "${BOLD}Installation Options:${NC}" echo "1. Install all fonts (recommended)" echo "2. Select specific fonts" echo "3. Exit" echo "" while true; do read -p "Choose an option (1-3): " choice case $choice in 1) SELECTED_FONTS="all" break ;; 2) echo -e "\n${YELLOW}Enter font numbers separated by commas (e.g., 1,3,5,8):${NC}" read -p "Font numbers: " SELECTED_FONTS if [[ "$SELECTED_FONTS" =~ ^[0-9,[:space:]]+$ ]]; then break else print_error "Invalid format. Please enter numbers separated by commas." fi ;; 3) print_info "Installation cancelled by user" exit 0 ;; *) print_error "Invalid option. Please choose 1, 2, or 3." ;; esac done } prepare_font_list() { local fonts_data="$TEMP_DIR/fonts.json" if [[ "$SELECTED_FONTS" == "all" ]]; then # Select all fonts for i in $(seq 0 $((TOTAL_FONTS - 1))); do echo "$i" >> "$TEMP_DIR/selected_fonts.txt" done else # Parse user selection echo "$SELECTED_FONTS" | tr ',' '\n' | while read -r num; do num=$(echo "$num" | tr -d '[:space:]') # Remove whitespace if [[ "$num" -ge 1 && "$num" -le "$TOTAL_FONTS" ]]; then echo "$((num - 1))" >> "$TEMP_DIR/selected_fonts.txt" else print_warning "Invalid font number: $num (skipping)" fi done fi if [[ ! -f "$TEMP_DIR/selected_fonts.txt" ]] || [[ ! -s "$TEMP_DIR/selected_fonts.txt" ]]; then print_error "No valid fonts selected" exit 1 fi SELECTED_COUNT=$(wc -l < "$TEMP_DIR/selected_fonts.txt") print_info "Selected $SELECTED_COUNT font(s) for installation" } download_and_install_fonts() { local fonts_data="$TEMP_DIR/fonts.json" local target_dir # Determine target directory if [[ "$INSTALL_MODE" == "system" ]]; then target_dir="$SYSTEM_FONTS_DIR" if [[ $EUID -ne 0 ]]; then print_step "Installing fonts system-wide (requires sudo)..." else print_step "Installing fonts system-wide..." fi else target_dir="$USER_FONTS_DIR" print_step "Installing fonts for current user..." fi print_info "Target directory: $target_dir" if [[ "$DRY_RUN" == true ]]; then print_warning "DRY RUN MODE - No files will be downloaded or installed" while read -r font_index; do local name=$(parse_json_field "$fonts_data" "$font_index" "name") local size=$(parse_json_field "$fonts_data" "$font_index" "size") echo -e "${CYAN}Would install:${NC} $name ($size)" done < "$TEMP_DIR/selected_fonts.txt" return 0 fi # Create target directory if [[ "$INSTALL_MODE" == "system" ]]; then if [[ $EUID -ne 0 ]]; then sudo mkdir -p "$target_dir" else mkdir -p "$target_dir" fi else mkdir -p "$target_dir" fi # Download and install fonts local current=0 local failed_fonts=() while read -r font_index; do current=$((current + 1)) local name=$(parse_json_field "$fonts_data" "$font_index" "name") local url=$(parse_json_field "$fonts_data" "$font_index" "url") local filename=$(parse_json_field "$fonts_data" "$font_index" "filename") local size=$(parse_json_field "$fonts_data" "$font_index" "size") show_progress "$current" "$SELECTED_COUNT" # Download font local temp_font="$TEMP_DIR/$filename" if curl -s --fail --connect-timeout 10 --max-time 60 "$url" -o "$temp_font"; then # Verify download if [[ -f "$temp_font" ]] && [[ -s "$temp_font" ]]; then # Install font if [[ "$INSTALL_MODE" == "system" ]]; then if [[ $EUID -ne 0 ]]; then sudo cp "$temp_font" "$target_dir/" else cp "$temp_font" "$target_dir/" fi else cp "$temp_font" "$target_dir/" fi [[ $VERBOSE == true ]] && echo -e "\n${GREEN}✓${NC} $name ($size)" else failed_fonts+=("$name") [[ $VERBOSE == true ]] && echo -e "\n${RED}✗${NC} $name (download verification failed)" fi else failed_fonts+=("$name") [[ $VERBOSE == true ]] && echo -e "\n${RED}✗${NC} $name (download failed)" fi done < "$TEMP_DIR/selected_fonts.txt" echo "" # New line after progress bar # Report results local successful=$((SELECTED_COUNT - ${#failed_fonts[@]})) print_success "Successfully installed $successful/$SELECTED_COUNT fonts" if [[ ${#failed_fonts[@]} -gt 0 ]]; then print_warning "Failed to install ${#failed_fonts[@]} font(s):" for font in "${failed_fonts[@]}"; do echo " - $font" done fi } refresh_font_cache() { print_step "Refreshing font cache..." if [[ "$DRY_RUN" == true ]]; then print_warning "DRY RUN MODE - Font cache would be refreshed" return 0 fi # Refresh user font cache if fc-cache -f "$USER_FONTS_DIR" 2>/dev/null; then [[ $VERBOSE == true ]] && print_success "User font cache refreshed" fi # Refresh system font cache if installing system-wide if [[ "$INSTALL_MODE" == "system" ]]; then if [[ $EUID -ne 0 ]]; then if sudo fc-cache -f "$SYSTEM_FONTS_DIR" 2>/dev/null; then [[ $VERBOSE == true ]] && print_success "System font cache refreshed" fi else if fc-cache -f "$SYSTEM_FONTS_DIR" 2>/dev/null; then [[ $VERBOSE == true ]] && print_success "System font cache refreshed" fi fi fi # Global font cache refresh if [[ "$INSTALL_MODE" == "system" ]]; then if [[ $EUID -ne 0 ]]; then sudo fc-cache -fv >/dev/null 2>&1 || true else fc-cache -fv >/dev/null 2>&1 || true fi else fc-cache -fv >/dev/null 2>&1 || true fi print_success "Font cache refreshed successfully" } # ============================================================================= # Cleanup and Error Handling # ============================================================================= cleanup() { if [[ -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" fi } trap cleanup EXIT # ============================================================================= # Help and Usage # ============================================================================= show_help() { cat << EOF ${BOLD}Linux-BF: Bangla Font Installer v$VERSION${NC} ${BOLD}USAGE:${NC} $0 [OPTIONS] ${BOLD}OPTIONS:${NC} -h, --help Show this help message -v, --version Show version information --verbose Enable verbose output --system Install fonts system-wide (requires sudo) --user Install fonts for current user only (default) --dry-run Preview what would be installed without making changes --force Install without confirmation prompts --select NUMBERS Select specific fonts by numbers (e.g., --select 1,3,5) --all Install all available fonts --list List available fonts and exit ${BOLD}EXAMPLES:${NC} $0 # Interactive installation (user-level) $0 --system # Interactive installation (system-wide) $0 --all --user # Install all fonts for current user $0 --select 1,3,5 --system # Install specific fonts system-wide $0 --dry-run --all # Preview installation of all fonts $0 --list # List available fonts ${BOLD}REMOTE INSTALLATION:${NC} curl -sSL https://banglafont.pages.dev/install.sh | bash curl -sSL https://banglafont.pages.dev/install.sh | sudo bash # system-wide ${BOLD}PATHS:${NC} User fonts: $USER_FONTS_DIR System fonts: $SYSTEM_FONTS_DIR Log file: $LOG_FILE ${BOLD}SUPPORT:${NC} Website: https://banglafont.pages.dev GitHub: https://github.com/abusayed0206/linux-bf Issues: https://github.com/abusayed0206/linux-bf/issues EOF } # ============================================================================= # JSON Parsing Functions (with fallbacks) # ============================================================================= # Parse JSON using jq if available, otherwise use basic bash parsing parse_json_array_length() { local json_file="$1" if command -v jq >/dev/null 2>&1; then jq length "$json_file" else # Basic fallback - count objects by counting opening braces after array start grep -o '{' "$json_file" | wc -l fi } parse_json_field() { local json_file="$1" local index="$2" local field="$3" if command -v jq >/dev/null 2>&1; then jq -r ".[$index].$field // \"\"" "$json_file" else # Basic fallback parsing - this is limited but functional case "$field" in name) sed -n "$((index + 1)),$((index + 50))p" "$json_file" | grep '"name"' | head -1 | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' ;; url) sed -n "$((index + 1)),$((index + 50))p" "$json_file" | grep '"url"' | head -1 | sed 's/.*"url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' ;; filename) sed -n "$((index + 1)),$((index + 50))p" "$json_file" | grep '"filename"' | head -1 | sed 's/.*"filename"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' ;; size) sed -n "$((index + 1)),$((index + 50))p" "$json_file" | grep '"size"' | head -1 | sed 's/.*"size"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' ;; category) sed -n "$((index + 1)),$((index + 50))p" "$json_file" | grep '"category"' | head -1 | sed 's/.*"category"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' || echo "general" ;; description) sed -n "$((index + 1)),$((index + 50))p" "$json_file" | grep '"description"' | head -1 | sed 's/.*"description"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' || echo "A beautiful Bangla font" ;; *) echo "" ;; esac fi } validate_json() { local json_file="$1" if command -v jq >/dev/null 2>&1; then jq empty "$json_file" 2>/dev/null else # Basic validation - check if file has matching braces and brackets local open_braces=$(grep -o '{' "$json_file" | wc -l) local close_braces=$(grep -o '}' "$json_file" | wc -l) local open_brackets=$(grep -o '\[' "$json_file" | wc -l) local close_brackets=$(grep -o '\]' "$json_file" | wc -l) [[ $open_braces -eq $close_braces && $open_brackets -eq $close_brackets ]] fi } # ============================================================================= # Main Function # ============================================================================= main() { # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -v|--version) echo "Linux-BF Installer v$VERSION" exit 0 ;; --verbose) VERBOSE=true shift ;; --system) INSTALL_MODE="system" shift ;; --user) INSTALL_MODE="user" shift ;; --dry-run) DRY_RUN=true shift ;; --force) FORCE_INSTALL=true shift ;; --select) SELECTED_FONTS="$2" shift 2 ;; --all) SELECTED_FONTS="all" shift ;; --list) # Initialize temp directory for listing mkdir -p "$TEMP_DIR" detect_distro check_dependencies fetch_fonts_data display_fonts exit 0 ;; *) print_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac done # Initialize print_header mkdir -p "$TEMP_DIR" log "=== Linux-BF Installation Started ===" log "Version: $VERSION" log "Install mode: $INSTALL_MODE" log "PID: $$" # System checks detect_distro check_dependencies check_permissions # Fetch and display fonts fetch_fonts_data display_fonts # Get user selection get_user_selection prepare_font_list # Confirmation if [[ "$FORCE_INSTALL" != true && "$DRY_RUN" != true ]]; then echo -e "\n${YELLOW}Installation Summary:${NC}" echo " Mode: $INSTALL_MODE" echo " Fonts: $SELECTED_COUNT selected" if [[ "$INSTALL_MODE" == "system" ]]; then echo " Target: $SYSTEM_FONTS_DIR" else echo " Target: $USER_FONTS_DIR" fi echo "" read -p "Proceed with installation? (y/N): " confirm case $confirm in [Yy]|[Yy][Ee][Ss]) echo "" ;; *) print_info "Installation cancelled by user" exit 0 ;; esac fi # Install fonts download_and_install_fonts refresh_font_cache # Final message if [[ "$DRY_RUN" != true ]]; then echo "" print_success "Installation completed successfully!" echo "" echo -e "${BOLD}${GREEN}🎉 Your Bangla fonts are now ready to use!${NC}" echo "" echo -e "${CYAN}Next steps:${NC}" echo " 1. Restart your applications to see the new fonts" echo " 2. Visit https://banglafont.pages.dev to preview fonts" echo " 3. Test fonts in your favorite text editor or browser" echo "" echo -e "${CYAN}Troubleshooting:${NC}" echo " • If fonts don't appear, try logging out and back in" echo " • Check the log file for details: $LOG_FILE" echo " • Report issues: https://github.com/abusayed0206/linux-bf/issues" echo "" echo -e "${BOLD}Thank you for using Linux-BF! 🇧🇩${NC}" else print_info "Dry run completed. No changes were made." fi log "=== Linux-BF Installation Completed ===" } # ============================================================================= # Script Entry Point # ============================================================================= # Check if script is being executed directly (not sourced) # Handle case where BASH_SOURCE might not be available (e.g., when piped from curl) if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then main "$@" fi